Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@anephenix/sarus

Package Overview
Dependencies
Maintainers
0
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@anephenix/sarus - npm Package Compare versions

Comparing version 0.5.0 to 0.6.0

88

__tests__/index/retryConnectionDelay.test.ts
// File Dependencies
import Sarus from "../../src/index";
import { WS } from "jest-websocket-mock";
import { calculateRetryDelayFactor } from "../../src/index";
import type { ExponentialBackoffParams } from "../../src/index";

@@ -64,1 +66,87 @@ const url = "ws://localhost:1234";

});
describe("Exponential backoff delay", () => {
describe("with rate 2, backoffLimit 8000 ms", () => {
// The initial delay shall be 1 s
const initialDelay = 1000;
const exponentialBackoff: ExponentialBackoffParams = {
backoffRate: 2,
// We put the ceiling at exactly 8000 ms
backoffLimit: 8000,
};
const attempts: [number, number][] = [
[1000, 0],
[2000, 1],
[4000, 2],
[8000, 3],
[8000, 4],
];
it("will never be more than 8000 ms with rate set to 2", () => {
attempts.forEach(([delay, failedAttempts]) => {
expect(
calculateRetryDelayFactor(
exponentialBackoff,
initialDelay,
failedAttempts,
),
).toBe(delay);
});
});
it("should delay reconnection attempts exponentially", async () => {
// Somehow we need to convincen typescript here that "WebSocket" is
// totally valid. Could be because it doesn't assume WebSocket is part of
// global / the index key is missing
const webSocketSpy = jest.spyOn(global, "WebSocket" as any);
webSocketSpy.mockImplementation(() => {});
const setTimeoutSpy = jest.spyOn(global, "setTimeout");
const sarus = new Sarus({ url, exponentialBackoff });
expect(sarus.state).toStrictEqual({
kind: "connecting",
failedConnectionAttempts: 0,
});
let instance: WebSocket;
// Get the first WebSocket instance, and ...
[instance] = webSocketSpy.mock.instances;
if (!instance.onopen) {
throw new Error();
}
// tell the sarus instance that it is open, and ...
instance.onopen(new Event("open"));
if (!instance.onclose) {
throw new Error();
}
// close it immediately
instance.onclose(new CloseEvent("close"));
expect(sarus.state).toStrictEqual({
kind: "closed",
});
let cb: Sarus["connect"];
// We iteratively call sarus.connect() and let it fail, seeing
// if it reaches 8000 as a delay and stays there
attempts.forEach(([delay, failedAttempts]) => {
const call =
setTimeoutSpy.mock.calls[setTimeoutSpy.mock.calls.length - 1];
if (!call) {
throw new Error();
}
// Make sure that setTimeout was called with the correct delay
expect(call[1]).toBe(delay);
cb = call[0];
cb();
// Get the most recent WebSocket instance
instance =
webSocketSpy.mock.instances[webSocketSpy.mock.instances.length - 1];
if (!instance.onclose) {
throw new Error();
}
instance.onclose(new CloseEvent("close"));
expect(sarus.state).toStrictEqual({
kind: "connecting",
failedConnectionAttempts: failedAttempts + 1,
});
});
});
});
});

38

__tests__/index/state.test.ts

@@ -18,7 +18,12 @@ // File Dependencies

const sarus: Sarus = new Sarus(sarusConfig);
expect(sarus.state).toBe("connecting");
// Since Sarus jumps into connecting directly, 1 connection attempt is made
// right in the beginning, but none have failed
expect(sarus.state).toStrictEqual({
kind: "connecting",
failedConnectionAttempts: 0,
});
// We wait until we are connected, and see a "connected" state
await server.connected;
expect(sarus.state).toBe("connected");
expect(sarus.state.kind).toBe("connected");

@@ -28,14 +33,21 @@ // When the connection drops, the state will be "closed"

