UNPKG

happner

Version:

distributed application engine with evented storage and mesh services

334 lines (251 loc) 10.6 kB
;(function (isBrowser) { var Promisify, Messenger, MeshError; var Internals = {}; if (isBrowser) { window.Happner = window.Happner || {}; window.Happner.Internals = Internals; Promisify = Happner.Promisify; Messenger = Happner.Messenger; MeshError = Happner.MeshError; } else { module.exports = Internals; Promisify = require('./promisify'); Messenger = require('./messenger'); MeshError = require('./mesh-error'); } Internals._initializeLocal = function (_this, description, config, isServer, callback) { _this.log.$$TRACE('_initializeLocal()'); if (!_this.post) Object.defineProperty(_this, 'post', { value: function (address) { _this.log.$$TRACE('post( %s', address); if (address.substring(0, 1) != '/') address = '/' + address; if (address.split('/').length == 3) address = '/' + _this._mesh.config.name + address; if (!_this._mesh.exchange[address]) throw new MeshError('missing address ' + address); var messenger = _this._mesh.exchange[address]; messenger.deliver.apply(messenger, arguments); } }); _this._mesh = _this._mesh || {}; _this._mesh.endpoints = _this._mesh.endpoints || {}; if (config.name) { _this._mesh.endpoints[config.name] = { description: description, local: isServer, name: config.name, data: isBrowser ? _this.data : _this.data || _this._mesh.data, } } // Externals var exchangeAPI = _this.exchange = (_this.exchange || {}); var eventAPI = _this.event = (_this.event || {}); // Internals _this._mesh = _this._mesh || {}; _this._mesh.exchange = _this._mesh.exchange || {}; Internals._updateEndpoint(_this, config.name, exchangeAPI, eventAPI, callback); }; Internals._updateEndpoint = function (_this, endpointName, exchangeAPI, eventAPI, callback) { _this.log.$$TRACE('_updateEndpoint( %s', endpointName); Internals._updateExchangeAPILayer(_this, endpointName, exchangeAPI) .then(function () { return Internals._updateEventAPILayer(_this, endpointName, eventAPI) }) .then(function (result) { callback(null, result); }) .catch(function (error) { callback(error); }); }; Internals._updateExchangeAPILayer = Promisify(function (_this, endpointName, exchangeAPI, callback) { _this.log.$$TRACE('_updateExchangeAPILayer( %s', endpointName); exchangeAPI[endpointName] = exchangeAPI[endpointName] || {}; var endpoint = _this._mesh.endpoints[endpointName]; var components = endpoint.description.components; var messenger = endpoint.messenger; if (endpoint.local && !components) { // - InitializeLocal on server occurs before components are created. // // - So on the first call this endpoint's component descriptions are empty. // // - Subsequent calls are made here with each component creation // assembling it's APIs component by component (to allow runtime // insertion of new components to initialize along the same code path) // // - The loop uses the messenger.initialized list to determine which // are new components to configure into the messenger. return callback(); } if (!messenger) { messenger = endpoint.messenger = new Messenger(endpoint, _this._mesh); } var runningComponents = Object.keys(messenger.initialized); var intendedComponents = Object.keys(components); var createComponents; var destroyComponents; // Initialize components into this endpoint's messenger createComponents = intendedComponents // Filter out components that are already initialized in the messenger. .filter(function (componentName) { return typeof messenger.initialized[componentName] == 'undefined'; }) .map(function (componentName) { // New Component var componentExchange = exchangeAPI[endpointName][componentName] = {}; var componentDesciption = components[componentName]; if (endpointName == _this._mesh.config.name) { exchangeAPI[componentName] = exchangeAPI[componentName] || {}; } // Create exchangeAPI 'Requestors' for each method Object.keys(componentDesciption.methods) .forEach(function (methodName) { var remoteRequestor, localRequestor; var requestPath = '/' + endpointName + '/' + componentName + '/' + methodName; var description = componentDesciption.methods[methodName]; var alias = description.alias; remoteRequestor = Promisify(function () { _this.post.apply(this, arguments); }, { unshift: requestPath }); if (endpoint.local) { localRequestor = Promisify(function () { var args = Array.prototype.slice.call(arguments); var origin = _this._mesh.exchange[requestPath].session; var operate = _this._mesh.elements[componentName].component.instance.operate; var callback = args.pop(); // Maintain similarity with message passing approach by using setImmediate on call and callback setImmediate(function () { operate(methodName, args, function (meshError, results) { setImmediate(function () { if (meshError) return callback(meshError); // unlikely since bypassing most of exchange callback.apply(this, results); // results = [error, results...] }); }, origin); }); }); } componentExchange[methodName] = remoteRequestor; if (alias) { componentExchange[alias] = remoteRequestor; } if (endpointName == _this._mesh.config.name) { exchangeAPI[componentName] = exchangeAPI[componentName] || {}; exchangeAPI[componentName][methodName] = localRequestor || remoteRequestor; if (alias) { exchangeAPI[componentName][alias] = localRequestor || remoteRequestor; } } }); // Return componentName for the .map to create the // array of newComponents. return componentName; }); destroyComponents = runningComponents // Filter for components no longer inteded .filter(function (componentName) { return intendedComponents.indexOf(componentName) == -1; }) .map(function (componentName) { // TODO: consider leaving a stub that callsback with a ComponentDeletedError // var componentDesciption = endpoint.previousDescription; delete exchangeAPI[endpointName][componentName]; delete _this.event[endpointName][componentName]; if (endpointName == _this._mesh.config.name) { delete exchangeAPI[componentName]; delete _this.event[componentName]; } return componentName; }) messenger.updateRequestors(createComponents, destroyComponents, callback); }); Internals._updateEventAPILayer = Promisify(function (_this, endpointName, eventAPI, callback) { _this.log.$$TRACE('_updateEventAPILayer( %s', endpointName); eventAPI[endpointName] = eventAPI[endpointName] || {}; var endpoint = _this._mesh.endpoints[endpointName]; var components = endpoint.description.components; if (endpoint.local && !components) return callback(); Object.keys(components) .filter(function (componentName) { return typeof eventAPI[endpointName][componentName] == 'undefined'; }) .forEach(function (componentName) { var eventKey = '/_events/' + endpointName + '/' + componentName + '/'; var subscriber = { // TODO: once() on: function (key, handler, onDone) { if (!onDone) { onDone = function (e) { if (e) _this.log.warn('subscribe to \'%s\' failed', key, e); } } endpoint.data.on(eventKey + key, {event_type: 'set'}, handler, onDone); }, onAsync: function (key, handler) { // return the promise from happn return endpoint.data.on(eventKey + key, {event_type: 'set'}, handler); }, off: function (key, offDone) { if (!offDone) { offDone = function (e) { if (e) _this.log.warn('unsubscribe from \'%s\' failed', key, e); } } if (typeof key == "number") { endpoint.data.off(key, offDone); } else { endpoint.data.off(eventKey + key, offDone); } }, offAsync: function (key) { return endpoint.data.off(eventKey + key); }, offPath: function (path, offDone) { if (!offDone) { offDone = function (e) { if (e) _this.log.warn('unsubscribe from \'%s\' failed', path, e); } } endpoint.data.offPath(path, offDone); }, }; eventAPI[endpointName][componentName] = subscriber; if (endpointName == _this._mesh.config.name) { eventAPI[componentName] = subscriber; } }) callback(); }); Internals._attachProxyPipeline = function (_this, description, happn, config, callback) { _this.log.$$TRACE('_attachProxyPipeline()'); if (!config.endpoints || isBrowser) return callback(); Promise.all(Object.keys(config.endpoints).map(function (endpointName) { return new Promise(function (resolve, reject) { var endpointConfig = config.endpoints[endpointName]; try { if (endpointConfig.proxy) { _this._mesh.exchange[endpointName].proxy.register(config, function (e) { eachCallback(e); }); } else resolve(); } catch (e) { reject(e); } }); })) .then(function (res) { callback(null); }) .catch(function (err) { _this.log.error('Failed to attach to the proxy pipeline', err); callback(err); }); } })(typeof module !== 'undefined' && typeof module.exports !== 'undefined' ? false : true);