@push.rocks/smartsocket 2.0.27 → 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.
Files changed (29) hide show
  1. package/dist_bundle/bundle.js +11562 -7527
  2. package/dist_bundle/bundle.js.map +4 -4
  3. package/dist_ts/00_commitinfo_data.d.ts +1 -1
  4. package/dist_ts/00_commitinfo_data.js +3 -3
  5. package/dist_ts/interfaces/index.d.ts +1 -0
  6. package/dist_ts/interfaces/index.js +2 -1
  7. package/dist_ts/interfaces/message.d.ts +55 -0
  8. package/dist_ts/interfaces/message.js +2 -0
  9. package/dist_ts/smartsocket.classes.smartsocket.d.ts +10 -6
  10. package/dist_ts/smartsocket.classes.smartsocket.js +43 -39
  11. package/dist_ts/smartsocket.classes.smartsocketclient.d.ts +15 -0
  12. package/dist_ts/smartsocket.classes.smartsocketclient.js +148 -74
  13. package/dist_ts/smartsocket.classes.socketconnection.d.ts +15 -4
  14. package/dist_ts/smartsocket.classes.socketconnection.js +139 -62
  15. package/dist_ts/smartsocket.classes.socketrequest.js +18 -12
  16. package/dist_ts/smartsocket.classes.socketserver.d.ts +15 -15
  17. package/dist_ts/smartsocket.classes.socketserver.js +152 -45
  18. package/dist_ts/smartsocket.pluginstyped.d.ts +31 -12
  19. package/package.json +26 -27
  20. package/readme.md +216 -74
  21. package/ts/00_commitinfo_data.ts +2 -2
  22. package/ts/interfaces/index.ts +1 -0
  23. package/ts/interfaces/message.ts +67 -0
  24. package/ts/smartsocket.classes.smartsocket.ts +47 -40
  25. package/ts/smartsocket.classes.smartsocketclient.ts +183 -80
  26. package/ts/smartsocket.classes.socketconnection.ts +161 -74
  27. package/ts/smartsocket.classes.socketrequest.ts +18 -11
  28. package/ts/smartsocket.classes.socketserver.ts +161 -52
  29. package/ts/smartsocket.pluginstyped.ts +35 -12
@@ -16,8 +16,11 @@ 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
+ maxRetries?: number; // maximum number of reconnection attempts
22
+ initialBackoffDelay?: number; // initial backoff delay in ms
23
+ maxBackoffDelay?: number; // maximum backoff delay in ms
21
24
  }
22
25
 
