@soundworks/core 4.0.0-alpha.0 → 4.0.0-alpha.10

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.
@@ -18,7 +18,6 @@ import BaseSharedState from '../common/BaseSharedState.js';
18
18
  * @callback server.SharedState~deleteOnUpdateCallback
19
19
  */
20
20
 
21
-
22
21
  /**
23
22
  * The `SharedState` is one of the most important and versatile abstraction provided
24
23
  * by `soundworks`. It represents a set of parameters that are synchronized accross
@@ -0,0 +1,36 @@
1
+ import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';
2
+
3
+ // this file exists only for documentation purposesSharedStateCollection.js
4
+
5
+ /**
6
+ * @callback server.SharedStateCollection~onUpdateCallback
7
+ * @param {server.SharedState} state - State that triggered the update.
8
+ * @param {Object} newValues - Key / value pairs of the updates that have been
9
+ * applied to the state.
10
+ * @param {Object} oldValues - Key / value pairs of the updated params before
11
+ * the updates has been applied to the state.
12
+ * @param {Mixed} [context=null] - Optionnal context object that has been passed
13
+ * with the values updates in the `set` call.
14
+ */
15
+
16
+ /**
17
+ * The `SharedStateCollection` interface represent a collection of all states
18
+ * created from a given schema name on the network, at the execption of the ones
19
+ * created by the current node.
20
+ *
21
+ * ```
22
+ * const collection = await server.stateManager.getCollection('my-schema');
23
+ * const allValues = collection.getValues();
24
+ * collection.onUpdate((state, newValues, oldValues, context) => {
25
+ * // do something
26
+ * });
27
+ * ```
28
+ * @memberof server
29
+ * @extends BaseSharedStateCollection
30
+ * @inheritdoc
31
+ * @hideconstructor
32
+ */
33
+ class SharedStateCollection extends BaseSharedStateCollection {}
34
+
35
+ export default SharedStateCollection;
36
+
@@ -6,66 +6,80 @@ import {
6
6
  unpackStringMessage,
7
7
  } from '../common/sockets-utils.js';
8
8
 
9
- // const CONNECTING = 0;
10
- // const OPEN = 1;
11
- // const CLOSING = 2;
12
- // const CLOSED = 3;
13
- // const READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
9
+ // Status codes:
10
+ //
11
+ // CONNECTING = 0;
12
+ // OPEN = 1;
13
+ // CLOSING = 2;
14
+ // CLOSED = 3;
15
+ // READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
14
16
 
15
17
  /**
16
- * Simple wrapper with simple pubsub system built on top of `ws` sockets.
17
- * The abstraction contains two different socket:
18
- * - one configured for string (JSON compatible) messages
19
- * - one configured with `binaryType=arraybuffer` for streaming data more
20
- * efficiently.
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
21
  *
22
- * @see https://github.com/websockets/ws
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.
23
30
  *
24
31
  * @memberof server
32
+ * @hideconstructor
25
33
  */
