@soundworks/core 4.0.0-alpha.7 → 5.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/CHANGELOG.md +102 -0
- package/README.md +16 -50
- package/package.json +26 -44
- package/src/client/Client.js +306 -256
- package/src/client/{Context.js → ClientContext.js} +63 -54
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
- package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
- package/src/client/ClientSocket.js +270 -0
- package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
- package/src/client/index.js +36 -39
- package/src/common/BasePlugin.js +63 -58
- package/src/common/BasePluginManager.js +191 -142
- package/src/common/BaseStateManager.js +636 -143
- package/src/common/BatchedTransport.js +73 -0
- package/src/common/ParameterBag.js +90 -93
- package/src/common/PromiseStore.js +54 -0
- package/src/common/SharedState.js +821 -0
- package/src/common/SharedStateCollection.js +492 -0
- package/src/common/constants.js +14 -2
- package/src/common/logger.js +64 -68
- package/src/common/shared-state-types.js +82 -0
- package/src/common/sockets-utils.js +0 -64
- package/src/common/version.js +2 -0
- package/src/server/Server.js +559 -821
- package/src/server/ServerClient.js +74 -0
- package/src/server/{Context.js → ServerContext.js} +96 -77
- package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
- package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
- package/src/server/ServerPluginManager.js +177 -0
- package/src/server/ServerSocket.js +256 -0
- package/src/server/ServerSockets.js +150 -0
- package/src/server/ServerStateManager.js +706 -0
- package/src/server/SharedStatePrivate.js +277 -0
- package/src/server/audit-network-latency.worker.js +19 -4
- package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
- package/src/server/create-http-server.js +127 -0
- package/src/server/crypto.js +8 -7
- package/src/server/index.js +60 -17
- package/types/client/Client.d.ts +95 -70
- package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/ClientPlugin.d.ts +51 -0
- package/types/client/ClientPluginManager.d.ts +112 -0
- package/types/client/ClientSocket.d.ts +76 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
- package/types/client/index.d.ts +33 -6
- package/types/common/BasePlugin.d.ts +64 -53
- package/types/common/BasePluginManager.d.ts +84 -39
- package/types/common/BaseStateManager.d.ts +227 -49
- package/types/common/BatchedTransport.d.ts +17 -0
- package/types/common/ParameterBag.d.ts +73 -76
- package/types/common/PromiseStore.d.ts +11 -0
- package/types/common/SharedState.d.ts +291 -0
- package/types/common/SharedStateCollection.d.ts +311 -0
- package/types/common/constants.d.ts +10 -3
- package/types/common/logger.d.ts +8 -6
- package/types/common/shared-state-types.d.ts +95 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +2 -0
- package/types/server/Server.d.ts +215 -136
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/ServerContext.d.ts +209 -0
- package/types/server/ServerContextManager.d.ts +75 -0
- package/types/server/ServerPlugin.d.ts +57 -0
- package/types/server/ServerPluginManager.d.ts +115 -0
- package/types/server/ServerSocket.d.ts +69 -0
- package/types/server/ServerSockets.d.ts +42 -0
- package/types/server/ServerStateManager.d.ts +284 -0
- package/types/server/SharedStatePrivate.d.ts +23 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -2
- package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
- package/types/server/create-http-server.d.ts +5 -0
- package/types/server/crypto.d.ts +4 -0
- package/types/server/index.d.ts +51 -3
- package/.eslintrc +0 -15
- package/.github/workflows/gh-pages.yml +0 -31
- package/.github/workflows/unit-tests.yml +0 -18
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -35
- package/src/client/Socket.js +0 -402
- package/src/common/BaseSharedState.js +0 -517
- package/src/common/BaseSharedStateCollection.js +0 -179
- package/src/common/SharedStatePrivate.js +0 -192
- package/src/common/promise-store.js +0 -25
- package/src/server/Client.js +0 -55
- package/src/server/PluginManager.js +0 -170
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -36
- package/src/server/Socket.js +0 -396
- package/src/server/Sockets.js +0 -224
- package/src/server/StateManager.js +0 -620
- package/tsconfig.json +0 -21
- package/types/client/Client.d.ts.map +0 -1
- package/types/client/Context.d.ts.map +0 -1
- package/types/client/ContextManager.d.ts.map +0 -1
- package/types/client/Plugin.d.ts +0 -53
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts +0 -96
- package/types/client/PluginManager.d.ts.map +0 -1
- package/types/client/SharedState.d.ts +0 -85
- package/types/client/SharedState.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -138
- package/types/client/Socket.d.ts.map +0 -1
- package/types/client/StateManager.d.ts.map +0 -1
- package/types/client/index.d.ts.map +0 -1
- package/types/common/BasePlugin.d.ts.map +0 -1
- package/types/common/BasePluginManager.d.ts.map +0 -1
- package/types/common/BaseSharedState.d.ts +0 -173
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -20
- package/types/common/SharedStatePrivate.d.ts.map +0 -1
- package/types/common/constants.d.ts.map +0 -1
- package/types/common/logger.d.ts.map +0 -1
- package/types/common/promise-store.d.ts +0 -4
- package/types/common/promise-store.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/utils.d.ts +0 -5
- package/types/common/utils.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -40
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts +0 -59
- package/types/server/Context.d.ts.map +0 -1
- package/types/server/ContextManager.d.ts +0 -69
- package/types/server/ContextManager.d.ts.map +0 -1
- package/types/server/Plugin.d.ts +0 -70
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts +0 -91
- package/types/server/PluginManager.d.ts.map +0 -1
- package/types/server/Server.d.ts.map +0 -1
- package/types/server/SharedState.d.ts +0 -86
- package/types/server/SharedState.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -128
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -73
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -190
- package/types/server/StateManager.d.ts.map +0 -1
- package/types/server/audit-network-latency.worker.d.ts.map +0 -1
- package/types/server/audit-schema.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
package/src/server/Socket.js
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
import { getTime } from '@ircam/sc-gettime';
|
|
2
|
-
import {
|
|
3
|
-
packBinaryMessage,
|
|
4
|
-
unpackBinaryMessage,
|
|
5
|
-
packStringMessage,
|
|
6
|
-
unpackStringMessage,
|
|
7
|
-
} from '../common/sockets-utils.js';
|
|
8
|
-
|
|
9
|
-
// Status codes:
|
|
10
|
-
//
|
|
11
|
-
// CONNECTING = 0;
|
|
12
|
-
// OPEN = 1;
|
|
13
|
-
// CLOSING = 2;
|
|
14
|
-
// CLOSED = 3;
|
|
15
|
-
// READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The Socket class is a simple publish / subscribe wrapper built on top of the
|
|
19
|
-
* [ws](https://github.com/websockets/ws) library. An instance of {@link server.Socket}
|
|
20
|
-
* is automatically created per client when it connects (see {@link server.Client#socket}).
|
|
21
|
-
*
|
|
22
|
-
* _Important: In most cases, you should consider using a {@link client.SharedState}
|
|
23
|
-
* rather than directly using the sockets._
|
|
24
|
-
*
|
|
25
|
-
* The Socket class contains two different WebSockets:
|
|
26
|
-
* - a socket configured with `binaryType = 'blob'` for JSON compatible data
|
|
27
|
-
* types (i.e. string, number, boolean, object, array and null).
|
|
28
|
-
* - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming
|
|
29
|
-
* of binary data.
|
|
30
|
-
*
|
|
31
|
-
* @memberof server
|
|
32
|
-
* @hideconstructor
|
|
33
|
-
*/
|
|
34
|
-
class Socket {
|
|
35
|
-
constructor(ws, binaryWs, rooms, sockets, options = {}) {
|
|
36
|
-
/**
|
|
37
|
-
* Configuration object
|
|
38
|
-
*
|
|
39
|
-
* @type {object}
|
|
40
|
-
*/
|
|
41
|
-
this.config = {
|
|
42
|
-
pingInterval: 5 * 1000,
|
|
43
|
-
...options,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* `ws` socket instance configured with `binaryType=blob` (string)
|
|
48
|
-
*
|
|
49
|
-
* @type {object}
|
|
50
|
-
* @private
|
|
51
|
-
*/
|
|
52
|
-
this.ws = ws;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* `ws` socket instance configured with `binaryType=arraybuffer` (TypedArray)
|
|
56
|
-
*
|
|
57
|
-
* @type {object}
|
|
58
|
-
* @private
|
|
59
|
-
*/
|
|
60
|
-
this.binaryWs = binaryWs;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Reference to the sockets object, is mainly dedicated to allow
|
|
64
|
-
* broadcasting from a given socket instance.
|
|
65
|
-
*
|
|
66
|
-
* @type {server.Sockets}
|
|
67
|
-
* @example
|
|
68
|
-
* socket.sockets.broadcast('my-room', this, 'update-value', 1);
|
|
69
|
-
*/
|
|
70
|
-
this.sockets = sockets;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Reference to the rooms object
|
|
74
|
-
*
|
|
75
|
-
* @type {Map}
|
|
76
|
-
* @private
|
|
77
|
-
*/
|
|
78
|
-
this.rooms = rooms;
|
|
79
|
-
|
|
80
|
-
/** @private */
|
|
81
|
-
this._stringListeners = new Map();
|
|
82
|
-
/** @private */
|
|
83
|
-
this._binaryListeners = new Map();
|
|
84
|
-
|
|
85
|
-
// ----------------------------------------------------------
|
|
86
|
-
// init string socket
|
|
87
|
-
// ----------------------------------------------------------
|
|
88
|
-
this.ws.addEventListener('message', e => {
|
|
89
|
-
const [channel, args] = unpackStringMessage(e.data);
|
|
90
|
-
this._emit(false, channel, ...args);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// broadcast all `ws` "native" events
|
|
94
|
-
[
|
|
95
|
-
'close',
|
|
96
|
-
'error',
|
|
97
|
-
'message',
|
|
98
|
-
'open',
|
|
99
|
-
'ping',
|
|
100
|
-
'pong',
|
|
101
|
-
'unexpected-response',
|
|
102
|
-
'upgrade',
|
|
103
|
-
].forEach(eventName => {
|
|
104
|
-
this.ws.addEventListener(eventName, e => {
|
|
105
|
-
this._emit(false, eventName, e.data);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// ----------------------------------------------------------
|
|
110
|
-
// init binary socket
|
|
111
|
-
// ----------------------------------------------------------
|
|
112
|
-
this.binaryWs.addEventListener('message', e => {
|
|
113
|
-
const [channel, data] = unpackBinaryMessage(e.data);
|
|
114
|
-
this._emit(true, channel, data);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// broadcast all `ws` "native" events
|
|
118
|
-
[
|
|
119
|
-
'close',
|
|
120
|
-
'error',
|
|
121
|
-
'message',
|
|
122
|
-
'open',
|
|
123
|
-
'ping',
|
|
124
|
-
'pong',
|
|
125
|
-
'unexpected-response',
|
|
126
|
-
'upgrade',
|
|
127
|
-
].forEach(eventName => {
|
|
128
|
-
this.binaryWs.addEventListener(eventName, e => {
|
|
129
|
-
this._emit(true, eventName, e.data);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// heartbeat system (run only on string socket), adapted from:
|
|
134
|
-
// https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
|
|
135
|
-
this._isAlive = true;
|
|
136
|
-
let msg = {
|
|
137
|
-
type: 'add-measurement',
|
|
138
|
-
value: {
|
|
139
|
-
ping: 0,
|
|
140
|
-
pong: 0,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// heartbeat system, only on "regular" socket
|
|
145
|
-
this.ws.on('pong', () => {
|
|
146
|
-
this._isAlive = true;
|
|
147
|
-
|
|
148
|
-
msg.value.pong = getTime();
|
|
149
|
-
this.sockets._latencyStatsWorker.postMessage(msg);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
this._intervalId = setInterval(() => {
|
|
153
|
-
if (this._isAlive === false) {
|
|
154
|
-
// emit a 'close' event to go trough all the disconnection pipeline
|
|
155
|
-
this._emit(false, 'close');
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
this._isAlive = false;
|
|
160
|
-
msg.value.ping = getTime();
|
|
161
|
-
|
|
162
|
-
this.ws.ping();
|
|
163
|
-
}, this.config.pingInterval);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Removes all listeners and immediately close the two sockets. Is automatically
|
|
168
|
-
* called on `server.stop()`
|
|
169
|
-
*
|
|
170
|
-
* @private
|
|
171
|
-
*/
|
|
172
|
-
terminate() {
|
|
173
|
-
// clear ping/pong check
|
|
174
|
-
clearInterval(this._intervalId);
|
|
175
|
-
// clean rooms
|
|
176
|
-
for (let [_key, room] of this.rooms) {
|
|
177
|
-
room.delete(this);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// clear references to sockets and rooms
|
|
181
|
-
this.sockets = null;
|
|
182
|
-
this.rooms = null;
|
|
183
|
-
|
|
184
|
-
// clear all listeners
|
|
185
|
-
this._stringListeners.clear();
|
|
186
|
-
this._binaryListeners.clear();
|
|
187
|
-
|
|
188
|
-
// clear "native" listeners
|
|
189
|
-
[this.binaryWs, this.ws].forEach((socket) => {
|
|
190
|
-
[
|
|
191
|
-
'close',
|
|
192
|
-
'error',
|
|
193
|
-
'message',
|
|
194
|
-
'open',
|
|
195
|
-
'ping',
|
|
196
|
-
'pong',
|
|
197
|
-
'unexpected-response',
|
|
198
|
-
'upgrade',
|
|
199
|
-
].forEach(eventName => {
|
|
200
|
-
socket.removeAllListeners(eventName);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// clear binarySocket as this is called from the string one.
|
|
205
|
-
this.binaryWs.terminate();
|
|
206
|
-
this.ws.terminate();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* @param {boolean} binary - Emit to either the string or binary socket.
|
|
211
|
-
* @param {string} channel - Channel name.
|
|
212
|
-
* @param {...*} args - Content of the message.
|
|
213
|
-
* @private
|
|
214
|
-
*/
|
|
215
|
-
_emit(binary, channel, ...args) {
|
|
216
|
-
const listeners = binary ? this._binaryListeners : this._stringListeners;
|
|
217
|
-
|
|
218
|
-
if (listeners.has(channel)) {
|
|
219
|
-
const callbacks = listeners.get(channel);
|
|
220
|
-
callbacks.forEach(callback => callback(...args));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
226
|
-
* @param {string} channel - Channel name.
|
|
227
|
-
* @param {Function} callback - The function to be added to the listeners.
|
|
228
|
-
* @private
|
|
229
|
-
*/
|
|
230
|
-
_addListener(listeners, channel, callback) {
|
|
231
|
-
if (!listeners.has(channel)) {
|
|
232
|
-
listeners.set(channel, new Set());
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const callbacks = listeners.get(channel);
|
|
236
|
-
callbacks.add(callback);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
241
|
-
* @param {string} channel - Channel name.
|
|
242
|
-
* @param {Function} callback - The function to be removed from the listeners.
|
|
243
|
-
* @private
|
|
244
|
-
*/
|
|
245
|
-
_removeListener(listeners, channel, callback) {
|
|
246
|
-
if (listeners.has(channel)) {
|
|
247
|
-
const callbacks = listeners.get(channel);
|
|
248
|
-
callbacks.delete(callback);
|
|
249
|
-
|
|
250
|
-
if (callbacks.size === 0) {
|
|
251
|
-
listeners.delete(channel);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
258
|
-
* @param {string} [channel=null] - Channel name of the listeners to remove. If null
|
|
259
|
-
* all the listeners are cleared.
|
|
260
|
-
* @private
|
|
261
|
-
*/
|
|
262
|
-
_removeAllListeners(listeners, channel) {
|
|
263
|
-
if (channel === null) {
|
|
264
|
-
listeners.clear();
|
|
265
|
-
} else if (listeners.has(channel)) {
|
|
266
|
-
listeners.delete(channel);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Add the socket to a room
|
|
272
|
-
*
|
|
273
|
-
* @param {string} roomId - Id of the room.
|
|
274
|
-
*/
|
|
275
|
-
addToRoom(roomId) {
|
|
276
|
-
if (!this.rooms.has(roomId)) {
|
|
277
|
-
this.rooms.set(roomId, new Set());
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const room = this.rooms.get(roomId);
|
|
281
|
-
room.add(this);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Remove the socket from a room
|
|
286
|
-
*
|
|
287
|
-
* @param {string} roomId - Id of the room.
|
|
288
|
-
*/
|
|
289
|
-
removeFromRoom(roomId) {
|
|
290
|
-
if (this.rooms.has(roomId)) {
|
|
291
|
-
const room = this.rooms.get(roomId);
|
|
292
|
-
room.delete(this);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Send messages with JSON compatible data types on a given channel.
|
|
298
|
-
*
|
|
299
|
-
* @param {string} channel - Channel name.
|
|
300
|
-
* @param {...*} args - Payload of the message. As many arguments as needed, of
|
|
301
|
-
* JSON compatible data types (i.e. string, number, boolean, object, array and null).
|
|
302
|
-
*/
|
|
303
|
-
send(channel, ...args) {
|
|
304
|
-
const msg = packStringMessage(channel, ...args);
|
|
305
|
-
|
|
306
|
-
// 0 CONNECTING Socket has been created. The connection is not yet open.
|
|
307
|
-
// 1 OPEN The connection is open and ready to communicate.
|
|
308
|
-
// 2 CLOSING The connection is in the process of closing.
|
|
309
|
-
// 3 CLOSED The connection is closed or couldn't be opened.
|
|
310
|
-
if (this.ws.readyState === 1) {
|
|
311
|
-
this.ws.send(msg, (err) => {
|
|
312
|
-
if (err) {
|
|
313
|
-
console.error('error sending msg:', channel, args, err);
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Listen messages with JSON compatible data types on a given channel.
|
|
321
|
-
*
|
|
322
|
-
* @param {string} channel - Channel name.
|
|
323
|
-
* @param {Function} callback - Callback to execute when a message is received,
|
|
324
|
-
* arguments of the callback function will match the arguments sent using the
|
|
325
|
-
* {@link server.Socket#send} method.
|
|
326
|
-
*/
|
|
327
|
-
addListener(channel, callback) {
|
|
328
|
-
this._addListener(this._stringListeners, channel, callback);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Remove a listener of messages with JSON compatible data types from a given channel.
|
|
333
|
-
*
|
|
334
|
-
* @param {string} channel - Channel name.
|
|
335
|
-
* @param {Function} callback - Callback to remove.
|
|
336
|
-
*/
|
|
337
|
-
removeListener(channel, callback) {
|
|
338
|
-
this._removeListener(this._stringListeners, channel, callback);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Remove all listeners of messages with JSON compatible data types.
|
|
343
|
-
*
|
|
344
|
-
* @param {string} channel - Channel name.
|
|
345
|
-
*/
|
|
346
|
-
removeAllListeners(channel = null) {
|
|
347
|
-
this._removeAllListeners(this._stringListeners, channel);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Send binary messages on a given channel.
|
|
352
|
-
*
|
|
353
|
-
* @param {string} channel - Channel name.
|
|
354
|
-
* @param {TypedArray} typedArray - Binary data to be sent.
|
|
355
|
-
*/
|
|
356
|
-
sendBinary(channel, typedArray) {
|
|
357
|
-
const msg = packBinaryMessage(channel, typedArray);
|
|
358
|
-
|
|
359
|
-
this.binaryWs.send(msg, (err) => {
|
|
360
|
-
if (err) {
|
|
361
|
-
console.error('error sending msg:', channel, typedArray);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Listen binary messages on a given channel
|
|
368
|
-
*
|
|
369
|
-
* @param {string} channel - Channel name.
|
|
370
|
-
* @param {Function} callback - Callback to execute when a message is received.
|
|
371
|
-
*/
|
|
372
|
-
addBinaryListener(channel, callback) {
|
|
373
|
-
this._addListener(this._binaryListeners, channel, callback);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Remove a listener of binary compatible messages from a given channel
|
|
378
|
-
*
|
|
379
|
-
* @param {string} channel - Channel name.
|
|
380
|
-
* @param {Function} callback - Callback to remove.
|
|
381
|
-
*/
|
|
382
|
-
removeBinaryListener(channel, callback) {
|
|
383
|
-
this._removeListener(this._binaryListeners, channel, callback);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Remove all listeners of binary compatible messages on a given channel
|
|
388
|
-
*
|
|
389
|
-
* @param {string} channel - Channel of the message.
|
|
390
|
-
*/
|
|
391
|
-
removeAllBinaryListeners(channel = null) {
|
|
392
|
-
this._removeAllListeners(this._binaryListeners, channel);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export default Socket;
|
package/src/server/Sockets.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { Worker } from 'node:worker_threads';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
import querystring from 'querystring';
|
|
6
|
-
import { WebSocketServer } from 'ws';
|
|
7
|
-
import WebSocket from 'ws';
|
|
8
|
-
|
|
9
|
-
import Socket from './Socket.js';
|
|
10
|
-
|
|
11
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Manager all {@link server.Socket} instances.
|
|
15
|
-
*
|
|
16
|
-
* Most of the time, you shouldn't have to use this class instance directely, but
|
|
17
|
-
* it could be usefull in some situations, for broadcasting messages, creating rooms, etc.
|
|
18
|
-
*
|
|
19
|
-
* @memberof server
|
|
20
|
-
*/
|
|
21
|
-
class Sockets {
|
|
22
|
-
constructor() {
|
|
23
|
-
/** @private */
|
|
24
|
-
this._wss = null;
|
|
25
|
-
/** @private */
|
|
26
|
-
this._rooms = new Map();
|
|
27
|
-
// The special `'*'` romm stores all current connections.
|
|
28
|
-
this._rooms.set('*', new Set());
|
|
29
|
-
/** @private */
|
|
30
|
-
this._initializationCache = new Map();
|
|
31
|
-
|
|
32
|
-
/** @private */
|
|
33
|
-
this._latencyStatsWorker = null; // protected - used in Socket instances
|
|
34
|
-
/** @private */
|
|
35
|
-
this._auditState = null;
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Initialize sockets, all sockets are added to two rooms by default:
|
|
41
|
-
* - to the room corresponding to the client `role`
|
|
42
|
-
* - to the '*' room that holds all connected sockets
|
|
43
|
-
*
|
|
44
|
-
* @private
|
|
45
|
-
*/
|
|
46
|
-
async start(server, config, onConnectionCallback) {
|
|
47
|
-
// init audit stuff for network latency estimation
|
|
48
|
-
const workerPathname = path.join(__dirname, 'audit-network-latency.worker.js');
|
|
49
|
-
this._latencyStatsWorker = new Worker(workerPathname);
|
|
50
|
-
this._auditState = await server.getAuditState();
|
|
51
|
-
|
|
52
|
-
this._auditState.onUpdate(updates => {
|
|
53
|
-
if ('averageNetworkLatencyWindow' in updates || 'averageNetworkLatencyPeriod' in updates) {
|
|
54
|
-
this._latencyStatsWorker.postMessage({
|
|
55
|
-
type: 'config',
|
|
56
|
-
value: {
|
|
57
|
-
averageLatencyWindow: updates.averageNetworkLatencyWindow,
|
|
58
|
-
averageLatencyPeriod: updates.averageNetworkLatencyPeriod,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}, true);
|
|
63
|
-
|
|
64
|
-
this._latencyStatsWorker.on('message', msg => {
|
|
65
|
-
if (msg.type === 'computed-average-latency') {
|
|
66
|
-
this._auditState.set({ averageNetworkLatency: msg.value });
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// init ws server
|
|
71
|
-
this._wss = new WebSocketServer({
|
|
72
|
-
noServer: true,
|
|
73
|
-
path: `/${config.path}`, // @note - update according to existing config files (aka cosima-apps)
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
this._wss.on('connection', (ws, req) => {
|
|
77
|
-
const queryString = querystring.decode(req.url.split('?')[1]);
|
|
78
|
-
const { role, key, token } = queryString;
|
|
79
|
-
const binary = !!(parseInt(queryString.binary));
|
|
80
|
-
|
|
81
|
-
if (binary) {
|
|
82
|
-
ws.binaryType = 'arraybuffer';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!this._initializationCache.has(key)) {
|
|
86
|
-
this._initializationCache.set(key, { ws, binary });
|
|
87
|
-
} else {
|
|
88
|
-
const cached = this._initializationCache.get(key);
|
|
89
|
-
this._initializationCache.delete(key);
|
|
90
|
-
|
|
91
|
-
// should be in order, but just to be sure
|
|
92
|
-
const stringWs = cached.binary ? ws : cached.ws;
|
|
93
|
-
const binaryWs = cached.binary ? cached.ws : ws;
|
|
94
|
-
const socket = new Socket(stringWs, binaryWs, this._rooms, this, config);
|
|
95
|
-
|
|
96
|
-
socket.addToRoom('*');
|
|
97
|
-
socket.addToRoom(role);
|
|
98
|
-
|
|
99
|
-
onConnectionCallback(role, socket, token);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// check if client can connect
|
|
104
|
-
server.httpServer.on('upgrade', async (req, socket, head) => {
|
|
105
|
-
const queryString = querystring.decode(req.url.split('?')[1]);
|
|
106
|
-
|
|
107
|
-
const { role, token } = queryString;
|
|
108
|
-
|
|
109
|
-
if (server.isProtected(role)) {
|
|
110
|
-
// we don't have any ip in the upgrade request, so we just check the
|
|
111
|
-
// connection token is pending
|
|
112
|
-
const allowed = server.isValidConnectionToken(token);
|
|
113
|
-
|
|
114
|
-
if (!allowed) {
|
|
115
|
-
socket.destroy('not allowed');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
this._wss.handleUpgrade(req, socket, head, (ws) => {
|
|
120
|
-
this._wss.emit('connection', ws, req);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Terminate all existing sockets
|
|
127
|
-
*
|
|
128
|
-
* @private
|
|
129
|
-
*/
|
|
130
|
-
terminate() {
|
|
131
|
-
// terminate stat worker thread
|
|
132
|
-
this._latencyStatsWorker.terminate();
|
|
133
|
-
// clean sockets
|
|
134
|
-
const sockets = this._rooms.get('*');
|
|
135
|
-
sockets.forEach(socket => socket.terminate());
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** @private */
|
|
139
|
-
_broadcast(binary, roomIds, excludeSocket, channel, ...args) {
|
|
140
|
-
const method = binary ? 'sendBinary' : 'send';
|
|
141
|
-
let targets = new Set();
|
|
142
|
-
|
|
143
|
-
if (typeof roomIds === 'string' || Array.isArray(roomIds)) {
|
|
144
|
-
if (typeof roomIds === 'string') {
|
|
145
|
-
roomIds = [roomIds];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
roomIds.forEach(roomId => {
|
|
149
|
-
if (this._rooms.has(roomId)) {
|
|
150
|
-
const room = this._rooms.get(roomId);
|
|
151
|
-
room.forEach(socket => targets.add(socket));
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
} else {
|
|
155
|
-
targets = this._rooms.get('*');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
targets.forEach(socket => {
|
|
159
|
-
if (socket.ws.readyState === WebSocket.OPEN) {
|
|
160
|
-
if (excludeSocket !== null) {
|
|
161
|
-
if (socket !== excludeSocket) {
|
|
162
|
-
socket[method](channel, ...args);
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
socket[method](channel, ...args);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Add a socket to a room
|
|
173
|
-
*
|
|
174
|
-
* @param {server.Socket} socket - Socket to add to the room.
|
|
175
|
-
* @param {String} roomId - Id of the room.
|
|
176
|
-
*/
|
|
177
|
-
addToRoom(socket, roomId) {
|
|
178
|
-
socket.addToRoom(roomId);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Remove a socket from a room
|
|
183
|
-
*
|
|
184
|
-
* @param {server.Socket} socket - Socket to remove from the room.
|
|
185
|
-
* @param {String} roomId - Id of the room.
|
|
186
|
-
*/
|
|
187
|
-
removeFromRoom(socket, roomId) {
|
|
188
|
-
socket.removeFromRoom(roomId);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Send a message of JSON compatible data types to all client of given room(s).
|
|
193
|
-
* If no room is specified, the message is sent to all clients.
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* @param {String|Array} roomsIds - Ids of the rooms that must receive
|
|
197
|
-
* the message. If `null` the message is sent to all clients.
|
|
198
|
-
* @param {server.Socket} excludeSocket - Optionnal socket to ignore when
|
|
199
|
-
* broadcasting the message, typically the client at the origin of the message.
|
|
200
|
-
* @param {String} channel - Channel name.
|
|
201
|
-
* @param {...*} args - Payload of the message. As many arguments as needed, of
|
|
202
|
-
* JSON compatible data types (i.e. string, number, boolean, object, array and null).
|
|
203
|
-
*/
|
|
204
|
-
broadcast(roomIds, excludeSocket, channel, ...args) {
|
|
205
|
-
this._broadcast(false, roomIds, excludeSocket, channel, ...args);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Send a binary message to all client of given room(s). If no room is specified
|
|
210
|
-
* specified, the message is sent to all clients.
|
|
211
|
-
*
|
|
212
|
-
* @param {String|Array} roomsIds - Ids of the rooms that must receive
|
|
213
|
-
* the message. If `null` the message is sent to all clients.
|
|
214
|
-
* @param {server.Socket} excludeSocket - Optionnal socket to ignore when
|
|
215
|
-
* broadcasting the message, typically the client at the origin of the message.
|
|
216
|
-
* @param {string} channel - Channel name.
|
|
217
|
-
* @param {TypedArray} typedArray - Binary data to be sent.
|
|
218
|
-
*/
|
|
219
|
-
broadcastBinary(roomIds, excludeSocket, channel, typedArray) {
|
|
220
|
-
this._broadcast(true, roomIds, excludeSocket, channel, typedArray);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export default Sockets;
|