UNPKG

@twilio/voice-sdk

Version:
490 lines 36.3 kB
"use strict"; 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 __()); }; })(); 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 = 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 = new log_1.default('WSTransport'); /** * 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.error("Received websocket close event code: ".concat(event.code, ". Reason: ").concat(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.error("WebSocket received error: ".concat(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'); _this._log.debug('heartbeat'); return; } if (message && typeof message.data === 'string') { _this._log.debug("Received: ".concat(message.data)); } _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) { this._log.debug("Sending: ".concat(message)); // We can't send the message if the WebSocket isn't open if (!this._socket || this._socket.readyState !== WebSocket.OPEN) { this._log.debug('Cannot send message. WebSocket is not open.'); return false; } try { this._socket.send(message); } catch (e) { // Some unknown error occurred. Reset the socket to get a fresh session. this._log.error('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 #".concat(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.error('Could not connect to endpoint:', e.message); this._close(); this.emit('error', { code: 31000, message: e.message || "Could not connect to ".concat(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 ".concat(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 ".concat(delay, "ms")); if (attempt === 0) { _this._backoffStartTime.preferred = Date.now(); _this._log.info("Preferred backoff start; ".concat(_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 ".concat(delay, "ms")); if (attempt === 0) { _this._backoffStartTime.primary = Date.now(); _this._log.info("Primary backoff start; ".concat(_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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3N0cmFuc3BvcnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saWIvdHdpbGlvL3dzdHJhbnNwb3J0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsaUNBQXNDO0FBQ3RDLHFDQUFnQztBQUNoQyxtQ0FBMkM7QUFDM0MsNkJBQXdCO0FBRXhCLElBQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxTQUFTLENBQUM7QUFFdkMsSUFBTSx1QkFBdUIsR0FBRyxLQUFLLENBQUM7QUFDdEMsSUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDO0FBQzdCLElBQU0saUJBQWlCLEdBQUcsS0FBSyxDQUFDO0FBQ2hDLElBQU0sc0JBQXNCLEdBQUcsS0FBSyxDQUFDO0FBQ3JDLElBQU0sb0JBQW9CLEdBQUcsUUFBUSxDQUFDO0FBQ3RDLElBQU0sbUJBQW1CLEdBQUcsSUFBSSxDQUFDO0FBQ2pDLElBQU0saUJBQWlCLEdBQUcsS0FBSyxDQUFDO0FBUWhDOztHQUVHO0FBQ0gsSUFBWSxnQkFlWDtBQWZELFdBQVksZ0JBQWdCO0lBQzFCOztPQUVHO0lBQ0gsNkNBQXlCLENBQUE7SUFFekI7O09BRUc7SUFDSCxxQ0FBaUIsQ0FBQTtJQUVqQjs7T0FFRztJQUNILGlDQUFhLENBQUE7QUFDZixDQUFDLEVBZlcsZ0JBQWdCLGdDQUFoQixnQkFBZ0IsUUFlM0I7QUEwQ0Q7O0dBRUc7QUFDSDtJQUF5QywrQkFBWTtJQXdHbkQ7Ozs7T0FJRztJQUNILHFCQUFZLElBQWMsRUFBRSxPQUE2QztRQUE3Qyx3QkFBQSxFQUFBLFlBQTZDO1FBQ3ZFLFlBQUEsTUFBSyxXQUFFLFNBQUM7UUFwR1Y7O1dBRUc7UUFDSCxXQUFLLEdBQXFCLGdCQUFnQixDQUFDLE1BQU0sQ0FBQztRQVVsRDs7V0FFRztRQUNLLHVCQUFpQixHQUdyQjtZQUNGLFNBQVMsRUFBRSxJQUFJO1lBQ2YsT0FBTyxFQUFFLElBQUk7U0FDZCxDQUFDO1FBRUY7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