UNPKG

@twilio/voice-sdk

Version:
487 lines 35 kB
"use strict"; /** * @packageDocumentation * @module Tools * @internalapi */ 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) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WSTransportState = void 0; var events_1 = require("events"); var backoff_1 = require("./backoff"); var errors_1 = require("./errors"); var log_1 = require("./log"); var WebSocket = globalThis.WebSocket; var CONNECT_SUCCESS_TIMEOUT = 10000; var CONNECT_TIMEOUT = 5000; var HEARTBEAT_TIMEOUT = 15000; var MAX_PREFERRED_DURATION = 15000; var MAX_PRIMARY_DURATION = Infinity; var MAX_PREFERRED_DELAY = 1000; var MAX_PRIMARY_DELAY = 20000; /** * All possible states of WSTransport. */ var WSTransportState; (function (WSTransportState) { /** * The WebSocket is not open but is trying to connect. */ WSTransportState["Connecting"] = "connecting"; /** * The WebSocket is not open and is not trying to connect. */ WSTransportState["Closed"] = "closed"; /** * The underlying WebSocket is open and active. */ WSTransportState["Open"] = "open"; })(WSTransportState = exports.WSTransportState || (exports.WSTransportState = {})); /** * WebSocket Transport */ var WSTransport = /** @class */ (function (_super) { __extends(WSTransport, _super); /** * @constructor * @param uris - List of URI of the endpoints to connect to. * @param [options] - Constructor options. */ function WSTransport(uris, options) { if (options === void 0) { options = {}; } var _this = _super.call(this) || this; /** * The current state of the WSTransport. */ _this.state = WSTransportState.Closed; /** * Start timestamp values for backoffs. */ _this._backoffStartTime = { preferred: null, primary: null, }; /** * The URI that the transport is connecting or connected to. The value of this * property is `null` if a connection attempt has not been made yet. */ _this._connectedUri = null; /** * An instance of Logger to use. */ _this._log = log_1.default.getInstance(); /** * Whether we should attempt to fallback if we receive an applicable error * when trying to connect to a signaling endpoint. */ _this._shouldFallback = false; /** * The current uri index that the transport is connected to. */ _this._uriIndex = 0; /** * Move the uri index to the next index * If the index is at the end, the index goes back to the first one. */ _this._moveUriIndex = function () { _this._uriIndex++; if (_this._uriIndex >= _this._uris.length) { _this._uriIndex = 0; } }; /** * Called in response to WebSocket#close event. */ _this._onSocketClose = function (event) { _this._log.info("Received websocket close event code: " + event.code + ". Reason: " + event.reason); // 1006: Abnormal close. When the server is unreacheable // 1015: TLS Handshake error if (event.code === 1006 || event.code === 1015) { _this.emit('error', { code: 31005, message: event.reason || 'Websocket connection to Twilio\'s signaling servers were ' + 'unexpectedly ended. If this is happening consistently, there may ' + 'be an issue resolving the hostname provided. If a region or an ' + 'edge is being specified in Device setup, ensure it is valid.', twilioError: new errors_1.SignalingErrors.ConnectionError(), }); var wasConnected = ( // Only in Safari and certain Firefox versions, on network interruption, websocket drops right away with 1006 // Let's check current state if it's open, meaning we should not fallback // because we're coming from a previously connected session _this.state === WSTransportState.Open || // But on other browsers, websocket doesn't drop // but our heartbeat catches it, setting the internal state to "Connecting". // With this, we should check the previous state instead. _this._previousState === WSTransportState.Open); // Only fallback if this is not the first error // and if we were not connected previously if (_this._shouldFallback || !wasConnected) { _this._moveUriIndex(); } _this._shouldFallback = true; } _this._closeSocket(); }; /** * Called in response to WebSocket#error event. */ _this._onSocketError = function (err) { _this._log.info("WebSocket received error: " + err.message); _this.emit('error', { code: 31000, message: err.message || 'WSTransport socket error', twilioError: new errors_1.SignalingErrors.ConnectionDisconnected(), }); }; /** * Called in response to WebSocket#message event. */ _this._onSocketMessage = function (message) { // Clear heartbeat timeout on any incoming message, as they // all indicate an active connection. _this._setHeartbeatTimeout(); // Filter and respond to heartbeats if (_this._socket && message.data === '\n') { _this._socket.send('\n'); return; } _this.emit('message', message); }; /** * Called in response to WebSocket#open event. */ _this._onSocketOpen = function () { _this._log.info('WebSocket opened successfully.'); _this._timeOpened = Date.now(); _this._shouldFallback = false; _this._setState(WSTransportState.Open); clearTimeout(_this._connectTimeout); _this._resetBackoffs(); _this._setHeartbeatTimeout(); _this.emit('open'); }; _this._options = __assign(__assign({}, WSTransport.defaultConstructorOptions), options); _this._uris = uris; _this._backoff = _this._setupBackoffs(); return _this; } /** * Close the WebSocket, and don't try to reconnect. */ WSTransport.prototype.close = function () { this._log.info('WSTransport.close() called...'); this._close(); }; /** * Attempt to open a WebSocket connection. */ WSTransport.prototype.open = function () { this._log.info('WSTransport.open() called...'); if (this._socket && (this._socket.readyState === WebSocket.CONNECTING || this._socket.readyState === WebSocket.OPEN)) { this._log.info('WebSocket already open.'); return; } if (this._preferredUri) { this._connect(this._preferredUri); } else { this._connect(this._uris[this._uriIndex]); } }; /** * Send a message through the WebSocket connection. * @param message - A message to send to the endpoint. * @returns Whether the message was sent. */ WSTransport.prototype.send = function (message) { // We can't send the message if the WebSocket isn't open if (!this._socket || this._socket.readyState !== WebSocket.OPEN) { return false; } try { this._socket.send(message); } catch (e) { // Some unknown error occurred. Reset the socket to get a fresh session. this._log.info('Error while sending message:', e.message); this._closeSocket(); return false; } return true; }; /** * Update the preferred URI to connect to. Useful for Call signaling * reconnection, which requires connecting on the same edge. If `null` is * passed, the preferred URI is unset and the original `uris` array and * `uriIndex` is used to determine the signaling URI to connect to. * @param uri */ WSTransport.prototype.updatePreferredURI = function (uri) { this._preferredUri = uri; }; /** * Update acceptable URIs to reconnect to. Resets the URI index to 0. */ WSTransport.prototype.updateURIs = function (uris) { if (typeof uris === 'string') { uris = [uris]; } this._uris = uris; this._uriIndex = 0; }; /** * Close the WebSocket, and don't try to reconnect. */ WSTransport.prototype._close = function () { this._setState(WSTransportState.Closed); this._closeSocket(); }; /** * Close the WebSocket and remove all event listeners. */ WSTransport.prototype._closeSocket = function () { clearTimeout(this._connectTimeout); clearTimeout(this._heartbeatTimeout); this._log.info('Closing and cleaning up WebSocket...'); if (!this._socket) { this._log.info('No WebSocket to clean up.'); return; } this._socket.removeEventListener('close', this._onSocketClose); this._socket.removeEventListener('error', this._onSocketError); this._socket.removeEventListener('message', this._onSocketMessage); this._socket.removeEventListener('open', this._onSocketOpen); if (this._socket.readyState === WebSocket.CONNECTING || this._socket.readyState === WebSocket.OPEN) { this._socket.close(); } // Reset backoff counter if connection was open for long enough to be considered successful if (this._timeOpened && Date.now() - this._timeOpened > CONNECT_SUCCESS_TIMEOUT) { this._resetBackoffs(); } if (this.state !== WSTransportState.Closed) { this._performBackoff(); } delete this._socket; this.emit('close'); }; /** * Attempt to connect to the endpoint via WebSocket. * @param [uri] - URI string to connect to. * @param [retryCount] - Retry number, if this is a retry. Undefined if * first attempt, 1+ if a retry. */ WSTransport.prototype._connect = function (uri, retryCount) { var _this = this; this._log.info(typeof retryCount === 'number' ? "Attempting to reconnect (retry #" + retryCount + ")..." : 'Attempting to connect...'); this._closeSocket(); this._setState(WSTransportState.Connecting); this._connectedUri = uri; try { this._socket = new this._options.WebSocket(this._connectedUri); } catch (e) { this._log.info('Could not connect to endpoint:', e.message); this._close(); this.emit('error', { code: 31000, message: e.message || "Could not connect to " + this._connectedUri, twilioError: new errors_1.SignalingErrors.ConnectionDisconnected(), }); return; } this._socket.addEventListener('close', this._onSocketClose); this._socket.addEventListener('error', this._onSocketError); this._socket.addEventListener('message', this._onSocketMessage); this._socket.addEventListener('open', this._onSocketOpen); delete this._timeOpened; this._connectTimeout = setTimeout(function () { _this._log.info('WebSocket connection attempt timed out.'); _this._moveUriIndex(); _this._closeSocket(); }, this._options.connectTimeoutMs); }; /** * Perform a backoff. If a preferred URI is set (not null), then backoff * using the preferred mechanism. Otherwise, use the primary mechanism. */ WSTransport.prototype._performBackoff = function () { if (this._preferredUri) { this._log.info('Preferred URI set; backing off.'); this._backoff.preferred.backoff(); } else { this._log.info('Preferred URI not set; backing off.'); this._backoff.primary.backoff(); } }; /** * Reset both primary and preferred backoff mechanisms. */ WSTransport.prototype._resetBackoffs = function () { this._backoff.preferred.reset(); this._backoff.primary.reset(); this._backoffStartTime.preferred = null; this._backoffStartTime.primary = null; }; /** * Set a timeout to reconnect after HEARTBEAT_TIMEOUT milliseconds * have passed without receiving a message over the WebSocket. */ WSTransport.prototype._setHeartbeatTimeout = function () { var _this = this; clearTimeout(this._heartbeatTimeout); this._heartbeatTimeout = setTimeout(function () { _this._log.info("No messages received in " + HEARTBEAT_TIMEOUT / 1000 + " seconds. Reconnecting..."); _this._shouldFallback = true; _this._closeSocket(); }, HEARTBEAT_TIMEOUT); }; /** * Set the current and previous state */ WSTransport.prototype._setState = function (state) { this._previousState = this.state; this.state = state; }; /** * Set up the primary and preferred backoff mechanisms. */ WSTransport.prototype._setupBackoffs = function () { var _this = this; var preferredBackoffConfig = { factor: 2.0, jitter: 0.40, max: this._options.maxPreferredDelayMs, min: 100, }; this._log.info('Initializing preferred transport backoff using config: ', preferredBackoffConfig); var preferredBackoff = new backoff_1.default(preferredBackoffConfig); preferredBackoff.on('backoff', function (attempt, delay) { if (_this.state === WSTransportState.Closed) { _this._log.info('Preferred backoff initiated but transport state is closed; not attempting a connection.'); return; } _this._log.info("Will attempt to reconnect Websocket to preferred URI in " + delay + "ms"); if (attempt === 0) { _this._backoffStartTime.preferred = Date.now(); _this._log.info("Preferred backoff start; " + _this._backoffStartTime.preferred); } }); preferredBackoff.on('ready', function (attempt, _delay) { if (_this.state === WSTransportState.Closed) { _this._log.info('Preferred backoff ready but transport state is closed; not attempting a connection.'); return; } if (_this._backoffStartTime.preferred === null) { _this._log.info('Preferred backoff start time invalid; not attempting a connection.'); return; } if (Date.now() - _this._backoffStartTime.preferred > _this._options.maxPreferredDurationMs) { _this._log.info('Max preferred backoff attempt time exceeded; falling back to primary backoff.'); _this._preferredUri = null; _this._backoff.primary.backoff(); return; } if (typeof _this._preferredUri !== 'string') { _this._log.info('Preferred URI cleared; falling back to primary backoff.'); _this._preferredUri = null; _this._backoff.primary.backoff(); return; } _this._connect(_this._preferredUri, attempt + 1); }); var primaryBackoffConfig = { factor: 2.0, jitter: 0.40, max: this._options.maxPrimaryDelayMs, // We only want a random initial delay if there are any fallback edges // Initial delay between 1s and 5s both inclusive min: this._uris && this._uris.length > 1 ? Math.floor(Math.random() * (5000 - 1000 + 1)) + 1000 : 100, }; this._log.info('Initializing primary transport backoff using config: ', primaryBackoffConfig); var primaryBackoff = new backoff_1.default(primaryBackoffConfig); primaryBackoff.on('backoff', function (attempt, delay) { if (_this.state === WSTransportState.Closed) { _this._log.info('Primary backoff initiated but transport state is closed; not attempting a connection.'); return; } _this._log.info("Will attempt to reconnect WebSocket in " + delay + "ms"); if (attempt === 0) { _this._backoffStartTime.primary = Date.now(); _this._log.info("Primary backoff start; " + _this._backoffStartTime.primary); } }); primaryBackoff.on('ready', function (attempt, _delay) { if (_this.state === WSTransportState.Closed) { _this._log.info('Primary backoff ready but transport state is closed; not attempting a connection.'); return; } if (_this._backoffStartTime.primary === null) { _this._log.info('Primary backoff start time invalid; not attempting a connection.'); return; } if (Date.now() - _this._backoffStartTime.primary > _this._options.maxPrimaryDurationMs) { _this._log.info('Max primary backoff attempt time exceeded; not attempting a connection.'); return; } _this._connect(_this._uris[_this._uriIndex], attempt + 1); }); return { preferred: preferredBackoff, primary: primaryBackoff, }; }; Object.defineProperty(WSTransport.prototype, "uri", { /** * The uri the transport is currently connected to */ get: function () { return this._connectedUri; }, enumerable: false, configurable: true }); WSTransport.defaultConstructorOptions = { WebSocket: WebSocket, connectTimeoutMs: CONNECT_TIMEOUT, maxPreferredDelayMs: MAX_PREFERRED_DELAY, maxPreferredDurationMs: MAX_PREFERRED_DURATION, maxPrimaryDelayMs: MAX_PRIMARY_DELAY, maxPrimaryDurationMs: MAX_PRIMARY_DURATION, }; return WSTransport; }(events_1.EventEmitter)); exports.default = WSTransport; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3N0cmFuc3BvcnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saWIvdHdpbGlvL3dzdHJhbnNwb3J0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7OztHQUlHOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFSCxpQ0FBc0M7QUFDdEMscUNBQWdDO0FBQ2hDLG1DQUEyQztBQUMzQyw2QkFBd0I7QUFFeEIsSUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLFNBQVMsQ0FBQztBQUV2QyxJQUFNLHVCQUF1QixHQUFHLEtBQUssQ0FBQztBQUN0QyxJQUFNLGVBQWUsR0FBRyxJQUFJLENBQUM7QUFDN0IsSUFBTSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7QUFDaEMsSUFBTSxzQkFBc0IsR0FBRyxLQUFLLENBQUM7QUFDckMsSUFBTSxvQkFBb0IsR0FBRyxRQUFRLENBQUM7QUFDdEMsSUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUM7QUFDakMsSUFBTSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7QUFRaEM7O0dBRUc7QUFDSCxJQUFZLGdCQWVYO0FBZkQsV0FBWSxnQkFBZ0I7SUFDMUI7O09BRUc7SUFDSCw2Q0FBeUIsQ0FBQTtJQUV6Qjs7T0FFRztJQUNILHFDQUFpQixDQUFBO0lBRWpCOztPQUVHO0lBQ0gsaUNBQWEsQ0FBQTtBQUNmLENBQUMsRUFmVyxnQkFBZ0IsR0FBaEIsd0JBQWdCLEtBQWhCLHdCQUFnQixRQWUzQjtBQTBDRDs7R0FFRztBQUNIO0lBQXlDLCtCQUFZO0lBd0duRDs7OztPQUlHO0lBQ0gscUJBQVksSUFBYyxFQUFFLE9BQTZDO1FBQTdDLHdCQUFBLEVBQUEsWUFBNkM7UUFBekUsWUFDRSxpQkFBTyxTQU9SO1FBM0dEOztXQUVHO1FBQ0gsV0FBSyxHQUFxQixnQkFBZ0IsQ0FBQyxNQUFNLENBQUM7UUFVbEQ7O1dBRUc7UUFDSyx1QkFBaUIsR0FHckI7WUFDRixTQUFTLEVBQUUsSUFBSTtZQUNmLE9BQU8sRUFBRSxJ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