feat(SmartsocketClient): Improve client reconnection logic with exponential backoff and jitter; update socket.io and @types/node dependencies

This commit is contained in:
Philipp Kunz 2025-03-10 23:02:24 +00:00
parent 933f09d632
commit da10b25214
5 changed files with 8515 additions and 2817 deletions

103
changelog.md Normal file
View File

@ -0,0 +1,103 @@
# Changelog
## 2025-03-10 - 2.1.0 - feat(SmartsocketClient)
Improve client reconnection logic with exponential backoff and jitter; update socket.io and @types/node dependencies
- Bump engine.io from 6.5.4 to 6.6.4, socket.io and socket.io-client from 4.7.5 to 4.8.1
- Bump @types/node from ^20.12.7 to ^22.13.10
- Add new optional reconnection parameters (maxRetries, initialBackoffDelay, maxBackoffDelay) to SmartsocketClient options
- Implement exponential backoff with jitter for auto-reconnect and reset reconnection state on successful connection
## 2024-05-29 - 2.0.27 - docs
update description
## 2024-04-26 to 2024-03-30 - 2.0.26 … 2.0.24 - core & configuration
A series of internal fixes and configuration tweaks.
- fix(core): update
- update tsconfig
- update npmextra.json: githost
## 2023-09-10 to 2023-07-21 - 2.0.23 … 2.0.20 - core
Multiple minor core fixes were applied in rapid succession.
- fix(core): update
## 2023-07-21 to 2023-03-20 - 2.0.19 … 2.0.15 - core
Routine internal updates addressing core functionality.
- fix(core): update
## 2023-02-07 to 2022-03-24 - 2.0.14 … 2.0.0 - core
Further minor core updates were rolled out over several versions.
- fix(core): update
## 2022-03-14 - 1.2.22 - esm
A breaking change was introduced to switch the module system.
- BREAKING CHANGE(switch to esm): update
## 2022-01-20 to 2021-01-23 - 1.2.21 … 1.2.0 - core
A range of minor core fixes.
- fix(core): update
## 2020-12-26 - 1.1.71 - SmartsocketClient
New functionality in the socket client was added.
- feat(SmartsocketClient): socket client can now be stopped with .stop() in addition to .reconnect()
## 2020-12-26 to 2020-09-24 - 1.1.70 … 1.1.58 - core & test
A group of updates addressing both core mechanics and tests.
- fix(core): update
- fix(test): use @pushrocks/isohash instead of @pushrocks/smarthash
## 2019-11-08 to 2019-04-23 - 1.1.57 … 1.1.27 - core
Numerous versions in this period included only internal core fixes.
- fix(core): update
## 2019-01-31 to 2019-01-30 - 1.1.26 … 1.1.19 - build, docs & configuration
Updates went beyond the core, affecting build tooling and package metadata.
- fix(build): now building with tsbuild
- fix(readme): update
- fix(npmextra): adjust access level
- fix(scope): switch to @pushrocks
- fix(package.json): private setting
- fix(snyk): add .snyk file
- fix(structure): update to latest standards
## 2018-03-19 to 2018-03-15 - 1.1.18 … 1.1.12 - core & docs
Several improvements touching both functionality and documentation.
- now working as expected
- start transitioning to better SocketFunction handling
- add @types/node
- format and update README
- update to latest standards
## 2017-10-09 to 2017-07-07 - 1.1.11 … 1.1.07 - core & docs
Updates in this range improved both the internal mechanics and the developerfacing materials.
- allow setting of specific server
- fix not ending error correctly
- update to newest version
- update docs and tests
- remove taskbuffer
- update to latest standards
## 2016-09-25 to 2016-09-03 - 1.1.6 … 1.1.3 - docs & core
Minor improvements in documentation and code quality.
- improve README
- added docs
- fix scoping of socket roles and perform small syntax fixes
## 2016-09-02 to 2016-08-16 - 1.1.2 … 1.1.1 - dependencies & security
Several housekeeping tasks to update dependencies and improve security.
- updated dependencies and exported socketConnection
- now authenticating sockets by checking the password hash
## 2016-08-15 - 1.1.0 - docs
A documentation update was published.
- update README
## 2016-08-15 - 1.0.7 - networking
A key update made the socket client work bi-directionally, enabling mesh setups.
- now working in both directions so mesh setups work
## 2016-08-14 to 2016-08-07 - 1.0.6 … 1.0.0 - internal changes
From the initial release onward, several internal improvements were introduced:
- updated tests and structure
- reworked reconnection logic and added a request/response abstraction for transparent function calls
- initial release features with updated documentation and structure

View File

