diffusion
Advanced tools
Comparing version 5.7.5 to 5.8.0
{ | ||
"name": "diffusion", | ||
"version": "5.7.5", | ||
"version": "5.8.0", | ||
"description": "Diffusion Javascript UCI client", | ||
@@ -5,0 +5,0 @@ "keywords" : ["diffusion", "reappt", "websockets", "data"], |
@@ -29,5 +29,5 @@ var SessionImpl = require('session/session-impl'); | ||
*/ | ||
version : '5.7.5', | ||
version : '5.8.0', | ||
build : '5_dev#internal', | ||
build : '0_dev#internal', | ||
@@ -77,3 +77,3 @@ /** | ||
connect : function(options) { | ||
var sessionImpl = new SessionImpl(); | ||
var sessionImpl = new SessionImpl(options); | ||
var session = sessionImpl.get(); | ||
@@ -111,3 +111,3 @@ | ||
sessionImpl.connect(options); | ||
sessionImpl.connect(); | ||
@@ -114,0 +114,0 @@ return r; |
@@ -6,3 +6,3 @@ var interface = require('util/interface').interface; | ||
* An aggregate view of topics and their values, reflected as a single structure | ||
* that is updated in real-time. | ||
* that is updated in real time. | ||
* <P> | ||
@@ -9,0 +9,0 @@ * Because {@link View Views} are bound to a particular topic selector, |
@@ -105,2 +105,54 @@ var interface = require('util/interface').interface; | ||
'setSessionPropertiesListener', | ||
/** | ||
* Event types used within {@link Session.clients.SessionPropertiesListener#onSessionEvent}. | ||
* | ||
* <table class="table striped"> | ||
* <thead> | ||
* <tr> | ||
* <th>Event type</th> | ||
* <th>Description</th> | ||
* </tr> | ||
* </thead> | ||
* <tbody> | ||
* <tr> | ||
* <td><code>UPDATED</code></td> | ||
* <td>One or more relevant session properties have been updated.</td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>DISCONNECTED</code></td> | ||
* <td>A session has disconnected</td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>RECONNECTED</code></td> | ||
* <td>A session has reconnected</td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>FAILED_OVER</code></td> | ||
* <td>A session has failed over from one server to another in a cluster.</td> | ||
* </tr> | ||
* </tbody> | ||
* </table> | ||
* | ||
* @example | ||
* session.clients.setSessionPropertiesListener(props, { | ||
* // ... | ||
* | ||
* onSessionEvent : function(sessionID, event, properties, previous) { | ||
* switch (event) { | ||
* case session.clients.SessionEventType.DISCONNECTED : | ||
* console.log(sessionID + " has disconnected"); | ||
* break; | ||
* case session.clients.SessionEventType.RECONNECTED : | ||
* console.log(sessionID + " has reconnected"); | ||
* break; | ||
* } | ||
* } | ||
* | ||
* // ... | ||
* }); | ||
* | ||
* @property {Object} Session.clients.SessionEventType | ||
*/ | ||
'SessionEventType' | ||
]); | ||
@@ -107,0 +159,0 @@ |
@@ -246,2 +246,3 @@ var interface = require('util/interface').interface; | ||
* @return {String} The script | ||
* @function SystemAuthenticationScriptBuilder#build | ||
*/ | ||
@@ -257,2 +258,3 @@ 'build', | ||
* @return {ScriptBuilder} A new builder containing the changed roles | ||
* @function SystemAuthenticationScriptBuilder#assignRoles | ||
*/ | ||
@@ -269,2 +271,3 @@ 'assignRoles', | ||
* @return {ScriptBuilder} A new builder containing the new principal | ||
* @function SystemAuthenticationScriptBuilder#addPrincipal | ||
*/ | ||
@@ -294,2 +297,3 @@ 'addPrincipal', | ||
* @return {ScriptBuilder} A new builder containing the verification command | ||
* @function SystemAuthenticationScriptBuilder#verifyPassword | ||
*/ | ||
@@ -304,2 +308,3 @@ 'verifyPassword', | ||
* @return {ScriptBuilder} A new builder containing the remove command | ||
* @function SystemAuthenticationScriptBuilder#removePrincipal | ||
*/ | ||
@@ -314,2 +319,3 @@ 'removePrincipal', | ||
* @return {ScriptBuilder} A new builder containing the allow anonymous connections command. | ||
* @function SystemAuthenticationScriptBuilder#allowAnonymousConnections | ||
*/ | ||
@@ -323,2 +329,3 @@ 'allowAnonymousConnections', | ||
* @return {ScriptBuilder} A new builder containing the deny anonymous connections command. | ||
* @function SystemAuthenticationScriptBuilder#denyAnonymousConnections | ||
*/ | ||
@@ -333,2 +340,3 @@ 'denyAnonymousConnections', | ||
* @return {ScriptBuilder} A new builder containing the abstain anonymous connections command. | ||
* @function SystemAuthenticationScriptBuilder#abstainAnonymousConnections | ||
*/ | ||
@@ -335,0 +343,0 @@ 'abstainAnonymousConnections' |
@@ -22,3 +22,12 @@ var interface = require('util/interface').interface; | ||
* page. | ||
* <P> | ||
* When calling {@link Session#subscribe}, a {@link Subscription} object is | ||
* returned to bind event listeners to. This object may be closed via | ||
* {@link Subscription#close}, which will remove all event listeners and emit | ||
* no further events. Closing a {@link Subscription} object is different to | ||
* unsubscribing via {@link Session#unsubscribe} as it is a purely client-side | ||
* operation that only affects the single {@link Subscription} returned from the | ||
* initial {@link Session#subscribe} call. | ||
* | ||
* | ||
* @example | ||
@@ -69,3 +78,5 @@ * // Subscribe to a single topic with a callback function for updates | ||
* associated {@link Subscription}s to emit an <code>unsubscribe</code> | ||
* event. | ||
* event. Any {@link Subscription} objects produced from {@link Session#subscribe} | ||
* or {@link Session#stream} will remain open, and will continue to emit | ||
* updates for topics that the session has not been unsubscribed from. | ||
* | ||
@@ -85,4 +96,4 @@ * @example | ||
* This method operates in a similar way to {@link Session#subscribe}, except it will not cause the server to | ||
* send any topic updates. This allows the registration of listeners prior to actually subscribing, or to | ||
* add/remove listeners independently of subscriptions on the server. | ||
* send any topic updates unless already subscribed. This allows the registration of listeners prior to actually | ||
* subscribing, or to add/remove listeners independently of subscriptions on the server. | ||
* <P> | ||
@@ -106,2 +117,12 @@ * If no selector is provided, the {@link Subscription} stream created will receive all topic events that have not | ||
* @example | ||
* // Maintain a single listener independently of subscriptions/unsubscriptions | ||
* session.stream('?foo/.*').on('update', function(update, topic) { | ||
* console.log('Received an update for : ' + topic, update); | ||
* }); | ||
* | ||
* // The session can subscribe and unsubscribe from specific selectors separately from receiving updates. | ||
* session.subscribe('foo/bar'); | ||
* session.unsubscribe('foo/baz'); | ||
* | ||
* @example | ||
* // Receive topic events for topics that aren't handled by any other listeners | ||
@@ -108,0 +129,0 @@ * session.stream().on({ |
@@ -30,3 +30,3 @@ var Emitter = require('events/emitter'); | ||
function InternalSession(conversationSet, serviceRegistry, connectionFactory) { | ||
function InternalSession(conversationSet, serviceRegistry, connectionFactory, opts) { | ||
var emitter = Emitter.assign(this); | ||
@@ -100,3 +100,3 @@ | ||
var connection = connectionFactory.create(Aliases.create(), transports); | ||
var connection = connectionFactory.create(Aliases.create(), transports, opts.reconnect.timeout, 256); | ||
@@ -111,3 +111,3 @@ var serviceAdapter = new ServiceAdapter(this, serialisers, connection.send); | ||
serviceRegistry.addListener(serviceAdapter.addService); | ||
// Close the session | ||
@@ -129,3 +129,7 @@ function close(reason) { | ||
var request = ConnectionRequest.reconnect(token); | ||
var request = ConnectionRequest.reconnect( | ||
token, | ||
connection.getAvailableSequence(), | ||
connection.lastReceivedSequence); | ||
connection.connect(request, opts, opts.reconnect.timeout); | ||
@@ -147,3 +151,7 @@ } else { | ||
this.connect = function(opts) { | ||
function replaceConversationSet(err) { | ||
conversationSet.replace(err); | ||
} | ||
this.connect = function() { | ||
if (fsm.change('connecting')) { | ||
@@ -199,3 +207,3 @@ // Timeout applied for reconnect attempts from initial disconnect | ||
} | ||
}); | ||
}); | ||
@@ -211,6 +219,17 @@ // Handle connect response | ||
emitter.emit('connect', response.identity); | ||
} else if (response.response === ResponseCode.RECONNECTED) { | ||
} else if ( | ||
response.response === ResponseCode.RECONNECTED || | ||
response.response === ResponseCode.RECONNECTED_WITH_MESSAGE_LOSS) { | ||
clearTimeout(reconnectTimeout); | ||
log.info('Reconnected session'); | ||
if (response.response === ResponseCode.RECONNECTED_WITH_MESSAGE_LOSS) { | ||
connection.resetSequences(); | ||
replaceConversationSet(new Error("Peer is disconnected")); | ||
log.info("Reconnected session, but messages may have been lost"); | ||
} else { | ||
log.info('Reconnected session'); | ||
} | ||
sessionActivityMonitor.onNewConnection(connection, response); | ||
@@ -217,0 +236,0 @@ emitter.emit('reconnect'); |
@@ -26,5 +26,5 @@ var implements = require('util/interface').implements; | ||
var getProperties = serviceLocator.obtain(Services.GET_SESSION_PROPERTIES); | ||
var registration = serviceLocator.obtain(Services.SESSION_PROPERTIES_REGISTRATION); | ||
var registration = serviceLocator.obtain(Services.SESSION_PROPERTIES_REGISTRATION_2); | ||
internal.getServiceRegistry().add(Services.SESSION_PROPERTIES_EVENT, { | ||
internal.getServiceRegistry().add(Services.SESSION_PROPERTIES_EVENT_2, { | ||
onRequest : function(internal, message, callback) { | ||
@@ -36,2 +36,9 @@ conversationSet.respond(message.cid, message); | ||
this.SessionEventType = { | ||
UPDATED : 0, | ||
RECONNECTED : 1, | ||
FAILED_OVER : 2, | ||
DISCONNECTED : 3 | ||
}; | ||
this.getSessionProperties = function(sid, propertyKeys) { | ||
@@ -82,21 +89,25 @@ var emitter = new Emitter(); | ||
respond : function(message) { | ||
switch (message.type) { | ||
case SessionPropertiesEventType.OPEN: | ||
handler.onSessionOpen(message.sessionId, message.oldProperties); | ||
break; | ||
case SessionPropertiesEventType.UPDATE: | ||
handler.onSessionEvent( | ||
message.sessionId, | ||
message.updateType, | ||
message.newProperties, | ||
message.oldProperties); | ||
break; | ||
case SessionPropertiesEventType.CLOSE: | ||
handler.onSessionClose( | ||
message.sessionId, | ||
message.oldProperties, | ||
message.closeReason); | ||
break; | ||
default : | ||
logger.debug('Unknown event type received for session properties listener', message.type); | ||
for (var i = 0; i < message.events.length; ++i) { | ||
var event = message.events[i]; | ||
switch (event.type) { | ||
case SessionPropertiesEventType.OPEN: | ||
handler.onSessionOpen(event.sessionId, event.oldProperties); | ||
break; | ||
case SessionPropertiesEventType.UPDATE: | ||
handler.onSessionEvent( | ||
event.sessionId, | ||
event.updateType, | ||
event.newProperties, | ||
event.oldProperties); | ||
break; | ||
case SessionPropertiesEventType.CLOSE: | ||
handler.onSessionClose( | ||
event.sessionId, | ||
event.oldProperties, | ||
event.closeReason); | ||
break; | ||
default : | ||
logger.debug('Unknown event type received for session properties listener', event.type); | ||
} | ||
} | ||
@@ -103,0 +114,0 @@ }, |
@@ -263,3 +263,3 @@ var implements = require('util/interface').implements; | ||
var conversations = internal.getConversationSet(); | ||
var cid = conversations.new(updateResponseHandler(internal, path, handler)); | ||
var cid = conversations.new(updateResponseHandler(internal, valueCache, path, handler)); | ||
@@ -266,0 +266,0 @@ if (internal.checkConnected(emitter)) { |
@@ -17,4 +17,10 @@ var Services = require('services/services'); | ||
if (valueCache[topic]) { | ||
var delta = datatype.deltaType().diff(valueCache[topic], value); | ||
var deltaType = datatype.deltaType("binary"); | ||
var delta = deltaType.diff(valueCache[topic], value); | ||
if (delta === deltaType.noChange()) { | ||
callback(null, {}); | ||
return; | ||
} | ||
DELTA_SERVICE.send({ | ||
@@ -31,3 +37,5 @@ id : 0, | ||
} | ||
valueCache[topic] = value; | ||
}; | ||
}; |
@@ -48,4 +48,4 @@ var implements = require('util/interface').implements; | ||
self.on('unsubscribe', function(reason) { | ||
e.emit('unsubscribe', reason); | ||
self.on('unsubscribe', function(reason, topic) { | ||
e.emit('unsubscribe', reason, topic); | ||
}); | ||
@@ -52,0 +52,0 @@ |
var Services = require('services/services'); | ||
var DataTypes = require('data/datatypes'); | ||
var Emitter = require('events/emitter'); | ||
@@ -7,2 +7,8 @@ var Result = require('events/result'); | ||
var util = require('metadata/util'); | ||
function dataToBytes(d) { | ||
return d.$buffer.slice(d.$offset, d.$length); | ||
} | ||
function Updater(cid, dispatch) { | ||
@@ -24,3 +30,3 @@ var self = this; | ||
} else { | ||
dispatch(emitter, cid, topic, new Update.Update(value)); | ||
dispatch(emitter, cid, topic, value); | ||
} | ||
@@ -32,21 +38,55 @@ | ||
module.exports = function UpdateResponseHandler(internal, topic, handler) { | ||
module.exports = function UpdateResponseHandler(internal, valueCache, topic, handler) { | ||
var UPDATE_SOURCE_DEREGISTRATION = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_DEREGISTRATION); | ||
var UPDATE_SOURCE_UPDATE = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_UPDATE); | ||
var UPDATE_SOURCE_DELTA = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_DELTA); | ||
var UPDATE_SOURCE_SET = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_SET); | ||
var dispatch = function(emitter, cid, path, update) { | ||
if (internal.checkConnected(emitter)) { | ||
UPDATE_SOURCE_UPDATE.send({ | ||
cid : cid, | ||
path : path, | ||
update : update | ||
}, function(err, result) { | ||
if (err) { | ||
emitter.error(err); | ||
} else if (result.error) { | ||
emitter.error(new Error("Topic update error for topic " + path + " : " + result.error)); | ||
var dispatch = function(emitter, cid, path, content) { | ||
var callback = function(err, result) { | ||
if (err) { | ||
emitter.error(err); | ||
} else if (result.error) { | ||
emitter.error(new Error("Topic update error for topic " + path + " : " + result.error)); | ||
} else { | ||
emitter.emit('complete'); | ||
} | ||
}; | ||
if (internal.checkConnected(emitter)) { | ||
if (util.isMetadataValue(content)) { | ||
UPDATE_SOURCE_UPDATE.send({ | ||
cid : cid, | ||
path : path, | ||
update : new Update.Update(content) | ||
}, callback); | ||
} else { | ||
var datatype = DataTypes.get(content); | ||
var value = datatype.from(content); | ||
if (valueCache[path]) { | ||
var deltaType = datatype.deltaType("binary"); | ||
var delta = deltaType.diff(valueCache[path], value); | ||
if (delta === deltaType.noChange()) { | ||
callback(null, {}); | ||
return; | ||
} | ||
UPDATE_SOURCE_DELTA.send({ | ||
id : 0, | ||
cid : cid, | ||
path : path, | ||
bytes : dataToBytes(delta) | ||
}, callback); | ||
} else { | ||
emitter.emit('complete'); | ||
UPDATE_SOURCE_SET.send({ | ||
cid : cid, | ||
path : path, | ||
bytes : dataToBytes(value) | ||
}, callback); | ||
} | ||
}); | ||
valueCache[path] = value; | ||
} | ||
} | ||
@@ -53,0 +93,0 @@ }; |
@@ -9,3 +9,3 @@ var consts = require('protocol/consts'); | ||
type : consts.TYPE, | ||
version : consts.PROTOCOL_VERSION, | ||
version : consts.CURRENT_VERSION, | ||
capabilities : consts.CAPABILITIES | ||
@@ -15,5 +15,8 @@ }; | ||
function createReconnectionRequest(token) { | ||
function createReconnectionRequest(token, availableClientSequence, lastServerSequence) { | ||
var req = createConnectionRequest(); | ||
req.token = token; | ||
req.availableClientSequence = availableClientSequence; | ||
req.lastServerSequence = lastServerSequence; | ||
@@ -20,0 +23,0 @@ return req; |
@@ -22,5 +22,5 @@ var BufferInputStream = require('io/buffer-input-stream'); | ||
var version = input.read(); | ||
if (version < consts.PROTOCOL_VERSION) { | ||
if (version < consts.CURRENT_VERSION) { | ||
throw new Error('Unrecognised protocol version: ' + version); | ||
} else if (version > consts.PROTOCOL_VERSION) { | ||
} else if (version > consts.CURRENT_VERSION) { | ||
throw new Error('Unsupported protocol version: ' + version); | ||
@@ -37,2 +37,8 @@ } | ||
var recoverySequence = 0; | ||
if (responseCode === ResponseCode.RECONNECTED) { | ||
recoverySequence = input.readInt32(); | ||
} | ||
return { | ||
@@ -44,3 +50,4 @@ response : responseCode, | ||
version : version, | ||
success : true | ||
success : true, | ||
sequence : recoverySequence | ||
}; | ||
@@ -54,3 +61,4 @@ } else { | ||
systemPingPeriod : null, | ||
success : false | ||
success : false, | ||
sequence : 0 | ||
}; | ||
@@ -57,0 +65,0 @@ } |
@@ -5,3 +5,3 @@ module.exports = { | ||
PROTOCOL_BYTE : 35, | ||
PROTOCOL_VERSION : 7 | ||
CURRENT_VERSION : 8 | ||
}; |
@@ -8,19 +8,28 @@ function code(id, message) { | ||
var ResponseCode = { | ||
OK : code(100, "Connected successfully"), | ||
DOWNGRADE : code(102, "Server does not support the requested protocol level"), | ||
RECONNECTED : code(105, "Reconnected successfully"), | ||
REJECTED : code(111, "Connection rejected"), | ||
CONNECTION_UNSUPPORTED : code(112, "Connection type not supported by connector"), | ||
LICENSE_EXCEEDED : code(113, "Connection rejected due to license limit"), | ||
RECONNECTION_UNSUPPORTED : code(114, "Reconnection not supported by connector"), | ||
CONNECTION_PROTOCOL_ERROR : code(115, "Connection failed - protocol error"), | ||
AUTHENTICATION_FAILED : code(116, "Connection failed - authentication failed"), | ||
UNKNOWN_SESSION : code(117, "Reconnection failed - the session is unknown"), | ||
ERROR : code(127, "Connection failed due to server error") | ||
OK : code(100, "Connected successfully"), | ||
DOWNGRADE : code(102, "Server does not support the requested protocol level"), | ||
RECONNECTED : code(105, "Reconnected successfully"), | ||
RECONNECTED_WITH_MESSAGE_LOSS : code(106, "Reconnected with message loss"), | ||
REJECTED : code(111, "Connection rejected"), | ||
CONNECTION_UNSUPPORTED : code(112, "Connection type not supported by connector"), | ||
LICENSE_EXCEEDED : code(113, "Connection rejected due to license limit"), | ||
RECONNECTION_UNSUPPORTED : code(114, "Reconnection not supported by connector"), | ||
CONNECTION_PROTOCOL_ERROR : code(115, "Connection failed - protocol error"), | ||
AUTHENTICATION_FAILED : code(116, "Connection failed - authentication failed"), | ||
UNKNOWN_SESSION : code(117, "Reconnection failed - the session is unknown"), | ||
RECONNECTION_FAILED_MESSAGE_LOSS : code(118, "Reconnection failed due to message loss"), | ||
ERROR : code(127, "Connection failed due to server error") | ||
}; | ||
ResponseCode.isSuccess = function isSuccess(code) { | ||
return code === ResponseCode.OK || code === ResponseCode.RECONNECTED; | ||
switch (code) { | ||
case ResponseCode.OK : | ||
case ResponseCode.RECONNECTED : | ||
case ResponseCode.RECONNECTED_WITH_MESSAGE_LOSS : | ||
return true; | ||
default : | ||
return false; | ||
} | ||
}; | ||
module.exports = ResponseCode; |
@@ -13,3 +13,2 @@ var ClientControlOptions = require('services/control/client-control-options'); | ||
var result = { | ||
cid : ConversationIdSerialiser.read(input), | ||
sessionId : SessionIdSerialiser.read(input), | ||
@@ -16,0 +15,0 @@ type : BEES.read(input, SessionPropertiesEventType) |
@@ -156,2 +156,6 @@ var HashMap = require('hashmap'); | ||
serialisers.set( | ||
require('services/control/session-properties-event-batch'), | ||
require('services/control/session-properties-event-batch-serialiser') | ||
); | ||
serialisers.set( | ||
require('services/topic-update/update-source-registration-request'), | ||
@@ -189,2 +193,10 @@ require('services/topic-update/update-source-registration-request-serialiser') | ||
serialisers.set( | ||
require('services/topic-update/update-source-set-request'), | ||
require('services/topic-update/update-source-set-request-serialiser') | ||
); | ||
serialisers.set( | ||
require('services/topic-update/update-source-delta-request'), | ||
require('services/topic-update/update-source-delta-request-serialiser') | ||
); | ||
serialisers.set( | ||
require('services/missing-topic/missing-topic-request'), | ||
@@ -191,0 +203,0 @@ require('services/missing-topic/missing-topic-request-serialiser') |
@@ -33,2 +33,4 @@ var TopicSelector = require('../../selectors/topic-selector'); | ||
var UpdateSourceUpdate = require('services/topic-update/update-source-update'); | ||
var UpdateSourceSetRequest = require('services/topic-update/update-source-set-request'); | ||
var UpdateSourceDeltaRequest = require('services/topic-update/update-source-delta-request'); | ||
var UpdateSourceUpdateResponse = require('services/topic-update/update-source-update-response'); | ||
@@ -54,2 +56,3 @@ var UpdateSourceStateRequest = require('services/topic-update/update-source-state-request'); | ||
var SessionPropertiesEvent = require('services/control/session-properties-event'); | ||
var SessionPropertiesEventBatch = require('services/control/session-properties-event-batch'); | ||
@@ -259,3 +262,3 @@ var MissingTopicRequest = require('services/missing-topic/missing-topic-request'); | ||
id : 69, | ||
name : "Set session properties listener", | ||
name : "Set session properties listener - legacy, see SESSION_PROPERTIES_REGISTRATION_2", | ||
request : SetSessionPropertiesListener, | ||
@@ -266,6 +269,18 @@ response : null | ||
id : 70, | ||
name : "Session properties event", | ||
name : "Session properties event - legacy, see SESSION_PROPERTIES_EVENT_2", | ||
request : SessionPropertiesEvent, | ||
response : null | ||
}, | ||
UPDATE_SOURCE_SET : { | ||
id : 77, | ||
name : "Update source set", | ||
request : UpdateSourceSetRequest, | ||
response : UpdateSourceUpdateResponse | ||
}, | ||
UPDATE_SOURCE_DELTA : { | ||
id : 78, | ||
name : "Update source delta", | ||
request : UpdateSourceDeltaRequest, | ||
response : UpdateSourceUpdateResponse | ||
}, | ||
UPDATE_TOPIC_SET : { | ||
@@ -282,3 +297,15 @@ id : 79, | ||
response : UpdateTopicResponse | ||
}, | ||
SESSION_PROPERTIES_REGISTRATION_2 : { | ||
id : 81, | ||
name : "Session Properties registration 2", | ||
request : SetSessionPropertiesListener, | ||
response : null | ||
}, | ||
SESSION_PROPERTIES_EVENT_2 : { | ||
id : 82, | ||
name : "Session Properties event 2", | ||
request : SessionPropertiesEventBatch, | ||
response : null | ||
} | ||
}; |
@@ -6,4 +6,4 @@ // Internal components | ||
var ServiceRegistry = require('client/service-registry'); | ||
var ConversationSet = require('conversation/conversation-set'); | ||
var ConnectionFactory = require('v4-stack/connection-factory'); | ||
var DelegatingConversationSet = require('conversation/delegating-conversation-set'); | ||
@@ -24,10 +24,17 @@ // Service implementations | ||
*/ | ||
function SessionImpl() { | ||
function SessionImpl(options) { | ||
var emitter = new Emitter(undefined, undefined, ['connect', 'reconnect', 'disconnect']); | ||
var serviceRegistry = new ServiceRegistry(); | ||
var conversationSet = ConversationSet(function(cid, err) { | ||
var conversationSet = new DelegatingConversationSet(function(cid, err) { | ||
emitter.error(err); | ||
}); | ||
// Merge defaults | ||
if (typeof options === 'string') { | ||
options = new Options({ host : options }); | ||
} else { | ||
options = new Options(options); | ||
} | ||
// Assign default service implementations | ||
@@ -41,5 +48,10 @@ serviceRegistry.add(Services.USER_PING, PingService); | ||
var internalSession = | ||
new InternalSession(conversationSet, serviceRegistry, ConnectionFactory); | ||
var session = new Session(internalSession, emitter); | ||
new InternalSession(conversationSet, serviceRegistry, ConnectionFactory, options); | ||
// Clone the options so that we can safely modify and expose | ||
// Remove credentials so bad actors cannot access password | ||
var session = new Session(internalSession, emitter, options.with({ | ||
credentials : undefined | ||
})); | ||
// Bind session to internal session | ||
@@ -67,15 +79,4 @@ session.on('error', internalSession.close); | ||
// Connect the session and return it | ||
this.connect = function(options) { | ||
// Merge defaults | ||
if (typeof options === 'string') { | ||
options = new Options({ host : options }); | ||
} else { | ||
options = new Options(options); | ||
} | ||
// Clone the options so that we can safely modify and expose | ||
session.options = options.with({}); | ||
session.options.credentials = undefined; | ||
internalSession.connect(options); | ||
this.connect = function() { | ||
internalSession.connect(); | ||
}; | ||
@@ -82,0 +83,0 @@ |
@@ -56,2 +56,5 @@ | ||
/** | ||
* The subtransports will be looked up by transport name. | ||
*/ | ||
module.exports = { | ||
@@ -61,7 +64,15 @@ /** | ||
*/ | ||
ws : websocketSubTransportFactoryProvider(), | ||
WS : websocketSubTransportFactoryProvider(), | ||
/** | ||
* XHR subtransport. Supports detection of availability and construction when available. | ||
* HTTP polling subtransport. Supports detection of availability and construction when available. | ||
*/ | ||
xhr : xhrSubTransportFactoryProvider() | ||
XHR : xhrSubTransportFactoryProvider(), | ||
/** | ||
* WebSocket subtransport. Supports detection of availability and construction when available. | ||
*/ | ||
WEBSOCKET : websocketSubTransportFactoryProvider(), | ||
/** | ||
* HTTP polling subtransport. Supports detection of availability and construction when available. | ||
*/ | ||
HTTP_POLLING : xhrSubTransportFactoryProvider() | ||
}; |
@@ -0,1 +1,2 @@ | ||
var WSTransport = require('transports/ws'); | ||
@@ -10,9 +11,60 @@ var XHRTransport = require('transports/xhr'); | ||
var standardSubtransports = require('transports/subtransports'); | ||
var parsing = require('transports/parsing'); | ||
var logger = require('util/logger').create('Cascading'); | ||
/** | ||
* The known transports. Will be looked up by name. | ||
*/ | ||
var knownTransports = { | ||
WS : WSTransport, | ||
XHR : XHRTransport, | ||
WEBSOCKET : WSTransport, | ||
HTTP_POLLING : XHRTransport | ||
}; | ||
/** | ||
* Filter a list of transport names of any unknown or disabled transports. | ||
* @param {Array} requestedTransports - Array of transport names | ||
* @param {Subtransports} subtransports - The available subtransports | ||
* @returns {Array} - Filtered array of transport names | ||
*/ | ||
function filterTransports(requestedTransports, subtransports) { | ||
return requestedTransports.filter(function(name) { | ||
return name && knownTransports.hasOwnProperty(name); | ||
}).filter(function(name) { | ||
return subtransports[name].enabled; | ||
}); | ||
} | ||
/** | ||
* @returns {boolean} If the client should attempt to cascade on receiving a response | ||
*/ | ||
function isCascadableResponse(response) { | ||
return response === ResponseCode.ERROR; | ||
} | ||
/** | ||
* Create the selected transport if enabled. | ||
* @param {Subtransports} subtransports - The subtransports that can be made | ||
* @param {SessionOptions} options - The options used to connect | ||
* @param {String} name - The name of the subtransport to use | ||
* @returns {*} an available transport if enabled | ||
*/ | ||
function createTransport(subtransports, options, name) { | ||
var transport = knownTransports[name]; | ||
var subtransport = subtransports[name]; | ||
if (transport && subtransport && subtransport.enabled) { | ||
return new transport(options, subtransport.constructor); | ||
} else { | ||
// Any unknown transports should have been filtered out by the | ||
// filterTransports function | ||
throw new Error('Unknown transport name: ' + name); | ||
} | ||
} | ||
/** | ||
* Encapsulate the set of available transport mechanisms and expose them in an | ||
* implementation independent manner. | ||
*/ | ||
function Transports(subtransports) { | ||
function CascadeDriver(subtransports, opts, transports, cascadeNotifications) { | ||
var emitter = Emitter.assign(this); | ||
@@ -25,16 +77,3 @@ var self = this; | ||
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. | ||
@@ -46,5 +85,13 @@ */ | ||
*/ | ||
var hand = null; | ||
var onHandshakeSuccess = null; | ||
var onHandshakeError = null; | ||
var responseReceived = false; | ||
var transportFactory = createTransport.bind(null, subtransports, opts); | ||
/** | ||
* The current transport object. | ||
*/ | ||
var transport = selectTransport(); | ||
/** | ||
* Called when the transport emits a data event. | ||
@@ -60,2 +107,9 @@ */ | ||
function onClose(reason) { | ||
if (!responseReceived && cascade(true)) { | ||
// Attempting to cascade | ||
return; | ||
} | ||
// Transport closed without receiving any response | ||
// close event suppressed to prevent reentering this handler | ||
emitter.emit('close', reason); | ||
@@ -72,2 +126,28 @@ } | ||
/** | ||
* Called when the connection response is received. | ||
*/ | ||
function onConnectionResponse(response) { | ||
responseReceived = true; | ||
if (isCascadableResponse(response.response)) { | ||
if (cascade()) { | ||
// Attempting to cascade | ||
return; | ||
} | ||
// Transport closed and close event emitted | ||
} | ||
return onHandshakeSuccess(response); | ||
} | ||
/** | ||
* Called when the connection response cannot be parsed. | ||
*/ | ||
function onParsingError(error) { | ||
responseReceived = true; | ||
if (!cascade()) { | ||
// Transports exhausted | ||
return onHandshakeError(error); | ||
} | ||
} | ||
/** | ||
* Close the current transport quietly, without generating any events. | ||
@@ -86,17 +166,4 @@ */ | ||
/** | ||
* 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. | ||
* @returns the selected transport | ||
*/ | ||
@@ -108,67 +175,67 @@ function selectTransport(suppressClose) { | ||
logger.debug('Transports exhausted'); | ||
emitter.emit('transports-exhausted'); | ||
cascadeNotifications.emit('transports-exhausted'); | ||
if (!suppressClose) { | ||
emitter.emit('close'); | ||
} | ||
return; | ||
return null; | ||
} | ||
self.name = transports.shift(); | ||
transport = createTransport(); | ||
transport = transportFactory(self.name); | ||
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'); | ||
} | ||
logger.debug('Selecting transport', self.name); | ||
cascadeNotifications.emit('transport-selected', self.name); | ||
return transport; | ||
} | ||
/** | ||
* @returns true if attempting to connect | ||
*/ | ||
function internalConnect() { | ||
responseReceived = false; | ||
logger.debug('Attempting to connect'); | ||
cascadeNotifications.emit('cascading-connect'); | ||
transport.on('data', onData); | ||
transport.on('close', onClose); | ||
transport.on('error', onError); | ||
try { | ||
transport.connect( | ||
req, | ||
parsing.connectionResponse.bind(null, onConnectionResponse, onParsingError) | ||
); | ||
} | ||
catch(e) { | ||
if (!cascade()) { | ||
// Transports exhausted | ||
throw e; | ||
} | ||
} else { | ||
logger.debug('Selecting transport', self.name); | ||
emitter.emit('transport-selected', self.name); | ||
} | ||
return true; | ||
} | ||
this.get = function get(options) { | ||
opts = options; | ||
transports = opts.transports.slice(); | ||
selectTransport(); | ||
return self; | ||
}; | ||
this.cascade = function cascade(suppressClose) { | ||
/** | ||
* @returns true if attempting to connect, false if transports exhausted | ||
*/ | ||
function cascade(suppressClose) { | ||
closeQuietly(); | ||
selectTransport(suppressClose); | ||
if (transport === null) { | ||
return self; | ||
if (!selectTransport(suppressClose)) { | ||
return false; | ||
} | ||
emitter.emit('cascade'); | ||
cascadeNotifications.emit('cascade'); | ||
self.connect(req, hand); | ||
return internalConnect(); | ||
} | ||
return self; | ||
}; | ||
this.connect = function connect(request, handshake, handshakeError) { | ||
req = request; | ||
this.connect = function connect(request, handshake) { | ||
if (transport !== null) { | ||
logger.debug('Attempting to connect'); | ||
emitter.emit('cascading-connect'); | ||
onHandshakeSuccess = handshake; | ||
onHandshakeError = handshakeError; | ||
req = request; | ||
hand = handshake; | ||
transport.on('data', onData); | ||
transport.on('close', onClose); | ||
transport.on('error', onError); | ||
transport.connect(request, handshake); | ||
} | ||
internalConnect(); | ||
}; | ||
@@ -186,2 +253,3 @@ | ||
if (transport !== null) { | ||
responseReceived = true; // To prevent cascading on connection timeout | ||
// A close event will be emitted by the transport after the close is complete | ||
@@ -195,2 +263,20 @@ transport.close(); | ||
/** | ||
* Encapsulate the set of available transport mechanisms and expose them in an | ||
* implementation independent manner. | ||
*/ | ||
function Transports(subtransports) { | ||
var emitter = Emitter.assign(this); | ||
this.get = function get(options) { | ||
var validTransports = filterTransports(options.transports, subtransports); | ||
if (validTransports.length === 0) { | ||
return null; | ||
} | ||
return new CascadeDriver(subtransports, options, validTransports, emitter); | ||
}; | ||
} | ||
/** | ||
* Create a new transports object. | ||
@@ -197,0 +283,0 @@ */ |
@@ -1,4 +0,2 @@ | ||
var encodeAsString = require('v4-stack/credential-tunnel').encodeAsString; | ||
var Emitter = require('events/emitter'); | ||
@@ -26,2 +24,4 @@ | ||
uri += '&c=' + encodeURIComponent(req.token); | ||
uri += "&cs=" + req.availableClientSequence; | ||
uri += "&ss=" + req.lastServerSequence; | ||
} | ||
@@ -50,3 +50,3 @@ | ||
}; | ||
var socket; | ||
@@ -60,2 +60,3 @@ | ||
* @param {Function} handshake - The handshake handler function | ||
* @param {Function} dataWritten - The data written handler | ||
*/ | ||
@@ -76,2 +77,3 @@ this.connect = function connect(req, handshake) { | ||
socket.onmessage = handler; | ||
handshake(new Buffer(new Uint8Array(msg.data))); | ||
@@ -91,4 +93,10 @@ }; | ||
log.trace("Sending websocket message"); | ||
socket.send(message); | ||
return true; | ||
try { | ||
socket.send(message); | ||
return true; | ||
} catch (err) { | ||
log.error("Websocket send error", err); | ||
return false; | ||
} | ||
}; | ||
@@ -95,0 +103,0 @@ |
@@ -30,2 +30,4 @@ var encodeAsString = require('v4-stack/credential-tunnel').encodeAsString; | ||
headers.c = encodeURIComponent(req.token); | ||
headers.cs = req.availableClientSequence; | ||
headers.ss =req.lastServerSequence; | ||
} | ||
@@ -54,3 +56,3 @@ | ||
// Message queue | ||
// Message message-queue | ||
var queue = Queue.create(); | ||
@@ -75,2 +77,9 @@ var constructor = xhr; | ||
var serverResponse = handshake(new Buffer(handShakeData, 'binary')); | ||
if (!serverResponse) { | ||
// If the handshake could not be parsed or there was some sort of error return | ||
// The transport will be closed by the error handler | ||
return; | ||
} | ||
clientId = serverResponse.identity; | ||
@@ -102,12 +111,2 @@ | ||
/** | ||
* Send a messages to the server. | ||
* | ||
* @param {Message} message The message to be sent | ||
*/ | ||
this.dispatch = function dispatch(message) { | ||
this.sendMessage(message); | ||
return true; | ||
}; | ||
/** | ||
* Aborting any opening poll request. | ||
@@ -190,3 +189,3 @@ */ | ||
/** | ||
* Queue message to be sent to the message queue. | ||
* Queue message to be sent to the message message-queue. | ||
* | ||
@@ -197,5 +196,5 @@ * Delegate the actual request sending to #flush(). | ||
*/ | ||
this.sendMessage = function sendMessage(message) { | ||
this.dispatch = function dispatch(message) { | ||
if (aborted) { | ||
return; | ||
return false; | ||
} | ||
@@ -206,3 +205,3 @@ | ||
if (isSending) { | ||
return; | ||
return true; | ||
} | ||
@@ -214,3 +213,3 @@ | ||
/** | ||
* Flush all pending messages from the queue and send upstream. | ||
* Flush all pending messages from the message-queue and send upstream. | ||
*/ | ||
@@ -217,0 +216,0 @@ this.flush = function flush() { |
@@ -18,2 +18,37 @@ /** | ||
/** | ||
* Fill an array with a given value, within an optional range | ||
* | ||
* @param array {Array} - The array to fill | ||
* @param value {*} - The value to fill the array with | ||
* @param start {Number} [0] - Optional range start | ||
* @param end {Number} [array.length] - Optional range end | ||
*/ | ||
function fill(array, value, start, end) { | ||
start = start || 0; | ||
end = end || array.length; | ||
for (var i = start; i < end; ++i) { | ||
array[i] = value; | ||
} | ||
} | ||
/** | ||
* Create an array of a given size, optionally pre-filled with a specified initial value. | ||
* | ||
* @param size {Number} - The size of the array to create | ||
* @param initialValue {*} [] - The initial value to assign to each element | ||
*/ | ||
function ofSize(size, initialValue) { | ||
var a = []; | ||
a.length = size; | ||
if (initialValue !== undefined) { | ||
fill(a, initialValue); | ||
} | ||
return a; | ||
} | ||
var s = Array.prototype.slice; | ||
@@ -32,4 +67,6 @@ | ||
module.exports = { | ||
fill : fill, | ||
ofSize : ofSize, | ||
remove : remove, | ||
argumentsToArray : argumentsToArray | ||
}; |
/** | ||
* Cheap approximation to a square root. | ||
* @return a power of two that approximates the square root of a value | ||
* @return {Number} a power of two that approximates the square root of a value | ||
*/ | ||
@@ -21,2 +21,25 @@ module.exports.approximateSquareRoot = function(value) { | ||
return result; | ||
}; | ||
/** | ||
* Find the next integer, equal or higher to the value, which is a power of two. | ||
* | ||
* @param {number} value the value to search from | ||
*/ | ||
module.exports.findNextPowerOfTwo = function(value) { | ||
if (value < 0 || value > 1 << 30) { | ||
throw new Error("Illegal argument: " + value); | ||
} | ||
// See: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 | ||
value--; | ||
value |= value >> 1; | ||
value |= value >> 2; | ||
value |= value >> 4; | ||
value |= value >> 8; | ||
value |= value >> 16; | ||
value++; | ||
return value; | ||
}; |
@@ -12,2 +12,9 @@ /** | ||
/** | ||
* @returns {Number} number of queued items | ||
*/ | ||
this.length = function() { | ||
return messages.length; | ||
}; | ||
/** | ||
@@ -14,0 +21,0 @@ * Add a message to the messages queue. |
@@ -14,5 +14,6 @@ function reason(id, message, canReconnect) { | ||
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) | ||
IDLE_CONNECTION : reason(8, "The activity monitor detected the connection was idle", true), | ||
LOST_MESSAGES : reason(16, "Loss of messages has been detected", false) | ||
}; | ||
module.exports = CloseReason; |
@@ -1,5 +0,4 @@ | ||
var connectionResponseDeserialiser = require('protocol/connection-response-deserialiser'); | ||
var RecoveryBuffer = require('message-queue/recovery-buffer'); | ||
var BufferOutputStream = require('io/buffer-output-stream'); | ||
var ResponseCode = require('protocol/response-code'); | ||
var BufferOutputStream = require('io/buffer-output-stream'); | ||
var CloseReason = require('v4-stack/close-reason'); | ||
@@ -19,2 +18,5 @@ var Message = require('v4-stack/message'); | ||
// The default recovery buffer index size. | ||
var RECOVERY_BUFFER_INDEX_SIZE = global.DIFFUSION_RECOVERY_BUFFER_INDEX_SIZE || 8; | ||
/** | ||
@@ -28,3 +30,3 @@ * The layer that abstracts across transports and handles the connection-level | ||
*/ | ||
function Connection(aliases, transports) { | ||
function Connection(aliases, transports, reconnectTimeout, recoveryBufferSize) { | ||
var emitter = Emitter.assign(this); | ||
@@ -40,5 +42,13 @@ | ||
var lastSentSequence = 0; | ||
this.lastReceivedSequence = 0; | ||
var recoveryBuffer = new RecoveryBuffer(recoveryBufferSize, RECOVERY_BUFFER_INDEX_SIZE); | ||
var scheduledRecoveryBufferTrim; | ||
var scheduledClose; | ||
var closeReason; | ||
var transport = null; | ||
var self = this; | ||
@@ -59,2 +69,5 @@ fsm.on('change', function(previous, current) { | ||
} | ||
self.lastReceivedSequence++; | ||
emitter.emit('data', message); | ||
@@ -74,13 +87,4 @@ } catch (e) { | ||
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')) { | ||
clearInterval(scheduledRecoveryBufferTrim); | ||
clearTimeout(scheduledClose); | ||
@@ -100,47 +104,33 @@ | ||
function isCascadableResponse(response) { | ||
return response === ResponseCode.ERROR || | ||
response === ResponseCode.UNKNOWN_SESSION || | ||
response === ResponseCode.LICENSE_EXCEEDED; | ||
} | ||
/** | ||
* Handle connection responses. | ||
* @param response {ConnectionResponse} Parsed connection response | ||
*/ | ||
function onHandshakeSuccess(response) { | ||
if (response.response === ResponseCode.RECONNECTED) { | ||
var messageDelta = lastSentSequence - response.sequence + 1; | ||
// Handle connection responses | ||
function handshake(message) { | ||
logger.trace('Received connection handshake response'); | ||
if (recoveryBuffer.recover(messageDelta, dispatch)) { | ||
recoveryBuffer.clear(); | ||
lastSentSequence = response.sequence; | ||
} else { | ||
var outboundLoss = lastSentSequence - recoveryBuffer.size() - response.sequence + 1; | ||
var response; | ||
logger.warn("Unable to reconnect due to lost messages (" + outboundLoss + ")"); | ||
try { | ||
response = connectionResponseDeserialiser(message); | ||
} catch (e) { | ||
closeReason = CloseReason.HANDSHAKE_ERROR; | ||
if (fsm.change('disconnected')) { | ||
closeReason = CloseReason.LOST_MESSAGES; | ||
transport.close(); | ||
} | ||
transport = transport.cascade(); | ||
if (transport !== null && transport.name !== null) { | ||
// Attempting cascade | ||
return; | ||
return response; | ||
} | ||
// Transport closed and close event emitted | ||
clearTimeout(scheduledClose); | ||
logger.trace('Unable to deserialise handshake response', e); | ||
return; | ||
} | ||
logger.trace('Connection response: ', response.response); | ||
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')) { | ||
if (response.success && fsm.change('connected')) { | ||
logger.trace('Connection response: ', response.response); | ||
closeReason = CloseReason.TRANSPORT_ERROR; | ||
emitter.emit('connect', response); | ||
} else { | ||
logger.debug('Connection response: ', response.response); | ||
closeReason = CloseReason.HANDSHAKE_REJECTED; | ||
@@ -155,2 +145,17 @@ transport.close(); | ||
/** | ||
* Handle connection response parsing errors. | ||
* @param response {Error} Error thrown parsing the response | ||
*/ | ||
function onHandshakeError(error) { | ||
closeReason = CloseReason.HANDSHAKE_ERROR; | ||
// Transport closed and close event emitted | ||
clearTimeout(scheduledClose); | ||
logger.trace('Unable to deserialise handshake response', error); | ||
return; | ||
} | ||
/** | ||
* Establish a connection with a provided connection request and connection options. | ||
@@ -175,12 +180,4 @@ */ | ||
try { | ||
// Attempt to connect the transport | ||
transport.connect(request, handshake); | ||
} | ||
catch (e) { | ||
transport = transport.cascade(); | ||
if (transport === null || transport.name === null) { | ||
throw e; | ||
} | ||
} | ||
// Attempt to connect the transport | ||
transport.connect(request, onHandshakeSuccess, onHandshakeError); | ||
@@ -194,11 +191,33 @@ // Ensure we will emit a close reason if we're unable to connect within a timeout | ||
}, timeout); | ||
scheduledRecoveryBufferTrim = setInterval(function() { | ||
if (fsm.state === 'connected') { | ||
recoveryBuffer.flush(Date.now() - reconnectTimeout); | ||
} | ||
}, reconnectTimeout); | ||
} | ||
}; | ||
this.resetSequences = function() { | ||
recoveryBuffer.clear(); | ||
lastSentSequence = 0; | ||
this.lastReceivedSequence = 0; | ||
}; | ||
this.getAvailableSequence = function() { | ||
return lastSentSequence + 1 - recoveryBuffer.size(); | ||
}; | ||
// Internal dispatch method for converting a message into a buffer | ||
function dispatch(message) { | ||
var bos = new BufferOutputStream(); | ||
Message.writeToBuffer(message, bos); | ||
var bos = new BufferOutputStream(); | ||
Message.writeToBuffer(message, bos); | ||
return transport.dispatch(bos.getBuffer()); | ||
lastSentSequence += 1; | ||
recoveryBuffer.put(message); | ||
recoveryBuffer.markTime(Date.now()); | ||
transport.dispatch(bos.getBuffer()); | ||
} | ||
@@ -205,0 +224,0 @@ |
@@ -48,3 +48,3 @@ var DEFAULT_HOST = 'localhost'; | ||
var DEFAULT_TRANSPORTS = ['WS']; | ||
var DEFAULT_TRANSPORTS = ['WEBSOCKET']; | ||
@@ -54,10 +54,75 @@ /** | ||
* <P> | ||
* Reconnection will be automatically enabled, and accepts several option values. A boolean will set whether it is | ||
* enabled or disabled, using default settings. Passing a <code>number</code> will specify the timeout value, or a | ||
* <code>function</code> will specify the reconnection strategy. An object containing both <code>timeout</code> and | ||
* <code>strategy</code> keys can be used to specify both values. | ||
* <h5>Reconnection:</h5> | ||
* Reconnection is enabled by default, and accepts several different option values. | ||
* <table class="table striped"> | ||
* <thead> | ||
* <tr> | ||
* <th>Option type</th> | ||
* <th>Default value</th> | ||
* <th>Description</th> | ||
* </tr> | ||
* </thead> | ||
* <tbody> | ||
* <tr> | ||
* <td><code>boolean</code></td> | ||
* <td><code>true</code></td> | ||
* <td>Enables or disables reconnection. If set to <code>true</code>, reconnection will be enabled using the default | ||
* timeout value and a periodic back-off strategy.</td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>number</code></td> | ||
* <td><code>60000</code></td> | ||
* <td>Passing a number will enable reconnection with the default strategy and the reconnection timeout set to the | ||
* specified value. The reconnection timeout determines how long, in milliseconds, the client will remain in a | ||
* <code>disconnected</code> state before the client is closed.</td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>function</code></td> | ||
* <td><code>function(reconnect, abort) { | ||
* setTimeout(reconnect, 5000); | ||
* }</code></td> | ||
* <td>A strategy function that will be called when the client enters a <code>disconnected</code> state, and | ||
* subsequently if attempts to reconnect fail. Two arguments are provided, <code>reconnect</code> and <code>abort</code> | ||
* - these are functions to be called within the strategy. The <code>reconnect</code> argument will initiate a | ||
* reconnect attempt. <code>abort</code> may be called to abort reconnection, in which case the client will be closed. | ||
* </td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>{ timeout : <number>, strategy : <function> }</code></td> | ||
* <td><code>{ timeout : 60000, strategy : function(reconnect, abort) { | ||
* setTimeout(reconnect, 5000); | ||
* } }</code></td> | ||
* <td>An object containing both the timeout and strategy options as specified above, allowing both to be set together. | ||
* </td> | ||
* </tr> | ||
* </tbody> | ||
* </table> | ||
* <P> | ||
* The reconnection strategy should be a function that accepts two arguments; the first will be a function that can | ||
* be called to attempt to reconnect, the second a function that can be called to abort. If reconnection is aborted, | ||
* or times out, the session will close. | ||
* <h5>Reconnection:</h5> | ||
* The <code>transports</code> property configures how the session should connect. It can be set to either a | ||
* <code>string</code>, or an <code>array</code> of strings to provide a transport cascading capability. | ||
* <table class="table striped"> | ||
* <thead> | ||
* <tr> | ||
* <th>Transport key</th> | ||
* <th>Description</th> | ||
* </tr> | ||
* </thead> | ||
* <tbody> | ||
* <tr> | ||
* <td><code>ws</code>, <code>WS</code>, <code>WEBSOCKET</code></td> | ||
* <td>The websocket transport. A single, long-lived websocket connection will be used to send and receive data.</td> | ||
* </tr> | ||
* <tr> | ||
* <td><code>xhr</code>, <code>XHR</code>, <code>HTTP_POLLING</code></td> | ||
* <td>An XHR-based polling transport. Data will be queued on the client and server, and sent in batches.</td> | ||
* </tr> | ||
* </tbody> | ||
* </table> | ||
* <P> | ||
* The client will use the transports in the order provided, for example: | ||
* <code>transports: ['WS', 'XHR']</code> indicates that the client will attempt to connect with the WebSocket | ||
* transport, and if the connection fails, the client will attempt to connect with the HTTP Polling transport. When no | ||
* <code>transports</code> value is provided the client will default to using the WebSocket transport. Any string values | ||
* that do not have an associated transport will be ignored. | ||
* | ||
@@ -71,2 +136,3 @@ * @typedef Session.Options | ||
* @property {Boolean|Number|Function|Object} [reconnect=true] - Reconnection options. | ||
* @property {String|Array} [transports=["WEBSOCKET"]] - The transports to be used for connection establishment. | ||
*/ | ||
@@ -141,3 +207,5 @@ function Options(options) { | ||
this.transports = [options.transports]; | ||
} else if (typeof options.transports === 'object' && options.transports instanceof Array) { | ||
} else if (typeof options.transports === 'object' && | ||
options.transports instanceof Array && | ||
options.transports.length > 0) { | ||
this.transports = options.transports.slice(); | ||
@@ -164,2 +232,6 @@ } else { | ||
for (k in this) { | ||
o[k] = this[k]; | ||
} | ||
for (k in options) { | ||
@@ -169,8 +241,2 @@ o[k] = options[k]; | ||
for (k in this) { | ||
if (o[k] === undefined) { | ||
o[k] = this[k]; | ||
} | ||
} | ||
return new Options(o); | ||
@@ -177,0 +243,0 @@ }; |
@@ -98,3 +98,3 @@ // Namespaced features | ||
*/ | ||
function Session(internalSession, emitter) { | ||
function Session(internalSession, emitter, options) { | ||
/** | ||
@@ -109,4 +109,3 @@ * @property {String} sessionID - The unique id assigned to this session by the server. | ||
*/ | ||
// Will be set by SessionImpl#connect | ||
this.options = undefined; | ||
this.options = options; | ||
@@ -139,3 +138,8 @@ var self = this; | ||
/** | ||
* @return {boolean} Whether the session is currently connected or not | ||
* Indicates if this session is currently connected. | ||
* <P> | ||
* This is orthogonal to {@link Session#isClosed}, as a session may | ||
* be disconnected and attempting to reconnect. | ||
* | ||
* @return {boolean} Whether the session is currently connected or not. | ||
*/ | ||
@@ -147,2 +151,16 @@ this.isConnected = function() { | ||
/** | ||
* Indicates if this session is currently closed, or in the process of | ||
* closing. | ||
* <P> | ||
* This will not return <code>true</code> if the session is disconnected | ||
* but attempting to reconnect. | ||
* | ||
* @return {boolean} Whether the session is currently closed. | ||
*/ | ||
this.isClosed = function() { | ||
var state = internalSession.getState(); | ||
return state === "closing" || state === "closed"; | ||
}; | ||
/** | ||
* Exposes system authentication capabilities via a {@link Session.security}. | ||
@@ -149,0 +167,0 @@ * @property Session.security |
@@ -75,3 +75,19 @@ | ||
/** | ||
* Deprecated | ||
* Slave Topic. | ||
* <P> | ||
* A topic that references another topic (the master topic) which has data | ||
* (i.e. an alias). It effectively allows a topic's data to be shared across | ||
* more than one topic node. | ||
* <P> | ||
* A client cannot tell that it is subscribed to a slave topic. A client | ||
* requesting details of a slave topic will receive the details of the | ||
* master topic. A client subscribing to the slave topic will receive all | ||
* updates to the master topic. The slave topic itself may not be updated. | ||
* <P> | ||
* Any number of slave topics may reference the same master topic. | ||
* <P> | ||
* If a topic is removed that referenced by slave topics, all such slave | ||
* topics are also automatically removed. | ||
* <P> | ||
* Slave topics are unable to be created by the Javascript client, but may safely be subscribed to. | ||
*/ | ||
@@ -78,0 +94,0 @@ SLAVE : type(7, true, false), |
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
510860
264
14122