const SharedDispatch = require('./shared-dispatch'); const log = require('../util/log'); /** * This class provides a Worker with the means to participate in the message dispatch system managed by CentralDispatch. * From any context in the messaging system, the dispatcher's "call" method can call any method on any "service" * provided in any participating context. The dispatch system will forward function arguments and return values across * worker boundaries as needed. * @see {CentralDispatch} */ class WorkerDispatch extends SharedDispatch { constructor () { super(); /** * This promise will be resolved when we have successfully connected to central dispatch. * @type {Promise} * @see {waitForConnection} * @private */ this._connectionPromise = new Promise(resolve => { this._onConnect = resolve; }); /** * Map of service name to local service provider. * If a service is not listed here, it is assumed to be provided by another context (another Worker or the main * thread). * @see {setService} * @type {object} */ this.services = {}; this._onMessage = this._onMessage.bind(this, self); if (typeof self !== 'undefined') { self.onmessage = this._onMessage; } } /** * @returns {Promise} a promise which will resolve upon connection to central dispatch. If you need to make a call * immediately on "startup" you can attach a 'then' to this promise. * @example * dispatch.waitForConnection.then(() => { * dispatch.call('myService', 'hello'); * }) */ get waitForConnection () { return this._connectionPromise; } /** * Set a local object as the global provider of the specified service. * WARNING: Any method on the provider can be called from any worker within the dispatch system. * @param {string} service - a globally unique string identifying this service. Examples: 'vm', 'gui', 'extension9'. * @param {object} provider - a local object which provides this service. * @returns {Promise} - a promise which will resolve once the service is registered. */ setService (service, provider) { if (this.services.hasOwnProperty(service)) { log.warn(`Worker dispatch replacing existing service provider for ${service}`); } this.services[service] = provider; return this.waitForConnection.then(() => this._remoteCall(self, 'dispatch', 'setService', service)); } /** * Fetch the service provider object for a particular service name. * @override * @param {string} service - the name of the service to look up * @returns {{provider:(object|Worker), isRemote:boolean}} - the means to contact the service, if found * @protected */ _getServiceProvider (service) { // if we don't have a local service by this name, contact central dispatch by calling `postMessage` on self const provider = this.services[service]; return { provider: provider || self, isRemote: !provider }; } /** * Handle a call message sent to the dispatch service itself * @override * @param {Worker} worker - the worker which sent the message. * @param {DispatchCallMessage} message - the message to be handled. * @returns {Promise|undefined} - a promise for the results of this operation, if appropriate * @protected */ _onDispatchMessage (worker, message) { let promise; switch (message.method) { case 'handshake': promise = this._onConnect(); break; case 'terminate': // Don't close until next tick, after sending confirmation back setTimeout(() => self.close(), 0); promise = Promise.resolve(); break; default: log.error(`Worker dispatch received message for unknown method: ${message.method}`); } return promise; } } module.exports = new WorkerDispatch();