UNPKG

diffusion

Version:

Diffusion JavaScript client

492 lines (491 loc) 20.5 kB
"use strict"; /** * @module Client */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.InternalSessionImpl = void 0; var connection_activity_monitor_factory_1 = require("./../activity/connection-activity-monitor-factory"); var session_activity_monitor_1 = require("./../activity/session-activity-monitor"); var internal_session_1 = require("./../client/internal-session"); var service_adapter_1 = require("./../client/service-adapter"); var service_locator_1 = require("./../client/service-locator"); var session_context_impl_1 = require("./../client/session-context-impl"); var datatypes_1 = require("./../data/datatypes"); var emitter_1 = require("./../events/emitter"); var stream_1 = require("./../events/stream"); var session_lock_1 = require("./../features/session-lock"); var ConnectionRequest = require("./../protocol/connection-request"); var response_code_1 = require("./../protocol/response-code"); var stream_registry_1 = require("./../routing/stream-registry"); var topic_cache_1 = require("./../routing/topic-cache"); var topic_routing_1 = require("./../routing/topic-routing"); var transports_1 = require("./../transports/transports"); var fsm_1 = require("./../util/fsm"); var logger = require("./../util/logger"); var version_1 = require("./../version"); var close_reason_1 = require("../../client/close-reason"); var errors_1 = require("../../errors/errors"); var INITIALISING = internal_session_1.InternalSessionState.INITIALISING, CONNECTING = internal_session_1.InternalSessionState.CONNECTING, CONNECTED = internal_session_1.InternalSessionState.CONNECTED, DISCONNECTED = internal_session_1.InternalSessionState.DISCONNECTED, RECONNECTING = internal_session_1.InternalSessionState.RECONNECTING, CLOSING = internal_session_1.InternalSessionState.CLOSING, CLOSED = internal_session_1.InternalSessionState.CLOSED; var log = logger.create('Internal Session'); /** * Internal session implementation */ var InternalSessionImpl = /** @class */ (function (_super) { __extends(InternalSessionImpl, _super); /** * Create a new InternalSessionImpl instance * * @param conversationSetFactory the factory for generating conversation sets * @param serviceRegistry the service registry * @param connectionFactory a factory for creating the connection * @param opts the session options * * @throws a {@link RuntimeError} if the serviceRegistry contains duplicate service definitions. */ function InternalSessionImpl(conversationSetFactory, serviceRegistry, connectionFactory, opts, sessnActivityMonitorFactory) { var _a; if (sessnActivityMonitorFactory === void 0) { sessnActivityMonitorFactory = session_activity_monitor_1.sessionActivityMonitorFactory; } var _this = this; var factory = emitter_1.Emitter.create(); _this = _super.call(this, factory) || this; _this.emitter = factory.emitter(_this); _this.version = version_1.version; _this.principal = ''; _this.conversationSetFactory = conversationSetFactory; _this.conversationSet = conversationSetFactory(); _this.serviceRegistry = serviceRegistry; _this.opts = opts; _this.sessionActivityMonitorFactory = sessnActivityMonitorFactory; _this.fsm = new fsm_1.FSM(INITIALISING, (_a = {}, _a[INITIALISING] = [CONNECTING], _a[CONNECTING] = [CONNECTED, CLOSING], _a[CONNECTED] = [ DISCONNECTED, CLOSING, CLOSED ], _a[DISCONNECTED] = [RECONNECTING, CLOSING], _a[RECONNECTING] = [CONNECTED, CLOSING], _a[CLOSING] = [CLOSED], _a[CLOSED] = [], _a)); _this.fsm.on('change', function (previous, current) { log.debug('State changed: ' + previous + ' -> ' + current); }); _this.transports = transports_1.Transports.create(); // forward cascading notification events _this.transports.on({ 'transport-selected': function (name) { _this.emitter.emit('transport-selected', name); }, 'cascade': function () { _this.emitter.emit('cascade'); } }); _this.connection = connectionFactory(_this.transports, _this.opts.reconnect.timeout, 256); _this.serviceAdapter = new service_adapter_1.ServiceAdapter(_this, function (msg) { _this.connection.send(msg); }); _this.serviceLocator = new service_locator_1.ServiceLocatorImpl(_this, _this.serviceAdapter); _this.topicCache = new topic_cache_1.TopicCache(datatypes_1.DataTypes); _this.topicStreamRegistry = new stream_registry_1.StreamRegistryImpl(_this.topicCache); _this.topicRouting = new topic_routing_1.TopicRouting(_this, _this.serviceAdapter, _this.conversationSet, _this.topicCache, _this.topicStreamRegistry); _this.serviceRegistry.addListener(_this.serviceAdapter.addService.bind(_this.serviceAdapter)); // tslint:disable-next-line:strict-type-predicates if (session_lock_1.InternalSessionLocks !== undefined) { _this.sessionLocksFeature = new session_lock_1.InternalSessionLocks(_this, _this.fsm); } _this.attempts = 0; return _this; } Object.defineProperty(InternalSessionImpl.prototype, "sessionLocks", { get: function () { // tslint:disable-next-line:strict-type-predicates if (this.sessionLocksFeature === undefined) { // tslint:disable-next-line:strict-type-predicates if (session_lock_1.InternalSessionLocks !== undefined) { this.sessionLocksFeature = new session_lock_1.InternalSessionLocks(this, this.fsm); } else { throw new errors_1.RuntimeError('Session Locks feature has not been loaded'); } } return this.sessionLocksFeature; }, enumerable: false, configurable: true }); /** * Get the session's options * * @return the options */ InternalSessionImpl.prototype.getOptions = function () { return this.opts; }; /** * Get the current state * * @return the state of the finite state machine */ InternalSessionImpl.prototype.getState = function () { return this.fsm.state; }; /** * Add a callback that will be called when the session state changes * * @param callback the function to call when the session state changes */ InternalSessionImpl.prototype.onStateChange = function (callback) { this.fsm.on('change', callback); }; /** * Get the conversation set * * @return a reference to the conversation set */ InternalSessionImpl.prototype.getConversationSet = function () { return this.conversationSet; }; /** * Query if the session is connected * * @return `true` if the finite state machine is in the `connected` state */ InternalSessionImpl.prototype.isConnected = function () { return this.fsm.state === CONNECTED; }; /** * Get the service registry * * @return a reference to the service registry */ InternalSessionImpl.prototype.getServiceRegistry = function () { return this.serviceRegistry; }; /** * Get the session ID * * @return the session ID */ InternalSessionImpl.prototype.getSessionId = function () { return this.sessionID; }; /** * Get the server's maximum message size. */ InternalSessionImpl.prototype.getServerMaximumMessageSize = function () { return this.serverMaximumMessageSize; }; /** * Check if the session is connected * * If the session is not connected an error will be emitted on the session's stream * * @return `true` if the finite state machine is in the `connected` state */ InternalSessionImpl.prototype.checkConnected = function (reject) { if (this.isConnected()) { return true; } else { reject(new Error('The session is not connected. Operations are not possible at this time.')); return false; } }; /** * Close the session * * This will close all topic streams, discard all conversations and emit a * `close` event on the session's stream * * @param reason the reason for closing the session. This will be passed on to * the stream `close` event. */ InternalSessionImpl.prototype.internalClose = function (reason) { if (this.fsm.change(CLOSED)) { this.topicStreamRegistry.close(); this.conversationSet.discardAll(reason); this.emitter.close(reason); } else { log.debug('Unable to handle session close, session state: ', this.fsm.state); } }; /** * Attempt to reconnect after disconnecting * * @param opts the session options * @param token the token of the session when it was connected */ InternalSessionImpl.prototype.reconnect = function (opts, token) { if (this.fsm.change(RECONNECTING)) { log.info('Reconnect attempt (' + (++this.attempts) + ')'); var request = ConnectionRequest.reconnect(token, this.connection.getAvailableSequence(), this.connection.lastReceivedSequence); this.connection.connect(request, opts, opts.reconnect.timeout); } else { log.debug('Unable to attempt reconnect, session state: ', this.fsm.state); } }; /** * Cancel a reconnect event * * @param reason the reason for closing */ InternalSessionImpl.prototype.abort = function (reason) { if (reason === void 0) { reason = close_reason_1.CloseReasonEnum.RECONNECT_ABORTED; } if (this.fsm.change(CLOSING)) { log.debug('Aborting reconnect'); this.internalClose(reason); } else { log.debug('Unable to abort reconnect, session state: ', this.fsm.state); } }; /** * Replace the conversation set with a new one. * * @param err the error that caused the message loss */ InternalSessionImpl.prototype.replaceConversationSet = function (err) { var oldConversationSet = this.conversationSet; this.conversationSet = this.conversationSetFactory(); oldConversationSet.discardAll(err); log.debug('Replaced conversation set', err); }; /** * Attempt to connect to the server */ InternalSessionImpl.prototype.connect = function () { var _this = this; if (this.fsm.change(CONNECTING)) { // timeout applied for reconnect attempts from initial disconnect var reconnectTimeout_1; // construct a creation request var request = ConnectionRequest.connect(); // handle messages from connection this.connection.on('data', this.topicRouting.route.bind(this.topicRouting)); // close events are terminal this.connection.on('close', this.internalClose.bind(this)); this.sessionActivityMonitor = this.opts.activityMonitor ? this.sessionActivityMonitorFactory.create(connection_activity_monitor_factory_1.connectionActivityMonitorFactory) : this.sessionActivityMonitorFactory.NOOP; // handle connection disconnections this.connection.on('disconnect', function (reason) { log.debug('Connection disconnected, reason: ', reason); if (_this.fsm.change(DISCONNECTED) || _this.fsm.state === RECONNECTING) { _this.sessionActivityMonitor.onConnectionClosed(); // call reconnect if applicable if (_this.opts.reconnect.timeout > 0 && reason.canReconnect) { // only emit the disconnect state once if (_this.fsm.state === DISCONNECTED) { _this.emitter.emit('disconnect', reason); reconnectTimeout_1 = setTimeout(function () { if (_this.fsm.state !== CONNECTED) { _this.abort(close_reason_1.CloseReasonEnum.CONNECTION_TIMEOUT); } }, _this.opts.reconnect.timeout); } _this.opts.reconnect.strategy(_this.reconnect.bind(_this, _this.opts, _this.token), _this.abort.bind(_this), reason); } else { _this.abort(reason); } } else if (_this.fsm.change(CLOSING)) { _this.internalClose(reason); } else { _this.sessionActivityMonitor.onConnectionClosed(); log.debug('Unable to handle session disconnect, session state: ', _this.fsm.state); } }); // handle connect response this.connection.on('connect', function (response) { if (_this.fsm.change(CONNECTED)) { _this.token = response.token; if (response.response === response_code_1.responseCodes.OK) { _this.sessionID = response.identity; _this.serverMaximumMessageSize = response.maximumMessageSize; _this.sessionActivityMonitor.onNewConnection(_this.connection, response); _this.emitter.emit('connect', response.identity); } else if (response.response === response_code_1.responseCodes.RECONNECTED || response.response === response_code_1.responseCodes.RECONNECTED_WITH_MESSAGE_LOSS) { _this.attempts = 0; clearTimeout(reconnectTimeout_1); if (response.response === response_code_1.responseCodes.RECONNECTED_WITH_MESSAGE_LOSS) { _this.connection.resetSequences(); _this.replaceConversationSet(new Error('Peer is disconnected')); _this.topicCache.notifyUnsubscriptionOfAllTopics(_this.topicStreamRegistry); log.info('Reconnected session, but messages may have been lost'); } else { log.info('Reconnected session'); } _this.sessionActivityMonitor.onNewConnection(_this.connection, response); _this.emitter.emit('reconnect'); } } else { log.trace('Unknown connection response: ', response); } }); log.debug('Connecting with options:', this.opts); log.trace('Connecting with request:', request); try { this.connection.connect(request, this.opts, this.opts.connectionTimeout); } catch (e) { log.warn('Connection error', e); this.emitter.emit('error', e); if (this.fsm.change(CLOSING)) { this.internalClose(close_reason_1.CloseReasonEnum.CONNECTION_ERROR); } } if (this.opts.principal) { this.principal = this.opts.principal; } } else { log.warn('Unable to connect, session state: ', this.fsm.state); this.emitter.emit('error', new Error('Unable to connect, session state: ' + this.fsm.state)); } }; /** * Manually close the session */ InternalSessionImpl.prototype.close = function () { if (this.fsm.change(CLOSING)) { this.sessionActivityMonitor.onConnectionClosed(); this.connection.close(close_reason_1.CloseReasonEnum.CLOSED_BY_CLIENT); } else { log.debug('Unable to close, session state: ', this.fsm.state); } return this; }; /** * Get the error handler * * @return a handler that logs the error in the error logs */ InternalSessionImpl.prototype.getErrorHandler = function () { return function (error) { log.error('Session error:', error.message); }; }; /** * Get the service locator * * @return the service locator */ InternalSessionImpl.prototype.getServiceLocator = function () { return this.serviceLocator; }; /** * Get the service adapter * * @return the service adapter */ InternalSessionImpl.prototype.getServiceAdapter = function () { return this.serviceAdapter; }; /** * Get the session principal * * @return the session principal */ InternalSessionImpl.prototype.getPrincipal = function () { return this.principal; }; /** * Set the principal of the session * * @param newPrincipal the new principal */ InternalSessionImpl.prototype.setPrincipal = function (newPrincipal) { this.principal = newPrincipal; }; /** * Get the stream registry * * @return the stream registry */ InternalSessionImpl.prototype.getStreamRegistry = function () { return this.topicStreamRegistry; }; /** * Get the topic router * * @return the topic router */ InternalSessionImpl.prototype.getRouting = function () { return this.topicRouting; }; /** * System ping handler * * Forwards the call to the activity monitor */ InternalSessionImpl.prototype.onSystemPing = function () { this.sessionActivityMonitor.onSystemPing(); }; /** * Request a session lock * * @param lockName the lock name * @param scope the lock scope * @return a {@link Result} that resolves with the acquired session lock */ InternalSessionImpl.prototype.lock = function (lockName, scope, handler) { return this.sessionLocks.lock(lockName, scope, handler); }; /** * Unlock a session lock by acquisision * * @param acquisition the session lock acquisition * @return a {@link Result} that completes when the lock is released */ InternalSessionImpl.prototype.unlock = function (acquisition) { return this.sessionLocks.unlock(acquisition); }; /** * @inheritdoc */ InternalSessionImpl.prototype.isShared = function () { return false; }; /** * @inheritdoc */ InternalSessionImpl.prototype.getContext = function () { return session_context_impl_1.sessionContext; }; /** * @inheritdoc */ InternalSessionImpl.prototype.updateFeatures = function () { // tslint:disable-next-line:strict-type-predicates if (this.sessionLocks === undefined && session_lock_1.InternalSessionLocks !== undefined) { this.sessionLocks = new session_lock_1.InternalSessionLocks(this, this.fsm); } }; return InternalSessionImpl; }(stream_1.StreamImpl)); exports.InternalSessionImpl = InternalSessionImpl;