@ -33,9 +33,9 @@
"@push.rocks/smartpromise": "^4.0.3", "@push.rocks/smartpromise": "^4.0.3",
"@push.rocks/smartrx": "^3.0.7", "@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smarttime": "^4.0.6", "@push.rocks/smarttime": "^4.0.6",
"engine.io": "6.5.4", "engine.io": "6.6.4",
"socket.io": "4.7.5", "socket.io": "4.8.1",
"socket.io-client": "4.7.5" "socket.io-client": "4.8.1"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.66", "@git.zone/tsbuild": "^2.1.66",
@ -43,7 +43,7 @@
"@git.zone/tsrun": "^1.2.44", "@git.zone/tsrun": "^1.2.44",
"@git.zone/tstest": "^1.0.77", "@git.zone/tstest": "^1.0.77",
"@push.rocks/tapbundle": "^5.0.23", "@push.rocks/tapbundle": "^5.0.23",
"@types/node": "^20.12.7" "@types/node": "^22.13.10"
}, },
"private": false, "private": false,
"files": [ "files": [

11074
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
/** /**
* autocreated commitinfo by @pushrocks/commitinfo * autocreated commitinfo by @push.rocks/commitinfo
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartsocket', name: '@push.rocks/smartsocket',
version: '2.0.27', version: '2.1.0',
description: 'Provides easy and secure websocket communication mechanisms, including server and client implementation, function call routing, connection management, and tagging.' description: 'Provides easy and secure websocket communication mechanisms, including server and client implementation, function call routing, connection management, and tagging.'
} }

View File

@ -18,6 +18,9 @@ export interface ISmartsocketClientOptions {
url: string; url: string;
alias: string; // an alias makes it easier to identify this client in a multo client environment alias: string; // an alias makes it easier to identify this client in a multo client environment
autoReconnect?: boolean; autoReconnect?: boolean;
maxRetries?: number; // maximum number of reconnection attempts
initialBackoffDelay?: number; // initial backoff delay in ms
maxBackoffDelay?: number; // maximum backoff delay in ms
} }
export class SmartsocketClient { export class SmartsocketClient {
@ -32,6 +35,11 @@ export class SmartsocketClient {
public serverUrl: string; public serverUrl: string;
public serverPort: number; public serverPort: number;
public autoReconnect: boolean; public autoReconnect: boolean;
public maxRetries: number;
public initialBackoffDelay: number;
public maxBackoffDelay: number;
public currentRetryCount = 0;
public currentBackoffDelay: number;
// status handling // status handling
public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>(); public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
@ -79,6 +87,10 @@ export class SmartsocketClient {
this.serverUrl = optionsArg.url; this.serverUrl = optionsArg.url;
this.serverPort = optionsArg.port; this.serverPort = optionsArg.port;
this.autoReconnect = optionsArg.autoReconnect; this.autoReconnect = optionsArg.autoReconnect;
this.maxRetries = optionsArg.maxRetries ?? 100; // Default to 100 retries
this.initialBackoffDelay = optionsArg.initialBackoffDelay ?? 1000; // Default to 1 second
this.maxBackoffDelay = optionsArg.maxBackoffDelay ?? 60000; // Default to 1 minute
this.currentBackoffDelay = this.initialBackoffDelay;
} }
public addSocketFunction(socketFunction: SocketFunction<any>) { public addSocketFunction(socketFunction: SocketFunction<any>) {
@ -89,6 +101,10 @@ export class SmartsocketClient {
* connect the client to the server * connect the client to the server
*/ */
public async connect() { public async connect() {
// Reset retry counters on new connection attempt
this.currentRetryCount = 0;
this.currentBackoffDelay = this.initialBackoffDelay;
const done = plugins.smartpromise.defer(); const done = plugins.smartpromise.defer();
const smartenvInstance = new plugins.smartenv.Smartenv(); const smartenvInstance = new plugins.smartenv.Smartenv();
const socketIoClient: any = await smartenvInstance.getEnvAwareModule({ const socketIoClient: any = await smartenvInstance.getEnvAwareModule({
@ -216,8 +232,27 @@ export class SmartsocketClient {
if (this.autoReconnect && useAutoReconnectSetting && this.eventStatus !== 'connecting') { if (this.autoReconnect && useAutoReconnectSetting && this.eventStatus !== 'connecting') {
this.updateStatus('connecting'); this.updateStatus('connecting');
console.log('debounced reconnect!');
await plugins.smartdelay.delayForRandom(10000, 20000); // Check if we've exceeded the maximum number of retries
if (this.currentRetryCount >= this.maxRetries) {
logger.log('warn', `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
this.disconnectRunning = false;
return;
}
// Increment retry counter
this.currentRetryCount++;
// Calculate backoff with jitter (±20% randomness)
const jitter = this.currentBackoffDelay * 0.2 * (Math.random() * 2 - 1);
const delay = Math.min(this.currentBackoffDelay + jitter, this.maxBackoffDelay);
logger.log('info', `Reconnect attempt ${this.currentRetryCount}/${this.maxRetries} in ${Math.round(delay)}ms`);
// Apply exponential backoff for next time (doubling with each attempt)
this.currentBackoffDelay = Math.min(this.currentBackoffDelay * 2, this.maxBackoffDelay);
await plugins.smartdelay.delayFor(delay);
this.disconnectRunning = false; this.disconnectRunning = false;
await this.connect(); await this.connect();
} else { } else {
@ -230,6 +265,8 @@ export class SmartsocketClient {
*/ */
public async stop() { public async stop() {
this.autoReconnect = false; this.autoReconnect = false;
this.currentRetryCount = 0;
this.currentBackoffDelay = this.initialBackoffDelay;
await this.disconnect(); await this.disconnect();
} }
@ -262,5 +299,19 @@ export class SmartsocketClient {
this.eventSubject.next(statusArg); this.eventSubject.next(statusArg);
} }
this.eventStatus = statusArg; this.eventStatus = statusArg;
// Reset reconnection state when connection is successful
if (statusArg === 'connected') {
this.currentRetryCount = 0;
this.currentBackoffDelay = this.initialBackoffDelay;
}
}
/**
* Resets the reconnection state
*/
public resetReconnectionState() {
this.currentRetryCount = 0;
this.currentBackoffDelay = this.initialBackoffDelay;
} }
} }