diffusion
Advanced tools
Comparing version 5.6.7 to 5.7.0
{ | ||
"name": "diffusion", | ||
"version": "5.6.7", | ||
"version": "5.7.0", | ||
"description": "Diffusion Javascript UCI client", | ||
@@ -5,0 +5,0 @@ "keywords" : ["diffusion", "reappt", "websockets", "data"], |
var SessionImpl = require('session/session-impl'); | ||
var DataTypes = require('data/datatypes'); | ||
var Metadata = require('metadata/metadata'); | ||
var TopicSelectors = require('./topics/topic-selectors'); | ||
var Topics = require('./topics/topics'); | ||
var TopicSelectors = require('./selectors/topic-selectors'); | ||
var ErrorReport = require('services/error-report'); | ||
@@ -27,5 +29,5 @@ var ClientControl = require('services/control/client-control-options'); | ||
*/ | ||
version : '5.6.7', | ||
version : '5.7.0', | ||
build : '7_dev#internal', | ||
build : '0_01#37241', | ||
@@ -114,2 +116,7 @@ /** | ||
/** | ||
* Access {@link DataTypes}. | ||
*/ | ||
datatypes : DataTypes, | ||
/** | ||
* Access {@link Selectors}. | ||
@@ -125,2 +132,7 @@ */ | ||
/** | ||
* Access {@link Topics}. | ||
*/ | ||
topics : Topics, | ||
/** | ||
* Access {@link ErrorReport}. | ||
@@ -127,0 +139,0 @@ */ |
@@ -109,2 +109,26 @@ var interface = require('util/interface').interface; | ||
/** | ||
* Produce a {@link TypedSubscription} from this subscription stream, in order to receive values as a particular | ||
* {@link DataType}. The {@link TypedSubscription} will only receive values from topics that are of matching type | ||
* for the provided data type. | ||
* <P> | ||
* The lifecycle of the produced {@link TypedSubscription} is independent of this subscription. If this subscription | ||
* was created using {@link Session#stream} without a selector (i.e as a fallback stream), the | ||
* {@link TypedSubscription} will also be registered as a fallback stream. | ||
* | ||
* @param {DataType} datatype - The data type to produce a stream for. | ||
* @returns {TypedSubscription} A new Typed Subscription stream for the provided data type | ||
* | ||
* @example | ||
* // Produce a typed subscription for receiving JSON values. | ||
* var datatype = diffusion.datatypes.json(); | ||
* | ||
* session.subscribe('foo').asType(datatype).on('value', function(topic, value) { | ||
* //... | ||
* }); | ||
* | ||
* @function Subscription#asType | ||
*/ | ||
'asType', | ||
/** | ||
@@ -111,0 +135,0 @@ * Produce a {@link View} based on the topics selected by this |
@@ -43,7 +43,32 @@ var interface = require('util/interface').interface; | ||
* <ul> | ||
* <li><code>READ_TOPIC</code> - Subscribe or fetch topics</li> | ||
* <li><code>SELECT_TOPIC</code> - Use a topic selector that selects the topic path. | ||
* <p> | ||
* A session must have this permission for the path prefix of any topic selector | ||
* used to subscribe or fetch. | ||
* <p> | ||
* When the subscription or fetch request completes, the resulting topics | ||
* are further filtered based on the <code>READ_TOPIC</code> permission. | ||
* <p> | ||
* A session that has <code>READ_TOPIC</code> but not | ||
* <code>SELECT_TOPIC</code> for a particular topic path cannot | ||
* subscribe directly to topics belonging to the path. However, the session can | ||
* be independently subscribed by a control session that has the | ||
* <code>MODIFY_SESSION</code> global permission in addition to the | ||
* appropriate <code>SELECT_TOPIC</code> permission. | ||
* | ||
* </li> | ||
* <li><code>READ_TOPIC</code> - Required to receive information from a topic. | ||
* <p> | ||
* If a session does not have read_topic permission for a topic, the topic | ||
* will be excluded from the results of subscription or fetch operations for | ||
* the session, and the topic's details cannot be retrieved by the session. | ||
* | ||
* <p> | ||
* This permission is also required for a session to receive messages | ||
* based on the topic path associated with the message. | ||
* </li> | ||
* <li><code>UPDATE_TOPIC</code> - Update topics</li> | ||
* <li><code>MODIFY_TOPIC</code> - Add or remove topics</li> | ||
* <li><code>SEND_TO_SESSION</code> - Send a message another session</li> | ||
* <li><code>SEND_TO_MESSAGE_HANDLER</code> - Send a message ot a handler registered with the server</li> | ||
* <li><code>SEND_TO_MESSAGE_HANDLER</code> - Send a message to a handler registered with the server</li> | ||
* </ul> | ||
@@ -50,0 +75,0 @@ * |
@@ -26,21 +26,61 @@ var interface = require('util/interface').interface; | ||
* When a topic is created, it must be of a particular type, which constrains the kind of values that the topic will | ||
* allow. This type can either be explicitly provided, or inferred from a default value. | ||
* allow. This type can either be explicitly provided, or inferred from a default value. | ||
* <h5>Adding from value</h5> | ||
* <P> | ||
* The simplest way to add a topic is to provide a value. The derived types are described below: | ||
* <P> | ||
* <table class="table striped"> | ||
* <thead> | ||
* <tr> | ||
* <th>Value type</th> | ||
* <th>Topic type</th> | ||
* <th>Metadata</th> | ||
* <th>Initial value</th> | ||
* </tr> | ||
* </thead> | ||
* <tbody> | ||
* <tr> | ||
* <td>{@link DataTypes.JSON} or JSON object</td> | ||
* <td>{@link Session.topics.TopicType.JSON}</td> | ||
* <td>Not applicable</td> | ||
* <td>The supplied value</td> | ||
* </tr> | ||
* <tr> | ||
* <td>{@link DataTypes.Binary} or Buffer</td> | ||
* <td>{@link Session.topics.TopicType.BINARY}</td> | ||
* <td>Not applicable</td> | ||
* <td>The supplied value</td> | ||
* </tr> | ||
* <tr> | ||
* <td>{@link RecordContent}</td> | ||
* <td>{@link Session.topics.TopicType.RECORD}</td> | ||
* <td>{@link Metadata.RecordContent}</td> | ||
* <td>The supplied content</td> | ||
* </tr> | ||
* <tr> | ||
* <td>String</td> | ||
* <td>{@link Session.topics.TopicType.SINGLE_VALUE}</td> | ||
* <td>{@link Metadata.String}</td> | ||
* <td>The supplied value</td> | ||
* </tr> | ||
* <tr> | ||
* <td>Number</td> | ||
* <td>{@link Session.topics.TopicType.SINGLE_VALUE}</td> | ||
* <td>{@link Metadata.Integer} or {@link Metadata.Decimal}, depending on the decimal format of the number.</td> | ||
* <td>String representation of the supplied value</td> | ||
* </tr> | ||
* </tbody> | ||
* </table> | ||
* <P> | ||
* <h5>Adding from topic type</h5> | ||
* <P> | ||
* It is possible to directly specify the type of the topic to create, without an initial value. In this case, | ||
* just provide a string path and a {@link Session.topics.TopicType}. | ||
* <P> | ||
* <h5>Adding from metadata</h5> | ||
* <P> | ||
* To explicitly set the desired metadata type, provide a {@link Metadata} instance, which can be constructed from | ||
* the <code>diffusion.metadata</code> namespace. | ||
* the <code>diffusion.metadata</code> namespace. Optionally, a value corresponding to that same metadata definition | ||
* can be provided as the third parameter, to be set as the initial topic value. | ||
* <P> | ||
* It is also possible to provide an initial value for the topic, which will be stored as the topic's value upon | ||
* creation, and used to determine the type of allowable subsequent updates. The type of topic created will be based | ||
* on the type of the value: | ||
* <dl> | ||
* <dt>Number (integer)</dt> | ||
* <dd>{@link Metadata.Integer}</dd> | ||
* <dt>Number (decimal)</dt> | ||
* <dd>{@link Metadata.Decimal}</dd> | ||
* <dt>String</dt> | ||
* <dd>{@link Metadata.String}</dd> | ||
* <dt>Record</dt> | ||
* <dd>{@link Metadata.RecordContent}</dd> | ||
* </dl> | ||
* <P> | ||
* If the topic was added, or a topic already exists with the same path and type, the operation will succeed. If | ||
@@ -64,2 +104,16 @@ * there is a problem with adding the topic, then the result will be rejected with the error provided. | ||
* @example | ||
* // Create a topic with a datatype value | ||
* var value = diffusion.datatypes.json().from({ "hello" : "world" }); | ||
* | ||
* session.topics.add('foo/json1', value); | ||
* | ||
* @example | ||
* // Create a topic with a JSON value directly | ||
* session.topics.add('foo/json2', { "hello" : "world" }); | ||
* | ||
* @example | ||
* // Create a topic with a Topic Type | ||
* session.topics.add('foo/binary', session.topics.TopicType.BINARY); | ||
* | ||
* @example | ||
* // Create a topic using metadata and an initial value | ||
@@ -74,4 +128,6 @@ * var metadata = new diffusion.metadata.RecordContent(); | ||
* | ||
* session.topics.add('foo/bing', metadata, builder.build()); | ||
* var value = builder.build(); | ||
* | ||
* session.topics.add('foo/bing', metadata, value); | ||
* | ||
* @example | ||
@@ -86,4 +142,4 @@ * // Handle the add topic result | ||
* @param {String} path - The topic path to create. | ||
* @param {Metadata} [metadata] - The topic metadata. | ||
* @param {Object} [initial] - The initial topic value. | ||
* @param {Object|Session.topics.TopicType|Metadata} [supplied] - The supplied topic type/value. | ||
* @param {Object} [initial] - The initial topic value (if using metadata). | ||
* @returns {Result} A {@link Result} for this operation | ||
@@ -182,6 +238,9 @@ * @function Session.topics#add | ||
* <P> | ||
* An <code>error</code> event will be emitted if the update was not successful. It is necessary for the topic to | ||
* exist, and that the value type must be valid for the topic e.g. a topic added with {@link Metadata.Integer} | ||
* cannot accept a decimal value. | ||
* The value provided will be internally handled according to the same rules as defined for values passed to | ||
* {@link Session.topics#add}. If a value is of an incompatible type for the topic being updated, then the update | ||
* will be rejected. | ||
* <P> | ||
* It is necessary for the topic to exist. An <code>error</code> event will be emitted if the update was not | ||
* successful. | ||
* <P> | ||
* If the session is closed when calling this method, the returned result will also emit an <code>error</code> | ||
@@ -193,2 +252,9 @@ * event. | ||
* | ||
* @example | ||
* // Update topic with JSON content | ||
* var content = diffusion.datatypes.json().from({ "foo" : "bar" }); | ||
* | ||
* session.topics.update('foo/bar', content); | ||
* | ||
* @example | ||
* // Update topic with content from metadata | ||
@@ -259,6 +325,183 @@ * var builder = metadata.builder(); | ||
*/ | ||
'registerUpdateSource' | ||
'registerUpdateSource', | ||
/** | ||
* Register a {@link MissingTopicHandler} to handle requests for a branch of | ||
* the topic tree. | ||
* <p> | ||
* The provided handler is called when a client subscribes or fetches using | ||
* a topic selector that matches no existing topics. This allows a control | ||
* client to intercede when another session requests a topic that does not | ||
* exist. The control client may {@link Session.topics.add create the topic}, | ||
* perform some other action, or do nothing, before allowing the client | ||
* operation to proceed by calling {@link MissingTopicNotification#proceed() proceed()}. | ||
* Alternatively, the control client can call {@link MissingTopicNotification#cancel() | ||
* cancel()} to discard the request. | ||
* | ||
* <p> | ||
* A control client can register multiple handlers, but may only register a | ||
* single handler for a given topic path. See | ||
* {@link MissingTopicHandler#onRegister}. | ||
* A handler will only be called for topic selectors with a | ||
* {@link TopicSelector#prefix path prefix} that starts with or is | ||
* equal to {@code topicPath}. If the path prefix matches multiple handlers, | ||
* the one registered for the most specific (longest) topic path will be | ||
* called. | ||
* <P> | ||
* If the session is closed or the handler could not be registered, the returned | ||
* {@link Result} will call its failure callback, and the handler's | ||
* {@link MissingTopicHandler#onClose} or {@link MissingTopicHandler#onError} method | ||
* will be called. | ||
* | ||
* @param {String} topicPath identifies a branch in the topic tree | ||
* | ||
* @param {MissingTopicHandler} handler specifies the handler for the specified branch (unless | ||
* overridden by a handler registered against a more specific branch) | ||
* | ||
* @returns {Result} A result for this registration | ||
* | ||
* @function Session.topics#addMissingTopicHandler | ||
*/ | ||
'addMissingTopicHandler' | ||
]); | ||
/** | ||
* Handler called when a client session subscribes or fetches using a topic | ||
* selector that matches no topics. This interface must be implemented by the user. | ||
* <P> | ||
* Handler instances can be registered using | ||
* {@link Session.topics.#addMissingTopicHandler addMissingTopicHandler}. | ||
* | ||
* @class MissingTopicHandler | ||
*/ | ||
module.exports.MissingTopicHandler = interface('MissingTopicHandler', [ | ||
/** | ||
* Called when a client session requests a topic that does not exist, | ||
* and the topic path belongs to part of the topic tree for which this | ||
* handler was registered. | ||
* | ||
* <p> | ||
* The handler implementation should take the appropriate action (for | ||
* example, create the topic), and then call | ||
* {@link MissingTopicNotification#proceed() proceed} on the supplied | ||
* {@code notification}. This allows the client request to continue and | ||
* successfully resolve against the topic if it was created. | ||
* | ||
* <p> | ||
* Alternatively, the handler can call | ||
* {@link MissingTopicNotification#cancel() cancel} to discard the | ||
* request. A handler should always call <code>proceed</code> or | ||
* <code>cancel</code>, otherwise resources will continue to be reserved | ||
* on the server until the notification times out. | ||
* | ||
* @param {MissingTopicNotification} notification - The missing topic notification | ||
* | ||
* @function MissingTopicHandler#onMissingTopic | ||
*/ | ||
'onMissingTopic', | ||
/** | ||
* Called when the handler has been successfully registered with the server. | ||
* <P> | ||
* A session can register a single handler for a given branch of the topic tree. If there is already a handler | ||
* registered for the topic path the operation will fail and {@link MissingTopicHandler#onClose onClose} will be | ||
* called. | ||
* <P> | ||
* To deregister the handler, call the <pre>deregister</pre> function supplied. | ||
* | ||
* @param {String} path - The registration path | ||
* @param {Function} deregister - A function that may be called to deregister this handler | ||
* | ||
* @function MissingTopicHandler#onRegister | ||
*/ | ||
'onRegister', | ||
/** | ||
* Called when the handler is closed. The handler will be closed if the session is closed, or if the handler is | ||
* unregistered. | ||
* <P> | ||
* Once closed, no further calls will be made for the handler. | ||
* | ||
* @param {String} topicPath - The registration path | ||
* | ||
* @function MissingTopicHandler#onClose | ||
*/ | ||
'onClose', | ||
/** | ||
* Notification of a contextual error related to this handler. This is | ||
* analogous to an unchecked exception being raised. Situations in which | ||
* <code>onError</code> is called include the session being closed before the | ||
* handler is registered, a communication timeout, or a problem with the | ||
* provided parameters. No further calls will be made to this handler. | ||
* | ||
* @param {String} topicPath - The registration path | ||
* | ||
* @function MissingTopicHandler#onError | ||
*/ | ||
'onError' | ||
]); | ||
/** | ||
* Notification that a session has made a request using a selector that does | ||
* not match any topics. | ||
* <P> | ||
* Processing of the initial request will be halted until | ||
* {@link MissingTopicNotification#proceed proceed} is called, at which point | ||
* the selector will be resolved against the topic tree again. | ||
* <P> | ||
* If after calling <code>proceed</code> the selector still does not | ||
* match against any topics, no further notifications will be provided. | ||
* <P> | ||
* Should {@link MissingTopicNotification#cancel cancel} be called, or the | ||
* notification time out, the request will be discarded. The requesting | ||
* session will not be notified that their request has been cancelled. | ||
*/ | ||
module.exports.MissingTopicNotification = interface('MissingTopicNotification', [ | ||
/** | ||
* @property {String} The common root topic path derived from the requested topic selector | ||
*/ | ||
'path', | ||
/** | ||
* @property {TopicSelector} The topic selector that triggered this notification | ||
*/ | ||
'selector', | ||
/** | ||
* @property {String} Session ID of the client session that triggered this notification | ||
*/ | ||
'sessionID', | ||
/** | ||
* Instruct the server to complete processing of the session request. | ||
* <P> | ||
* This may be called after additional operations (such as adding | ||
* topics) have been performed, to allow the requested selector to be | ||
* resolved against the updated topic tree. | ||
* | ||
* <p> | ||
* For subscription requests, the topic selector will be added to the | ||
* client's topic selections. This will cause the client session to | ||
* become subscribed to topics that match the selector if they are added | ||
* later. | ||
* | ||
* @function MissingTopicNotification#proceed | ||
*/ | ||
'proceed', | ||
/** | ||
* Cancel the client request on the server. | ||
* <P> | ||
* Calling this will prevent any further processing of the request. For | ||
* subscription requests, the topic selector will be discarded. The | ||
* client session will not become subscribed to topics that match the | ||
* selector if they are added later. | ||
* | ||
* @function MissingTopicNotification#cancel | ||
*/ | ||
'cancel' | ||
]); | ||
/** | ||
* The TopicUpdateHandler interface for exclusive updates. This interface must be implemented by the user, to be | ||
@@ -365,1 +608,63 @@ * registered via {@link Session.topics#registerUpdateSource}. | ||
]); | ||
/** | ||
* The reason that a topic could not be added. | ||
* | ||
* @readonly | ||
* @enum | ||
*/ | ||
module.exports.TopicAddFailReason = { | ||
EXISTS : { | ||
id : 1, | ||
reason : "The topic already exists with the same details" | ||
}, | ||
EXISTS_MISMATCH : { | ||
id : 2, | ||
reason : "The topic already exists, with different details" | ||
}, | ||
INVALID_PATH : { | ||
id : 3, | ||
reason : "The topic path is invalid" | ||
}, | ||
INVALID_DETAILS : { | ||
id : 4, | ||
reason : "The topic details are invalid" | ||
}, | ||
USER_CODE_ERROR : { | ||
id : 5, | ||
reason : "A user supplied class could not be found or instantiated" | ||
}, | ||
TOPIC_NOT_FOUND : { | ||
id : 6, | ||
reason : "A referenced topic could not be found" | ||
}, | ||
PERMISSIONS_FAILURE : { | ||
id : 7, | ||
reason : "Invalid permissions to add a topic at the specified path" | ||
}, | ||
INITIALISE_ERROR : { | ||
id : 8, | ||
reason : "The topic could not be initialised, supplied value may be of the wrong format" | ||
}, | ||
UNEXPECTED_ERROR : { | ||
id : 9, | ||
reason : "An unexpected error occured while creating the topic" | ||
} | ||
}; | ||
/** | ||
* The reason that a topic could not be updated. | ||
* | ||
* @readonly | ||
* @enum | ||
*/ | ||
module.exports.UpdateFailReason = { | ||
SUCCESS : 0, | ||
INCOMPATIBLE_UPDATE : 1, | ||
UPDATE_FAILED : 2, | ||
INVALID_UPDATER : 3, | ||
MISSING_TOPIC : 4, | ||
INVALID_ADDRESS : 5, | ||
DUPLICATES : 6, | ||
EXCLUSIVE_UPDATER_CONFLICT : 7 | ||
}; |
var interface = require('util/interface').interface; | ||
/** | ||
* The Topic Feature. | ||
*/ | ||
var Topics = interface('Topics', [ | ||
@@ -7,0 +4,0 @@ /** |
var Emitter = require('events/emitter'); | ||
var sessionActivityMonitorModule = require('activity/session-activity-monitor'); | ||
var ServiceAdapter = require('client/service-adapter'); | ||
var ServiceLocator = require('client/service-locator'); | ||
var TopicRouting = require('client/routing'); | ||
var StreamRegistry = require('routing/stream-registry'); | ||
var TopicRouting = require('routing/topic-routing'); | ||
var TopicCache = require('routing/topic-cache'); | ||
var DataTypes = require('data/datatypes'); | ||
var serialisers = require('services/serialisers'); | ||
@@ -22,2 +28,4 @@ | ||
var connectionActivityMonitorFactory = require('activity/connection-activity-monitor-factory'); | ||
function InternalSession(conversationSet, serviceRegistry, connectionFactory) { | ||
@@ -29,2 +37,3 @@ var emitter = Emitter.assign(this); | ||
var token; | ||
var sessionActivityMonitor; | ||
@@ -63,2 +72,6 @@ var fsm = FSM.create('initialising', { | ||
this.getSessionId = function() { | ||
return sessionID; | ||
}; | ||
this.setPrincipal = function(newPrincipal) { | ||
@@ -76,9 +89,24 @@ principal = newPrincipal; | ||
}; | ||
var connection = connectionFactory.create(Aliases.create(), Transports.create()); | ||
var transports = Transports.create(); | ||
// Forward cascading notification events | ||
transports.on({ | ||
'transport-selected' : function(name) { | ||
emitter.emit('transport-selected', name); | ||
}, | ||
cascade : function() { | ||
emitter.emit('cascade'); | ||
} | ||
}); | ||
var connection = connectionFactory.create(Aliases.create(), transports); | ||
var serviceAdapter = new ServiceAdapter(this, serialisers, connection.send); | ||
var serviceLocator = new ServiceLocator(this, serialisers, serviceAdapter); | ||
var routing = new TopicRouting(serviceAdapter); | ||
var topicCache = new TopicCache(DataTypes); | ||
var topicStreamRegistry = new StreamRegistry(topicCache); | ||
var topicRouting = new TopicRouting(serviceAdapter, topicCache, topicStreamRegistry); | ||
serviceRegistry.addListener(serviceAdapter.addService); | ||
@@ -128,3 +156,3 @@ | ||
// Handle messages from connection | ||
connection.on('data', routing.route); | ||
connection.on('data', topicRouting.route); | ||
@@ -134,2 +162,8 @@ // Close events are terminal | ||
if (opts.activityMonitor) { | ||
sessionActivityMonitor = sessionActivityMonitorModule.create(connectionActivityMonitorFactory); | ||
} else { | ||
sessionActivityMonitor = sessionActivityMonitorModule.NOOP; | ||
} | ||
// Handle connection disconnections | ||
@@ -140,2 +174,3 @@ connection.on('disconnect', function(reason) { | ||
if (fsm.change('disconnected') || fsm.state === 'reconnecting') { | ||
sessionActivityMonitor.onConnectionClosed(); | ||
// Call reconnect if applicable | ||
@@ -163,2 +198,3 @@ if (opts.reconnect.timeout > 0 && reason.canReconnect) { | ||
} else { | ||
sessionActivityMonitor.onConnectionClosed(); | ||
log.debug('Unable to handle session disconnect, session state: ', fsm.state); | ||
@@ -175,3 +211,3 @@ } | ||
sessionID = response.identity; | ||
sessionActivityMonitor.onNewConnection(connection, response); | ||
emitter.emit('connect', response.identity); | ||
@@ -182,2 +218,3 @@ } else if (response.response === ResponseCode.RECONNECTED) { | ||
log.info('Reconnected session'); | ||
sessionActivityMonitor.onNewConnection(connection, response); | ||
emitter.emit('reconnect'); | ||
@@ -215,2 +252,3 @@ } | ||
if (fsm.change('closing')) { | ||
sessionActivityMonitor.onConnectionClosed(); | ||
connection.close(CloseReason.CLOSED_BY_CLIENT); | ||
@@ -234,7 +272,15 @@ } else { | ||
this.getStreamRegistry = function() { | ||
return topicStreamRegistry; | ||
}; | ||
this.getRouting = function() { | ||
return routing; | ||
return topicRouting; | ||
}; | ||
this.onSystemPing = function() { | ||
sessionActivityMonitor.onSystemPing(); | ||
}; | ||
} | ||
module.exports = InternalSession; |
@@ -35,8 +35,18 @@ var CommandHeader = require('services/command-header'); | ||
onResponse : function(cid, input) { | ||
var response = responseSerialiser.read(input); | ||
callback(null, response); | ||
remove(pending, cid); | ||
try { | ||
var response = responseSerialiser.read(input); | ||
return true; | ||
callback(null, response); | ||
remove(pending, cid); | ||
return true; | ||
} | ||
catch (e) { | ||
e.message = internalSession.getSessionId() + | ||
' failed to process response for service \'' + | ||
service.name + | ||
'\' cid=<' + cid + | ||
'> : ' + e.message; | ||
throw e; | ||
} | ||
}, | ||
@@ -43,0 +53,0 @@ onDiscard : function(cid, err) { |
@@ -1,2 +0,2 @@ | ||
var HashMap = require('hashmap').HashMap; | ||
var HashMap = require('hashmap'); | ||
@@ -3,0 +3,0 @@ /** |
@@ -1,2 +0,2 @@ | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var Codec = require('io/codec'); | ||
@@ -3,0 +3,0 @@ var util = require('content/util'); |
@@ -45,3 +45,5 @@ var Codec = require('io/codec'); | ||
function getBytes(content) { | ||
if (isRecordContent(content)) { | ||
if (content.$buffer) { | ||
return content.$buffer.slice(content.$offset, content.$length); | ||
} else if (isRecordContent(content)) { | ||
return getRecordContentBytes(content); | ||
@@ -48,0 +50,0 @@ } else { |
@@ -108,2 +108,8 @@ var Services = require('services/services'); | ||
module.exports.registerTopicHandler = function registerTopicHandler(internal, params, adapter) { | ||
return registerHandler(internal, params, adapter, | ||
Services.TOPIC_CONTROL_REGISTRATION, | ||
Services.TOPIC_CONTROL_DEREGISTRATION); | ||
}; | ||
module.exports.ResponseHandlerState = ResponseHandlerState; |
@@ -1,2 +0,2 @@ | ||
var HashMap = require('hashmap').HashMap; | ||
var HashMap = require('hashmap'); | ||
var Long = require('long'); | ||
@@ -3,0 +3,0 @@ |
@@ -73,3 +73,7 @@ var Long = require('long'); | ||
var buffer = this.buffer.slice(this.pos, found); | ||
this.pos = found + 1; | ||
if (found === this.count) { | ||
this.pos = found; | ||
} else { | ||
this.pos = found + 1; | ||
} | ||
@@ -76,0 +80,0 @@ return buffer; |
@@ -43,3 +43,3 @@ function ensureCapacity(bos, min) { | ||
function BufferOutputStream(initial) { | ||
if (initial instanceof Buffer) { | ||
if (Buffer.isBuffer(initial)) { | ||
this.buffer = initial; | ||
@@ -69,9 +69,14 @@ this.count = initial.length; | ||
* | ||
* @param {Buffer} - The bytes to append. | ||
* @param {Buffer} buffer - The bytes to append. | ||
* @param {Number} [offset=0] - The offset to start copying from the provided buffer | ||
* @param {Number} [length=buffer.length] - The amount of bytes to copy from the provided buffer | ||
*/ | ||
BufferOutputStream.prototype.writeMany = function(buffer) { | ||
ensureCapacity(this, this.count + buffer.length); | ||
BufferOutputStream.prototype.writeMany = function(buffer, offset, length) { | ||
offset = offset || 0; | ||
length = length || buffer.length; | ||
ensureCapacity(this, this.count + length); | ||
buffer.copy(this.buffer, this.count); | ||
this.count += buffer.length; | ||
buffer.copy(this.buffer, this.count, offset, offset + length); | ||
this.count += length; | ||
}; | ||
@@ -78,0 +83,0 @@ |
@@ -8,3 +8,3 @@ // Hack to avoid circular dependencies when checking types / deriving values | ||
var MString = require('./string'); | ||
var Type = require('./type'); | ||
var Type = require('../../topics/topic-type'); | ||
@@ -47,2 +47,10 @@ function createFromSingleValue(value) { | ||
function isMetadataValue(value) { | ||
return isMetadata(value) || | ||
typeof value === "string" || | ||
typeof value === "number" || | ||
typeof value === "boolean" || | ||
value === undefined; | ||
} | ||
function deriveMetadata(value) { | ||
@@ -64,3 +72,4 @@ if (value === undefined) { | ||
isMetadata : isMetadata, | ||
isMetadataValue : isMetadataValue, | ||
deriveMetadata : deriveMetadata | ||
}; |
// Utils that make use of MRecordContent | ||
var Type = require('./type'); | ||
var Type = require('../../topics/topic-type'); | ||
var util = require('./util-single-value'); | ||
var cutil = require('content/util'); | ||
@@ -21,2 +20,6 @@ | ||
function isMetadataValue(value) { | ||
return MRecordContent.isPrototypeOf(value) || util.isMetadataValue(value) || cutil.isRecordContent(value); | ||
} | ||
function deriveMetadata(value) { | ||
@@ -34,3 +37,4 @@ if (isMetadata(value)) { | ||
isMetadata : isMetadata, | ||
isMetadataValue : isMetadataValue, | ||
deriveMetadata : deriveMetadata | ||
}; |
var BufferInputStream = require('io/buffer-input-stream'); | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -34,2 +34,3 @@ var SessionId = require('session/session-id'); | ||
var sessionToken = SessionTokenDeserialiser(input); | ||
var systemPingPeriod = input.readInt64(); | ||
@@ -40,2 +41,3 @@ return { | ||
token : sessionToken, | ||
systemPingPeriod : systemPingPeriod, | ||
version : version, | ||
@@ -50,2 +52,3 @@ success : true | ||
token : null, | ||
systemPingPeriod : null, | ||
success : false | ||
@@ -52,0 +55,0 @@ }; |
@@ -5,3 +5,3 @@ module.exports = { | ||
PROTOCOL_BYTE : 35, | ||
PROTOCOL_VERSION : 6 | ||
PROTOCOL_VERSION : 7 | ||
}; |
@@ -8,5 +8,5 @@ function code(id, message) { | ||
var ResponseCode = { | ||
OK : code(100, "Connected succesfully"), | ||
OK : code(100, "Connected successfully"), | ||
DOWNGRADE : code(102, "Server does not support the requested protocol level"), | ||
RECONNECTED : code(105, "Reconnected succesfully"), | ||
RECONNECTED : code(105, "Reconnected successfully"), | ||
REJECTED : code(111, "Connection rejected"), | ||
@@ -13,0 +13,0 @@ CONNECTION_UNSUPPORTED : code(112, "Connection type not supported by connector"), |
@@ -9,3 +9,5 @@ var xmljson = require('./xmljson'); | ||
write : function(output, schema) { | ||
Codec.writeString(output, xmljson.json2xml(schema)); | ||
if (schema && (schema.field || schema.message)) { | ||
Codec.writeString(output, xmljson.json2xml(schema)); | ||
} | ||
} | ||
@@ -12,0 +14,0 @@ }; |
@@ -7,3 +7,3 @@ var Codec = require('io/codec'); | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var AddTopicFailureReason = require('services/add-topic/add-topic-failure-reason'); | ||
@@ -10,0 +10,0 @@ var TopicDetailsSerialiser = require('topics/details/topic-details-serialiser'); |
@@ -1,2 +0,2 @@ | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var Codec = require('io/codec'); | ||
@@ -3,0 +3,0 @@ |
var Codec = require('io/codec'); | ||
var Configuration = require('api/features/security').SystemAuthentication; | ||
var Configuration = require('features/security').SystemAuthentication; | ||
var ByteEncodedEnumSerialiser = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var ByteEncodedEnumSerialiser = require('serialisers/byte-encoded-enum-serialiser'); | ||
var SystemPrincipalSerialiser = require('./system-principal-serialiser'); | ||
@@ -6,0 +6,0 @@ |
var Codec = require('io/codec'); | ||
var SystemPrincipal = require('api/features/security').SystemPrincipal; | ||
var SystemPrincipal = require('features/security').SystemPrincipal; | ||
@@ -4,0 +4,0 @@ var serialiser = { |
var Codec = require('io/codec'); | ||
var ByteEncodedEnumSerialiser = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var ByteEncodedEnumSerialiser = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -4,0 +4,0 @@ var CommandError = require('./command-error'); |
var ControlGroupSerialiser = require('control/control-group-serialiser'); | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -4,0 +4,0 @@ var Services = require('services/services'); |
var ControlGroupSerialiser = require('control/control-group-serialiser'); | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -4,0 +4,0 @@ var Services = require('services/services'); |
@@ -8,3 +8,3 @@ var ClientControlOptions = require('services/control/client-control-options'); | ||
var Codec = require('io/codec'); | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -11,0 +11,0 @@ var serialiser = { |
var ControlGroupSerialiser = require('control/control-group-serialiser'); | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -4,0 +4,0 @@ var Services = require('services/services'); |
@@ -1,5 +0,5 @@ | ||
var TopicPermission = require('api/features/security').TopicPermission; | ||
var GlobalPermission = require('api/features/security').GlobalPermission; | ||
var TopicPermission = require('features/security').TopicPermission; | ||
var GlobalPermission = require('features/security').GlobalPermission; | ||
var Converter = require('api/serialisers/enum-converter'); | ||
var Converter = require('serialisers/enum-converter'); | ||
var curryR = require('util/function').curryR; | ||
@@ -6,0 +6,0 @@ var Codec = require('io/codec'); |
@@ -1,2 +0,2 @@ | ||
var Configuration = require('api/features/security').Configuration; | ||
var Configuration = require('features/security').Configuration; | ||
var RoleSerialiser = require('services/security/role-serialiser'); | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var Codec = require('io/codec'); | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
var HashMap = require('hashmap').HashMap; | ||
var HashMap = require('hashmap'); | ||
@@ -9,3 +9,3 @@ var CommandHeader = require('services/command-header'); | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
var TopicSelectorSerialiser = require('topics/topic-selector-serialiser'); | ||
@@ -28,3 +28,3 @@ | ||
serialisers.set(Boolean, require('api/serialisers/boolean-serialiser')); | ||
serialisers.set(Boolean, require('serialisers/boolean-serialiser')); | ||
@@ -53,4 +53,8 @@ // Assign command serialisers | ||
serialisers.set( | ||
require('./update-topic/update-topic-request'), | ||
require('./update-topic/update-topic-request-serialiser') | ||
); | ||
serialisers.set( | ||
require('./update-topic/update-topic-response'), | ||
require('./update-topic/update-topic-serialiser') | ||
require('./update-topic/update-topic-response-serialiser') | ||
); | ||
@@ -178,2 +182,14 @@ serialisers.set( | ||
); | ||
serialisers.set( | ||
require('services/update-topic/update-topic-set-request'), | ||
require('services/update-topic/update-topic-set-request-serialiser') | ||
); | ||
serialisers.set( | ||
require('services/update-topic/update-topic-delta-request'), | ||
require('services/update-topic/update-topic-delta-request-serialiser') | ||
); | ||
serialisers.set( | ||
require('services/missing-topic/missing-topic-request'), | ||
require('services/missing-topic/missing-topic-request-serialiser') | ||
); | ||
module.exports = serialisers; |
@@ -1,2 +0,2 @@ | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
@@ -10,3 +10,6 @@ var SubscriptionNotification = require('services/subscription-notification/subscription-notification'); | ||
var RemoveTopic = require('services/remove-topic/remove-topic'); | ||
var UpdateTopic = require('services/update-topic/update-topic-response'); | ||
var UpdateTopicRequest = require('services/update-topic/update-topic-request'); | ||
var UpdateTopicSetRequest = require('services/update-topic/update-topic-set-request'); | ||
var UpdateTopicDeltaRequest = require('services/update-topic/update-topic-delta-request'); | ||
var UpdateTopicResponse = require('services/update-topic/update-topic-response'); | ||
@@ -52,2 +55,4 @@ var SecurityCommandScript = require('services/authentication/security-command-script'); | ||
var MissingTopicRequest = require('services/missing-topic/missing-topic-request'); | ||
module.exports = { | ||
@@ -108,2 +113,8 @@ SUBSCRIBE : { | ||
}, | ||
MISSING_TOPIC : { | ||
id : 50, | ||
name : "Missing topic", | ||
request : MissingTopicRequest, | ||
response : Boolean | ||
}, | ||
TOPIC_SCOPED_WILL_REGISTRATION : { | ||
@@ -166,4 +177,4 @@ id : 53, | ||
name : "Update Topic", | ||
request : UpdateTopic, | ||
response : UpdateTopic | ||
request : UpdateTopicRequest, | ||
response : UpdateTopicResponse | ||
}, | ||
@@ -259,3 +270,15 @@ SERVER_CONTROL_REGISTRATION : { | ||
response : null | ||
}, | ||
UPDATE_TOPIC_SET : { | ||
id : 79, | ||
name : "Update topic set", | ||
request : UpdateTopicSetRequest, | ||
response : UpdateTopicResponse | ||
}, | ||
UPDATE_TOPIC_DELTA : { | ||
id : 80, | ||
name : "Update topic delta", | ||
request : UpdateTopicDeltaRequest, | ||
response : UpdateTopicResponse | ||
} | ||
}; |
@@ -1,2 +0,2 @@ | ||
var EnumSerialiser = require('api/serialisers/enum-converter'); | ||
var EnumSerialiser = require('serialisers/enum-converter'); | ||
@@ -3,0 +3,0 @@ var State = { |
var UpdateSourceUpdateResponse = require('services/topic-update/update-source-update-response'); | ||
var EnumSerialiser = require('api/serialisers/enum-converter'); | ||
var EnumSerialiser = require('serialisers/enum-converter'); | ||
var Codec = require('io/codec'); | ||
@@ -4,0 +4,0 @@ |
@@ -7,3 +7,3 @@ // API | ||
var UnsubscriptionNotification = require('./unsubscription-notification'); | ||
var ByteEncodedEnumSerialiser = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var ByteEncodedEnumSerialiser = require('serialisers/byte-encoded-enum-serialiser'); | ||
@@ -10,0 +10,0 @@ module.exports = { |
@@ -22,14 +22,18 @@ module.exports = { | ||
}, | ||
EXCLUSIVE_UPDATER_CONFLICT : { | ||
INVALID_ADDRESS : { | ||
id : 5, | ||
reason : "An exclusive update source is already registered for the topic branch" | ||
reason : "The update for a Paged Topic contained an invalid index" | ||
}, | ||
INVALID_ADDRESS : { | ||
DUPLICATES : { | ||
id : 6, | ||
reason : "The update for a Paged Topic contained an invalid index" | ||
reason : "The update for a Paged Topic contained invalid duplicate items" | ||
}, | ||
DUPLICATES : { | ||
EXCLUSIVE_UPDATER_CONFLICT : { | ||
id : 7, | ||
reason : "The update for a Paged Topic contained invalid duplicate items" | ||
reason : "An exclusive update source is already registered for the topic branch" | ||
}, | ||
DELTA_WITHOUT_VALUE : { | ||
id : 8, | ||
reason : "A delta was applied to a topic that does not yet have a value" | ||
} | ||
}; |
@@ -1,2 +0,2 @@ | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var TopicWillParameters = require('./topic-will-parameters'); | ||
@@ -3,0 +3,0 @@ var Codec = require('io/codec'); |
@@ -1,2 +0,2 @@ | ||
var BEES = require('api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var WillRegistrationResult = require('./will-registration-result'); | ||
@@ -3,0 +3,0 @@ |
@@ -11,5 +11,6 @@ // Internal components | ||
var Services = require('services/services'); | ||
var PingService = require('api/services/ping-service'); | ||
var NotifySubscriptionService = require('api/services/notify-subscription-service'); | ||
var NotifyUnsubscriptionService = require('api/services/notify-unsubscription-service'); | ||
var MonitoredPingService = require('client/services/monitored-ping-service'); | ||
var PingService = require('client/services/ping-service'); | ||
var NotifySubscriptionService = require('client/services/notify-subscription-service'); | ||
var NotifyUnsubscriptionService = require('client/services/notify-unsubscription-service'); | ||
@@ -33,3 +34,3 @@ // API components | ||
serviceRegistry.add(Services.USER_PING, PingService); | ||
serviceRegistry.add(Services.SYSTEM_PING, PingService); | ||
serviceRegistry.add(Services.SYSTEM_PING, MonitoredPingService); | ||
serviceRegistry.add(Services.SUBSCRIPTION_NOTIFICATION, NotifySubscriptionService); | ||
@@ -39,3 +40,4 @@ serviceRegistry.add(Services.UNSUBSCRIPTION_NOTIFICATION, NotifyUnsubscriptionService); | ||
// Create the lower-level session instance | ||
var internalSession = new InternalSession(conversationSet, serviceRegistry, ConnectionFactory); | ||
var internalSession = | ||
new InternalSession(conversationSet, serviceRegistry, ConnectionFactory); | ||
var session = new Session(internalSession, emitter); | ||
@@ -42,0 +44,0 @@ |
var Codec = require('io/codec'); | ||
var util = require('metadata/util'); | ||
var createSchema = require('schema/schema'); | ||
var serialiser = require('schema/serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var TopicType = require('../../../topics/topic-type'); | ||
@@ -14,26 +12,23 @@ /** | ||
read : function(input) { | ||
var details = {}; | ||
if (Codec.readBoolean(input)) { | ||
var type = Codec.readInt32(input); | ||
details.type = BEES.read(input, TopicType); | ||
if (Codec.readBoolean(input)) { | ||
return serialiser.read(input); | ||
details.schema = serialiser.read(input); | ||
} | ||
} | ||
return null; | ||
return details; | ||
}, | ||
write : function(output, meta) { | ||
if (meta) { | ||
write : function(output, details) { | ||
if (details) { | ||
Codec.writeBoolean(output, true); | ||
var type = util.getType(meta); | ||
Codec.writeInt32(output, type); | ||
BEES.write(output, details.type); | ||
var schema = createSchema(meta); | ||
if (schema) { | ||
if (details.schema) { | ||
Codec.writeBoolean(output, true); | ||
serialiser.write(output, schema); | ||
} else { | ||
Codec.writeBoolean(output, false); | ||
serialiser.write(output, details.schema); | ||
} | ||
@@ -40,0 +35,0 @@ |
@@ -5,3 +5,3 @@ var inherits = require('inherits'); | ||
var utils = require('topics/topic-path-utils'); | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
@@ -8,0 +8,0 @@ var DQ = utils.DescendantQualifier; |
var inherits = require('inherits'); | ||
var utils = require('topics/topic-path-utils'); | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
@@ -5,0 +5,0 @@ var DQ = utils.DescendantQualifier; |
var inherits = require('inherits'); | ||
var utils = require('topics/topic-path-utils'); | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
@@ -5,0 +5,0 @@ var DELIMITER = "////"; |
@@ -5,3 +5,3 @@ var inherits = require('inherits'); | ||
var utils = require('topics/topic-path-utils'); | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
@@ -8,0 +8,0 @@ var DQ = utils.DescendantQualifier; |
@@ -6,3 +6,3 @@ var split = require('util/string').split; | ||
// API Topic Selector | ||
var TopicSelector = require('../../topics/topic-selector'); | ||
var TopicSelector = require('../../selectors/topic-selector'); | ||
@@ -9,0 +9,0 @@ // Internal topic classes |
var WSTransport = require('transports/ws'); | ||
var XHRTransport = require('transports/xhr'); | ||
var encodeAsString = require('v4-stack/credential-tunnel').encodeAsString; | ||
var Emitter = require('events/emitter'); | ||
var Queue = require('util/queue'); | ||
var ResponseCode = require('protocol/response-code'); | ||
var BufferInputStream = require('io/buffer-input-stream'); | ||
var BufferOutputStream = require('io/buffer-output-stream'); | ||
var standardSubtransports = require('transports/subtransports'); | ||
var logger = require('util/logger').create('Cascading'); | ||
@@ -7,19 +16,183 @@ /** | ||
*/ | ||
function Transports() { | ||
function Transports(subtransports) { | ||
var emitter = Emitter.assign(this); | ||
var self = this; | ||
/** | ||
* Get a new transport. If multiple transports are available, this will return | ||
* the next available type. | ||
* | ||
* @param {Options} options - Connection options to pass to the transport | ||
* The name of the currently selected transport. | ||
*/ | ||
this.get = function get(opts) { | ||
return new WSTransport(opts); | ||
this.name = null; | ||
/** | ||
* The connection options. | ||
*/ | ||
var opts = null; | ||
/** | ||
* The transports queue for cascading. | ||
*/ | ||
var transports = null; | ||
/** | ||
* The current transport object. | ||
*/ | ||
var transport = null; | ||
/** | ||
* The connection request. | ||
*/ | ||
var req = null; | ||
/** | ||
* The handshake. | ||
*/ | ||
var hand = null; | ||
/** | ||
* Called when the transport emits a data event. | ||
*/ | ||
function onData(data) { | ||
emitter.emit('data', data); | ||
} | ||
/** | ||
* Called when the transport emits a close event. | ||
*/ | ||
function onClose(reason) { | ||
emitter.emit('close', reason); | ||
} | ||
/** | ||
* Called when the transport emits an error event. | ||
*/ | ||
function onError(error) { | ||
emitter.emit('error', error); | ||
} | ||
/** | ||
* Close the current transport quietly, without generating any events. | ||
*/ | ||
function closeQuietly() { | ||
if (transport) { | ||
transport.off('data', onData); | ||
transport.off('close', onClose); | ||
transport.off('error', onError); | ||
transport.close(); | ||
transport = null; | ||
} | ||
} | ||
/** | ||
* Create the selected transport if enabled. | ||
* @returns {*} an available transport if enabled | ||
*/ | ||
function createTransport() { | ||
if (self.name === 'WS' && subtransports.ws.enabled) { | ||
return new WSTransport(opts, subtransports.ws.constructor); | ||
} else if (self.name === 'XHR' && subtransports.xhr.enabled) { | ||
return new XHRTransport(opts, subtransports.xhr.constructor); | ||
} else { | ||
return null; | ||
} | ||
} | ||
/** | ||
* Select and create the next enabled transport. | ||
*/ | ||
function selectTransport(suppressClose) { | ||
if (transports.length === 0) { | ||
self.name = null; | ||
transport = null; | ||
logger.debug('Transports exhausted'); | ||
emitter.emit('transports-exhausted'); | ||
if (!suppressClose) { | ||
emitter.emit('close'); | ||
} | ||
return; | ||
} | ||
self.name = transports.shift(); | ||
transport = createTransport(); | ||
if (transport === null) { | ||
if (transports.length > 0) { | ||
selectTransport(); | ||
} else { | ||
self.name = null; | ||
transport = null; | ||
logger.debug('Transports exhausted'); | ||
emitter.emit('transports-exhausted'); | ||
if (!suppressClose) { | ||
emitter.emit('close'); | ||
} | ||
} | ||
} else { | ||
logger.debug('Selecting transport', self.name); | ||
emitter.emit('transport-selected', self.name); | ||
} | ||
} | ||
this.get = function get(options) { | ||
opts = options; | ||
transports = opts.transports.slice(); | ||
selectTransport(); | ||
return self; | ||
}; | ||
this.cascade = function cascade(suppressClose) { | ||
closeQuietly(); | ||
selectTransport(suppressClose); | ||
if (transport === null) { | ||
return self; | ||
} | ||
emitter.emit('cascade'); | ||
self.connect(req, hand); | ||
return self; | ||
}; | ||
this.connect = function connect(request, handshake) { | ||
if (transport !== null) { | ||
logger.debug('Attempting to connect'); | ||
emitter.emit('cascading-connect'); | ||
req = request; | ||
hand = handshake; | ||
transport.on('data', onData); | ||
transport.on('close', onClose); | ||
transport.on('error', onError); | ||
transport.connect(request, handshake); | ||
} | ||
}; | ||
this.dispatch = function dispatch(message) { | ||
if (transport === null) { | ||
throw new Error('Unable to send message when no transport is set'); | ||
} | ||
transport.dispatch(message); | ||
}; | ||
this.close = function close() { | ||
if (transport !== null) { | ||
// A close event will be emitted by the transport after the close is complete | ||
transport.close(); | ||
transport = null; | ||
} | ||
}; | ||
} | ||
Transports.create = function() { | ||
return new Transports(); | ||
/** | ||
* Create a new transports object. | ||
*/ | ||
Transports.create = function(subtransports) { | ||
if (subtransports) { | ||
return new Transports(subtransports); | ||
} else { | ||
return new Transports(standardSubtransports); | ||
} | ||
}; | ||
module.exports = Transports; |
@@ -0,3 +1,3 @@ | ||
var encodeAsString = require('v4-stack/credential-tunnel').encodeAsString; | ||
var NodeWebSocket = require('ws'); | ||
@@ -9,13 +9,2 @@ var Emitter = require('events/emitter'); | ||
var constructor; | ||
// Browser version does not include 'ws' lib; use native impl | ||
if (typeof NodeWebSocket === 'function') { | ||
log.debug('Using Node WS library'); | ||
constructor = NodeWebSocket; | ||
} else { | ||
log.debug('Using native websocket'); | ||
constructor = WebSocket; | ||
} | ||
/** | ||
@@ -49,3 +38,3 @@ * Construct a valid websocket URI from the given options. | ||
function WSTransport(opts, ws) { | ||
constructor = ws || constructor; | ||
var constructor = ws; | ||
@@ -113,10 +102,2 @@ if (!constructor || !constructor instanceof Function) { | ||
/** | ||
* | ||
* @returns {*} If the transport can be used | ||
*/ | ||
WSTransport.isEnabled = function() { | ||
return constructor; | ||
}; | ||
module.exports = WSTransport; |
var Update = require('./update'); | ||
var BEES = require('../api/serialisers/byte-encoded-enum-serialiser'); | ||
var BEES = require('serialisers/byte-encoded-enum-serialiser'); | ||
var ContentSerialiser = require('../content/serialiser'); | ||
@@ -4,0 +4,0 @@ |
@@ -13,5 +13,6 @@ function reason(id, message, canReconnect) { | ||
TRANSPORT_ERROR : reason(6, "There was an unexpected error with the connection", true), | ||
CONNECTION_ERROR : reason(7, "A connection to the server was unable to be established", true) | ||
CONNECTION_ERROR : reason(7, "A connection to the server was unable to be established", true), | ||
IDLE_CONNECTION : reason(8, "The activity monitor detected the connection was idle", true) | ||
}; | ||
module.exports = CloseReason; |
@@ -40,3 +40,3 @@ var connectionResponseDeserialiser = require('protocol/connection-response-deserialiser'); | ||
var closeReason; | ||
var transport; | ||
var transport = null; | ||
@@ -57,3 +57,2 @@ fsm.on('change', function(previous, current) { | ||
} | ||
emitter.emit('data', message); | ||
@@ -64,2 +63,3 @@ } catch (e) { | ||
if (fsm.change('closed')) { | ||
// Should this be TRANSPORT_ERROR? | ||
closeReason = CloseReason.CONNECTION_ERROR; | ||
@@ -73,6 +73,16 @@ transport.close(); | ||
function onClose(reason) { | ||
if (fsm.state === 'connecting' && closeReason === CloseReason.CONNECTION_ERROR) { | ||
transport = transport.cascade(true); | ||
if (transport !== null && transport.name !== null) { | ||
// Attempting cascade | ||
return; | ||
} | ||
// Transport closed, close event suppressed to prevent reentering this handler | ||
} | ||
if (fsm.change('disconnected') || fsm.change('closed')) { | ||
clearTimeout(scheduledClose); | ||
logger.trace('Transport closed: ', reason); | ||
logger.trace('Transport closed: ', closeReason); | ||
@@ -89,8 +99,12 @@ if (fsm.state === 'disconnected') { | ||
function isCascadableResponse(response) { | ||
return response === ResponseCode.ERROR || | ||
response === ResponseCode.UNKNOWN_SESSION || | ||
response === ResponseCode.LICENSE_EXCEEDED; | ||
} | ||
// Handle connection responses | ||
function handshake(message) { | ||
clearTimeout(scheduledClose); | ||
logger.trace('Received connection handshake response'); | ||
logger.trace('Received connection handshake response'); | ||
var response; | ||
@@ -101,7 +115,15 @@ | ||
} catch (e) { | ||
logger.trace('Unable to deserialise handshake response', e); | ||
closeReason = CloseReason.HANDSHAKE_ERROR; | ||
transport.close(); | ||
transport = transport.cascade(); | ||
if (transport !== null && transport.name !== null) { | ||
// Attempting cascade | ||
return; | ||
} | ||
// Transport closed and close event emitted | ||
clearTimeout(scheduledClose); | ||
logger.trace('Unable to deserialise handshake response', e); | ||
return; | ||
@@ -111,4 +133,12 @@ } | ||
logger.trace('Connection response: ', response.response); | ||
if (response.success && fsm.change('connected')) { | ||
if (isCascadableResponse(response.response)) { | ||
closeReason = CloseReason.HANDSHAKE_REJECTED; | ||
transport = transport.cascade(); | ||
if (transport !== null && transport.name !== null) { | ||
// Attempting cascade | ||
return; | ||
} | ||
// Transport closed and close event emitted | ||
} else if (response.success && fsm.change('connected')) { | ||
closeReason = CloseReason.TRANSPORT_ERROR; | ||
@@ -120,2 +150,5 @@ emitter.emit('connect', response); | ||
} | ||
clearTimeout(scheduledClose); | ||
return response; | ||
} | ||
@@ -136,2 +169,4 @@ | ||
closeReason = CloseReason.CONNECTION_ERROR; | ||
// Begin a connection cycle with a transport | ||
transport = transports.get(opts); | ||
@@ -141,5 +176,14 @@ | ||
transport.on('close', onClose); | ||
transport.connect(request, handshake); | ||
try { | ||
// Attempt to connect the transport | ||
transport.connect(request, handshake); | ||
} | ||
catch (e) { | ||
transport = transport.cascade(); | ||
if (transport === null || transport.name === null) { | ||
throw e; | ||
} | ||
} | ||
// Ensure we will emit a close reason if we're unable to connect within a timeout | ||
@@ -197,2 +241,10 @@ scheduledClose = setTimeout(function() { | ||
this.closeIdleConnection = function() { | ||
if (fsm.change('disconnecting')) { | ||
logger.debug('Connection detected as idle'); | ||
closeReason = CloseReason.IDLE_CONNECTION; | ||
transport.close(); | ||
} | ||
}; | ||
/** | ||
@@ -199,0 +251,0 @@ * Get the current state of the connection. |
@@ -63,2 +63,7 @@ var inherits = require('inherits'); | ||
var type = bis.read(); | ||
if (systemHeaders[type] === undefined) { | ||
throw new Error('Invalid message type: ' + type); | ||
} | ||
var headers = bis.readUntil(0x01).toString().split('\u0002'); | ||
@@ -75,3 +80,2 @@ | ||
} | ||
return new Message(type, encoding.NONE, fields, body); | ||
@@ -107,2 +111,3 @@ }; | ||
var sh = []; | ||
var headers = ''; | ||
@@ -113,7 +118,13 @@ systemHeaders[message.type].forEach(function(h) { | ||
// CLOSE_REQUEST has no header or body | ||
if (systemHeaders[message.type].length === 0) { | ||
return headers; | ||
} | ||
if (sh.length > 0) { | ||
return sh.join('\u0002') + '\u0001'; | ||
headers = sh.join('\u0002'); | ||
headers += '\u0001'; | ||
} | ||
return '\u0001'; | ||
return headers; | ||
}; | ||
@@ -120,0 +131,0 @@ |
var DEFAULT_HOST = 'localhost'; | ||
var DEFAULT_PORT = 80; | ||
var DEFAULT_SECURE_PORT = 443; | ||
@@ -7,2 +9,27 @@ | ||
// Browser environment checks | ||
if (typeof window !== 'undefined') { | ||
if (window.location.hostname) { | ||
DEFAULT_HOST = window.location.hostname; | ||
} | ||
// A blank port indicates it's the default associated with the protocol (80 or 443) | ||
if (window.location.protocol === "http:") { | ||
DEFAULT_SECURE = false; | ||
if (window.location.port) { | ||
DEFAULT_PORT = parseInt(window.location.port); | ||
} | ||
} | ||
if (window.location.protocol === "https:") { | ||
DEFAULT_SECURE = true; | ||
// If we're served over https and have an explicit port, assume that as default | ||
if (window.location.port) { | ||
DEFAULT_SECURE_PORT = parseInt(window.location.port); | ||
} | ||
} | ||
} | ||
var DEFAULT_RECONNECT_TIMEOUT = 60000; | ||
@@ -20,2 +47,6 @@ var DEFAULT_RECONNECT_STRATEGY = function(start, abort) { | ||
var DEFAULT_ACTIVITY_MONITOR = true; | ||
var DEFAULT_TRANSPORTS = ['WS']; | ||
/** | ||
@@ -47,3 +78,5 @@ * Provide Session configuration options. | ||
// in options.host. | ||
if (options.host && options.host.indexOf(':') > -1) { | ||
if (options.host === undefined) { | ||
options.host = DEFAULT_HOST; | ||
} else if (options.host.indexOf(':') > -1) { | ||
var parts = options.host.split(':'); | ||
@@ -54,2 +87,3 @@ | ||
} | ||
options.host = parts[0]; | ||
@@ -60,17 +94,16 @@ } | ||
if (options.port === undefined) { // Default to secure on secure port. | ||
options.secure = true; | ||
options.port = DEFAULT_SECURE_PORT; | ||
options.secure = DEFAULT_SECURE; | ||
} else { // If specified port 80, default to insecure else secure. | ||
options.secure = options.port === DEFAULT_PORT ? false : true; | ||
options.secure = options.port === DEFAULT_SECURE_PORT ? true : false; | ||
} | ||
} else { | ||
if (options.port === undefined) { // Security specified but not port, choose 443 or 80? | ||
options.port = options.secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT; | ||
} | ||
} | ||
this.host = options.host || DEFAULT_HOST; | ||
this.port = options.port || DEFAULT_SECURE_PORT; | ||
this.secure = (options.secure !== undefined) ? options.secure : DEFAULT_SECURE; | ||
if (options.port === undefined) { // Security specified but not port, choose 443 or 80? | ||
options.port = options.secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT; | ||
} | ||
this.host = options.host; | ||
this.port = options.port; | ||
this.secure = options.secure; | ||
if (options.reconnect === undefined || (typeof options.reconnect === 'boolean') && options.reconnect) { | ||
@@ -103,6 +136,23 @@ this.reconnect = { | ||
if (options.principal !== undefined) { | ||
this.principal = options.principal || DEFAULT_PRINCIPAL; | ||
this.credentials = options.credentials || DEFAULT_PASSWORD; | ||
if (options.principal !== undefined) { | ||
this.principal = options.principal || DEFAULT_PRINCIPAL; | ||
this.credentials = options.credentials || DEFAULT_PASSWORD; | ||
} | ||
if (typeof options.transports === 'string') { | ||
this.transports = [options.transports]; | ||
} else if (typeof options.transports === 'object' && options.transports instanceof Array) { | ||
this.transports = options.transports.slice(); | ||
} else { | ||
this.transports = DEFAULT_TRANSPORTS.slice(); | ||
} | ||
toUpperCaseTransportList(this.transports); | ||
this.activityMonitor = (options.activityMonitor !== undefined) ? options.activityMonitor : DEFAULT_ACTIVITY_MONITOR; | ||
} | ||
function toUpperCaseTransportList(transports) { | ||
for (var i = 0; i < transports.length; i++) { | ||
transports[i] = transports[i].toUpperCase(); | ||
} | ||
} | ||
@@ -109,0 +159,0 @@ |
// Namespaced features | ||
var Messages = require('api/features/messages'); | ||
var Security = require('api/features/security').Security; | ||
var TopicControl = require('api/features/topic-control'); | ||
var ClientControl = require('api/features/client-control'); | ||
var Messages = require('features/messages'); | ||
var Security = require('features/security').Security; | ||
var TopicControl = require('features/topic-control'); | ||
var ClientControl = require('features/client-control'); | ||
// Features to implement | ||
var features = [ | ||
require('api/features/topics') | ||
require('features/topics') | ||
]; | ||
@@ -11,0 +11,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
478090
255
13292