UNPKG

@signiant/media-shuttle-sdk-base

Version:

The base parent sdk behind other media shuttle sdks (e.g. media-shuttle-sdk)

289 lines (288 loc) 14.1 kB
/* eslint-disable import/first */ 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); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; /** * A channel provider implemented with websockets */ import MessageChannelProvider from '../MessageChannelProvider'; import { LogManager } from '../../../system'; import { constants } from '../../index'; import { CodedError, ErrorCode } from '../../../../external'; import ErrorMessages from '../../../../external/common/ErrorMessages'; var WebsocketChannelProvider = /** @class */ (function (_super) { __extends(WebsocketChannelProvider, _super); function WebsocketChannelProvider(params, url, sessionId, encryptionInfo, maxConnectAttempts, baseReconnectDelay) { var _this = _super.call(this, params) || this; _this._websocket = null; _this._connected = false; _this._url = url; _this._sessionId = sessionId; _this._encryptionInfo = encryptionInfo; _this._connectAttempts = 0; _this._unsubscribed = false; _this._maxConnectAttempts = maxConnectAttempts; _this._baseReconnectDelay = baseReconnectDelay; return _this; } WebsocketChannelProvider.prototype.initialize = function (callback) { LogManager.debug('Initializing WebsocketChannelProvider'); if (window.WebSocket) { try { this._msgCallback = callback; this.startWebsocket(); } catch (err) { /** * This error is unlikely to appear because the Websocket class will not throw an error even if * there is a failure to connect, we need to provide an onerror event listener for that (which we * do, see the onSocketError function) */ throw new CodedError({ code: ErrorCode.WEBSOCKET_INITIALIZATION_ERROR, message: ErrorMessages.generatedErrorMessages[ErrorCode.WEBSOCKET_INITIALIZATION_ERROR](err.message), }); } } else { LogManager.warn('WebSocket is NOT supported by your Browser - will use alternate messaging method'); } }; WebsocketChannelProvider.prototype.startWebsocket = function () { /** * The Websocket channel provider typically lives underneath the WebsocketAggregateChannelProvider. Once a * connection to any websocket is established the other connections are reset. */ if (this._unsubscribed === true) { LogManager.info("WebSocketChannelProvider for url ".concat(this._url, " has been unsubscribed, ignoring further attempts to start websocket")); return; } //The increment here is important and must be kept even if log statement is removed. LogManager.info("Starting Websocket for url [".concat(this._url, "] attempt [").concat(++this._connectAttempts, "]")); this._websocket = new WebSocket(this._url); this._websocket.onerror = this.onSocketError.bind(this); this._websocket.onmessage = this.onHandshakeMessage.bind(this); this._websocket.onopen = this.onSocketOpen.bind(this); this._websocket.onclose = this.onSocketClose.bind(this); }; WebsocketChannelProvider.prototype.publish = function (message) { if (this._websocket) { var messageString = JSON.stringify(message); LogManager.debug("Publishing message: ".concat(messageString)); this._websocket.send(messageString); } }; WebsocketChannelProvider.prototype.onHandshakeMessage = function (evt) { LogManager.debug("onHandshakeMessage received: ".concat(evt.data)); var messageParsed = JSON.parse(evt.data); if (this.isAckAckMessageFromApp(messageParsed)) { LogManager.info('WebsocketChannel ' + this._websocket.url + ' is connected'); //route all further messages through the normal mechanism this._websocket.onmessage = this.onMessage.bind(this); this._connected = true; //send ack^3 before calling onMessage() this.sendAckAckAckToApp(); //this should trigger active provider event this.onMessage(evt); } else { LogManager.warn("WebSocketChannel ".concat(this._websocket.url, " received unexpected handshake message: "), evt.data); this._websocket.close(); } }; WebsocketChannelProvider.prototype.onSocketError = function (evt) { this._connected = false; if (typeof this._errorCallback === 'function') { this._errorCallback(evt); } }; WebsocketChannelProvider.prototype.onSocketOpen = function () { LogManager.debug('Websocket has been opened'); this.sendAckToApp(); }; WebsocketChannelProvider.prototype.onSocketClose = function (evt) { return __awaiter(this, void 0, void 0, function () { var timeoutMillis_1, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 4, , 5]); this._connected = false; this._websocket = null; if (!(typeof this._errorCallback === 'function')) return [3 /*break*/, 1]; this._errorCallback(evt); return [3 /*break*/, 3]; case 1: /** * We prefer websockets to Pubnub but realistically if we have exhausted our max attempts we are unlikely * to achieve anything with further attempts other than flood the console with unhelpful logs. * * connect attempts is incremented in the startWebsocket calls so this is not an infinite loop. */ if (this._connectAttempts >= this._maxConnectAttempts) { /** * If things are working properly we should never hit this error because 1 WebSocketChannel will connect * successfully and the others will be unsubscribed by the WebsocketAggregateChannelProvider. */ LogManager.error("Exceeded maximum reconnect attempts connecting to ".concat(this._url, ", aborting")); /** * Since this channel is dead we will unsubscribe to free up resources. */ this.unsubscribeFromChannels(); return [2 /*return*/]; } timeoutMillis_1 = this._connectAttempts * this._baseReconnectDelay; LogManager.warn("Failed to connect to ".concat(this._url, ", waiting ").concat(timeoutMillis_1 / 1000, " seconds")); return [4 /*yield*/, new Promise(function (res) { setTimeout(res, timeoutMillis_1); })]; case 2: _a.sent(); this.startWebsocket(); _a.label = 3; case 3: return [3 /*break*/, 5]; case 4: err_1 = _a.sent(); //Should ensure we never have an unhandled promise from this function. LogManager.error("Unexpected error, processing socket close event: ".concat(err_1.message)); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); }; WebsocketChannelProvider.prototype.onMessage = function (evt) { LogManager.debug("onMessage received: ".concat(evt.data)); if (this._msgCallback) { try { var messageParsed = JSON.parse(evt.data); this._msgCallback(messageParsed); } catch (e) { LogManager.warn('WebsocketChannel cannot handle message', evt.data, e.message); } } }; WebsocketChannelProvider.prototype.getProviderInfo = function () { return 'websocket'; }; WebsocketChannelProvider.prototype.unsubscribeFromChannels = function () { this._unsubscribed = true; this._errorCallback = null; this._msgCallback = null; this._connected = false; if (this._websocket) { this._websocket.close(); this._websocket = null; } }; WebsocketChannelProvider.prototype.getHandshakeMessage = function () { return __assign(__assign({}, this.getChannelParams()), { apiVersion: constants.sdkVersion, sessionid: this._sessionId }); }; WebsocketChannelProvider.prototype.sendAckToApp = function () { this.publish(this.getHandshakeMessage()); }; WebsocketChannelProvider.prototype.isAckAckMessageFromApp = function (message) { var goodMessage = false; if (message) { if (message.context === 'session-active') { if (message.content) { if (message.content.app_api_version && message.content.app_encryption && message.content.app_version) { goodMessage = true; this._clientInfo = message.content; } } } } return goodMessage; }; WebsocketChannelProvider.prototype.sendAckAckAckToApp = function () { var ackAckAckMsg = { provider: this.getProviderInfo(), encryptionInfo: this._encryptionInfo, }; var fullRequest = { type: 'request', context: 'ackackack', content: ackAckAckMsg, }; this.publish(fullRequest); }; Object.defineProperty(WebsocketChannelProvider.prototype, "url", { get: function () { return this._url; }, enumerable: false, configurable: true }); Object.defineProperty(WebsocketChannelProvider.prototype, "clientInfo", { get: function () { return this._clientInfo; }, enumerable: false, configurable: true }); return WebsocketChannelProvider; }(MessageChannelProvider)); export default WebsocketChannelProvider;