UNPKG

diffusion

Version:

Diffusion JavaScript client

321 lines (250 loc) 10.7 kB
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 StreamRegistry = require('routing/stream-registry'); var TopicRouting = require('routing/topic-routing'); var TopicCache = require('routing/topic-cache'); var InternalSessionLocks = require('../features/session-lock').InternalSessionLocks; var DataTypes = require('data/datatypes'); var serialisers = require('services/serialisers'); var ConnectionRequest = require('protocol/connection-request'); var ResponseCode = require('protocol/response-code'); var CloseReason = require('../../client/close-reason'); var Transports = require('transports/transports'); var log = require('util/logger').create('Internal Session'); var curry = require('util/function').curry; var FSM = require('util/fsm'); var connectionActivityMonitorFactory = require('activity/connection-activity-monitor-factory'); function InternalSession(conversationSetFactory, serviceRegistry, connectionFactory, opts) { var emitter = Emitter.assign(this); var principal = ""; var sessionID; var token; var sessionActivityMonitor; var conversationSet = conversationSetFactory.create(); var fsm = FSM.create('initialising', { initialising: ['connecting'], connecting: ['connected', 'closing'], connected: ['disconnected', 'closing', 'closed'], disconnected: ['reconnecting', 'closing'], reconnecting: ['connected', 'closing'], closing: ['closed'], closed: [] }); fsm.on('change', function (previous, current) { log.debug('State changed: ' + previous + ' -> ' + current); }); var self = this; this.getOptions = function() { return opts; }; this.getState = function () { return fsm.state; }; this.getConversationSet = function () { return conversationSet; }; this.isConnected = function () { return fsm.state === 'connected'; }; this.getServiceRegistry = function () { return serviceRegistry; }; this.getSessionId = function () { return sessionID; }; this.setPrincipal = function (newPrincipal) { principal = newPrincipal; }; this.checkConnected = function (emitter) { if (self.isConnected()) { return true; } else { emitter.error(new Error('The session is not connected. Operations are not possible at this time.')); return false; } }; 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(transports, opts.reconnect.timeout, 256); var serviceAdapter = new ServiceAdapter(this, serialisers, connection.send); var serviceLocator = new ServiceLocator(this, serialisers, serviceAdapter); var topicCache = new TopicCache(DataTypes); var topicStreamRegistry = new StreamRegistry(topicCache); var topicRouting = new TopicRouting(this, serviceAdapter, conversationSet, topicCache, topicStreamRegistry); serviceRegistry.addListener(serviceAdapter.addService); // Close the session function close(reason) { if (fsm.change('closed')) { topicStreamRegistry.close(); conversationSet.discardAll(reason); emitter.close(reason); } else { log.debug('Unable to handle session close, session state: ', fsm.state); } } // Attempt to reconnect based on existing options / sessionID var attempts = 0; var reconnect = function (opts) { if (fsm.change('reconnecting')) { log.info('Reconnect attempt (' + (++attempts) + ')'); var request = ConnectionRequest.reconnect( token, connection.getAvailableSequence(), connection.lastReceivedSequence); connection.connect(request, opts, opts.reconnect.timeout); } else { log.debug('Unable to attempt reconnect, session state: ', fsm.state); } }; // Cancel a reconnect event var abort = function (reason) { if (fsm.change('closing')) { log.debug('Aborting reconnect'); close(reason || CloseReason.RECONNECT_ABORTED); } else { log.debug('Unable to abort reconnect, session state: ', fsm.state); } }; function replaceConversationSet(err) { var oldConversationSet = conversationSet; conversationSet = conversationSetFactory.create(); oldConversationSet.discardAll(err); log.debug('Replaced conversation set', err); } this.connect = function () { if (fsm.change('connecting')) { // Timeout applied for reconnect attempts from initial disconnect var reconnectTimeout; // Construct a creation request var request = ConnectionRequest.connect(); // Handle messages from connection connection.on('data', topicRouting.route); // Close events are terminal connection.on('close', close); if (opts.activityMonitor) { sessionActivityMonitor = sessionActivityMonitorModule.create(connectionActivityMonitorFactory); } else { sessionActivityMonitor = sessionActivityMonitorModule.NOOP; } // Handle connection disconnections connection.on('disconnect', function (reason) { log.debug('Connection disconnected, reason: ', reason); if (fsm.change('disconnected') || fsm.state === 'reconnecting') { sessionActivityMonitor.onConnectionClosed(); // Call reconnect if applicable if (opts.reconnect.timeout > 0 && reason.canReconnect) { opts.token = token; // Only emit the disconnect state once if (fsm.state === 'disconnected') { emitter.emit('disconnect', reason); reconnectTimeout = setTimeout(function () { if (fsm.state !== 'connected') { abort(CloseReason.CONNECTION_TIMEOUT); } }, opts.reconnect.timeout); } opts.reconnect.strategy(curry(reconnect, opts), abort, reason); } else { abort(reason); } } else if (fsm.change('closing')) { close(reason); } else { sessionActivityMonitor.onConnectionClosed(); log.debug('Unable to handle session disconnect, session state: ', fsm.state); } }); // Handle connect response connection.on('connect', function (response) { if (fsm.change('connected')) { token = response.token; if (response.response === ResponseCode.OK) { sessionID = response.identity; sessionActivityMonitor.onNewConnection(connection, response); emitter.emit('connect', response.identity); } else if ( response.response === ResponseCode.RECONNECTED || response.response === ResponseCode.RECONNECTED_WITH_MESSAGE_LOSS) { attempts = 0; clearTimeout(reconnectTimeout); if (response.response === ResponseCode.RECONNECTED_WITH_MESSAGE_LOSS) { connection.resetSequences(); replaceConversationSet(new Error("Peer is disconnected")); topicCache.notifyUnsubscriptionOfAllTopics(topicStreamRegistry); log.info("Reconnected session, but messages may have been lost"); } else { log.info('Reconnected session'); } sessionActivityMonitor.onNewConnection(connection, response); emitter.emit('reconnect'); } } else { log.trace('Unknown connection response: ', response); } }); log.debug('Connecting with options:', opts); log.trace('Connecting with request:', request); try { connection.connect(request, opts); } catch (e) { log.warn('Connection error', e); emitter.emit('error', e); if (fsm.change('closing')) { close(CloseReason.CONNECTION_ERROR); } } if (opts.principal) { principal = opts.principal; } } else { log.warn('Unable to connect, session state: ', fsm.state); emitter.emit('error', new Error('Unable to connect, session state: ' + fsm.state)); } }; this.close = function () { if (fsm.change('closing')) { sessionActivityMonitor.onConnectionClosed(); connection.close(CloseReason.CLOSED_BY_CLIENT); } else { log.debug('Unable to close, session state: ', fsm.state); } }; this.getErrorHandler = function () { return function (error) { log.error("Session error:", error.message); }; }; this.getServiceLocator = function () { return serviceLocator; }; this.getServiceAdapter = function () { return serviceAdapter; }; this.getPrincipal = function () { return principal; }; this.getStreamRegistry = function () { return topicStreamRegistry; }; this.getRouting = function () { return topicRouting; }; this.onSystemPing = function () { sessionActivityMonitor.onSystemPing(); }; var sessionLocks = new InternalSessionLocks(this, fsm); this.lock = function(lockName, scope) { return sessionLocks.lock(lockName, scope); }; } module.exports = InternalSession;