@push.rocks/smartsocket 2.1.0 → 4.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.
- package/dist_bundle/bundle.js +2593 -4557
- package/dist_bundle/bundle.js.map +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/interfaces/index.d.ts +1 -0
- package/dist_ts/interfaces/index.js +2 -1
- package/dist_ts/interfaces/message.d.ts +53 -0
- package/dist_ts/interfaces/message.js +2 -0
- package/dist_ts/smartsocket.classes.smartsocket.d.ts +10 -6
- package/dist_ts/smartsocket.classes.smartsocket.js +43 -39
- package/dist_ts/smartsocket.classes.smartsocketclient.d.ts +4 -0
- package/dist_ts/smartsocket.classes.smartsocketclient.js +122 -75
- package/dist_ts/smartsocket.classes.socketconnection.d.ts +19 -9
- package/dist_ts/smartsocket.classes.socketconnection.js +143 -67
- package/dist_ts/smartsocket.classes.socketrequest.js +22 -12
- package/dist_ts/smartsocket.classes.socketserver.d.ts +15 -13
- package/dist_ts/smartsocket.classes.socketserver.js +152 -45
- package/dist_ts/smartsocket.pluginstyped.d.ts +54 -11
- package/package.json +24 -25
- package/readme.md +220 -74
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/interfaces/index.ts +1 -0
- package/ts/interfaces/message.ts +67 -0
- package/ts/smartsocket.classes.smartsocket.ts +47 -40
- package/ts/smartsocket.classes.smartsocketclient.ts +151 -90
- package/ts/smartsocket.classes.socketconnection.ts +165 -79
- package/ts/smartsocket.classes.socketrequest.ts +22 -11
- package/ts/smartsocket.classes.socketserver.ts +161 -52
- package/ts/smartsocket.pluginstyped.ts +58 -12
|
@@ -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
|
|
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,170 @@ export class SmartsocketClient {
|
|
|
97
97
|
this.socketFunctions.add(socketFunction);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
private isReconnecting = false;
|
|
101
|
+
private isConnecting = false;
|
|
102
|
+
|
|
100
103
|
/**
|
|
101
104
|
* connect the client to the server
|
|
102
105
|
*/
|
|
103
106
|
public async connect() {
|
|
104
|
-
//
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
// Prevent duplicate connection attempts
|
|
108
|
+
if (this.isConnecting) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.isConnecting = true;
|
|
112
|
+
|
|
113
|
+
// Only reset retry counters on fresh connection (not during auto-reconnect)
|
|
114
|
+
if (!this.isReconnecting) {
|
|
115
|
+
this.currentRetryCount = 0;
|
|
116
|
+
this.currentBackoffDelay = this.initialBackoffDelay;
|
|
117
|
+
}
|
|
118
|
+
this.isReconnecting = false;
|
|
119
|
+
|
|
108
120
|
const done = plugins.smartpromise.defer();
|
|
109
121
|
const smartenvInstance = new plugins.smartenv.Smartenv();
|
|
110
|
-
|
|
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);
|
|
122
|
+
|
|
120
123
|
logger.log('info', 'trying to connect...');
|
|
121
|
-
|
|
124
|
+
|
|
125
|
+
// Construct WebSocket URL
|
|
126
|
+
const protocol = this.serverUrl.startsWith('https') ? 'wss' : 'ws';
|
|
127
|
+
const host = this.serverUrl.replace(/^https?:\/\//, '');
|
|
128
|
+
const socketUrl = `${protocol}://${host}:${this.serverPort}`;
|
|
129
|
+
|
|
130
|
+
// Get WebSocket implementation (native in browser, ws in Node)
|
|
131
|
+
let WebSocketClass: typeof WebSocket;
|
|
132
|
+
if (typeof WebSocket !== 'undefined') {
|
|
133
|
+
// Browser environment
|
|
134
|
+
WebSocketClass = WebSocket;
|
|
135
|
+
} else {
|
|
136
|
+
// Node.js environment
|
|
137
|
+
const wsModule = await smartenvInstance.getSafeNodeModule('ws');
|
|
138
|
+
WebSocketClass = wsModule.default || wsModule;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const socket = new WebSocketClass(socketUrl);
|
|
142
|
+
this.currentSocket = socket;
|
|
143
|
+
|
|
122
144
|
this.socketConnection = new SocketConnection({
|
|
123
145
|
alias: this.alias,
|
|
124
146
|
authenticated: false,
|
|
125
147
|
side: 'client',
|
|
126
148
|
smartsocketHost: this,
|
|
127
|
-
socket:
|
|
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(),
|
|
149
|
+
socket: socket as any,
|
|
136
150
|
});
|
|
137
151
|
|
|
152
|
+
// Increment attempt ID to invalidate any pending timers from previous attempts
|
|
153
|
+
this.connectionAttemptId++;
|
|
154
|
+
const currentAttemptId = this.connectionAttemptId;
|
|
155
|
+
|
|
138
156
|
const timer = new plugins.smarttime.Timer(5000);
|
|
139
157
|
timer.start();
|
|
140
158
|
timer.completed.then(() => {
|
|
141
|
-
this
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
// Only fire timeout if this is still the current connection attempt
|
|
160
|
+
if (currentAttemptId === this.connectionAttemptId && this.eventStatus !== 'connected') {
|
|
161
|
+
this.updateStatus('timedOut');
|
|
162
|
+
logger.log('warn', 'connection to server timed out.');
|
|
163
|
+
this.disconnect(true);
|
|
164
|
+
}
|
|
144
165
|
});
|
|
145
166
|
|
|
146
|
-
//
|
|
147
|
-
|
|
167
|
+
// Handle connection open
|
|
168
|
+
socket.addEventListener('open', () => {
|
|
148
169
|
timer.reset();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Handle messages
|
|
173
|
+
socket.addEventListener('message', async (event: MessageEvent | { data: string }) => {
|
|
174
|
+
try {
|
|
175
|
+
const data = typeof event.data === 'string' ? event.data : event.data.toString();
|
|
176
|
+
const message: interfaces.ISocketMessage = JSON.parse(data);
|
|
177
|
+
|
|
178
|
+
switch (message.type) {
|
|
179
|
+
case 'authRequest':
|
|
180
|
+
timer.reset();
|
|
181
|
+
const authRequestPayload = message.payload as interfaces.IAuthRequestPayload;
|
|
182
|
+
logger.log('info', `server ${authRequestPayload.serverAlias} requested authentication`);
|
|
183
|
+
this.remoteShortId = authRequestPayload.serverAlias;
|
|
171
184
|
|
|
172
|
-
|
|
173
|
-
|
|
185
|
+
// Send authentication data
|
|
186
|
+
this.socketConnection.sendMessage({
|
|
187
|
+
type: 'auth',
|
|
188
|
+
payload: { alias: this.alias },
|
|
189
|
+
});
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'authResponse':
|
|
193
|
+
const authResponse = message.payload as interfaces.IAuthResponsePayload;
|
|
194
|
+
if (authResponse.success) {
|
|
195
|
+
logger.log('info', 'client is authenticated');
|
|
196
|
+
this.socketConnection.authenticated = true;
|
|
197
|
+
} else {
|
|
198
|
+
logger.log('warn', `authentication failed: ${authResponse.error}`);
|
|
199
|
+
await this.disconnect();
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
case 'serverReady':
|
|
204
|
+
// Set up function request listening
|
|
205
|
+
await this.socketConnection.listenToFunctionRequests();
|
|
206
|
+
|
|
207
|
+
// Handle retagging
|
|
208
|
+
const oldTagStore = this.tagStore;
|
|
209
|
+
this.tagStoreSubscription?.unsubscribe();
|
|
210
|
+
for (const keyArg of Object.keys(this.tagStore)) {
|
|
211
|
+
this.socketConnection.addTag(this.tagStore[keyArg]);
|
|
212
|
+
}
|
|
213
|
+
this.tagStoreSubscription = this.socketConnection.tagStoreObservable.subscribe(
|
|
214
|
+
(tagStoreArg) => {
|
|
215
|
+
this.tagStore = tagStoreArg;
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
for (const tag of Object.keys(oldTagStore)) {
|
|
220
|
+
await this.addTag(oldTagStore[tag]);
|
|
221
|
+
}
|
|
222
|
+
this.updateStatus('connected');
|
|
223
|
+
this.isConnecting = false;
|
|
224
|
+
done.resolve();
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
default:
|
|
228
|
+
// Other messages are handled by SocketConnection
|
|
229
|
+
this.socketConnection.handleMessage(message);
|
|
230
|
+
break;
|
|
174
231
|
}
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
});
|
|
232
|
+
} catch (err) {
|
|
233
|
+
// Not a valid JSON message, ignore
|
|
234
|
+
}
|
|
189
235
|
});
|
|
190
236
|
|
|
191
|
-
//
|
|
192
|
-
|
|
237
|
+
// Handle disconnection and errors
|
|
238
|
+
const closeHandler = async () => {
|
|
239
|
+
// Only handle close if this is still the current socket and we're not already disconnecting
|
|
240
|
+
if (this.currentSocket === socket && !this.disconnectRunning) {
|
|
241
|
+
logger.log(
|
|
242
|
+
'info',
|
|
243
|
+
`SocketConnection with >alias ${this.alias} on >side client disconnected`
|
|
244
|
+
);
|
|
245
|
+
await this.disconnect(true);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
193
248
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
249
|
+
const errorHandler = async () => {
|
|
250
|
+
if (this.currentSocket === socket && !this.disconnectRunning) {
|
|
251
|
+
await this.disconnect(true);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
socket.addEventListener('close', closeHandler);
|
|
256
|
+
socket.addEventListener('error', errorHandler);
|
|
198
257
|
|
|
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
258
|
return done.promise;
|
|
206
259
|
}
|
|
207
260
|
|
|
208
261
|
private disconnectRunning = false;
|
|
262
|
+
private currentSocket: WebSocket | null = null;
|
|
263
|
+
private connectionAttemptId = 0; // Increment on each connect attempt to invalidate old timers
|
|
209
264
|
|
|
210
265
|
/**
|
|
211
266
|
* disconnect from the server
|
|
@@ -215,13 +270,19 @@ export class SmartsocketClient {
|
|
|
215
270
|
return;
|
|
216
271
|
}
|
|
217
272
|
this.disconnectRunning = true;
|
|
273
|
+
this.isConnecting = false;
|
|
218
274
|
this.updateStatus('disconnecting');
|
|
219
275
|
this.tagStoreSubscription?.unsubscribe();
|
|
276
|
+
|
|
277
|
+
// Store reference to current socket before cleanup
|
|
278
|
+
const socketToClose = this.currentSocket;
|
|
279
|
+
this.currentSocket = null;
|
|
280
|
+
|
|
220
281
|
if (this.socketConnection) {
|
|
221
282
|
await this.socketConnection.disconnect();
|
|
222
283
|
this.socketConnection = undefined;
|
|
223
284
|
logger.log('ok', 'disconnected socket!');
|
|
224
|
-
} else {
|
|
285
|
+
} else if (!socketToClose) {
|
|
225
286
|
this.disconnectRunning = false;
|
|
226
287
|
logger.log('warn', 'tried to disconnect, without a SocketConnection');
|
|
227
288
|
return;
|
|
@@ -232,28 +293,29 @@ export class SmartsocketClient {
|
|
|
232
293
|
|
|
233
294
|
if (this.autoReconnect && useAutoReconnectSetting && this.eventStatus !== 'connecting') {
|
|
234
295
|
this.updateStatus('connecting');
|
|
235
|
-
|
|
296
|
+
|
|
236
297
|
// Check if we've exceeded the maximum number of retries
|
|
237
298
|
if (this.currentRetryCount >= this.maxRetries) {
|
|
238
299
|
logger.log('warn', `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
|
|
239
300
|
this.disconnectRunning = false;
|
|
240
301
|
return;
|
|
241
302
|
}
|
|
242
|
-
|
|
303
|
+
|
|
243
304
|
// Increment retry counter
|
|
244
305
|
this.currentRetryCount++;
|
|
245
|
-
|
|
306
|
+
|
|
246
307
|
// Calculate backoff with jitter (±20% randomness)
|
|
247
308
|
const jitter = this.currentBackoffDelay * 0.2 * (Math.random() * 2 - 1);
|
|
248
309
|
const delay = Math.min(this.currentBackoffDelay + jitter, this.maxBackoffDelay);
|
|
249
|
-
|
|
310
|
+
|
|
250
311
|
logger.log('info', `Reconnect attempt ${this.currentRetryCount}/${this.maxRetries} in ${Math.round(delay)}ms`);
|
|
251
|
-
|
|
312
|
+
|
|
252
313
|
// Apply exponential backoff for next time (doubling with each attempt)
|
|
253
314
|
this.currentBackoffDelay = Math.min(this.currentBackoffDelay * 2, this.maxBackoffDelay);
|
|
254
|
-
|
|
315
|
+
|
|
255
316
|
await plugins.smartdelay.delayFor(delay);
|
|
256
317
|
this.disconnectRunning = false;
|
|
318
|
+
this.isReconnecting = true;
|
|
257
319
|
await this.connect();
|
|
258
320
|
} else {
|
|
259
321
|
this.disconnectRunning = false;
|
|
@@ -279,7 +341,6 @@ export class SmartsocketClient {
|
|
|
279
341
|
functionNameArg: T['method'],
|
|
280
342
|
dataArg: T['request']
|
|
281
343
|
): Promise<T['response']> {
|
|
282
|
-
const done = plugins.smartpromise.defer();
|
|
283
344
|
const socketRequest = new SocketRequest<T>(this, {
|
|
284
345
|
side: 'requesting',
|
|
285
346
|
originSocketConnection: this.socketConnection,
|
|
@@ -299,14 +360,14 @@ export class SmartsocketClient {
|
|
|
299
360
|
this.eventSubject.next(statusArg);
|
|
300
361
|
}
|
|
301
362
|
this.eventStatus = statusArg;
|
|
302
|
-
|
|
363
|
+
|
|
303
364
|
// Reset reconnection state when connection is successful
|
|
304
365
|
if (statusArg === 'connected') {
|
|
305
366
|
this.currentRetryCount = 0;
|
|
306
367
|
this.currentBackoffDelay = this.initialBackoffDelay;
|
|
307
368
|
}
|
|
308
369
|
}
|
|
309
|
-
|
|
370
|
+
|
|
310
371
|
/**
|
|
311
372
|
* Resets the reconnection state
|
|
312
373
|
*/
|
|
@@ -26,7 +26,7 @@ export interface ISocketConnectionConstructorOptions {
|
|
|
26
26
|
authenticated: boolean;
|
|
27
27
|
side: TSocketConnectionSide;
|
|
28
28
|
smartsocketHost: Smartsocket | SmartsocketClient;
|
|
29
|
-
socket: pluginsTyped.
|
|
29
|
+
socket: pluginsTyped.TWebSocket | pluginsTyped.IWebSocketLike;
|
|
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.
|
|
50
|
+
public socket: pluginsTyped.TWebSocket | pluginsTyped.IWebSocketLike;
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.socket.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 as interfaces.ISocketMessage<interfaces.IFunctionCallPayload>);
|
|
86
|
+
break;
|
|
87
|
+
case 'functionResponse':
|
|
88
|
+
this.handleFunctionResponse(messageData as interfaces.ISocketMessage<interfaces.IFunctionCallPayload>);
|
|
89
|
+
break;
|
|
90
|
+
case 'tagUpdate':
|
|
91
|
+
this.handleTagUpdate(messageData as interfaces.ISocketMessage<interfaces.ITagUpdatePayload>);
|
|
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<interfaces.IFunctionCallPayload>): 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<interfaces.IFunctionCallPayload>): 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<interfaces.ITagUpdatePayload>): 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,54 +173,88 @@ export class SocketConnection {
|
|
|
99
173
|
done.resolve();
|
|
100
174
|
}
|
|
101
175
|
});
|
|
102
|
-
this.
|
|
176
|
+
this.sendMessage({
|
|
177
|
+
type: 'tagUpdate',
|
|
178
|
+
payload: { tags: this.tagStore },
|
|
179
|
+
});
|
|
103
180
|
await done.promise;
|
|
104
181
|
}
|
|
105
182
|
|
|
106
183
|
/**
|
|
107
|
-
*
|
|
108
|
-
* @param tagIdArg
|
|
184
|
+
* Gets a tag by id
|
|
109
185
|
*/
|
|
110
|
-
public
|
|
186
|
+
public getTagById(tagIdArg: interfaces.ITag['id']): interfaces.ITag | undefined {
|
|
111
187
|
return this.tagStore[tagIdArg];
|
|
112
188
|
}
|
|
113
189
|
|
|
114
190
|
/**
|
|
115
|
-
*
|
|
191
|
+
* Removes a tag from a connection
|
|
116
192
|
*/
|
|
117
|
-
public
|
|
193
|
+
public removeTagById(tagIdArg: interfaces.ITag['id']): void {
|
|
118
194
|
delete this.tagStore[tagIdArg];
|
|
119
195
|
this.tagStoreObservable.next(this.tagStore);
|
|
120
|
-
this.
|
|
196
|
+
this.sendMessage({
|
|
197
|
+
type: 'tagUpdate',
|
|
198
|
+
payload: { tags: this.tagStore },
|
|
199
|
+
});
|
|
121
200
|
}
|
|
122
201
|
|
|
123
202
|
// authenticating --------------------------
|
|
124
203
|
|
|
125
204
|
/**
|
|
126
|
-
* authenticate the socket
|
|
205
|
+
* authenticate the socket (server side)
|
|
127
206
|
*/
|
|
128
|
-
public authenticate() {
|
|
129
|
-
const done = plugins.smartpromise.defer();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
207
|
+
public authenticate(): Promise<SocketConnection> {
|
|
208
|
+
const done = plugins.smartpromise.defer<SocketConnection>();
|
|
209
|
+
|
|
210
|
+
// Set up message handler for authentication
|
|
211
|
+
const messageHandler = (event: pluginsTyped.TMessageEvent) => {
|
|
212
|
+
try {
|
|
213
|
+
const data = typeof event.data === 'string' ? event.data : event.data.toString();
|
|
214
|
+
const message: interfaces.ISocketMessage = JSON.parse(data);
|
|
215
|
+
|
|
216
|
+
if (message.type === 'auth') {
|
|
217
|
+
const authData = message.payload as interfaces.IAuthPayload;
|
|
218
|
+
logger.log('info', 'received authentication data...');
|
|
219
|
+
|
|
220
|
+
if (authData.alias) {
|
|
221
|
+
this.alias = authData.alias;
|
|
222
|
+
this.authenticated = true;
|
|
223
|
+
|
|
224
|
+
// Send authentication response
|
|
225
|
+
this.sendMessage({
|
|
226
|
+
type: 'authResponse',
|
|
227
|
+
payload: { success: true },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
logger.log('ok', `socket with >>alias ${this.alias} is authenticated!`);
|
|
231
|
+
done.resolve(this);
|
|
232
|
+
} else {
|
|
233
|
+
this.authenticated = false;
|
|
234
|
+
this.sendMessage({
|
|
235
|
+
type: 'authResponse',
|
|
236
|
+
payload: { success: false, error: 'No alias provided' },
|
|
237
|
+
});
|
|
238
|
+
this.disconnect();
|
|
239
|
+
done.reject('a socket tried to connect, but could not authenticate.');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
logger.log('warn', `Failed to parse auth message: ${err instanceof Error ? err.message : String(err)}`);
|
|
144
244
|
}
|
|
145
|
-
});
|
|
146
|
-
const requestAuthPayload: interfaces.IRequestAuthPayload = {
|
|
147
|
-
serverAlias: this.smartsocketRef.alias,
|
|
148
245
|
};
|
|
149
|
-
|
|
246
|
+
|
|
247
|
+
(this.socket as pluginsTyped.IWebSocketLike).addEventListener('message', messageHandler);
|
|
248
|
+
|
|
249
|
+
// Request authentication
|
|
250
|
+
const requestAuthPayload: interfaces.TAuthRequestMessage = {
|
|
251
|
+
type: 'authRequest',
|
|
252
|
+
payload: {
|
|
253
|
+
serverAlias: (this.smartsocketRef as Smartsocket).alias,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
this.sendMessage(requestAuthPayload);
|
|
257
|
+
|
|
150
258
|
return done.promise;
|
|
151
259
|
}
|
|
152
260
|
|
|
@@ -158,43 +266,18 @@ export class SocketConnection {
|
|
|
158
266
|
public listenToFunctionRequests() {
|
|
159
267
|
const done = plugins.smartpromise.defer();
|
|
160
268
|
if (this.authenticated) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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');
|
|
269
|
+
// Set up message handler for all messages
|
|
270
|
+
const messageHandler = (event: pluginsTyped.TMessageEvent) => {
|
|
271
|
+
try {
|
|
272
|
+
const data = typeof event.data === 'string' ? event.data : event.data.toString();
|
|
273
|
+
const message: interfaces.ISocketMessage = JSON.parse(data);
|
|
274
|
+
this.handleMessage(message);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
logger.log('warn', `Failed to parse socket message: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
277
|
}
|
|
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
|
-
});
|
|
278
|
+
};
|
|
189
279
|
|
|
190
|
-
this.socket.
|
|
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
|
-
});
|
|
280
|
+
(this.socket as pluginsTyped.IWebSocketLike).addEventListener('message', messageHandler);
|
|
198
281
|
|
|
199
282
|
logger.log(
|
|
200
283
|
'info',
|
|
@@ -211,7 +294,10 @@ export class SocketConnection {
|
|
|
211
294
|
|
|
212
295
|
// disconnecting ----------------------
|
|
213
296
|
public async disconnect() {
|
|
214
|
-
this.socket.
|
|
297
|
+
if (this.socket.readyState === 1 || this.socket.readyState === 0) {
|
|
298
|
+
this.socket.close();
|
|
299
|
+
}
|
|
300
|
+
allSocketConnections.remove(this);
|
|
215
301
|
this.updateStatus('disconnected');
|
|
216
302
|
}
|
|
217
303
|
|