@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.
- package/dist_bundle/bundle.js +2476 -4455
- 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 +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 +3 -0
- package/dist_ts/smartsocket.classes.smartsocketclient.js +114 -75
- 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 -13
- package/dist_ts/smartsocket.classes.socketserver.js +152 -45
- package/dist_ts/smartsocket.pluginstyped.d.ts +32 -11
- package/package.json +24 -25
- package/readme.md +216 -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 +142 -90
- 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,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,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
|
-
//
|
|
105
|
-
this.
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
//
|
|
147
|
-
|
|
160
|
+
// Handle connection open
|
|
161
|
+
socket.addEventListener('open', () => {
|
|
148
162
|
timer.reset();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
});
|
|
224
|
+
} catch (err) {
|
|
225
|
+
// Not a valid JSON message, ignore
|
|
226
|
+
}
|
|
189
227
|
});
|
|
190
228
|
|
|
191
|
-
//
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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:
|
|
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
|
|