@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.
- package/dist_bundle/bundle.js +11562 -7527
- package/dist_bundle/bundle.js.map +4 -4
- package/dist_ts/00_commitinfo_data.d.ts +1 -1
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/interfaces/index.d.ts +1 -0
- package/dist_ts/interfaces/index.js +2 -1
- package/dist_ts/interfaces/message.d.ts +55 -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 +15 -0
- package/dist_ts/smartsocket.classes.smartsocketclient.js +148 -74
- package/dist_ts/smartsocket.classes.socketconnection.d.ts +15 -4
- package/dist_ts/smartsocket.classes.socketconnection.js +139 -62
- package/dist_ts/smartsocket.classes.socketrequest.js +18 -12
- package/dist_ts/smartsocket.classes.socketserver.d.ts +15 -15
- package/dist_ts/smartsocket.classes.socketserver.js +152 -45
- package/dist_ts/smartsocket.pluginstyped.d.ts +31 -12
- package/package.json +26 -27
- package/readme.md +216 -74
- package/ts/00_commitinfo_data.ts +2 -2
- 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 +183 -80
- package/ts/smartsocket.classes.socketconnection.ts +161 -74
- package/ts/smartsocket.classes.socketrequest.ts +18 -11
- package/ts/smartsocket.classes.socketserver.ts +161 -52
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
//
|
|
131
|
-
|
|
160
|
+
// Handle connection open
|
|
161
|
+
socket.addEventListener('open', () => {
|
|
132
162
|
timer.reset();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
//
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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);
|
|
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.
|
|
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.
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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');
|
|
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.
|
|
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.
|
|
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
|
|