@twilio/voice-sdk
Version:
Twilio's JavaScript Voice SDK
490 lines • 36.3 kB
JavaScript
"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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