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
JavaScript
"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