UNPKG

websocket-ts

Version:

<div> <div align="center"> <img src="https://raw.githubusercontent.com/jjxxs/websocket-ts/gh-pages/websocket-ts-logo.svg" alt="websocket-ts" width="300" height="65" /> </div> <p align="center"> <img src="https://github.com/jjxxs/websocket-ts

435 lines 19.4 kB
"use strict"; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Websocket = void 0; var websocket_event_1 = require("./websocket_event"); /** * A websocket wrapper that can be configured to reconnect automatically and buffer messages when the websocket is not connected. */ var Websocket = /** @class */ (function () { /** * Creates a new websocket. * * @param url to connect to. * @param protocols optional protocols to use. * @param options optional options to use. */ function Websocket(url, protocols, options) { var _this = this; var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; this._closedByUser = false; // whether the websocket was closed by the user /** * Handles the 'open' event of the browser-native websocket. * @param event to handle. */ this.handleOpenEvent = function (event) { return _this.handleEvent(websocket_event_1.WebsocketEvent.open, event); }; /** * Handles the 'error' event of the browser-native websocket. * @param event to handle. */ this.handleErrorEvent = function (event) { return _this.handleEvent(websocket_event_1.WebsocketEvent.error, event); }; /** * Handles the 'close' event of the browser-native websocket. * @param event to handle. */ this.handleCloseEvent = function (event) { return _this.handleEvent(websocket_event_1.WebsocketEvent.close, event); }; /** * Handles the 'message' event of the browser-native websocket. * @param event to handle. */ this.handleMessageEvent = function (event) { return _this.handleEvent(websocket_event_1.WebsocketEvent.message, event); }; this._url = url; this._protocols = protocols; // make a copy of the options to prevent the user from changing them this._options = { buffer: options === null || options === void 0 ? void 0 : options.buffer, retry: { maxRetries: (_a = options === null || options === void 0 ? void 0 : options.retry) === null || _a === void 0 ? void 0 : _a.maxRetries, instantReconnect: (_b = options === null || options === void 0 ? void 0 : options.retry) === null || _b === void 0 ? void 0 : _b.instantReconnect, backoff: (_c = options === null || options === void 0 ? void 0 : options.retry) === null || _c === void 0 ? void 0 : _c.backoff, }, listeners: { open: __spreadArray([], ((_e = (_d = options === null || options === void 0 ? void 0 : options.listeners) === null || _d === void 0 ? void 0 : _d.open) !== null && _e !== void 0 ? _e : []), true), close: __spreadArray([], ((_g = (_f = options === null || options === void 0 ? void 0 : options.listeners) === null || _f === void 0 ? void 0 : _f.close) !== null && _g !== void 0 ? _g : []), true), error: __spreadArray([], ((_j = (_h = options === null || options === void 0 ? void 0 : options.listeners) === null || _h === void 0 ? void 0 : _h.error) !== null && _j !== void 0 ? _j : []), true), message: __spreadArray([], ((_l = (_k = options === null || options === void 0 ? void 0 : options.listeners) === null || _k === void 0 ? void 0 : _k.message) !== null && _l !== void 0 ? _l : []), true), retry: __spreadArray([], ((_o = (_m = options === null || options === void 0 ? void 0 : options.listeners) === null || _m === void 0 ? void 0 : _m.retry) !== null && _o !== void 0 ? _o : []), true), reconnect: __spreadArray([], ((_q = (_p = options === null || options === void 0 ? void 0 : options.listeners) === null || _p === void 0 ? void 0 : _p.reconnect) !== null && _q !== void 0 ? _q : []), true), }, }; this._underlyingWebsocket = this.tryConnect(); } Object.defineProperty(Websocket.prototype, "url", { /** * Getter for the url. * * @return the url. */ get: function () { return this._url; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "protocols", { /** * Getter for the protocols. * * @return the protocols, or undefined if none were provided. */ get: function () { return this._protocols; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "buffer", { /** * Getter for the buffer. * * @return the buffer, or undefined if none was provided. */ get: function () { return this._options.buffer; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "maxRetries", { /** * Getter for the maxRetries. * * @return the maxRetries, or undefined if none was provided (no limit). */ get: function () { return this._options.retry.maxRetries; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "instantReconnect", { /** * Getter for the instantReconnect. * * @return the instantReconnect, or undefined if none was provided. */ get: function () { return this._options.retry.instantReconnect; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "backoff", { /** * Getter for the backoff. * * @return the backoff, or undefined if none was provided. */ get: function () { return this._options.retry.backoff; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "closedByUser", { /** * Whether the websocket was closed by the user. A websocket is closed by the user by calling close(). * * @return true if the websocket was closed by the user, false otherwise. */ get: function () { return this._closedByUser; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "lastConnection", { /** * Getter for the last 'open' event, e.g. the last time the websocket was connected. * * @return the last 'open' event, or undefined if the websocket was never connected. */ get: function () { return this._lastConnection; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "underlyingWebsocket", { /** * Getter for the underlying websocket. This can be used to access the browser's native websocket directly. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket * @return the underlying websocket. */ get: function () { return this._underlyingWebsocket; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "readyState", { /** * Getter for the readyState of the underlying websocket. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState * @return the readyState of the underlying websocket. */ get: function () { return this._underlyingWebsocket.readyState; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "bufferedAmount", { /** * Getter for the bufferedAmount of the underlying websocket. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/bufferedAmount * @return the bufferedAmount of the underlying websocket. */ get: function () { return this._underlyingWebsocket.bufferedAmount; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "extensions", { /** * Getter for the extensions of the underlying websocket. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/extensions * @return the extensions of the underlying websocket. */ get: function () { return this._underlyingWebsocket.extensions; }, enumerable: false, configurable: true }); Object.defineProperty(Websocket.prototype, "binaryType", { /** * Getter for the binaryType of the underlying websocket. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType * @return the binaryType of the underlying websocket. */ get: function () { return this._underlyingWebsocket.binaryType; }, /** * Setter for the binaryType of the underlying websocket. * * @param value to set, 'blob' or 'arraybuffer'. */ set: function (value) { this._underlyingWebsocket.binaryType = value; }, enumerable: false, configurable: true }); /** * Sends data over the websocket. * * If the websocket is not connected and a buffer was provided on creation, the data will be added to the buffer. * If no buffer was provided or the websocket was closed by the user, the data will be dropped. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send * @param data to send. */ Websocket.prototype.send = function (data) { if (this.closedByUser) return; // no-op if closed by user if (this._underlyingWebsocket.readyState === this._underlyingWebsocket.OPEN) { this._underlyingWebsocket.send(data); // websocket is connected, send data } else if (this.buffer !== undefined) { this.buffer.add(data); // websocket is not connected, add data to buffer } }; /** * Close the websocket. No connection-retry will be attempted after this. * * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close * @param code optional close code. * @param reason optional close reason. */ Websocket.prototype.close = function (code, reason) { this.cancelScheduledConnectionRetry(); // cancel any scheduled retries this._closedByUser = true; // mark websocket as closed by user this._underlyingWebsocket.close(code, reason); // close underlying websocket with provided code and reason }; /** * Adds an event listener for the given event-type. * * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener * @param type of the event to add the listener for. * @param listener to add. * @param options to use when adding the listener. */ Websocket.prototype.addEventListener = function (type, listener, options) { this._options.listeners[type].push({ listener: listener, options: options }); // add listener to list of listeners }; /** * Removes one or more event listener for the given event-type that match the given listener and options. * * @param type of the event to remove the listener for. * @param listener to remove. * @param options that were used when the listener was added. */ Websocket.prototype.removeEventListener = function (type, listener, options) { var isListenerNotToBeRemoved = function (l) { return l.listener !== listener || l.options !== options; }; this._options.listeners[type] = this._options.listeners[type].filter(isListenerNotToBeRemoved); // only keep listeners that are not to be removed }; /** * Creates a new browser-native websocket and connects it to the given URL with the given protocols * and adds all event listeners to the browser-native websocket. * * @return the created browser-native websocket which is also stored in the '_underlyingWebsocket' property. */ Websocket.prototype.tryConnect = function () { this._underlyingWebsocket = new WebSocket(this.url, this.protocols); // create new browser-native websocket and add all event listeners this._underlyingWebsocket.addEventListener(websocket_event_1.WebsocketEvent.open, this.handleOpenEvent); this._underlyingWebsocket.addEventListener(websocket_event_1.WebsocketEvent.close, this.handleCloseEvent); this._underlyingWebsocket.addEventListener(websocket_event_1.WebsocketEvent.error, this.handleErrorEvent); this._underlyingWebsocket.addEventListener(websocket_event_1.WebsocketEvent.message, this.handleMessageEvent); return this._underlyingWebsocket; }; /** * Removes all event listeners from the browser-native websocket and closes it. */ Websocket.prototype.clearWebsocket = function () { this._underlyingWebsocket.removeEventListener(websocket_event_1.WebsocketEvent.open, this.handleOpenEvent); this._underlyingWebsocket.removeEventListener(websocket_event_1.WebsocketEvent.close, this.handleCloseEvent); this._underlyingWebsocket.removeEventListener(websocket_event_1.WebsocketEvent.error, this.handleErrorEvent); this._underlyingWebsocket.removeEventListener(websocket_event_1.WebsocketEvent.message, this.handleMessageEvent); this._underlyingWebsocket.close(); }; /** * Dispatch an event to all listeners of the given event-type. * * @param type of the event to dispatch. * @param event to dispatch. */ Websocket.prototype.dispatchEvent = function (type, event) { var _this = this; var eventListeners = this._options.listeners[type]; var newEventListeners = []; eventListeners.forEach(function (_a) { var listener = _a.listener, options = _a.options; listener(_this, event); // invoke listener with event if (options === undefined || options.once === undefined || !options.once) { newEventListeners.push({ listener: listener, options: options }); // only keep listener if it isn't a once-listener } }); this._options.listeners[type] = newEventListeners; // replace old listeners with new listeners that don't include once-listeners }; /** * Handles the given event by dispatching it to all listeners of the given event-type. * * @param type of the event to handle. * @param event to handle. */ Websocket.prototype.handleEvent = function (type, event) { switch (type) { case websocket_event_1.WebsocketEvent.close: this.dispatchEvent(type, event); this.scheduleConnectionRetryIfNeeded(); // schedule a new connection retry if the websocket was closed by the server break; case websocket_event_1.WebsocketEvent.open: if (this.backoff !== undefined && this._lastConnection !== undefined) { // websocket was reconnected, dispatch reconnect event and reset backoff var detail = { retries: this.backoff.retries, lastConnection: new Date(this._lastConnection), }; var event_1 = new CustomEvent(websocket_event_1.WebsocketEvent.reconnect, { detail: detail, }); this.dispatchEvent(websocket_event_1.WebsocketEvent.reconnect, event_1); this.backoff.reset(); } this._lastConnection = new Date(); this.dispatchEvent(type, event); // dispatch open event and send buffered data this.sendBufferedData(); break; case websocket_event_1.WebsocketEvent.retry: this.dispatchEvent(type, event); // dispatch retry event and try to connect this.clearWebsocket(); // clear the old websocket this.tryConnect(); break; default: this.dispatchEvent(type, event); // dispatch event to all listeners of the given event-type break; } }; /** * Sends buffered data if there is a buffer defined. */ Websocket.prototype.sendBufferedData = function () { if (this.buffer === undefined) { return; // no buffer defined, nothing to send } for (var ele = this.buffer.read(); ele !== undefined; ele = this.buffer.read()) { this.send(ele); // send buffered data } }; /** * Schedules a connection-retry if there is a backoff defined and the websocket was not closed by the user. */ Websocket.prototype.scheduleConnectionRetryIfNeeded = function () { var _this = this; if (this.closedByUser) { return; // user closed the websocket, no retry } if (this.backoff === undefined) { return; // no backoff defined, no retry } // handler dispatches the retry event to all listeners of the retry event-type var handleRetryEvent = function (detail) { var event = new CustomEvent(websocket_event_1.WebsocketEvent.retry, { detail: detail }); _this.handleEvent(websocket_event_1.WebsocketEvent.retry, event); }; // create retry event detail, depending on the 'instantReconnect' option var retryEventDetail = { backoff: this._options.retry.instantReconnect === true ? 0 : this.backoff.next(), retries: this._options.retry.instantReconnect === true ? 0 : this.backoff.retries, lastConnection: this._lastConnection, }; // schedule a new connection-retry if the maximum number of retries is not reached yet if (this._options.retry.maxRetries === undefined || retryEventDetail.retries <= this._options.retry.maxRetries) { this.retryTimeout = globalThis.setTimeout(function () { return handleRetryEvent(retryEventDetail); }, retryEventDetail.backoff); } }; /** * Cancels the scheduled connection-retry, if there is one. */ Websocket.prototype.cancelScheduledConnectionRetry = function () { globalThis.clearTimeout(this.retryTimeout); }; return Websocket; }()); exports.Websocket = Websocket; //# sourceMappingURL=websocket.js.map