26
34
  class Socket {
27
35
  constructor(ws, binaryWs, rooms, sockets, options = {}) {
28
-
29
36
  /**
30
- * Reference to the sockets object, is mainly dedicated to allow
31
- * broadcasting from a given socket instance.
32
- * @type {server.Sockets}
33
- * @example
34
- * socket.sockets.broadcast('my-room', this, 'update-value', 1);
37
+ * Configuration object
38
+ *
39
+ * @type {object}
35
40
  */
36
- this.sockets = sockets;
41
+ this.config = {
42
+ pingInterval: 5 * 1000,
43
+ ...options,
44
+ };
37
45
 
38
46
  /**
39
47
  * `ws` socket instance configured with `binaryType=blob` (string)
48
+ *
49
+ * @type {object}
40
50
  * @private
41
- * @type {Object}
42
51
  */
43
52
  this.ws = ws;
44
53
 
45
54
  /**
46
55
  * `ws` socket instance configured with `binaryType=arraybuffer` (TypedArray)
56
+ *
57
+ * @type {object}
47
58
  * @private
48
- * @type {Object}
49
59
  */
50
60
  this.binaryWs = binaryWs;
51
61
 
52
62
  /**
53
- * `ws` socket instance configured with `binaryType=arraybuffer` (TypedArray)
54
- * @private
55
- * @type {Map}
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);
56
69
  */
57
- this.rooms = rooms;
70
+ this.sockets = sockets;
58
71
 
59
72
  /**
60
- * Configuration object
61
- * @type {Object}
73
+ * Reference to the rooms object
74
+ *
75
+ * @type {Map}
76
+ * @private
62
77
  */
63
- this.config = {
64
- pingInterval: 5 * 1000,
65
- ...options,
66
- };
78
+ this.rooms = rooms;
67
79
 
80
+ /** @private */
68
81
  this._stringListeners = new Map();
82
+ /** @private */
69
83
  this._binaryListeners = new Map();
70
84
 
71
85
  // ----------------------------------------------------------
@@ -150,9 +164,11 @@ class Socket {
150
164
  }
151
165
 
152
166
  /**
153
- * Called when the string socket closes (aka client reload).
167
+ * Removes all listeners and immediately close the two sockets. Is automatically
168
+ * called on `server.stop()`
169
+ *
170
+ * @private
154
171
  */
155
- /** @private */
156
172
  terminate() {
157
173
  // clear ping/pong check
158
174
  clearInterval(this._intervalId);
@@ -190,7 +206,12 @@ class Socket {
190
206
  this.ws.terminate();
191
207
  }
192
208
 
193
- /** @private */
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
+ */
194
215
  _emit(binary, channel, ...args) {
195
216
  const listeners = binary ? this._binaryListeners : this._stringListeners;
196
217
 
@@ -200,7 +221,12 @@ class Socket {
200
221
  }
201
222
  }
202
223
 
203
- /** @private */
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
+ */
204
230
  _addListener(listeners, channel, callback) {
205
231
  if (!listeners.has(channel)) {
206
232
  listeners.set(channel, new Set());
@@ -210,7 +236,12 @@ class Socket {
210
236
  callbacks.add(callback);
211
237
  }
212
238
 
213
- /** @private */
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
+ */
214
245
  _removeListener(listeners, channel, callback) {
215
246
  if (listeners.has(channel)) {
216
247
  const callbacks = listeners.get(channel);
@@ -222,16 +253,24 @@ class Socket {
222
253
  }
223
254
  }
224
255
 
225
- /** @private */
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
+ */
226
262
  _removeAllListeners(listeners, channel) {
227
- if (listeners.has(channel)) {
263
+ if (channel === null) {
264
+ listeners.clear();
265
+ } else if (listeners.has(channel)) {
228
266
  listeners.delete(channel);
229
267
  }
230
268
  }
231
269
 
232
270
  /**
233
271
  * Add the socket to a room
234
- * @param {String} roomId - Id of the room
272
+ *
273
+ * @param {string} roomId - Id of the room.
235
274
  */
236
275
  addToRoom(roomId) {
237
276
  if (!this.rooms.has(roomId)) {
@@ -244,7 +283,8 @@ class Socket {
244
283
 
245
284
  /**
246
285
  * Remove the socket from a room
247
- * @param {String} roomId - Id of the room
286
+ *
287
+ * @param {string} roomId - Id of the room.
248
288
  */
249
289
  removeFromRoom(roomId) {
250
290
  if (this.rooms.has(roomId)) {
@@ -254,10 +294,11 @@ class Socket {
254
294
  }
255
295
 
256
296
  /**
257
- * Sends JSON compatible messages on a given channel
297
+ * Send messages with JSON compatible data types on a given channel.
258
298
  *
259
- * @param {String} channel - Channel of the message
260
- * @param {...*} args - Arguments of the message (as many as needed, of any type)
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).
261
302
  */
262
303
  send(channel, ...args) {
263
304
  const msg = packStringMessage(channel, ...args);
@@ -276,39 +317,41 @@ class Socket {
276
317
  }
277
318
 
278
319
  /**
279
- * Listen JSON compatible messages on a given channel
320
+ * Listen messages with JSON compatible data types on a given channel.
280
321
  *
281
- * @param {String} channel - Channel of the message
282
- * @param {Function} callback - Callback to execute when a message is received
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.
283
326
  */
284
327
  addListener(channel, callback) {
285
328
  this._addListener(this._stringListeners, channel, callback);
286
329
  }
287
330
 
288
331
  /**
289
- * Remove a listener from JSON compatible messages on a given channel
332
+ * Remove a listener of messages with JSON compatible data types from a given channel.
290
333
  *
291
- * @param {String} channel - Channel of the message
292
- * @param {Function} callback - Callback to cancel
334
+ * @param {string} channel - Channel name.
335
+ * @param {Function} callback - Callback to remove.
293
336
  */
294
337
  removeListener(channel, callback) {
295
338
  this._removeListener(this._stringListeners, channel, callback);
296
339
  }
297
340
 
298
341
  /**
299
- * Remove all listeners from JSON compatible messages on a given channel
342
+ * Remove all listeners of messages with JSON compatible data types.
300
343
  *
301
- * @param {String} channel - Channel of the message
344
+ * @param {string} channel - Channel name.
302
345
  */
303
- removeAllListeners(channel) {
346
+ removeAllListeners(channel = null) {
304
347
  this._removeAllListeners(this._stringListeners, channel);
305
348
  }
306
349
 
307
350
  /**
308
- * Sends binary messages on a given channel
351
+ * Send binary messages on a given channel.
309
352
  *
310
- * @param {String} channel - Channel of the message
311
- * @param {TypedArray} typedArray - Data to send
353
+ * @param {string} channel - Channel name.
354
+ * @param {TypedArray} typedArray - Binary data to be sent.
312
355
  */
313
356
  sendBinary(channel, typedArray) {
314
357
  const msg = packBinaryMessage(channel, typedArray);
@@ -323,29 +366,29 @@ class Socket {
323
366
  /**
324
367
  * Listen binary messages on a given channel
325
368
  *
326
- * @param {String} channel - Channel of the message
327
- * @param {Function} callback - Callback to execute when a message is received
369
+ * @param {string} channel - Channel name.
370
+ * @param {Function} callback - Callback to execute when a message is received.
328
371
  */
329
372
  addBinaryListener(channel, callback) {
330
373
  this._addListener(this._binaryListeners, channel, callback);
331
374
  }
332
375
 
333
376
  /**
334
- * Remove a listener from binary compatible messages on a given channel
377
+ * Remove a listener of binary compatible messages from a given channel
335
378
  *
336
- * @param {String} channel - Channel of the message
337
- * @param {Function} callback - Callback to cancel
379
+ * @param {string} channel - Channel name.
380
+ * @param {Function} callback - Callback to remove.
338
381
  */
339
382
  removeBinaryListener(channel, callback) {
340
383
  this._removeListener(this._binaryListeners, channel, callback);
341
384
  }
342
385
 
343
386
  /**
344
- * Remove all listeners from binary compatible messages on a given channel
387
+ * Remove all listeners of binary compatible messages on a given channel
345
388
  *
346
- * @param {String} channel - Channel of the message
389
+ * @param {string} channel - Channel of the message.
347
390
  */
348
- removeAllBinaryListeners(channel) {
391
+ removeAllBinaryListeners(channel = null) {
349
392
  this._removeAllListeners(this._binaryListeners, channel);
350
393
  }
351
394
  }
@@ -2,16 +2,17 @@ import { Worker } from 'node:worker_threads';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
+ import querystring from 'querystring';
5
6
  import { WebSocketServer } from 'ws';
6
7
  import WebSocket from 'ws';
7
- import querystring from 'querystring';
8
8
 
9
9
  import Socket from './Socket.js';
10
10
 
11
11
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
12
12
 
13
13
  /**
14
- * Websocket server that creates and host all {@link server.Socket} instance.
14
+ * Manager all {@link server.Socket} instances.
15
+ *
15
16
  * Most of the time, you shouldn't have to use this class instance directely, but
16
17
  * it could be usefull in some situations, for broadcasting messages, creating rooms, etc.
17
18
  *
@@ -36,9 +37,9 @@ class Sockets {
36
37
  }
37
38
 
38
39
  /**
39
- * Initialize sockets, all sockets are added by default added to two rooms:
40
+ * Initialize sockets, all sockets are added to two rooms by default:
40
41
  * - to the room corresponding to the client `role`
41
- * - to the '*' that holds all connected sockets
42
+ * - to the '*' room that holds all connected sockets
42
43
  *
43
44
  * @private
44
45
  */
@@ -68,13 +69,13 @@ class Sockets {
68
69
 
69
70
  // init ws server
70
71
  this._wss = new WebSocketServer({
71
- server: server.httpServer,
72
+ noServer: true,
72
73
  path: `/${config.path}`, // @note - update according to existing config files (aka cosima-apps)
73
74
  });
74
75
 
75
76
  this._wss.on('connection', (ws, req) => {
76
77
  const queryString = querystring.decode(req.url.split('?')[1]);
77
- const { role, key } = queryString;
78
+ const { role, key, token } = queryString;
78
79
  const binary = !!(parseInt(queryString.binary));
79
80
 
80
81
  if (binary) {
@@ -95,12 +96,37 @@ class Sockets {
95
96
  socket.addToRoom('*');
96
97
  socket.addToRoom(role);
97
98
 
98
- onConnectionCallback(role, socket);
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
+ }
99
117
  }
118
+
119
+ this._wss.handleUpgrade(req, socket, head, (ws) => {
120
+ this._wss.emit('connection', ws, req);
121
+ });
100
122
  });
101
123
  }
102
124
 
103
- /** @protected */
125
+ /**
126
+ * Terminate all existing sockets
127
+ *
128
+ * @private
129
+ */
104
130
  terminate() {
105
131
  // terminate stat worker thread
106
132
  this._latencyStatsWorker.terminate();
@@ -146,7 +172,7 @@ class Sockets {
146
172
  * Add a socket to a room
147
173
  *
148
174
  * @param {server.Socket} socket - Socket to add to the room.
149
- * @param {String} roomId - Id of the room
175
+ * @param {String} roomId - Id of the room.
150
176
  */
151
177
  addToRoom(socket, roomId) {
152
178
  socket.addToRoom(roomId);
@@ -156,39 +182,39 @@ class Sockets {
156
182
  * Remove a socket from a room
157
183
  *
158
184
  * @param {server.Socket} socket - Socket to remove from the room.
159
- * @param {String} [roomId=null] - Id of the room
185
+ * @param {String} roomId - Id of the room.
160
186
  */
161
187
  removeFromRoom(socket, roomId) {
162
188
  socket.removeFromRoom(roomId);
163
189
  }
164
190
 
165
191
  /**
166
- * Send a string message to all client of given room(s). If no room
167
- * not specified, the message is sent to all clients
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
+ *
168
195
  *
169
196
  * @param {String|Array} roomsIds - Ids of the rooms that must receive
170
- * the message. If null the message is sent to all clients
171
- * @param {server.Socket} excludeSocket - Optionnal
172
- * socket to ignore when broadcasting the message, typically the client
173
- * at the origin of the message
174
- * @param {String} channel - Channel of the message
175
- * @param {...*} args - Arguments of the message (as many as needed, of any type)
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).
176
203
  */
177
204
  broadcast(roomIds, excludeSocket, channel, ...args) {
178
205
  this._broadcast(false, roomIds, excludeSocket, channel, ...args);
179
206
  }
180
207
 
181
208
  /**
182
- * Send a binary message (TypedArray) to all client of given room(s). If no room
183
- * not specified, the message is sent to all clients
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.
184
211
  *
185
212
  * @param {String|Array} roomsIds - Ids of the rooms that must receive
186
- * the message. If null the message is sent to all clients
187
- * @param {server.Socket} excludeSocket - Optionnal
188
- * socket to ignore when broadcasting the message, typically the client
189
- * at the origin of the message
190
- * @param {String} channel - Channel of the message
191
- * @param {...*} args - Arguments of the message (as many as needed, of any type)
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.
192
218
  */
193
219
  broadcastBinary(roomIds, excludeSocket, channel, typedArray) {
194
220
  this._broadcast(true, roomIds, excludeSocket, channel, typedArray);