await server.closed;
expect(sarus.state).toBe("closed");
expect(sarus.state).toStrictEqual({
kind: "closed",
});
// Restart server
server = new WS(url);
// We wait a while, and the status is "connecting" again
await delay(1);
expect(sarus.state).toBe("connecting");
// In the beginning, no connection attempts have been made, since in the
// case of a closed connection, we wait a bit until we try to connect again.
expect(sarus.state).toStrictEqual({
kind: "connecting",
failedConnectionAttempts: 0,
});
// We restart the server and let the Sarus instance reconnect:
server = new WS(url);
// When we connect in our mock server, we are "connected" again
await server.connected;
expect(sarus.state).toBe("connected");
expect(sarus.state.kind).toBe("connected");

@@ -51,9 +63,9 @@ // Cleanup

const sarus: Sarus = new Sarus(sarusConfig);
expect(sarus.state).toBe("connecting");
expect(sarus.state.kind).toBe("connecting");
await server.connected;
expect(sarus.state).toBe("connected");
expect(sarus.state.kind).toBe("connected");
// The user can disconnect and the state will be "disconnected"
sarus.disconnect();
expect(sarus.state).toBe("disconnected");
expect(sarus.state.kind).toBe("disconnected");
await server.closed;

@@ -64,9 +76,9 @@

