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
JavaScript
(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