23
26
  export class SmartsocketClient {
@@ -32,6 +35,11 @@ export class SmartsocketClient {
32
35
  public serverUrl: string;
33
36
  public serverPort: number;
34
37
  public autoReconnect: boolean;
38
+ public maxRetries: number;
39
+ public initialBackoffDelay: number;
40
+ public maxBackoffDelay: number;
41
+ public currentRetryCount = 0;
42
+ public currentBackoffDelay: number;
35
43
 
36
44
  // status handling
37
45
  public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
@@ -79,117 +87,172 @@ export class SmartsocketClient {
79
87
  this.serverUrl = optionsArg.url;
80
88
  this.serverPort = optionsArg.port;
81
89
  this.autoReconnect = optionsArg.autoReconnect;
90
+ this.maxRetries = optionsArg.maxRetries ?? 100; // Default to 100 retries
91
+ this.initialBackoffDelay = optionsArg.initialBackoffDelay ?? 1000; // Default to 1 second
92
+ this.maxBackoffDelay = optionsArg.maxBackoffDelay ?? 60000; // Default to 1 minute
93
+ this.currentBackoffDelay = this.initialBackoffDelay;
82
94
  }
83
95
 
84
96
  public addSocketFunction(socketFunction: SocketFunction<any>) {
85
97
  this.socketFunctions.add(socketFunction);
86
98
  }
87
99
 
100
+ private isReconnecting = false;
101
+
88
102
  /**
89
103
  * connect the client to the server
90
104
  */
91
105
  public async connect() {
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
+
92
113
  const done = plugins.smartpromise.defer();
93
114
  const smartenvInstance = new plugins.smartenv.Smartenv();
94
- const socketIoClient: any = await smartenvInstance.getEnvAwareModule({
95
- nodeModuleName: 'socket.io-client',
96
- webUrlArg: 'https://cdn.jsdelivr.net/npm/socket.io-client@4/dist/socket.io.js',
97
- getFunction: () => {
98
- const socketIoBrowserModule = (globalThis as any).io;
99
- // console.log('loaded socket.io for browser');
100
- return socketIoBrowserModule;
101
- },
102
- });
103
- // console.log(socketIoClient);
115
+
104
116
  logger.log('info', 'trying to connect...');
105
- 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
+
106
137
  this.socketConnection = new SocketConnection({
107
138
  alias: this.alias,
108
139
  authenticated: false,
109
140
  side: 'client',
110
141
  smartsocketHost: this,
111
- socket: await socketIoClient
112
- .connect(socketUrl, {
113
- multiplex: true,
114
- rememberUpgrade: true,
115
- autoConnect: false,
116
- reconnectionAttempts: 0,
117
- rejectUnauthorized: socketUrl.startsWith('https://localhost') ? false : true,
118
- })
119
- .open(),
142
+ socket: socket as any,
120
143
  });
121
144
 
145
+ // Increment attempt ID to invalidate any pending timers from previous attempts
146
+ this.connectionAttemptId++;
147
+ const currentAttemptId = this.connectionAttemptId;
148
+
122
149
  const timer = new plugins.smarttime.Timer(5000);
123
150
  timer.start();
124
151
  timer.completed.then(() => {
125
- this.updateStatus('timedOut');
126
- logger.log('warn', 'connection to server timed out.');
127
- 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
+ }
128
158
  });
129
159
 
130
- // authentication flow
131
- this.socketConnection.socket.on('requestAuth', (dataArg: interfaces.IRequestAuthPayload) => {
160
+ // Handle connection open
161
+ socket.addEventListener('open', () => {
132
162
  timer.reset();
133
- logger.log('info', `server ${dataArg.serverAlias} requested authentication`);
134
-
135
- // lets register the authenticated event
136
- this.socketConnection.socket.on('authenticated', async () => {
137
- this.remoteShortId = dataArg.serverAlias;
138
- logger.log('info', 'client is authenticated');
139
- this.socketConnection.authenticated = true;
140
- await this.socketConnection.listenToFunctionRequests();
141
- });
142
-
143
- this.socketConnection.socket.on('serverFullyReactive', async () => {
144
- // lets take care of retagging
145
- const oldTagStore = this.tagStore;
146
- this.tagStoreSubscription?.unsubscribe();
147
- for (const keyArg of Object.keys(this.tagStore)) {
148
- this.socketConnection.addTag(this.tagStore[keyArg]);
149
- }
150
- this.tagStoreSubscription = this.socketConnection.tagStoreObservable.subscribe(
151
- (tagStoreArg) => {
152
- this.tagStore = tagStoreArg;
153
- }
154
- );
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;
184
+
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
+ );
155
211
 
156
- for (const tag of Object.keys(oldTagStore)) {
157
- await this.addTag(oldTagStore[tag]);
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;
158
223
  }
159
- this.updateStatus('connected');
160
- done.resolve();
161
- });
162
-
163
- // lets register the forbidden event
164
- this.socketConnection.socket.on('forbidden', async () => {
165
- logger.log('warn', `disconnecting due to being forbidden to use the ressource`);
166
- await this.disconnect();
167
- });
168
-
169
- // lets provide the actual auth data
170
- this.socketConnection.socket.emit('dataAuth', {
171
- alias: this.alias,
172
- });
224
+ } catch (err) {
225
+ // Not a valid JSON message, ignore
226
+ }
173
227
  });
174
228
 
175
- // handle connection
176
- 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
+ };
177
240
 
178
- // handle disconnection and errors
179
- this.socketConnection.socket.on('disconnect', async () => {
180
- await this.disconnect(true);
181
- });
241
+ const errorHandler = async () => {
242
+ if (this.currentSocket === socket && !this.disconnectRunning) {
243
+ await this.disconnect(true);
244
+ }
245
+ };
246
+
247
+ socket.addEventListener('close', closeHandler);
248
+ socket.addEventListener('error', errorHandler);
182
249
 
183
- this.socketConnection.socket.on('reconnect_failed', async () => {
184
- await this.disconnect(true);
185
- });
186
- this.socketConnection.socket.on('connect_error', async () => {
187
- await this.disconnect(true);
188
- });
189
250
  return done.promise;
190
251
  }