sarus.connect();
expect(sarus.state).toBe("connecting");
expect(sarus.state.kind).toBe("connecting");
await server.connected;
// XXX for some reason the test will fail without waiting 10 ms here
await delay(10);
expect(sarus.state).toBe("connected");
expect(sarus.state.kind).toBe("connected");
server.close();
});
});
# CHANGELOG
### 0.6.0 - Saturday 17th August, 2024
- Fixed a performance regression relating to the auditEventListeners function (PR#461)
- Minimize production dependencies by removing current package.json dependencies (PR#424)
= Added support for Exponential backoff on reconnection attempts (PR#403)
- Updated dependencies
### 0.5.0 - Saturday 27th April, 2024

@@ -4,0 +11,0 @@

import { PartialEventListenersInterface, EventListenersInterface } from "./lib/validators";
export interface ExponentialBackoffParams {
backoffRate: number;
backoffLimit: number;
}
export declare function calculateRetryDelayFactor(params: ExponentialBackoffParams, initialDelay: number, failedConnectionAttempts: number): number;
export interface SarusClassParams {

@@ -10,2 +15,3 @@ url: string;

retryConnectionDelay?: boolean | number;
exponentialBackoff?: ExponentialBackoffParams;
storageType?: string;

@@ -24,3 +30,4 @@ storageKey?: string;

* @param {number} param0.retryProcessTimePeriod - An optional number for how long the time period between retrying to send a messgae to a WebSocket server should be
* @param {number} param0.retryConnectionDelay - A parameter for the amount of time to delay a reconnection attempt by, in miliseconds.
* @param {boolean|number} param0.retryConnectionDelay - An optional parameter for whether to delay WebSocket reconnection attempts by a time period. If true, the delay is 1000ms, otherwise it is the number passed. The default value when this parameter is undefined will be interpreted as 1000ms.
* @param {ExponentialBackoffParams} param0.exponentialBackoff - An optional containing configuration for exponential backoff. If this parameter is undefined, exponential backoff is disabled. The minimum delay is determined by retryConnectionDelay. If retryConnectionDelay is set is false, this setting will not be in effect.
* @param {string} param0.storageType - An optional string specifying the type of storage to use for persisting messages in the message queue

@@ -38,2 +45,3 @@ * @param {string} param0.storageKey - An optional string specifying the key used to store the messages data against in sessionStorage/localStorage

retryConnectionDelay: number;
exponentialBackoff?: ExponentialBackoffParams;
storageType: string;

@@ -43,3 +51,12 @@ storageKey: string;

ws: WebSocket | undefined;
state: "connecting" | "connected" | "disconnected" | "closed";
state: {
kind: "connecting";
failedConnectionAttempts: number;
} | {
kind: "connected";
} | {
kind: "disconnected";
} | {
kind: "closed";
};
constructor(props: SarusClassParams);

@@ -83,3 +100,8 @@ /**

*/
auditEventListeners(eventListeners: PartialEventListenersInterface): EventListenersInterface;
auditEventListeners(eventListeners: PartialEventListenersInterface | undefined): {
open: Function[];
message: Function[];
error: Function[];
close: Function[];
};
/**

@@ -90,3 +112,4 @@ * Connects the WebSocket client, and attaches event listeners

/**
* Reconnects the WebSocket client based on the retryConnectionDelay setting.
* Reconnects the WebSocket client based on the retryConnectionDelay and
* ExponentialBackoffParam setting.
*/

@@ -93,0 +116,0 @@ reconnect(): void;

"use strict";
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 __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {

@@ -23,2 +12,3 @@ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {

Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateRetryDelayFactor = calculateRetryDelayFactor;
// File Dependencies

@@ -80,2 +70,16 @@ var constants_1 = require("./lib/constants");

};
/*
* Calculate the exponential backoff delay for a given number of connection
* attempts.
* @param {ExponentialBackoffParams} params - configuration parameters for
* exponential backoff.
* @param {number} initialDelay - the initial delay before any backoff is
* applied
* @param {number} failedConnectionAttempts - the number of connection attempts
* that have previously failed
* @returns {void} - set does not return
*/
function calculateRetryDelayFactor(params, initialDelay, failedConnectionAttempts) {
return Math.min(initialDelay * Math.pow(params.backoffRate, failedConnectionAttempts), params.backoffLimit);
}
/**

@@ -91,3 +95,4 @@ * The Sarus client class

* @param {number} param0.retryProcessTimePeriod - An optional number for how long the time period between retrying to send a messgae to a WebSocket server should be
* @param {number} param0.retryConnectionDelay - A parameter for the amount of time to delay a reconnection attempt by, in miliseconds.
* @param {boolean|number} param0.retryConnectionDelay - An optional parameter for whether to delay WebSocket reconnection attempts by a time period. If true, the delay is 1000ms, otherwise it is the number passed. The default value when this parameter is undefined will be interpreted as 1000ms.
* @param {ExponentialBackoffParams} param0.exponentialBackoff - An optional containing configuration for exponential backoff. If this parameter is undefined, exponential backoff is disabled. The minimum delay is determined by retryConnectionDelay. If retryConnectionDelay is set is false, this setting will not be in effect.
* @param {string} param0.storageType - An optional string specifying the type of storage to use for persisting messages in the message queue

@@ -119,9 +124,24 @@ * @param {string} param0.storageKey - An optional string specifying the key used to store the messages data against in sessionStorage/localStorage

*
* this.reconnect() is called internally when automatic reconnection is
* enabled, but can also be called by the user
* When disconnected by the WebSocket itself (i.e., this.ws.onclose),
* this.reconnect() is called automatically if reconnection is enabled.
* this.reconnect() can also be called by the user, for example if
* this.disconnect() was purposefully called and reconnection is desired.
*
* The current state is specified by the 'kind' property of state
* Each state can have additional data contained in properties other than
* 'kind'. Those properties might be unique to one state, or contained in
* several states. To access a property, it might be necessary to narrow down
* the 'kind' of state.
*
* The initial state is connecting, as a Sarus client tries to connect right
* after the constructor wraps up.
*/
this.state = "connecting";
this.state = {
kind: "connecting",
failedConnectionAttempts: 0,
};
// Extract the properties that are passed to the class
var url = props.url, binaryType = props.binaryType, protocols = props.protocols, _b = props.eventListeners, eventListeners = _b === void 0 ? constants_1.DEFAULT_EVENT_LISTENERS_OBJECT : _b, reconnectAutomatically = props.reconnectAutomatically, retryProcessTimePeriod = props.retryProcessTimePeriod, // TODO - write a test case to check this
retryConnectionDelay = props.retryConnectionDelay, _c = props.storageType, storageType = _c === void 0 ? "memory" : _c, _d = props.storageKey, storageKey = _d === void 0 ? "sarus" : _d;
var url = props.url, binaryType = props.binaryType, protocols = props.protocols, eventListeners = props.eventListeners, // = DEFAULT_EVENT_LISTENERS_OBJECT,
reconnectAutomatically = props.reconnectAutomatically, retryProcessTimePeriod = props.retryProcessTimePeriod, // TODO - write a test case to check this
retryConnectionDelay = props.retryConnectionDelay, exponentialBackoff = props.exponentialBackoff, _b = props.storageType, storageType = _b === void 0 ? "memory" : _b, _c = props.storageKey, storageKey = _c === void 0 ? "sarus" : _c;
this.eventListeners = this.auditEventListeners(eventListeners);

@@ -161,2 +181,8 @@ // Sets the WebSocket server url for the client to connect to.

/*
When a exponential backoff parameter object is provided, reconnection
attemptions will be increasingly delayed by an exponential factor.
This feature is disabled by default.
*/
this.exponentialBackoff = exponentialBackoff;
/*
Sets the storage type for the messages in the message queue. By default

@@ -268,10 +294,8 @@ it is an in-memory option, but can also be set as 'session' for

Sarus.prototype.auditEventListeners = function (eventListeners) {
var defaultEventListeners = {
open: [],
message: [],
error: [],
close: [],
return {
open: (eventListeners === null || eventListeners === void 0 ? void 0 : eventListeners.open) || [],
message: (eventListeners === null || eventListeners === void 0 ? void 0 : eventListeners.message) || [],
error: (eventListeners === null || eventListeners === void 0 ? void 0 : eventListeners.error) || [],
close: (eventListeners === null || eventListeners === void 0 ? void 0 : eventListeners.close) || [],
};
var mergedEventListeners = __assign(__assign({}, defaultEventListeners), eventListeners); // Type assertion added here
return mergedEventListeners;
};

@@ -282,3 +306,6 @@ /**

Sarus.prototype.connect = function () {
this.state = "connecting";
// If we aren't already connecting, we are now
if (this.state.kind !== "connecting") {
this.state = { kind: "connecting", failedConnectionAttempts: 0 };
}
this.ws = new WebSocket(this.url, this.protocols);

@@ -291,8 +318,20 @@ this.setBinaryType();

/**
* Reconnects the WebSocket client based on the retryConnectionDelay setting.
* Reconnects the WebSocket client based on the retryConnectionDelay and
* ExponentialBackoffParam setting.
*/
Sarus.prototype.reconnect = function () {
var self = this;
var retryConnectionDelay = self.retryConnectionDelay;
setTimeout(self.connect, retryConnectionDelay);
var retryConnectionDelay = self.retryConnectionDelay, exponentialBackoff = self.exponentialBackoff;
// If we are already in a "connecting" state, we need to refer to the
// current amount of connection attemps to correctly calculate the
// exponential delay -- if exponential backoff is enabled.
var failedConnectionAttempts = self.state.kind === "connecting"
? self.state.failedConnectionAttempts
: 0;
// If no exponential backoff is enabled, retryConnectionDelay will
// be scaled by a factor of 1 and it will stay the original value.
var delay = exponentialBackoff
? calculateRetryDelayFactor(exponentialBackoff, retryConnectionDelay, failedConnectionAttempts)
: retryConnectionDelay;
setTimeout(self.connect, delay);
};

@@ -306,3 +345,3 @@ /**

Sarus.prototype.disconnect = function (overrideDisableReconnect) {
this.state = "disconnected";
this.state = { kind: "disconnected" };
var self = this;

@@ -426,6 +465,22 @@ // We do this to prevent automatic reconnections;

if (eventName === "open") {
self.state = "connected";
self.state = { kind: "connected" };
}
else if (eventName === "close" && self.reconnectAutomatically) {
self.state = "closed";
var state = self.state;
// If we have previously been "connecting", we carry over the amount
// of failed connection attempts and add 1, since the current
// connection attempt failed. We stay "connecting" instead of
// "closed", since we've never been fully "connected" in the first
// place.
if (state.kind === "connecting") {
self.state = {
kind: "connecting",
failedConnectionAttempts: state.failedConnectionAttempts + 1,
};
}
else {
// If we were in a different state, we assume that our connection
// freshly closed and have not made any failed connection attempts.
self.state = { kind: "closed" };
}
self.removeEventListeners();

@@ -432,0 +487,0 @@ self.reconnect();

{
"name": "@anephenix/sarus",
"version": "0.5.0",
"version": "0.6.0",
"description": "A WebSocket JavaScript library",

@@ -28,3 +28,2 @@ "main": "dist/index.js",

"@types/window-or-global": "^1.0.6",
"coveralls": "^3.1.1",
"dom-storage": "^2.1.0",

@@ -38,3 +37,2 @@ "ip-regex": "^5.0.0",

"mock-socket": "^9.2.1",
"npm-upgrade": "^3.1.0",
"prettier": "^3.0.3",

@@ -47,3 +45,2 @@ "ts-jest": "^29.1.0",

"watch": "tsc --project tsconfig.json --watch",
"cover": "jest --coverage --coverageReporters=text-lcov | coveralls",
"test": "jest --coverage",

@@ -50,0 +47,0 @@ "prettier": "prettier src __tests__ --write",

@@ -5,3 +5,3 @@ # Sarus

[![npm version](https://badge.fury.io/js/%40anephenix%2Fsarus.svg)](https://badge.fury.io/js/%40anephenix%2Fsarus) ![example workflow](https://github.com/anephenix/sarus/actions/workflows/node.js.yml/badge.svg) [![Maintainability](https://api.codeclimate.com/v1/badges/0671cfc9630a97854b30/maintainability)](https://codeclimate.com/github/anephenix/sarus/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/0671cfc9630a97854b30/test_coverage)](https://codeclimate.com/github/anephenix/sarus/test_coverage)
[![npm version](https://badge.fury.io/js/%40anephenix%2Fsarus.svg)](https://badge.fury.io/js/%40anephenix%2Fsarus) ![example workflow](https://github.com/anephenix/sarus/actions/workflows/node.js.yml/badge.svg) [![Maintainability](https://api.codeclimate.com/v1/badges/0671cfc9630a97854b30/maintainability)](https://codeclimate.com/github/anephenix/sarus/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/0671cfc9630a97854b30/test_coverage)](https://codeclimate.com/github/anephenix/sarus/test_coverage) [![Socket Badge](https://socket.dev/api/badge/npm/package/@anephenix/sarus)](https://socket.dev/npm/package/@anephenix/sarus)

@@ -356,2 +356,64 @@ ### Features

### Exponential backoff
Configure exponential backoff like so:
```typescript
import Sarus from '@anephenix/sarus';
const sarus = new Sarus({
url: 'wss://ws.anephenix.com',
exponentialBackoff: {
// Exponential factor, here 2 will result in
// 1 s, 2 s, 4 s, and so on increasing delays
backoffRate: 2,
// Never wait more than 2000 seconds
backoffLimit: 2000,
},
});
```
When a connection attempt repeatedly fails, decreasing the delay
exponentially between each subsequent reconnection attempt is called
[Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff). The
idea is that if a connection attempt failed after 1 second, and 2 seconds, then it is
not necessary to check it on the 3rd second, since the probability of a
reconnection succeeding on the third attempt is most likely not going up.
Therefore, increasing the delay between each attempt factors in the assumption
that a connection is not more likely to succeed by repeatedly probing in regular
intervals.
This decreases both the load on the client, as well as on the server. For
a client, fewer websocket connection attempts decrease the load on the client
and on the network connection. For the server, should websocket requests fail
within, then the load for handling repeatedly failing requests will fall
as well. Furthermore, the burden on the network will also be decreased. Should
for example a server refuse to accept websocket connections for one client,
then there is the possibility that other clients will also not be able to connect.
Sarus implements _truncated exponential backoff_, meaning that the maximum
reconnection delay is capped by another factor `backoffLimit` and will never
exceed it. The exponential backoff rate itself is determined by `backoffRate`.
If `backoffRate` is 2, then the delays will be 1 s, 2 s, 4 s, and so on.
The algorithm for reconnection looks like this in pseudocode:
```javascript
// Configurable
const backoffRate = 2;
// The maximum delay will be 400s
const backoffLimit = 400;
let notConnected = false;
let connectionAttempts = 1;
while (notConnected) {
const delay = Math.min(
Math.pow(connectionAttempts, backoffRate),
backoffLimit,
);
await delay(delay);
notConnected = tryToConnect();
connectionAttempts += 1;
}
```
### Advanced options

@@ -358,0 +420,0 @@

@@ -76,2 +76,29 @@ // File Dependencies

export interface ExponentialBackoffParams {
backoffRate: number;
backoffLimit: number;
}
/*
* Calculate the exponential backoff delay for a given number of connection
* attempts.
* @param {ExponentialBackoffParams} params - configuration parameters for
* exponential backoff.
* @param {number} initialDelay - the initial delay before any backoff is
* applied
* @param {number} failedConnectionAttempts - the number of connection attempts
* that have previously failed
* @returns {void} - set does not return
*/
export function calculateRetryDelayFactor(
params: ExponentialBackoffParams,
initialDelay: number,
failedConnectionAttempts: number,
): number {
return Math.min(
initialDelay * Math.pow(params.backoffRate, failedConnectionAttempts),
params.backoffLimit,
);
}
export interface SarusClassParams {

@@ -85,2 +112,3 @@ url: string;

retryConnectionDelay?: boolean | number;
exponentialBackoff?: ExponentialBackoffParams;
storageType?: string;

@@ -100,3 +128,4 @@ storageKey?: string;

* @param {number} param0.retryProcessTimePeriod - An optional number for how long the time period between retrying to send a messgae to a WebSocket server should be
* @param {number} param0.retryConnectionDelay - A parameter for the amount of time to delay a reconnection attempt by, in miliseconds.
* @param {boolean|number} param0.retryConnectionDelay - An optional parameter for whether to delay WebSocket reconnection attempts by a time period. If true, the delay is 1000ms, otherwise it is the number passed. The default value when this parameter is undefined will be interpreted as 1000ms.
* @param {ExponentialBackoffParams} param0.exponentialBackoff - An optional containing configuration for exponential backoff. If this parameter is undefined, exponential backoff is disabled. The minimum delay is determined by retryConnectionDelay. If retryConnectionDelay is set is false, this setting will not be in effect.
* @param {string} param0.storageType - An optional string specifying the type of storage to use for persisting messages in the message queue

@@ -115,2 +144,3 @@ * @param {string} param0.storageKey - An optional string specifying the key used to store the messages data against in sessionStorage/localStorage

retryConnectionDelay: number;
exponentialBackoff?: ExponentialBackoffParams;
storageType: string;

@@ -141,6 +171,24 @@ storageKey: string;

*
* this.reconnect() is called internally when automatic reconnection is
* enabled, but can also be called by the user
* When disconnected by the WebSocket itself (i.e., this.ws.onclose),
* this.reconnect() is called automatically if reconnection is enabled.
* this.reconnect() can also be called by the user, for example if
* this.disconnect() was purposefully called and reconnection is desired.
*
* The current state is specified by the 'kind' property of state
* Each state can have additional data contained in properties other than
* 'kind'. Those properties might be unique to one state, or contained in
* several states. To access a property, it might be necessary to narrow down
* the 'kind' of state.
*
* The initial state is connecting, as a Sarus client tries to connect right
* after the constructor wraps up.
*/
state: "connecting" | "connected" | "disconnected" | "closed" = "connecting";
state:
| { kind: "connecting"; failedConnectionAttempts: number }
| { kind: "connected" }
| { kind: "disconnected" }
| { kind: "closed" } = {
kind: "connecting",
failedConnectionAttempts: 0,
};

@@ -153,6 +201,7 @@ constructor(props: SarusClassParams) {

protocols,
eventListeners = DEFAULT_EVENT_LISTENERS_OBJECT,
eventListeners, // = DEFAULT_EVENT_LISTENERS_OBJECT,
reconnectAutomatically,
retryProcessTimePeriod, // TODO - write a test case to check this
retryConnectionDelay,
exponentialBackoff,
storageType = "memory",

@@ -203,2 +252,9 @@ storageKey = "sarus",

/*
When a exponential backoff parameter object is provided, reconnection
attemptions will be increasingly delayed by an exponential factor.
This feature is disabled by default.
*/
this.exponentialBackoff = exponentialBackoff;
/*
Sets the storage type for the messages in the message queue. By default

@@ -314,16 +370,11 @@ it is an in-memory option, but can also be set as 'session' for

*/
auditEventListeners(eventListeners: PartialEventListenersInterface) {
const defaultEventListeners: EventListenersInterface = {
open: [],
message: [],
error: [],
close: [],
auditEventListeners(
eventListeners: PartialEventListenersInterface | undefined,
) {
return {
open: eventListeners?.open || [],
message: eventListeners?.message || [],
error: eventListeners?.error || [],
close: eventListeners?.close || [],
};
const mergedEventListeners: EventListenersInterface = {
...defaultEventListeners,
...eventListeners,
} as EventListenersInterface; // Type assertion added here
return mergedEventListeners;
}

@@ -335,3 +386,6 @@

connect() {
this.state = "connecting";
// If we aren't already connecting, we are now
if (this.state.kind !== "connecting") {
this.state = { kind: "connecting", failedConnectionAttempts: 0 };
}
this.ws = new WebSocket(this.url, this.protocols);

@@ -344,8 +398,27 @@ this.setBinaryType();

/**
* Reconnects the WebSocket client based on the retryConnectionDelay setting.
* Reconnects the WebSocket client based on the retryConnectionDelay and
* ExponentialBackoffParam setting.
*/
reconnect() {
const self = this;
const { retryConnectionDelay } = self;
setTimeout(self.connect, retryConnectionDelay);
const { retryConnectionDelay, exponentialBackoff } = self;
// If we are already in a "connecting" state, we need to refer to the
// current amount of connection attemps to correctly calculate the
// exponential delay -- if exponential backoff is enabled.
const failedConnectionAttempts =
self.state.kind === "connecting"
? self.state.failedConnectionAttempts
: 0;
// If no exponential backoff is enabled, retryConnectionDelay will
// be scaled by a factor of 1 and it will stay the original value.
const delay = exponentialBackoff
? calculateRetryDelayFactor(
exponentialBackoff,
retryConnectionDelay,
failedConnectionAttempts,
)
: retryConnectionDelay;
setTimeout(self.connect, delay);
}

@@ -360,3 +433,3 @@

disconnect(overrideDisableReconnect?: boolean) {
this.state = "disconnected";
this.state = { kind: "disconnected" };
const self = this;

@@ -491,8 +564,23 @@ // We do this to prevent automatic reconnections;

WS_EVENT_NAMES.forEach((eventName) => {
self.ws[`on${eventName}`] = (e: Function) => {
self.ws[`on${eventName}`] = (e: Event | CloseEvent | MessageEvent) => {
self.eventListeners[eventName].forEach((f: Function) => f(e));
if (eventName === "open") {
self.state = "connected";
self.state = { kind: "connected" };
} else if (eventName === "close" && self.reconnectAutomatically) {
self.state = "closed";
const { state } = self;
// If we have previously been "connecting", we carry over the amount
// of failed connection attempts and add 1, since the current
// connection attempt failed. We stay "connecting" instead of
// "closed", since we've never been fully "connected" in the first
// place.
if (state.kind === "connecting") {
self.state = {
kind: "connecting",
failedConnectionAttempts: state.failedConnectionAttempts + 1,
};
} else {
// If we were in a different state, we assume that our connection
// freshly closed and have not made any failed connection attempts.
self.state = { kind: "closed" };
}
self.removeEventListeners();

@@ -499,0 +587,0 @@ self.reconnect();

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc