ServiceManager.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. export default class ServiceManager{createRemoteService(serviceName){if(!this._remoteConnection){const url=Root.Runtime.queryParam('service-backend');if(!url){console.error('No endpoint address specified');return(Promise.resolve(null));}
  2. this._remoteConnection=new Connection(new RemoteServicePort(url));}
  3. return this._remoteConnection._createService(serviceName);}
  4. createAppService(appName,serviceName){let url=appName+'.js';const remoteBase=Root.Runtime.queryParam('remoteBase');const debugFrontend=Root.Runtime.queryParam('debugFrontend');const isUnderTest=Host.isUnderTest();const queryParams=[];if(remoteBase){queryParams.push('remoteBase='+remoteBase);}
  5. if(debugFrontend){queryParams.push('debugFrontend='+debugFrontend);}
  6. if(isUnderTest){queryParams.push('isUnderTest=true');}
  7. if(queryParams.length){url+=`?${queryParams.join('&')}`;}
  8. const worker=new Worker(url);const connection=new Connection(new WorkerServicePort(worker));return connection._createService(serviceName);}}
  9. class Connection{constructor(port){this._port=port;this._port.setHandlers(this._onMessage.bind(this),this._connectionClosed.bind(this));this._lastId=1;this._callbacks=new Map();this._services=new Map();}
  10. _createService(serviceName){return this._sendCommand(serviceName+'.create').then(result=>{if(!result){console.error('Could not initialize service: '+serviceName);return null;}
  11. const service=new Service(this,serviceName,result.id);this._services.set(serviceName+':'+result.id,service);return service;});}
  12. _serviceDisposed(service){this._services.delete(service._serviceName+':'+service._objectId);if(!this._services.size){this._port.close();}}
  13. _sendCommand(method,params){const id=this._lastId++;const message=JSON.stringify({id:id,method:method,params:params||{}});return this._port.send(message).then(success=>{if(!success){return Promise.resolve(null);}
  14. return new Promise(fulfill=>this._callbacks.set(id,fulfill));});}
  15. _onMessage(data){let object;try{object=JSON.parse(data);}catch(e){console.error(e);return;}
  16. if(object.id){if(object.error){console.error('Service error: '+object.error);}
  17. this._callbacks.get(object.id)(object.error?null:object.result);this._callbacks.delete(object.id);return;}
  18. const tokens=object.method.split('.');const serviceName=tokens[0];const methodName=tokens[1];const service=this._services.get(serviceName+':'+object.params.id);if(!service){console.error('Unable to lookup stub for '+serviceName+':'+object.params.id);return;}
  19. service._dispatchNotification(methodName,object.params);}
  20. _connectionClosed(){for(const callback of this._callbacks.values()){callback(null);}
  21. this._callbacks.clear();for(const service of this._services.values()){service._dispatchNotification('disposed');}
  22. this._services.clear();}}
  23. export class Service{constructor(connection,serviceName,objectId){this._connection=connection;this._serviceName=serviceName;this._objectId=objectId;this._notificationHandlers=new Map();}
  24. dispose(){const params={id:this._objectId};return this._connection._sendCommand(this._serviceName+'.dispose',params).then(()=>{this._connection._serviceDisposed(this);});}
  25. on(methodName,handler){this._notificationHandlers.set(methodName,handler);}
  26. send(methodName,params){params=params||{};params.id=this._objectId;return this._connection._sendCommand(this._serviceName+'.'+methodName,params);}
  27. _dispatchNotification(methodName,params){const handler=this._notificationHandlers.get(methodName);if(!handler){console.error('Could not report notification \''+methodName+'\' on \''+this._objectId+'\'');return;}
  28. handler(params);}}
  29. class RemoteServicePort{constructor(url){this._url=url;}
  30. setHandlers(messageHandler,closeHandler){this._messageHandler=messageHandler;this._closeHandler=closeHandler;}
  31. _open(){if(!this._connectionPromise){this._connectionPromise=new Promise(promiseBody.bind(this));}
  32. return this._connectionPromise;function promiseBody(fulfill){let socket;try{socket=new WebSocket((this._url));socket.onmessage=onMessage.bind(this);socket.onclose=onClose.bind(this);socket.onopen=onConnect.bind(this);}catch(e){fulfill(false);}
  33. function onConnect(){this._socket=socket;fulfill(true);}
  34. function onMessage(event){this._messageHandler(event.data);}
  35. function onClose(){if(!this._socket){fulfill(false);}
  36. this._socketClosed(!!this._socket);}}}
  37. send(message){return this._open().then(()=>{if(this._socket){this._socket.send(message);return true;}
  38. return false;});}
  39. close(){return this._open().then(()=>{if(this._socket){this._socket.close();this._socketClosed(true);}
  40. return true;});}
  41. _socketClosed(notifyClient){this._socket=null;delete this._connectionPromise;if(notifyClient){this._closeHandler();}}}
  42. class WorkerServicePort{constructor(worker){this._worker=worker;let fulfill;this._workerPromise=new Promise(resolve=>fulfill=resolve);this._worker.onmessage=onMessage.bind(this);this._worker.onclose=this._closeHandler;function onMessage(event){if(event.data==='workerReady'){fulfill(true);return;}
  43. this._messageHandler(event.data);}}
  44. setHandlers(messageHandler,closeHandler){this._messageHandler=messageHandler;this._closeHandler=closeHandler;}
  45. send(message){return this._workerPromise.then(()=>{try{this._worker.postMessage(message);return true;}catch(e){return false;}});}
  46. close(){return this._workerPromise.then(()=>{if(this._worker){this._worker.terminate();}
  47. return false;});}}
  48. self.Services=self.Services||{};Services=Services||{};Services.ServiceManager=ServiceManager;Services.ServiceManager.Service=Service;Services.serviceManager=new ServiceManager();