191
252
 
192
253
  private disconnectRunning = false;
254
+ private currentSocket: WebSocket | null = null;
255
+ private connectionAttemptId = 0; // Increment on each connect attempt to invalidate old timers
193
256
 
194
257
  /**
195
258
  * disconnect from the server
@@ -201,11 +264,16 @@ export class SmartsocketClient {
201
264
  this.disconnectRunning = true;
202
265
  this.updateStatus('disconnecting');
203
266
  this.tagStoreSubscription?.unsubscribe();
267
+
268
+ // Store reference to current socket before cleanup
269
+ const socketToClose = this.currentSocket;
270
+ this.currentSocket = null;
271
+
204
272
  if (this.socketConnection) {
205
273
  await this.socketConnection.disconnect();
206
274
  this.socketConnection = undefined;
207
275
  logger.log('ok', 'disconnected socket!');
208
- } else {
276
+ } else if (!socketToClose) {
209
277
  this.disconnectRunning = false;
210
278
  logger.log('warn', 'tried to disconnect, without a SocketConnection');
211
279
  return;
@@ -216,9 +284,29 @@ export class SmartsocketClient {
216
284
 
217
285
  if (this.autoReconnect && useAutoReconnectSetting && this.eventStatus !== 'connecting') {
218
286
  this.updateStatus('connecting');
219
- console.log('debounced reconnect!');
220
- await plugins.smartdelay.delayForRandom(10000, 20000);
287
+
288
+ // Check if we've exceeded the maximum number of retries
289
+ if (this.currentRetryCount >= this.maxRetries) {
290
+ logger.log('warn', `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
291
+ this.disconnectRunning = false;
292
+ return;
293
+ }
294
+
295
+ // Increment retry counter
296
+ this.currentRetryCount++;
297
+
298
+ // Calculate backoff with jitter (±20% randomness)
299
+ const jitter = this.currentBackoffDelay * 0.2 * (Math.random() * 2 - 1);
300
+ const delay = Math.min(this.currentBackoffDelay + jitter, this.maxBackoffDelay);
301
+
302
+ logger.log('info', `Reconnect attempt ${this.currentRetryCount}/${this.maxRetries} in ${Math.round(delay)}ms`);
303
+
304
+ // Apply exponential backoff for next time (doubling with each attempt)
305
+ this.currentBackoffDelay = Math.min(this.currentBackoffDelay * 2, this.maxBackoffDelay);
306
+
307
+ await plugins.smartdelay.delayFor(delay);
221
308
  this.disconnectRunning = false;
309
+ this.isReconnecting = true;
222
310
  await this.connect();
223
311
  } else {
224
312
  this.disconnectRunning = false;
@@ -230,6 +318,8 @@ export class SmartsocketClient {
230
318
  */
231
319
  public async stop() {
232
320
  this.autoReconnect = false;
321
+ this.currentRetryCount = 0;
322
+ this.currentBackoffDelay = this.initialBackoffDelay;
233
323
  await this.disconnect();
234
324
  }
235
325
 
@@ -242,7 +332,6 @@ export class SmartsocketClient {
242
332
  functionNameArg: T['method'],
243
333
  dataArg: T['request']
244
334
  ): Promise<T['response']> {
245
- const done = plugins.smartpromise.defer();
246
335
  const socketRequest = new SocketRequest<T>(this, {
247
336
  side: 'requesting',
248
337
  originSocketConnection: this.socketConnection,
@@ -262,5 +351,19 @@ export class SmartsocketClient {
262
351
  this.eventSubject.next(statusArg);
263
352
  }
264
353
  this.eventStatus = statusArg;
354
+
355
+ // Reset reconnection state when connection is successful
356
+ if (statusArg === 'connected') {
357
+ this.currentRetryCount = 0;
358
+ this.currentBackoffDelay = this.initialBackoffDelay;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Resets the reconnection state
364
+ */
365
+ public resetReconnectionState() {
366
+ this.currentRetryCount = 0;
367
+ this.currentBackoffDelay = this.initialBackoffDelay;
265
368
  }
266
369
  }
@@ -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