@push.rocks/smartsocket 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ import { logger } from './smartsocket.logging.js';
16
16
  export interface ISmartsocketClientOptions {
17
17
  port: number;
18
18
  url: string;
19
- alias: string; // an alias makes it easier to identify this client in a multo client environment
19
+ alias: string; // an alias makes it easier to identify this client in a multi client environment
20
20
  autoReconnect?: boolean;
21
21
  maxRetries?: number; // maximum number of reconnection attempts
22
22
  initialBackoffDelay?: number; // initial backoff delay in ms
@@ -97,115 +97,162 @@ export class SmartsocketClient {
97
97
  this.socketFunctions.add(socketFunction);
98
98
  }
99
99
 
100
+ private isReconnecting = false;
101
+
100
102
  /**
101
103
  * connect the client to the server
102
104
  */
103
105
  public async connect() {
104
- // Reset retry counters on new connection attempt
105
- this.currentRetryCount = 0;
106
- this.currentBackoffDelay = this.initialBackoffDelay;
107
-
106
+ // Only reset retry counters on fresh connection (not during auto-reconnect)
107
+ if (!this.isReconnecting) {
108
+ this.currentRetryCount = 0;
109
+ this.currentBackoffDelay = this.initialBackoffDelay;
110
+ }
111
+ this.isReconnecting = false;
112
+
108
113
  const done = plugins.smartpromise.defer();
109
114
  const smartenvInstance = new plugins.smartenv.Smartenv();
110
- const socketIoClient: any = await smartenvInstance.getEnvAwareModule({
111
- nodeModuleName: 'socket.io-client',
112
- webUrlArg: 'https://cdn.jsdelivr.net/npm/socket.io-client@4/dist/socket.io.js',
113
- getFunction: () => {
114
- const socketIoBrowserModule = (globalThis as any).io;
115
- // console.log('loaded socket.io for browser');
116
- return socketIoBrowserModule;
117
- },
118
- });
119
- // console.log(socketIoClient);
115
+
120
116
  logger.log('info', 'trying to connect...');
121
- const socketUrl = `${this.serverUrl}:${this.serverPort}`;
117
+
118
+ // Construct WebSocket URL
119
+ const protocol = this.serverUrl.startsWith('https') ? 'wss' : 'ws';
120
+ const host = this.serverUrl.replace(/^https?:\/\//, '');
121
+ const socketUrl = `${protocol}://${host}:${this.serverPort}`;
122
+
123
+ // Get WebSocket implementation (native in browser, ws in Node)
124
+ let WebSocketClass: typeof WebSocket;
125
+ if (typeof WebSocket !== 'undefined') {
126
+ // Browser environment
127
+ WebSocketClass = WebSocket;
128
+ } else {
129
+ // Node.js environment
130
+ const wsModule = await smartenvInstance.getSafeNodeModule('ws');
131
+ WebSocketClass = wsModule.default || wsModule;
132
+ }
133
+
134
+ const socket = new WebSocketClass(socketUrl);
135
+ this.currentSocket = socket;
136
+
122
137
  this.socketConnection = new SocketConnection({
123
138
  alias: this.alias,
124
139
  authenticated: false,
125
140
  side: 'client',
126
141
  smartsocketHost: this,
127
- socket: await socketIoClient
128
- .connect(socketUrl, {
129
- multiplex: true,
130
- rememberUpgrade: true,
131
- autoConnect: false,
132
- reconnectionAttempts: 0,
133
- rejectUnauthorized: socketUrl.startsWith('https://localhost') ? false : true,
134
- })
135
- .open(),
142
+ socket: socket as any,
136
143
  });
137
144
 
145
+ // Increment attempt ID to invalidate any pending timers from previous attempts
146
+ this.connectionAttemptId++;
147
+ const currentAttemptId = this.connectionAttemptId;
148
+
138
149
  const timer = new plugins.smarttime.Timer(5000);
139
150
  timer.start();
140
151
  timer.completed.then(() => {
141
- this.updateStatus('timedOut');
142
- logger.log('warn', 'connection to server timed out.');
143
- this.disconnect(true);
152
+ // Only fire timeout if this is still the current connection attempt
153
+ if (currentAttemptId === this.connectionAttemptId && this.eventStatus !== 'connected') {
154
+ this.updateStatus('timedOut');
155
+ logger.log('warn', 'connection to server timed out.');
156
+ this.disconnect(true);
157
+ }
144
158
  });
145
159
 
146
- // authentication flow
147
- this.socketConnection.socket.on('requestAuth', (dataArg: interfaces.IRequestAuthPayload) => {
160
+ // Handle connection open
161
+ socket.addEventListener('open', () => {
148
162
  timer.reset();
149
- logger.log('info', `server ${dataArg.serverAlias} requested authentication`);
150
-
151
- // lets register the authenticated event
152
- this.socketConnection.socket.on('authenticated', async () => {
153
- this.remoteShortId = dataArg.serverAlias;
154
- logger.log('info', 'client is authenticated');
155
- this.socketConnection.authenticated = true;
156
- await this.socketConnection.listenToFunctionRequests();
157
- });
158
-
159
- this.socketConnection.socket.on('serverFullyReactive', async () => {
160
- // lets take care of retagging
161
- const oldTagStore = this.tagStore;
162
- this.tagStoreSubscription?.unsubscribe();
163
- for (const keyArg of Object.keys(this.tagStore)) {
164
- this.socketConnection.addTag(this.tagStore[keyArg]);
165
- }
166
- this.tagStoreSubscription = this.socketConnection.tagStoreObservable.subscribe(
167
- (tagStoreArg) => {
168
- this.tagStore = tagStoreArg;
169
- }
170
- );
163
+ });
164
+
165
+ // Handle messages
166
+ socket.addEventListener('message', async (event: MessageEvent | { data: string }) => {
167
+ try {
168
+ const data = typeof event.data === 'string' ? event.data : event.data.toString();
169
+ const message: interfaces.ISocketMessage = JSON.parse(data);
170
+
171
+ switch (message.type) {
172
+ case 'authRequest':
173
+ timer.reset();
174
+ const authRequestPayload = message.payload as interfaces.IAuthRequestPayload;
175
+ logger.log('info', `server ${authRequestPayload.serverAlias} requested authentication`);
176
+ this.remoteShortId = authRequestPayload.serverAlias;
177
+
178
+ // Send authentication data
179
+ this.socketConnection.sendMessage({
180
+ type: 'auth',
181
+ payload: { alias: this.alias },
182
+ });
183
+ break;
171
184
 
172
- for (const tag of Object.keys(oldTagStore)) {
173
- await this.addTag(oldTagStore[tag]);
185
+ case 'authResponse':
186
+ const authResponse = message.payload as interfaces.IAuthResponsePayload;
187
+ if (authResponse.success) {
188
+ logger.log('info', 'client is authenticated');
189
+ this.socketConnection.authenticated = true;
190
+ } else {
191
+ logger.log('warn', `authentication failed: ${authResponse.error}`);
192
+ await this.disconnect();
193
+ }
194
+ break;
195
+
196
+ case 'serverReady':
197
+ // Set up function request listening
198
+ await this.socketConnection.listenToFunctionRequests();
199
+
200
+ // Handle retagging
201
+ const oldTagStore = this.tagStore;
202
+ this.tagStoreSubscription?.unsubscribe();
203
+ for (const keyArg of Object.keys(this.tagStore)) {
204
+ this.socketConnection.addTag(this.tagStore[keyArg]);
205
+ }
206
+ this.tagStoreSubscription = this.socketConnection.tagStoreObservable.subscribe(
207
+ (tagStoreArg) => {
208
+ this.tagStore = tagStoreArg;
209
+ }
210
+ );
211
+
212
+ for (const tag of Object.keys(oldTagStore)) {
213
+ await this.addTag(oldTagStore[tag]);
214
+ }
215
+ this.updateStatus('connected');
216
+ done.resolve();
217
+ break;
218
+
219
+ default:
220
+ // Other messages are handled by SocketConnection
221
+ this.socketConnection.handleMessage(message);
222
+ break;
174
223
  }
175
- this.updateStatus('connected');
176
- done.resolve();
177
- });
178
-
179
- // lets register the forbidden event
180
- this.socketConnection.socket.on('forbidden', async () => {
181
- logger.log('warn', `disconnecting due to being forbidden to use the ressource`);
182
- await this.disconnect();
183
- });
184
-
185
- // lets provide the actual auth data
186
- this.socketConnection.socket.emit('dataAuth', {
187
- alias: this.alias,
188
- });
224
+ } catch (err) {
225
+ // Not a valid JSON message, ignore
226
+ }
189
227
  });
190
228
 
191
- // handle connection
192
- this.socketConnection.socket.on('connect', async () => {});
229
+ // Handle disconnection and errors
230
+ const closeHandler = async () => {
231
+ // Only handle close if this is still the current socket and we're not already disconnecting
232
+ if (this.currentSocket === socket && !this.disconnectRunning) {
233
+ logger.log(
234
+ 'info',
235
+ `SocketConnection with >alias ${this.alias} on >side client disconnected`
236
+ );
237
+ await this.disconnect(true);
238
+ }
239
+ };
240
+
241
+ const errorHandler = async () => {
242
+ if (this.currentSocket === socket && !this.disconnectRunning) {
243
+ await this.disconnect(true);
244
+ }
245
+ };
193
246
 
194
- // handle disconnection and errors
195
- this.socketConnection.socket.on('disconnect', async () => {
196
- await this.disconnect(true);
197
- });
247
+ socket.addEventListener('close', closeHandler);
248
+ socket.addEventListener('error', errorHandler);
198
249
 
199
- this.socketConnection.socket.on('reconnect_failed', async () => {
200
- await this.disconnect(true);
201
- });
202
- this.socketConnection.socket.on('connect_error', async () => {
203
- await this.disconnect(true);
204
- });
205
250
  return done.promise;
206
251
  }
207
252
 
208
253
  private disconnectRunning = false;
254
+ private currentSocket: WebSocket | null = null;
255
+ private connectionAttemptId = 0; // Increment on each connect attempt to invalidate old timers
209
256
 
210
257
  /**
211
258
  * disconnect from the server
@@ -217,11 +264,16 @@ export class SmartsocketClient {
217
264
  this.disconnectRunning = true;
218
265
  this.updateStatus('disconnecting');
219
266
  this.tagStoreSubscription?.unsubscribe();
267
+
268
+ // Store reference to current socket before cleanup
269
+ const socketToClose = this.currentSocket;
270
+ this.currentSocket = null;
271
+
220
272
  if (this.socketConnection) {
221
273
  await this.socketConnection.disconnect();
222
274
  this.socketConnection = undefined;
223
275
  logger.log('ok', 'disconnected socket!');
224
- } else {
276
+ } else if (!socketToClose) {
225
277
  this.disconnectRunning = false;
226
278
  logger.log('warn', 'tried to disconnect, without a SocketConnection');
227
279
  return;
@@ -232,28 +284,29 @@ export class SmartsocketClient {
232
284
 
233
285
  if (this.autoReconnect && useAutoReconnectSetting && this.eventStatus !== 'connecting') {
234
286
  this.updateStatus('connecting');
235
-
287
+
236
288
  // Check if we've exceeded the maximum number of retries
237
289
  if (this.currentRetryCount >= this.maxRetries) {
238
290
  logger.log('warn', `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
239
291
  this.disconnectRunning = false;
240
292
  return;
241
293
  }
242
-
294
+
243
295
  // Increment retry counter
244
296
  this.currentRetryCount++;
245
-
297
+
246
298
  // Calculate backoff with jitter (±20% randomness)
247
299
  const jitter = this.currentBackoffDelay * 0.2 * (Math.random() * 2 - 1);
248
300
  const delay = Math.min(this.currentBackoffDelay + jitter, this.maxBackoffDelay);
249
-
301
+
250
302
  logger.log('info', `Reconnect attempt ${this.currentRetryCount}/${this.maxRetries} in ${Math.round(delay)}ms`);
251
-
303
+
252
304
  // Apply exponential backoff for next time (doubling with each attempt)
253
305
  this.currentBackoffDelay = Math.min(this.currentBackoffDelay * 2, this.maxBackoffDelay);
254
-
306
+
255
307
  await plugins.smartdelay.delayFor(delay);
256
308
  this.disconnectRunning = false;
309
+ this.isReconnecting = true;
257
310
  await this.connect();
258
311
  } else {
259
312
  this.disconnectRunning = false;
@@ -279,7 +332,6 @@ export class SmartsocketClient {
279
332
  functionNameArg: T['method'],
280
333
  dataArg: T['request']
281
334
  ): Promise<T['response']> {
282
- const done = plugins.smartpromise.defer();
283
335
  const socketRequest = new SocketRequest<T>(this, {
284
336
  side: 'requesting',
285
337
  originSocketConnection: this.socketConnection,
@@ -299,14 +351,14 @@ export class SmartsocketClient {
299
351
  this.eventSubject.next(statusArg);
300
352
  }
301
353
  this.eventStatus = statusArg;
302
-
354
+
303
355
  // Reset reconnection state when connection is successful
304
356
  if (statusArg === 'connected') {
305
357
  this.currentRetryCount = 0;
306
358
  this.currentBackoffDelay = this.initialBackoffDelay;
307
359
  }
308
360
  }
309
-
361
+
310
362
  /**
311
363
  * Resets the reconnection state
312
364
  */
@@ -26,7 +26,7 @@ export interface ISocketConnectionConstructorOptions {
26
26
  authenticated: boolean;
27
27
  side: TSocketConnectionSide;
28
28
  smartsocketHost: Smartsocket | SmartsocketClient;
29
- socket: pluginsTyped.socketIo.Socket | pluginsTyped.socketIoClient.Socket;
29
+ socket: WebSocket | pluginsTyped.ws.WebSocket;
30
30
  }
31
31
 
32
32
  /**
@@ -47,7 +47,7 @@ export class SocketConnection {
47
47
  public side: TSocketConnectionSide;
48
48
  public authenticated: boolean = false;
49
49
  public smartsocketRef: Smartsocket | SmartsocketClient;
50
- public socket: pluginsTyped.socketIo.Socket | pluginsTyped.socketIoClient.Socket;
50
+ public socket: WebSocket | pluginsTyped.ws.WebSocket;
51
51
 
52
52
  public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
53
53
  public eventStatus: interfaces.TConnectionStatus = 'new';
@@ -65,20 +65,94 @@ export class SocketConnection {
65
65
 
66
66
  // standard behaviour that is always true
67
67
  allSocketConnections.add(this);
68
+ }
68
69
 
69
- // handle connection
70
- this.socket.on('connect', async () => {
71
- this.updateStatus('connected');
72
- });
73
- this.socket.on('disconnect', async () => {
74
- logger.log(
75
- 'info',
76
- `SocketConnection with >alias ${this.alias} on >side ${this.side} disconnected`
77
- );
78
- await this.disconnect();
79
- allSocketConnections.remove(this);
80
- this.eventSubject.next('disconnected');
81
- });
70
+ /**
71
+ * Sends a message through the socket
72
+ */
73
+ public sendMessage(message: interfaces.ISocketMessage): void {
74
+ if (this.socket.readyState === 1) { // WebSocket.OPEN
75
+ this.socket.send(JSON.stringify(message));
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Handles incoming messages
81
+ */
82
+ public handleMessage(messageData: interfaces.ISocketMessage): void {
83
+ switch (messageData.type) {
84
+ case 'function':
85
+ this.handleFunctionCall(messageData);
86
+ break;
87
+ case 'functionResponse':
88
+ this.handleFunctionResponse(messageData);
89
+ break;
90
+ case 'tagUpdate':
91
+ this.handleTagUpdate(messageData);
92
+ break;
93
+ default:
94
+ // Authentication messages are handled by the server/client classes
95
+ break;
96
+ }
97
+ }
98
+
99
+ private handleFunctionCall(messageData: interfaces.ISocketMessage): void {
100
+ const requestData: ISocketRequestDataObject<any> = {
101
+ funcCallData: {
102
+ funcName: messageData.payload.funcName,
103
+ funcDataArg: messageData.payload.funcData,
104
+ },
105
+ shortId: messageData.id,
106
+ };
107
+
108
+ const referencedFunction: SocketFunction<any> =
109
+ this.smartsocketRef.socketFunctions.findSync((socketFunctionArg) => {
110
+ return socketFunctionArg.name === requestData.funcCallData.funcName;
111
+ });
112
+
113
+ if (referencedFunction) {
114
+ const localSocketRequest = new SocketRequest(this.smartsocketRef, {
115
+ side: 'responding',
116
+ originSocketConnection: this,
117
+ shortId: requestData.shortId,
118
+ funcCallData: requestData.funcCallData,
119
+ });
120
+ localSocketRequest.createResponse();
121
+ } else {
122
+ logger.log('warn', `function ${requestData.funcCallData.funcName} not found or out of scope`);
123
+ }
124
+ }
125
+
126
+ private handleFunctionResponse(messageData: interfaces.ISocketMessage): void {
127
+ const responseData: ISocketRequestDataObject<any> = {
128
+ funcCallData: {
129
+ funcName: messageData.payload.funcName,
130
+ funcDataArg: messageData.payload.funcData,
131
+ },
132
+ shortId: messageData.id,
133
+ };
134
+
135
+ const targetSocketRequest = SocketRequest.getSocketRequestById(
136
+ this.smartsocketRef,
137
+ responseData.shortId
138
+ );
139
+ if (targetSocketRequest) {
140
+ targetSocketRequest.handleResponse(responseData);
141
+ }
142
+ }
143
+
144
+ private handleTagUpdate(messageData: interfaces.ISocketMessage): void {
145
+ const tagStoreArg = messageData.payload.tags as interfaces.TTagStore;
146
+ if (!plugins.smartjson.deepEqualObjects(this.tagStore, tagStoreArg)) {
147
+ this.tagStore = tagStoreArg;
148
+ // Echo back to confirm
149
+ this.sendMessage({
150
+ type: 'tagUpdate',
151
+ payload: { tags: this.tagStore },
152
+ });
153
+ this.tagStoreObservable.next(this.tagStore);
154
+ }
155
+ this.remoteTagStoreObservable.next(tagStoreArg);
82
156
  }
83
157
 
84
158
  /**
@@ -99,7 +173,10 @@ export class SocketConnection {
99
173
  done.resolve();
100
174
  }
101
175
  });
102
- this.socket.emit('updateTagStore', this.tagStore);
176
+ this.sendMessage({
177
+ type: 'tagUpdate',
178
+ payload: { tags: this.tagStore },
179
+ });
103
180
  await done.promise;
104
181
  }
105
182
 
@@ -117,36 +194,68 @@ export class SocketConnection {
117
194
  public async removeTagById(tagIdArg: interfaces.ITag['id']) {
118
195
  delete this.tagStore[tagIdArg];
119
196
  this.tagStoreObservable.next(this.tagStore);
120
- this.socket.emit('updateTagStore', this.tagStore);
197
+ this.sendMessage({
198
+ type: 'tagUpdate',
199
+ payload: { tags: this.tagStore },
200
+ });
121
201
  }
122
202
 
123
203
  // authenticating --------------------------
124
204
 
125
205
  /**
126
- * authenticate the socket
206
+ * authenticate the socket (server side)
127
207
  */
128
- public authenticate() {
129
- const done = plugins.smartpromise.defer();
130
- this.socket.on('dataAuth', async (dataArg: ISocketConnectionAuthenticationObject) => {
131
- logger.log('info', 'received authentication data...');
132
- this.socket.removeAllListeners('dataAuth');
133
- if (dataArg.alias) {
134
- // TODO: authenticate password
135
- this.alias = dataArg.alias;
136
- this.authenticated = true;
137
- this.socket.emit('authenticated');
138
- logger.log('ok', `socket with >>alias ${this.alias} is authenticated!`);
139
- done.resolve(this);
140
- } else {
141
- this.authenticated = false;
142
- await this.disconnect();
143
- done.reject('a socket tried to connect, but could not authenticated.');
208
+ public authenticate(): Promise<SocketConnection> {
209
+ const done = plugins.smartpromise.defer<SocketConnection>();
210
+
211
+ // Set up message handler for authentication
212
+ const messageHandler = (event: MessageEvent | { data: string }) => {
213
+ try {
214
+ const data = typeof event.data === 'string' ? event.data : event.data.toString();
215
+ const message: interfaces.ISocketMessage = JSON.parse(data);
216
+
217
+ if (message.type === 'auth') {
218
+ const authData = message.payload as interfaces.IAuthPayload;
219
+ logger.log('info', 'received authentication data...');
220
+
221
+ if (authData.alias) {
222
+ this.alias = authData.alias;
223
+ this.authenticated = true;
224
+
225
+ // Send authentication response
226
+ this.sendMessage({
227
+ type: 'authResponse',
228
+ payload: { success: true },
229
+ });
230
+
231
+ logger.log('ok', `socket with >>alias ${this.alias} is authenticated!`);
232
+ done.resolve(this);
233
+ } else {
234
+ this.authenticated = false;
235
+ this.sendMessage({
236
+ type: 'authResponse',
237
+ payload: { success: false, error: 'No alias provided' },
238
+ });
239
+ this.disconnect();
240
+ done.reject('a socket tried to connect, but could not authenticate.');
241
+ }
242
+ }
243
+ } catch (err) {
244
+ // Not a valid message, ignore
144
245
  }
145
- });
146
- const requestAuthPayload: interfaces.IRequestAuthPayload = {
147
- serverAlias: this.smartsocketRef.alias,
148
246
  };
149
- this.socket.emit('requestAuth', requestAuthPayload);
247
+
248
+ this.socket.addEventListener('message', messageHandler as any);
249
+
250
+ // Request authentication
251
+ const requestAuthPayload: interfaces.TAuthRequestMessage = {
252
+ type: 'authRequest',
253
+ payload: {
254
+ serverAlias: (this.smartsocketRef as Smartsocket).alias,
255
+ },
256
+ };
257
+ this.sendMessage(requestAuthPayload);
258
+
150
259
  return done.promise;
151
260
  }
152
261
 
@@ -158,43 +267,18 @@ export class SocketConnection {
158
267
  public listenToFunctionRequests() {
159
268
  const done = plugins.smartpromise.defer();
160
269
  if (this.authenticated) {
161
- this.socket.on('function', (dataArg: ISocketRequestDataObject<any>) => {
162
- // check if requested function is available to the socket's scope
163
- // logger.log('info', 'function request received');
164
- const referencedFunction: SocketFunction<any> =
165
- this.smartsocketRef.socketFunctions.findSync((socketFunctionArg) => {
166
- return socketFunctionArg.name === dataArg.funcCallData.funcName;
167
- });
168
- if (referencedFunction) {
169
- // logger.log('ok', 'function in access scope');
170
- const localSocketRequest = new SocketRequest(this.smartsocketRef, {
171
- side: 'responding',
172
- originSocketConnection: this,
173
- shortId: dataArg.shortId,
174
- funcCallData: dataArg.funcCallData,
175
- });
176
- localSocketRequest.createResponse(); // takes care of creating response and sending it back
177
- } else {
178
- logger.log('warn', 'function not existent or out of access scope');
270
+ // Set up message handler for all messages
271
+ const messageHandler = (event: MessageEvent | { data: string }) => {
272
+ try {
273
+ const data = typeof event.data === 'string' ? event.data : event.data.toString();
274
+ const message: interfaces.ISocketMessage = JSON.parse(data);
275
+ this.handleMessage(message);
276
+ } catch (err) {
277
+ // Not a valid JSON message, ignore
179
278
  }
180
- });
181
- this.socket.on('functionResponse', (dataArg: ISocketRequestDataObject<any>) => {
182
- // logger.log('info', `received response for request with id ${dataArg.shortId}`);
183
- const targetSocketRequest = SocketRequest.getSocketRequestById(
184
- this.smartsocketRef,
185
- dataArg.shortId
186
- );
187
- targetSocketRequest.handleResponse(dataArg);
188
- });
279
+ };
189
280
 
190
- this.socket.on('updateTagStore', async (tagStoreArg: interfaces.TTagStore) => {
191
- if (!plugins.smartjson.deepEqualObjects(this.tagStore, tagStoreArg)) {
192
- this.tagStore = tagStoreArg;
193
- this.socket.emit('updateTagStore', this.tagStore);
194
- this.tagStoreObservable.next(this.tagStore);
195
- }
196
- this.remoteTagStoreObservable.next(tagStoreArg);
197
- });
281
+ this.socket.addEventListener('message', messageHandler as any);
198
282
 
199
283
  logger.log(
200
284
  'info',
@@ -211,7 +295,10 @@ export class SocketConnection {
211
295
 
212
296
  // disconnecting ----------------------
213
297
  public async disconnect() {
214
- this.socket.disconnect(true);
298
+ if (this.socket.readyState === 1 || this.socket.readyState === 0) {
299
+ this.socket.close();
300
+ }
301
+ allSocketConnections.remove(this);
215
302
  this.updateStatus('disconnected');
216
303
  }
217
304