diffusion
Version:
Diffusion JavaScript client
493 lines (492 loc) • 20.6 kB
JavaScript
"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));
/* istanbul ignore else */ // 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) {
/* istanbul ignore else */ // 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);
/* istanbul ignore else */
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 errors_1.IllegalStateError('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 Promise} 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 Promise} 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.sessionLocksFeature === undefined && /* istanbul ignore next */ session_lock_1.InternalSessionLocks !== undefined) {
this.sessionLocksFeature = new session_lock_1.InternalSessionLocks(this, this.fsm);
}
};
return InternalSessionImpl;
}(stream_1.StreamImpl));
exports.InternalSessionImpl = InternalSessionImpl;