UNPKG

endpointjs

Version:

Endpoint.js enables modules within a web application to discover and use each other, whether that be on the same web page, other browser windows and tabs, iframes, servers and web workers in a reactive way by providing robust discovery, execution and stre

1,594 lines (1,390 loc) 617 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ (function (__filename){ /* * (C) 2016 * Booz Allen Hamilton, All rights reserved * Powered by InnoVision, created by the GIAT * * Endpoint.js was developed at the * National Geospatial-Intelligence Agency (NGA) in collaboration with * Booz Allen Hamilton [http://www.boozallen.com]. The government has * "unlimited rights" and is releasing this software to increase the * impact of government investments by providing developers with the * opportunity to take things in new directions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint -W097 */ /* globals process,__filename */ 'use strict'; var Endpoint = require('../../endpoint/endpoint'), EventEmitter = require('events').EventEmitter, inherits = require('util').inherits, format = require('util').format, uuid = require('node-uuid'), constants = require('../../util/constants'), addressTool = require('../../routing/address'), clientInstance = require('./client-instance'), resolver = require('./resolver'), appUtils = require('../../util/appUtils'), log = appUtils.getLogger(__filename); inherits(Adapter, Endpoint); module.exports = Adapter; /** * An Adapter is one of the two main parts of the Endpoint.js API (along with Facade) and performs two roles: * - Expose functions of an object to be executed remotely * - Emit events to remote clients * @example <caption>Creating an adapter</caption> * var adapter = window.endpoint.registerAdapter('mapapi', '1.0', mapapi); * @augments Endpoint * @param {EndpointManager} endpointManager - used to track the endpoint * @param {Object} settings * @param {String} settings.name - the name of this api * @param {String} settings.version - the version of this api * @param {String} settings.resolver - used to determine whether to respond to queries * @param {String} settings.metadata - the metadata used by the resolver * @param {String} settings.object - the object api to expose * @param {String} settings.neighborhood - whether to accept local, group (default), or universal messages * @constructor */ function Adapter(endpointManager, settings) { if (!(this instanceof Adapter)) { return new Adapter(endpointManager, settings); } // Call parent constructor Adapter.super_.call(this, endpointManager, { type: constants.EndpointType.ADAPTER, id: uuid(), identification: format('[name: %s] [version: %s]', settings.name, settings.version) } ); // Settings this._name = settings.name; this._version = '' + settings.version; this._object = settings.object || {}; this._resolver = settings.resolver || resolver({instanceId: endpointManager.getInstanceId(), id: this.getId()}); this._metadata = settings.metadata || {}; this._neighborhood = appUtils.getNeighborhood(settings.neighborhood, 'group'); if (this._neighborhood === constants.Neighborhood.GLOBAL) { this.close(); throw new Error('Cannot use GLOBAL neighborhood for adapter. Must use UNIVERSAL'); } var busAddress = 'adapter|' + this.getName() + '|' + this.getVersion(); // Verify that there are no listeners currently for the given adapter. if (EventEmitter.listenerCount(this.getBus(), busAddress) > 0) { this.close(); throw new Error('That adapter is already registered: ' + busAddress); } // Configuration this._maxAdapterInstances = endpointManager.getConfiguration().get('maxAdapterInstances'); this._maxHops = endpointManager.getConfiguration().get('maxHops'); // Operational Settings this._clientInstances = {}; this._clientInstancesCount = 0; this._currentContext = null; this._facadeEvents = null; // Listen to the global bus for requests, and respond. this.registerBusEvent(busAddress, this._handleRegistryRequest.bind(this)); // Listen for connect requests this.registerDefaultMessengerListener(); log.log(log.DEBUG, 'Created %s', this); // Send out a notification that this adapter has been created locally. // This is done so that queries that are outstanding get a notification of the // api. var bus = this.getBus(); var event = 'register|' + this.getName() + '|' + this.getVersion(); appUtils.nextTick(function() { log.log(log.DEBUG2, 'Sending register event for %s', this); bus.emit(constants.Neighborhood.LOCAL, event); }.bind(this)); } /** * Return the name of this adapter * @returns {*} */ Adapter.prototype.getName = function() { return this._name; }; /** * This is the version of the interface */ Adapter.prototype.getVersion = function() { return this._version; }; /** * Return the object we're adapted to. */ Adapter.prototype.getObject = function() { return this._object; }; /** * Return a facade event emitter to send events to connected * facades */ Adapter.prototype.getEvents = function() { if (this._facadeEvents === null) { var _this = this; this._facadeEvents = { emit: function() { for (var instance in _this._clientInstances) { _this._clientInstances[instance].getEvents().emit .apply(_this._clientInstances[instance], arguments); } } }; } return this._facadeEvents; }; /** * Get the current metadata. */ Adapter.prototype.getMetadata = function() { return this._metadata; }; /** * Set a key metadata * @param metadata */ Adapter.prototype.setMetadata = function(metadata) { this._metadata = metadata || {}; }; /** * Sets the context used by call context to execute a call * @param context */ Adapter.prototype.setCurrentContext = function(context) { this._currentContext = context; }; /** * Get the current call context */ Adapter.prototype.getCurrentContext = function() { return this._currentContext; }; /** * Determine if we should reply to this registry request. * @param query * @private */ Adapter.prototype._handleRegistryRequest = function(address, source, query) { // Ensure that the source is within the expected neighborhood if (source > this._neighborhood) { return; } log.log(log.TRACE, 'Adapter request: %s', this); if (this._clientInstancesCount >= this._maxAdapterInstances) { log.log(log.WARN, 'Max client instance count reached [%s], ignoring adapter request', this._clientInstancesCount); return; } if (this._resolver.resolve(query.criteria, this._metadata, address)) { log.log(log.DEBUG3, 'Responding to adapter request [for: %s]: %s', query.id, this); // Send the response this.getMessenger().sendMessage( address, query.id, { type: 'api', id: this.getId(), address: address.getPathVector() }); } }; /** * This occurs when a facade decides to use this adapter. Create an instance for the * facade. * @param message * @private */ Adapter.prototype._handleMessage = function(message, source) { // Ensure that the source is within the expected neighborhood if (source > this._neighborhood) { return; } if (this._clientInstancesCount >= this._maxAdapterInstances) { return; } // Ensure valid message var address = addressTool(message.address); var inValid = !appUtils.isUuid(message.id) || !address.isValid(this._maxHops) || (message.hostAffinityId !== null && !appUtils.isUuid(message.hostAffinityId)); if (inValid) { log.log(log.WARN, 'Invalid adapter request: %j', message); return; } var instance = clientInstance( this.getEndpointManager(), { remoteAddress: address, remoteId: message.id, hostAffinityId: message.hostAffinityId, neighborhood: this._neighborhood, facadeId: message.facadeId, adapter: this } ); this._clientInstances[instance.getId()] = instance; this._clientInstancesCount += 1; // When the instance closes, remove it from our list. instance.on('closed', function() { delete this._clientInstances[instance.getId()]; this._clientInstancesCount -= 1; }.bind(this)); this.emit('client-instance', instance); }; /** * Cancel all strategies * @private */ Adapter.prototype._handleClose = function() { if (this._clientInstances) { var instances = Object.keys(this._clientInstances); for (var instance in instances) { this._clientInstances[instance].close(); } } }; }).call(this,"/js\\app\\api\\adapter\\adapter.js") },{"../../endpoint/endpoint":26,"../../routing/address":36,"../../util/appUtils":54,"../../util/constants":55,"./client-instance":2,"./resolver":5,"events":64,"node-uuid":69,"util":92}],2:[function(require,module,exports){ (function (__filename){ /* * (C) 2016 * Booz Allen Hamilton, All rights reserved * Powered by InnoVision, created by the GIAT * * Endpoint.js was developed at the * National Geospatial-Intelligence Agency (NGA) in collaboration with * Booz Allen Hamilton [http://www.boozallen.com]. The government has * "unlimited rights" and is releasing this software to increase the * impact of government investments by providing developers with the * opportunity to take things in new directions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint -W097 */ /* globals __filename */ 'use strict'; var Endpoint = require('../../endpoint/endpoint'), inherits = require('util').inherits, format = require('util').format, objectInstance = require('./object-instance'), uuid = require('node-uuid'), constants = require('../../util/constants'), appUtils = require('../../util/appUtils'), log = appUtils.getLogger(__filename); inherits(ClientInstance, Endpoint); module.exports = ClientInstance; /** * This represents an instance of a remote facade for one of my published * adapters. This client instance is an event emitter which is emitted * to an instance of an Endpoint.js adapter when someone tries to use it. * @augments Endpoint * @param {EndpointManager} endpointManager - used to track the endpoint * @param {Object} settings * @param {String} settings.adapter - the adapter this client instance belongs to * @param {Address} settings.remoteAddress - the facade address * @param {String} settings.remoteId - the facade id * @param {String} settings.hostAffinityId - the id to listen to host affinity for disconnections * @param {Number} settings.neighborhood - the granularity of requests to accept * @param {String} settings.facadeId - the facade that will serve as the initial facade * @constructor */ function ClientInstance(endpointManager, settings) { if (!(this instanceof ClientInstance)) { return new ClientInstance(endpointManager, settings); } // Call parent constructor ClientInstance.super_.call(this, endpointManager, { type: constants.EndpointType.CLIENT_INSTANCE, id: uuid(), identification: format('[name: %s] [version: %s]', settings.adapter.getName(), settings.adapter.getVersion()) } ); // Register the streamer & messenger to receive messages from externals this.registerDefaultMessengerListener(); // Configuration this._maxClientObjects = endpointManager.getConfiguration().get('maxClientObjects'); // Pending call contexts this._facadeEvents = null; // Cache the input settings this._adapter = settings.adapter; this._remoteAddress = settings.remoteAddress; this._remoteId = settings.remoteId; this._hostAffinityId = settings.hostAffinityId; this._neighborhood = settings.neighborhood; // Start out as being 'connected' because we send the 'connect' statement below // to the facade this._remoteConnected = true; // This is the list of objects known to this instance this._objects = {}; this._totalObjects = 0; // Bootstrap the object list with the initial object var rootObjectInstance = this.createObjectInstance(this._adapter.getName(), this._adapter.getObject(), settings.facadeId); // If the object instance closes, then treat the client as closing too. rootObjectInstance.attachEndpoint(this); // Tell the facade that we're here! this.getMessenger().sendMessage( this._remoteAddress, this._remoteId, { type: 'connect', id: this.getId(), object: rootObjectInstance.getApi() }); // Setup host affinity listener this.trackEndpointAffinity(this._hostAffinityId); log.log(log.DEBUG, 'Created %s', this); } /** * Return the adapter referenced by this instance. * @returns {*} */ ClientInstance.prototype.getAdapter = function() { return this._adapter; }; /** * This function is used mainly to retrieve object instances * when they are passed as arguments to other facade functions * @param {String} id - unique identifer for the object * @return {ObjectInstance} - the given instance */ ClientInstance.prototype.getObjectInstance = function(id) { return this._objects[id]; }; /** * Returns the remote address of the facade this client instance * is connected to * @returns {*} */ ClientInstance.prototype.getRemoteAddress = function() { return this._remoteAddress; }; /** * Returns the remote id of the facade this client instance * is connected to * @returns {*} */ ClientInstance.prototype.getRemoteId = function() { return this._remoteId; }; /** * Return the ID being used for host affinity * @returns {*} */ ClientInstance.prototype.getHostAffinityId = function() { return this._hostAffinityId; }; /** * Return a facade event emitter to send events to connected * facade */ ClientInstance.prototype.getEvents = function() { if (this._facadeEvents === null) { // Event Facade to send events to the connected facade var _this = this; this._facadeEvents = { emit: function() { var event = []; for (var i = 0; i < arguments.length; i++) { event.push(arguments[i]); } _this.getMessenger().sendMessage( _this._remoteAddress, _this._remoteId, { type: 'event', event: event }); } }; } return this._facadeEvents; }; /** * This function will take the given object, wrap it in an object instance * and store it locally, managing its lifespan * @param object * @param remoteId - the id of the remote facade * @param [parentEndpoint] - if the parent endpoint closes, so will this object */ ClientInstance.prototype.createObjectInstance = function(name, object, remoteId, parentEndpoint) { if (this._totalObjects > this._maxClientObjects) { log.log(log.WARN, 'Max client objects exceeded [total: %s] for %s', this._maxClientObjects, this); return null; } // Create the object endpoint, and return it to the user var objectInst = objectInstance( this.getEndpointManager(), { name: name, object: object, remoteId: remoteId, clientInstance: this }); this._objects[objectInst.getId()] = objectInst; this._totalObjects += 1; // When I close, then remove myself from the managed list of objects. objectInst.on('closed', function() { delete this._objects[objectInst.getId()]; this._totalObjects -= 1; }.bind(this)); // If the parent closes, then close me. parentEndpoint = parentEndpoint || this; parentEndpoint.attachEndpoint(objectInst); return objectInst; }; /** * Handle an API request from a remote facade. * @param message */ ClientInstance.prototype._handleMessage = function(message, source) { // Ensure that the source is within the expected neighborhood if (source > this._neighborhood) { return; } var callType = message.type; if (callType) { switch (callType) { case 'disconnect': this._remoteConnected = false; this.close(); return; } } log.log(log.ERROR, 'Malformed message: %j for %s', message, this); }; /** * Be sure to tell the remote client that we're closing. All child object instances * will automatically be closed because they are attached to me. * @param affinityClosure - whether host affinity forced this closure * @private */ ClientInstance.prototype._handleClose = function(affinityClosure) { // Tell the remote we're closing! if (this._remoteConnected && !affinityClosure) { this.getMessenger().sendMessage( this._remoteAddress, this._remoteId, { id: this.getId(), type: 'disconnect' }); } }; }).call(this,"/js\\app\\api\\adapter\\client-instance.js") },{"../../endpoint/endpoint":26,"../../util/appUtils":54,"../../util/constants":55,"./object-instance":4,"node-uuid":69,"util":92}],3:[function(require,module,exports){ (function (__filename){ /* * (C) 2016 * Booz Allen Hamilton, All rights reserved * Powered by InnoVision, created by the GIAT * * Endpoint.js was developed at the * National Geospatial-Intelligence Agency (NGA) in collaboration with * Booz Allen Hamilton [http://www.boozallen.com]. The government has * "unlimited rights" and is releasing this software to increase the * impact of government investments by providing developers with the * opportunity to take things in new directions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint -W097 */ /* globals __filename */ 'use strict'; var through2 = require('through2'), appUtils = require('../../util/appUtils'), log = appUtils.getLogger(__filename); module.exports = Context; /** * A context is an execution context for an API call on an adapter. * It has the current input stream, output stream, and some utility functions * for dealing with streams. * @param objectInstance - the object instance this context belongs to * @constructor */ function Context(objectInstance) { if (!(this instanceof Context)) { return new Context(objectInstance); } this._objectInstance = objectInstance; this._inputStream = null; this._outputStream = null; this._cb = null; this._asyncMode = false; this._buffered = false; this._periodicCounter = 0; } /** * Ensure the context isn't stale. */ Context.prototype.incrementPeriodic = function() { this._periodicCounter += 1; }; /** * Get the current periodic count */ Context.prototype.getPeriodic = function() { return this._periodicCounter; }; /** * The client instance which made the call. * @returns {*} */ Context.prototype.getClientInstance = function() { return this._objectInstance.getClientInstance(); }; /** * The object instance which made the call. * @returns {*} */ Context.prototype.getObjectInstance = function() { return this._objectInstance; }; /** * Whether the input stream is buffered */ Context.prototype.isBuffered = function() { return this._buffered; }; /** * Whether the input stream is buffered */ Context.prototype.setBuffered = function(buffered) { this._buffered = buffered; }; /** * Whether this is an asynch call, meaning that it won't * return a result immediately */ Context.prototype.setAsyncMode = function() { this._asyncMode = true; }; /** * Whether this context is in async mode. */ Context.prototype.isAsync = function() { return this._asyncMode; }; /** * End an async call by returning a result * @param result */ Context.prototype.setAsyncResult = function(result) { this._cb('result', result); }; /** * End an async call by throwing an error * @param result */ Context.prototype.setAsyncError = function(error) { this._cb('error', error); }; /** * Whether the inputStream value is null * @returns {boolean} */ Context.prototype.hasInputStream = function() { if (this._inputStream !== null) { return true; } return false; }; /** * Whether the inputStream value is null * @returns {boolean} */ Context.prototype.hasOutputStream = function() { if (this._outputStream !== null) { return true; } return false; }; /** * Input stream from the facade that called this, or another client * instance. * @returns {*} */ Context.prototype.getInputStream = function() { return this._inputStream; }; /** * Sets the input stream for this instance */ Context.prototype.setInputStream = function(inputStream) { this._inputStream = inputStream; }; /** * Output stream is used to pipe to another facade function, stream, or then. * @returns {*} */ Context.prototype.getOutputStream = function() { return this._outputStream; }; /** * Sets the output stream for this instance */ Context.prototype.setOutputStream = function(outputStream) { this._outputStream = outputStream; }; /** * Execute the given API function on the adapter. * @param args */ Context.prototype.execute = function(func, args, cb) { var objectInstance = this.getObjectInstance(); var adapter = this.getClientInstance().getAdapter(); log.log(log.DEBUG3, 'Executing %s on %s', func, objectInstance); this._cb = cb; // Call the method var result; try { // Set call context on adapter. adapter.setCurrentContext(this); // Call the method var obj = objectInstance.getObject(); if (obj[func]) { result = obj[func].apply(obj, args); } else { throw new Error('Unknown function name'); } // Wrap the end() of streams, if there are streams. Done here so that // user defined 'end()' function executes first. if (this.hasInputStream() && this.hasOutputStream()) { this.getInputStream().on('end', function() { this.getOutputStream().end(); }.bind(this)); this.getOutputStream().on('end', function() { this.getInputStream().end(); }.bind(this)); } // Clear call context adapter.setCurrentContext(null); // Send the result back. if (!this.isAsync()) { cb('result', result); } } catch (e) { // Clear call context adapter.setCurrentContext(null); log.log(log.WARN, 'Issue executing API call [func: %s] [args: %j] [exception: %s] [trace: %s]', func, args, e.toString(), e.stack); // Send the result back. cb('error', e); } }; /** * End the input/output streams if they're set */ Context.prototype.cancel = function() { if (this.hasInputStream()) { this.getInputStream().end(); } if (this.hasOutputStream()) { this.getOutputStream().end(); } }; /** * Transform the stream by taking input and transforming, and output and * transforming in the reverse direction * @param forwardTransformFunc * @param reverseTransformFunc */ Context.prototype.transformDuplexStream = function(forwardTransformFunc, reverseTransformFunc) { var func = through2.obj; if (this._buffered) { func = through2; } this._inputStream.pipe(func(forwardTransformFunc)).pipe(this._outputStream); this._outputStream.pipe(func(reverseTransformFunc)).pipe(this._inputStream); }; /** * Transform the stream by taking input stream through the transformFunc * function * @param transformFunc */ Context.prototype.transformStream = function(transformFunc) { var func = through2.obj; if (this._buffered) { func = through2; } this._inputStream.pipe(func(transformFunc)).pipe(this._outputStream); }; }).call(this,"/js\\app\\api\\adapter\\context.js") },{"../../util/appUtils":54,"through2":89}],4:[function(require,module,exports){ (function (__filename){ /* * (C) 2016 * Booz Allen Hamilton, All rights reserved * Powered by InnoVision, created by the GIAT * * Endpoint.js was developed at the * National Geospatial-Intelligence Agency (NGA) in collaboration with * Booz Allen Hamilton [http://www.boozallen.com]. The government has * "unlimited rights" and is releasing this software to increase the * impact of government investments by providing developers with the * opportunity to take things in new directions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint -W097 */ /* globals __filename */ 'use strict'; var Endpoint = require('../../endpoint/endpoint'), inherits = require('util').inherits, format = require('util').format, uuid = require('node-uuid'), callContext = require('./context'), address = require('../../routing/address'), constants = require('../../util/constants'), appUtils = require('../../util/appUtils'), log = appUtils.getLogger(__filename); inherits(ObjectInstance, Endpoint); module.exports = ObjectInstance; /** * This represents an instance of a remote facade for one of my published * adapters. This client instance is an event emitter which is emitted * to an instance of an Endpoint.js adapter when someone tries to use it. * @augments Endpoint * @param {EndpointManager} endpointManager - used to track the endpoint * @param {Object} settings * @param {String} settings.name - the name of this object instance, derived from adapter name * @param {String} settings.clientInstance - the client instance this object instance belongs to * @param {String} settings.remoteId - the id of the remote facade endpoint * @param {Object} settings.object - the object this instance represents * @constructor */ function ObjectInstance(endpointManager, settings) { if (!(this instanceof ObjectInstance)) { return new ObjectInstance(endpointManager, settings); } var adapter = settings.clientInstance.getAdapter(); // Call parent constructor ObjectInstance.super_.call(this, endpointManager, { type: constants.EndpointType.OBJECT_INSTANCE, id: uuid(), identification: format('[name: %s] [version: %s]', settings.name, adapter.getVersion()) } ); // Register the streamer & messenger to receive messages from externals this.registerDefaultStreamerListener(); this.registerDefaultMessengerListener(); // Pending call contexts this._name = settings.name; this._object = settings.object; this._clientInstance = settings.clientInstance; this._remoteId = settings.remoteId; this._contexts = {}; this._contextsCount = 0; // Object instance starts as connected this._remoteConnected = true; // Bootstrap the API for this object this._methodIndex = this._createMethodIndex(); log.log(log.DEBUG, 'Created %s', this); } /** * Returns the name of this object instance * @return {String} */ ObjectInstance.prototype.getName = function() { return this._name; }; /** * Return the object this instance is wrapping * @returns {*} */ ObjectInstance.prototype.getObject = function() { return this._object; }; /** * Returns the client instance assigned to this object */ ObjectInstance.prototype.getClientInstance = function() { return this._clientInstance; }; /** * Returns the remote address of the facade this client instance * is connected to * @returns {*} */ ObjectInstance.prototype.getRemoteAddress = function() { return this.getClientInstance().getRemoteAddress(); }; /** * Returns the remote id of the facade this client instance * is connected to * @returns {*} */ ObjectInstance.prototype.getRemoteId = function() { return this._remoteId; }; /** * Return or create a context. * @param callId * @param createIfNotFound * @returns {*} */ ObjectInstance.prototype.getContext = function(callId, createIfNotFound) { if (this.hasContext(callId)) { return this._contexts[callId]; } else if (createIfNotFound) { if (!appUtils.isUuid(callId)) { throw new Error('invalid context id'); } var context = this._contexts[callId] = callContext(this, callId); this._contextsCount += 1; if (this._contextsCount == 1) { // Ensure no stale contexts this.getEndpointManager().registerPeriodic(this); } return context; } return null; }; /** * Get an API response for this object instance * @return {Object} API response */ ObjectInstance.prototype.getApi = function() { return { id: this.getId(), methods: this.getMethodNames() }; }; /** * Return a list of method names registered with this adapter. */ ObjectInstance.prototype.getMethodNames = function() { return Object.keys(this._methodIndex); }; /** * * @param callId * @returns {boolean} */ ObjectInstance.prototype.hasContext = function(callId) { if (this._contexts[callId]) { return true; } return false; }; /** * Handle a stream creation event from a remote facade or client instance * @param fromUuid * @param stream */ ObjectInstance.prototype._handleStream = function(stream, opts) { var type = stream.meta.type; var callId = stream.meta.id; // If the affinity is lost, end the stream. this.attachStream(stream); if (callId) { var context; switch (type) { case 'input': context = this.getContext(callId, true); context.setInputStream(stream); context.setBuffered(!opts.objectMode); break; case 'output': context = this.getContext(callId, true); context.setOutputStream(stream); break; default: log.log(log.ERROR, 'Malformed stream: %j for %s', stream.meta, this); stream.end(); return; } // Tell the call originator that the remote stream is ready. this.getMessenger().sendMessage(this.getRemoteAddress(), callId, { type: 'stream-connected' }); } }; /** * Handle an API request from a remote facade. * @param message */ ObjectInstance.prototype._handleMessage = function(message, source) { // Ensure that the source is within the expected neighborhood if (source > this._neighborhood) { return; } var callId = message.id; var callType = message.type; if (callId && callType) { switch (callType) { case 'close': this._remoteConnected = false; this.close(); return; case 'remote-stream': this._establishRemoteStream(callId, message); return; case 'call-facade': case 'call-ignore': case 'call': this._callMethod(callId, callType, message); return; case 'cancel': this.cancel(callId); return; } } log.log(log.ERROR, 'Malformed message: %j for %s', message, this); }; /** * Call the given method, executing the callback when finished * @param callId * @param callType * @param message * @private */ ObjectInstance.prototype._callMethod = function(callId, callType, message) { // Execute the context/call var context = this.getContext(callId, true); // Convert arguments, looking for Facades if (message.xargs && message.xargs.length > 0) { for (var i = 0; i < message.xargs.length; i++) { var arg = message.xargs[i]; var id = message.args[arg]; var remote = this.getClientInstance().getObjectInstance(id); if (remote) { message.args[arg] = remote.getObject(); } else { log.log(log.WARN, 'Unknown object id: %s', id); } } } // This method will process the result & send the // result message to the facade var resultFunction = function(type, data) { // Remove the context since it's finished this.removeContext(callId); if (type == 'result') { result = { type: 'result' }; if (callType == 'call') { // Only return the result if requested result.value = data; } else if (callType == 'call-facade') { // Derive the new name for the object instance; var newName = format('%s.%s', this.getName(), message.func); // Register the result as a facade. var objectInstance = this.getClientInstance() .createObjectInstance(newName, data, message.facadeId, this); if (objectInstance) { result.value = objectInstance.getApi(); } else { result = { type: 'error', message: 'Could not create the object instance', name: 'Error' }; } } } else { result = { type: 'error', message: data.message, name: data.name }; } // Send the result this.getMessenger().sendMessage( this.getRemoteAddress(), callId, result); }.bind(this); // Make sure the function exists, and call it. var result; if (this.hasMethod(message.func)) { context.execute(message.func, message.args, resultFunction); } else { log.log(log.ERROR, 'Method does not exist: %s for %s', message.func, this); resultFunction('error', new Error('Method not found')); } }; /** * Create a stream to the remote client instance from a facade request. * @param callId * @param message * @private */ ObjectInstance.prototype._establishRemoteStream = function(callId, message) { var context = this.getContext(callId, true); // Parse the remote address from the metadata var desiredRemoteAddress = message.remoteAddress, desiredRemoteId = message.remoteId; // Create a route to the destination var streamAddress = this.getRemoteAddress().routeThrough(address(desiredRemoteAddress, true)); // Create the remote stream. var stream = this.getStreamer().createStream( desiredRemoteId, streamAddress, { id: message.callId, type: 'input' }, { objectMode: !message.buffered }); // If the affinity is lost, end the stream. this.attachStream(stream); this.getMessenger().sendMessage(this.getRemoteAddress(), callId, { type: 'stream-connected' }); // Connect it to the call context.setOutputStream(stream); }; /** * Whether the method is registered in the method index * @param name */ ObjectInstance.prototype.hasMethod = function(name) { return !!this._methodIndex[name]; }; /** * Create a list of method names registered with this adapter. */ ObjectInstance.prototype._createMethodIndex = function() { var methodIndex = {}; var obj = this._object; // Generate the methods. var total = 0; for (var prop in obj) { if (typeof (obj[prop]) == 'function' && prop.charAt(0) !== '_') { methodIndex[prop] = true; total += 1; } } log.log(log.DEBUG2, 'Counted %s functions in object for %s', total, this); return methodIndex; }; /** * Ensure no contexts are stale * @private */ ObjectInstance.prototype.performPeriodic = function() { var contexts = Object.keys(this._contexts); for (var i = 0; i < contexts.length; i++) { var callId = contexts[i]; var ctx = this._contexts[callId]; ctx.incrementPeriodic(); if (ctx.getPeriodic() > 2) { log.log(log.DEBUG, 'Context is stale [ctx: %s] for %s', callId, this); this.cancel(callId); } } }; /** * Cancel the given call and clean it up * @param callId */ ObjectInstance.prototype.cancel = function(callId) { if (this.hasContext(callId)) { this._contexts[callId].cancel(); this.removeContext(callId); } }; /** * Stop listening for periodic updates & remove the context * @param callId */ ObjectInstance.prototype.removeContext = function(callId) { if (this.hasContext(callId)) { delete this._contexts[callId]; this._contextsCount -= 1; if (this._contextsCount === 0) { this.getEndpointManager().unregisterPeriodic(this); } } }; /** * Cancel all contexts * @private */ ObjectInstance.prototype._handleClose = function(affinityClosure) { // Tell the remote we're closing! if (this._remoteConnected && !affinityClosure) { this.getMessenger().sendMessage( this.getRemoteAddress(), this._remoteId, { id: this.getId(), type: 'close' }); } // Close all contexts var contexts = Object.keys(this._contexts); for (var callId in contexts) { this.cancel(callId); } }; }).call(this,"/js\\app\\api\\adapter\\object-instance.js") },{"../../endpoint/endpoint":26,"../../routing/address":36,"../../util/appUtils":54,"../../util/constants":55,"./context":3,"node-uuid":69,"util":92}],5:[function(require,module,exports){ /* * (C) 2016 * Booz Allen Hamilton, All rights reserved * Powered by InnoVision, created by the GIAT * * Endpoint.js was developed at the * National Geospatial-Intelligence Agency (NGA) in collaboration with * Booz Allen Hamilton [http://www.boozallen.com]. The government has * "unlimited rights" and is releasing this software to increase the * impact of government investments by providing developers with the * opportunity to take things in new directions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint -W097 */ /* globals __filename */ 'use strict'; module.exports = Resolver; /** * The resolver is used to determine if the facade criteria matches the metadata * for the adapter. This is the default resolver for an adapter, but * a custom resolver can be used. * @example <caption>Using a custom resolver</caption> * var metadata = { * tags: ['blue', 'green'] * }; * * var resolver = { * resolve: function(metadata, criteria) { * for (var i = 0; i < criteria.tags.length; i++) { * if (metadata.tags.indexOf(tag) !== -1) { * return true; * } * } * return false; * } * }; * * var adapter = window.endpoint.registerAdapter('mapapi', '1.0', * mapapi, metadata, resolver); * @param {Object} settings * @param {String} settings.id - the endpoint (adapter) id * @param {String} settings.instanceId - the endpoint.js instance id * @constructor */ function Resolver(settings) { if (!(this instanceof Resolver)) { return new Resolver(settings); } // Endpoint.js instance id. this._id = settings.id; this._instanceId = settings.instanceId; } /** * Respond to the key request. * @param {Object} metadata - adapter metadata set on adapter creation * @param {Object} criteria - criteria sent with the query * @param {RemoteAddress} remoteAddress - remote address information * @return boolean - resolved - whether the criteria matches this metadata. */ Resolver.prototype.resolve = function(criteria, metadata, remoteAddress) { // Match anything if (!criteria) { return true; } // Only resolve based on Endpoint.js instance id right now. if (criteria.hasOwnProperty('instanceId')) { if (criteria.instanceId !== this._instanceId) { return false; } } // Only resolved if the instance Id matches. if (criteria.hasOwnProperty('id')) { if (criteria.id !== this._id) { return false; } } return true; }; },{}],6:[function(require,module,exports){ (function (__filename){ /* * (C) 2016 * Booz Allen Hamilton, All rights reserved * Powered by InnoVision, created by the GIAT * * Endpoint.js was developed at the * National Geospatial-Intelligence Agency (NGA) in collaboration with * Booz Allen Hamilton [http://www.boozallen.com]. The government has * "unlimited rights" and is releasing this software to increase the * impact of government investments by providing developers with the * opportunity to take things in new directions. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint -W097 */ /* globals __filename */ 'use strict'; var adapter = require('./adapter/adapter'), client = require('./facade/client'), query = require('./facade/query'), facadeManager = require('./facade-manager'), appUtils = require('../util/appUtils'), log = appUtils.getLogger(__filename); module.exports = Api; /** * The Api class is added to the window and exposed as 'window.endpoint'. It is * the main entry interface to call or execute methods within Endpoint.js * @param {EndpointManager} endpointManager - An instance of the EndpointManager class * @param {Configuration} config - system configuration * @constructor */ function Api(endpointManager, config) { if (!(this instanceof Api)) { return new Api(endpointManager, config); } // Sender ID for this Endpoint.js. this._id = config.get('instanceId'); // Used to pass to new instances of facade, query and adapter this._endpointManager = endpointManager; log.log(log.DEBUG, 'API Layer initialized'); } /** * Return the Endpoint.js instance id for this API. * @returns {*} */ Api.prototype.getInstanceId = function() { return this._id; }; /** * Return the endpoint manager * @returns {*} */ Api.prototype.getEndpointManager = function() { return this._endpointManager; }; /** * This function will return the configuration used * to initially setup Endpoint.js. This is useful in order * to add new links, remove links, or to add new sockets, workers, or windows * to existing links. */ Api.prototype.getConfiguration = function() { return this._endpointManager.getConfiguration(); }; /** * Search the registry and send out a request for a specific adapter name, * returing the created facade to the application. * @param name - name of the adapter to look for. * @param version - version of the adapter to look for. * @param {Object} [settings] - additional parameters * @param {Object} [settings.criteria] - options passed to the adapter's resolver * @param {String} [settings.neighborhood] - how wide of a request to make (default to group) * @param {String} [settings.bridgeId] - send query to only links that are on this bridge * @param {String} [settings.hostId] - send query only to this host * @param {Boolean} [settings.tryForever] - whether to continue sending out bus messages until the adapter is found */ Api.prototype.createQuery = function(name, version, settings) { settings = settings || {}; // Create a facade. return query( this._endpointManager, { name: name, version: version, criteria: settings.criteria || {}, neighborhood: settings.neighborhood || 'local', tryForever: settings.hasOwnProperty('tryForever') ? settings.tryForever : true, bridgeId: settings.bridgeId, hostId: settings.hostId } ); }; /** * Search the registry and send out a request for a specific adapter name, * returning the created facade to the application. Additionally, * the request for adapters will be limited to internal servers only. * @param name - name of the adapter to look for. * @param version - version of the adapter to look for. * @param {Object} [settings] - additional parameters * @param {Object} [settings.criteria] - see createQuery. * @param {String} [settings.neighborhood] - how wide of a request to make (default to group) * @param {Object} [settings.api] - use the given api (from createQuery) instead of querying * @param {String} [settings.bridgeId] - send query to only links that are on this bridge * @param {String} [s