@soundworks/core 4.0.0-alpha.21 → 4.0.0-alpha.23

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +14 -3
  2. package/package.json +15 -15
  3. package/src/client/Client.js +233 -188
  4. package/src/client/Socket.js +84 -247
  5. package/src/client/index.js +6 -0
  6. package/src/common/constants.js +0 -1
  7. package/src/common/logger.js +15 -0
  8. package/src/common/sockets-utils.js +0 -63
  9. package/src/common/version.js +2 -0
  10. package/src/server/Plugin.js +22 -10
  11. package/src/server/Server.js +37 -36
  12. package/src/server/Socket.js +126 -250
  13. package/src/server/Sockets.js +103 -127
  14. package/src/server/audit-network-latency.worker.js +4 -3
  15. package/src/server/index.js +6 -0
  16. package/types/client/Client.d.ts +41 -8
  17. package/types/client/Client.d.ts.map +1 -1
  18. package/types/client/Context.d.ts +40 -14
  19. package/types/client/Context.d.ts.map +1 -1
  20. package/types/client/Plugin.d.ts +42 -41
  21. package/types/client/Plugin.d.ts.map +1 -1
  22. package/types/client/PluginManager.d.ts +26 -11
  23. package/types/client/PluginManager.d.ts.map +1 -1
  24. package/types/client/SharedState.d.ts +1 -1
  25. package/types/client/SharedState.d.ts.map +1 -1
  26. package/types/client/SharedStateCollection.d.ts +41 -0
  27. package/types/client/SharedStateCollection.d.ts.map +1 -0
  28. package/types/client/Socket.d.ts +41 -30
  29. package/types/client/Socket.d.ts.map +1 -1
  30. package/types/client/StateManager.d.ts +1 -1
  31. package/types/client/StateManager.d.ts.map +1 -1
  32. package/types/client/index.d.ts +0 -8
  33. package/types/common/BasePlugin.d.ts +24 -19
  34. package/types/common/BasePlugin.d.ts.map +1 -1
  35. package/types/common/BasePluginManager.d.ts +17 -5
  36. package/types/common/BasePluginManager.d.ts.map +1 -1
  37. package/types/common/BaseSharedState.d.ts +100 -29
  38. package/types/common/BaseSharedState.d.ts.map +1 -1
  39. package/types/common/BaseSharedStateCollection.d.ts +183 -0
  40. package/types/common/BaseSharedStateCollection.d.ts.map +1 -0
  41. package/types/common/BaseStateManager.d.ts +83 -18
  42. package/types/common/BaseStateManager.d.ts.map +1 -1
  43. package/types/common/BatchedTransport.d.ts +21 -0
  44. package/types/common/BatchedTransport.d.ts.map +1 -0
  45. package/types/common/ParameterBag.d.ts +61 -40
  46. package/types/common/ParameterBag.d.ts.map +1 -1
  47. package/types/common/PromiseStore.d.ts +11 -0
  48. package/types/common/PromiseStore.d.ts.map +1 -0
  49. package/types/common/SharedStatePrivate.d.ts +4 -3
  50. package/types/common/SharedStatePrivate.d.ts.map +1 -1
  51. package/types/common/constants.d.ts +10 -1
  52. package/types/common/constants.d.ts.map +1 -1
  53. package/types/common/logger.d.ts +1 -1
  54. package/types/common/sockets-utils.d.ts.map +1 -1
  55. package/types/common/version.d.ts +3 -0
  56. package/types/common/version.d.ts.map +1 -0
  57. package/types/server/Client.d.ts +5 -0
  58. package/types/server/Client.d.ts.map +1 -1
  59. package/types/server/Context.d.ts +162 -22
  60. package/types/server/Context.d.ts.map +1 -1
  61. package/types/server/ContextManager.d.ts +4 -4
  62. package/types/server/ContextManager.d.ts.map +1 -1
  63. package/types/server/Plugin.d.ts +56 -55
  64. package/types/server/Plugin.d.ts.map +1 -1
  65. package/types/server/PluginManager.d.ts +26 -6
  66. package/types/server/PluginManager.d.ts.map +1 -1
  67. package/types/server/Server.d.ts +207 -70
  68. package/types/server/Server.d.ts.map +1 -1
  69. package/types/server/SharedState.d.ts +1 -1
  70. package/types/server/SharedState.d.ts.map +1 -1
  71. package/types/server/SharedStateCollection.d.ts +41 -0
  72. package/types/server/SharedStateCollection.d.ts.map +1 -0
  73. package/types/server/Socket.d.ts +93 -57
  74. package/types/server/Socket.d.ts.map +1 -1
  75. package/types/server/Sockets.d.ts +32 -25
  76. package/types/server/Sockets.d.ts.map +1 -1
  77. package/types/server/StateManager.d.ts +242 -27
  78. package/types/server/StateManager.d.ts.map +1 -1
  79. package/types/server/audit-network-latency.worker.d.ts +2 -1
  80. package/types/server/audit-schema.d.ts +8 -8
  81. package/types/server/crypto.d.ts +3 -0
  82. package/types/server/crypto.d.ts.map +1 -0
  83. package/types/server/index.d.ts +0 -4
  84. package/types/common/promise-store.d.ts +0 -4
  85. package/types/common/promise-store.d.ts.map +0 -1
  86. package/types/common/utils.d.ts +0 -5
  87. package/types/common/utils.d.ts.map +0 -1
@@ -4,9 +4,16 @@ import http from 'node:http';
4
4
  import https from 'node:https';
5
5
  import path from 'node:path';
6
6
  import os from 'node:os';
7
- import { X509Certificate, createPrivateKey } from 'node:crypto';
7
+ import {
8
+ X509Certificate,
9
+ createPrivateKey,
10
+ } from 'node:crypto';
8
11
 
9
- import { isPlainObject, idGenerator, getTime } from '@ircam/sc-utils';
12
+ import {
13
+ isPlainObject,
14
+ idGenerator,
15
+ getTime,
16
+ } from '@ircam/sc-utils';
10
17
  import chalk from 'chalk';
11
18
  import compression from 'compression';
12
19
  import express from 'express';
@@ -18,11 +25,18 @@ import pem from 'pem';
18
25
  import compile from 'template-literal';
19
26
 
20
27
  import auditSchema from './audit-schema.js';
21
- import { encryptData, decryptData } from './crypto.js';
28
+ import {
29
+ encryptData,
30
+ decryptData,
31
+ } from './crypto.js';
22
32
  import Client from './Client.js';
23
33
  import ContextManager from './ContextManager.js';
24
34
  import PluginManager from './PluginManager.js';
25
35
  import StateManager from './StateManager.js';
36
+ import {
37
+ kSocketClientId,
38
+ kSocketTerminate,
39
+ } from './Socket.js';
26
40
  import Sockets from './Sockets.js';
27
41
  import logger from '../common/logger.js';
28
42
  import {
@@ -32,7 +46,7 @@ import {
32
46
  CLIENT_HANDSHAKE_ERROR,
33
47
  AUDIT_STATE_NAME,
34
48
  } from '../common/constants.js';
35
-
49
+ import version from '../common/version.js';
36
50
 
37
51
  let _dbNamespaces = new Set();
38
52
 
@@ -80,27 +94,6 @@ const DEFAULT_CONFIG = {
80
94
 
81
95
  const TOKEN_VALID_DURATION = 20; // sec
82
96
 
83
- // set terminal title
84
- /** @private */
85
- function setTerminalTitle(server) {
86
- let title = '';
87
-
88
- if (server._auditState !== null) {
89
- const numClients = server._auditState.get('numClients');
90
- let numClientStrings = [];
91
- for (let name in numClients) {
92
- numClientStrings.push(`${name}: ${numClients[name]}`);
93
- }
94
-
95
- title = `${server.config.app.name} | ${numClientStrings.join(' - ')}`;
96
- } else {
97
- title = `${server.config.app.name}`;
98
- }
99
-
100
- const msg = String.fromCharCode(27) + ']0;' + title + String.fromCharCode(7);
101
- process.stdout.write(msg);
102
- }
103
-
104
97
  /**
105
98
  * The `Server` class is the main entry point for the server-side of a soundworks
106
99
  * application.
@@ -162,6 +155,7 @@ class Server {
162
155
  if (!isPlainObject(config)) {
163
156
  throw new Error(`[soundworks:Server] Invalid argument for Server constructor, config should be an object`);
164
157
  }
158
+
165
159
  /**
166
160
  * @description Given config object merged with the following defaults:
167
161
  * @example
@@ -225,6 +219,8 @@ class Server {
225
219
  }
226
220
  }
227
221
 
222
+ this.version = version;
223
+
228
224
  /**
229
225
  * Instance of the express router.
230
226
  *
@@ -334,7 +330,6 @@ class Server {
334
330
  this.stateManager.registerSchema(AUDIT_STATE_NAME, auditSchema);
335
331
 
336
332
  logger.configure(this.config.env.verbose);
337
- setTerminalTitle(this);
338
333
  }
339
334
 
340
335
  /**
@@ -383,7 +378,6 @@ class Server {
383
378
  }
384
379
  /** @private */
385
380
  this._auditState = await this.stateManager.create(AUDIT_STATE_NAME, { numClients });
386
- this._auditState.onUpdate(() => setTerminalTitle(this));
387
381
 
388
382
  // basic http authentication
389
383
  if (this.config.env.auth) {
@@ -694,7 +688,7 @@ Invalid certificate files, please check your:
694
688
  // this.httpsInfos.isValid = false; // for testing
695
689
  if (!this.httpsInfos.isValid) {
696
690
  logger.error(chalk.red` -------------------------------------------`);
697
- logger.error(chalk.red` > INVALID CERTIFICATE`);
691
+ logger.error(chalk.red` > INVALID CERTIFICATE `);
698
692
  logger.error(chalk.red` i.e. you pretend to be safe but you are not`);
699
693
  logger.error(chalk.red` -------------------------------------------`);
700
694
  } else {
@@ -856,16 +850,13 @@ Invalid certificate files, please check your:
856
850
  */
857
851
  _onSocketConnection(role, socket, connectionToken) {
858
852
  const client = new Client(role, socket);
853
+ socket[kSocketClientId] = client.id;
859
854
  const roles = Object.keys(this.config.app.clients);
860
855
 
861
856
  // this has been validated
862
857
  if (this.isProtected(role) && this.isValidConnectionToken(connectionToken)) {
863
858
  const { ip } = decryptData(connectionToken);
864
- const newData = {
865
- ip: ip,
866
- id: client.id,
867
- };
868
-
859
+ const newData = { ip, id: client.id };
869
860
  const newToken = encryptData(newData);
870
861
 
871
862
  client.token = newToken;
@@ -904,11 +895,11 @@ Invalid certificate files, please check your:
904
895
  }
905
896
 
906
897
  // clean sockets
907
- socket.terminate();
898
+ socket[kSocketTerminate]();
908
899
  });
909
900
 
910
901
  socket.addListener(CLIENT_HANDSHAKE_REQUEST, async payload => {
911
- const { role, registeredPlugins } = payload;
902
+ const { role, version, registeredPlugins } = payload;
912
903
 
913
904
  if (!roles.includes(role)) {
914
905
  console.error(`[soundworks.Server] A client with invalid role ("${role}") attempted to connect`);
@@ -920,6 +911,10 @@ Invalid certificate files, please check your:
920
911
  return;
921
912
  }
922
913
 
914
+ if (version !== this.version) {
915
+ logger.warnVersionDiscepancies(role, version, this.version);
916
+ }
917
+
923
918
  try {
924
919
  this.pluginManager.checkRegisteredPlugins(registeredPlugins);
925
920
  } catch (err) {
@@ -950,7 +945,7 @@ Invalid certificate files, please check your:
950
945
  this._onClientConnectCallbacks.forEach(callback => callback(client));
951
946
 
952
947
  const { id, uuid, token } = client;
953
- socket.send(CLIENT_HANDSHAKE_RESPONSE, { id, uuid, token });
948
+ socket.send(CLIENT_HANDSHAKE_RESPONSE, { id, uuid, token, version: this.version });
954
949
  });
955
950
  }
956
951
 
@@ -1060,6 +1055,12 @@ Invalid certificate files, please check your:
1060
1055
  },
1061
1056
  env: {
1062
1057
  type: config.env.type,
1058
+ // use to configure the socket if the server is running on a different
1059
+ // location than the one the client was served from (cf. #90)
1060
+ useHttps: config.env.useHttps,
1061
+ serverAddress: config.env.serverAddress,
1062
+ port: config.env.port,
1063
+ // other config, to review
1063
1064
  websockets: config.env.websockets,
1064
1065
  subpath: config.env.subpath,
1065
1066
  useMinifiedFile: useMinifiedFile[role],
@@ -5,27 +5,27 @@ import {
5
5
  PONG_MESSAGE,
6
6
  } from '../common/constants.js';
7
7
  import {
8
- packBinaryMessage,
9
- unpackBinaryMessage,
10
8
  packStringMessage,
11
9
  unpackStringMessage,
12
10
  } from '../common/sockets-utils.js';
11
+ import {
12
+ kSocketsLatencyStatsWorker,
13
+ kSocketsDebugPreventHeartBeat,
14
+ kSocketsRemoveFromAllRooms,
15
+ } from './Sockets.js';
13
16
 
14
- // Status codes:
15
- //
16
- // CONNECTING = 0;
17
- // OPEN = 1;
18
- // CLOSING = 2;
19
- // CLOSED = 3;
20
- // READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
17
+ export const kSocketClientId = Symbol('soundworks:socket-client-id');
18
+ export const kSocketTerminate = Symbol('soundworks:socket-terminate');
21
19
 
22
20
  /**
23
- * The Socket class is a simple publish / subscribe wrapper built on top of the
24
- * [ws](https://github.com/websockets/ws) library. An instance of {@link server.Socket}
25
- * is automatically created per client when it connects (see {@link server.Client#socket}).
21
+ * Simple publish / subscribe wrapper built on top of the
22
+ * [ws](https://github.com/websockets/ws) library.
23
+ *
24
+ * An instance of {@link server.Socket} is automatically created per client
25
+ * when it connects (see {@link server.Client#socket}).
26
26
  *
27
27
  * _Important: In most cases, you should consider using a {@link client.SharedState}
28
- * rather than directly using the sockets._
28
+ * rather than directly using the Socket instance._
29
29
  *
30
30
  * The Socket class contains two different WebSockets:
31
31
  * - a socket configured with `binaryType = 'blob'` for JSON compatible data
@@ -37,22 +37,17 @@ import {
37
37
  * @hideconstructor
38
38
  */
39
39
  class Socket {
40
- constructor(ws, binaryWs, rooms, sockets) {
41
- /**
42
- * `ws` socket instance configured with `binaryType=blob` (string)
43
- *
44
- * @type {object}
45
- * @private
46
- */
47
- this.ws = ws;
40
+ #socket = null;
41
+ #sockets = null;
42
+ #listeners = new Map();
43
+ #heartbeatId = null;
48
44
 
45
+ constructor(ws, sockets) {
49
46
  /**
50
- * `ws` socket instance configured with `binaryType=arraybuffer` (TypedArray)
51
- *
47
+ * `ws` socket instance configured with `binaryType=blob` (string)
52
48
  * @type {object}
53
- * @private
54
49
  */
55
- this.binaryWs = binaryWs;
50
+ this.#socket = ws;
56
51
 
57
52
  /**
58
53
  * Reference to the sockets object, is mainly dedicated to allow
@@ -62,28 +57,10 @@ class Socket {
62
57
  * @example
63
58
  * socket.sockets.broadcast('my-room', this, 'update-value', 1);
64
59
  */
65
- this.sockets = sockets;
60
+ this.#sockets = sockets;
66
61
 
67
- /**
68
- * Reference to the rooms object
69
- *
70
- * @type {Map}
71
- * @private
72
- */
73
- this.rooms = rooms;
74
-
75
- /** @private */
76
- this._stringListeners = new Map();
77
- /** @private */
78
- this._binaryListeners = new Map();
79
- /** @private */
80
- this._heartbeatId = null;
81
-
82
- // heartbeat system (run only on string socket), adapted from:
83
- // https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
84
- /** @private */
85
- this._isAlive = true;
86
- let msg = {
62
+ // @todo - review
63
+ const msg = {
87
64
  type: 'add-measurement',
88
65
  value: {
89
66
  ping: 0,
@@ -91,43 +68,49 @@ class Socket {
91
68
  },
92
69
  };
93
70
 
94
- // ----------------------------------------------------------
95
- // String socket
96
- // implements ping/pong behavior
97
- // ----------------------------------------------------------
98
- this.ws.addEventListener('message', e => {
71
+ let heartbeatMissed = 0;
72
+ // heartbeat system (run only on string socket), adapted from:
73
+ // https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
74
+ this.#socket.addEventListener('message', e => {
99
75
  if (e.data === PONG_MESSAGE) {
100
- this._isAlive = true;
76
+ heartbeatMissed = 0;
101
77
 
102
78
  msg.value.pong = getTime();
103
- this.sockets._latencyStatsWorker.postMessage(msg);
79
+ this.#sockets[kSocketsLatencyStatsWorker].postMessage(msg);
104
80
  // do not propagate ping / pong messages
105
81
  return;
106
82
  }
107
83
 
108
84
  const [channel, args] = unpackStringMessage(e.data);
109
- this._emit(false, channel, ...args);
85
+ this.#dispatchEvent(channel, ...args);
110
86
  });
111
87
 
112
- this._heartbeatId = setInterval(() => {
113
- if (this._isAlive === false) {
114
- // emit a 'close' event to go trough all the disconnection pipeline
115
- this._emit(false, 'close');
116
- return;
88
+ this.#heartbeatId = setInterval(() => {
89
+ // we didn't receive the pong message
90
+ if (heartbeatMissed > 0) {
91
+ // Emit a 'close' event to go trough all the disconnection pipeline
92
+ //
93
+ // @note - this seems to create false positive disconnections when
94
+ // client is busy, e.g. when loading large sound files so let's just warn
95
+ // until we gather more feedback
96
+ // cf. https://making.close.com/posts/reliable-websockets/
97
+ console.warn(`[Socket] client (id: ${this[kSocketClientId]}) did not respond to ping message in time (missed: ${heartbeatMissed}, interval: ${PING_INTERVAL})`);
98
+ // this.#dispatchEvent('close');
99
+ // return;
117
100
  }
118
101
 
119
- this._isAlive = false;
102
+ heartbeatMissed += 1;
120
103
  msg.value.ping = getTime();
121
104
 
122
- this.ws.send(PING_MESSAGE);
105
+ this.#socket.send(PING_MESSAGE);
123
106
  }, PING_INTERVAL);
124
107
 
125
108
  // for testing purpose
126
- if (this.sockets._DEBUG_PREVENT_HEARTBEAT === true) {
127
- clearInterval(this._heartbeatId);
109
+ if (this.#sockets[kSocketsDebugPreventHeartBeat] === true) {
110
+ clearInterval(this.#heartbeatId);
128
111
  }
129
112
 
130
- // broadcast all "native" events
113
+ // propagate "native" events
131
114
  [
132
115
  'close',
133
116
  'error',
@@ -138,165 +121,69 @@ class Socket {
138
121
  'unexpected-response',
139
122
  'upgrade',
140
123
  ].forEach(eventName => {
141
- this.ws.addEventListener(eventName, e => {
142
- this._emit(false, eventName, e.data);
124
+ this.#socket.addEventListener(eventName, e => {
125
+ this.#dispatchEvent(eventName, e.data);
143
126
  });
144
127
  });
145
-
146
- // ----------------------------------------------------------
147
- // Binary socket
148
- // ----------------------------------------------------------
149
- this.binaryWs.addEventListener('message', e => {
150
- const [channel, data] = unpackBinaryMessage(e.data);
151
- this._emit(true, channel, data);
152
- });
153
-
154
- // broadcast all "native" events
155
- [
156
- 'close',
157
- 'error',
158
- 'message',
159
- 'open',
160
- 'ping',
161
- 'pong',
162
- 'unexpected-response',
163
- 'upgrade',
164
- ].forEach(eventName => {
165
- this.binaryWs.addEventListener(eventName, e => {
166
- this._emit(true, eventName, e.data);
167
- });
168
- });
169
- }
170
-
171
- /**
172
- * Removes all listeners and immediately close the two sockets. Is automatically
173
- * called on `server.stop()`
174
- *
175
- * @private
176
- */
177
- terminate() {
178
- // clear ping/pong check
179
- clearInterval(this._heartbeatId);
180
-
181
- // clean rooms
182
- for (let [_key, room] of this.rooms) {
183
- room.delete(this);
184
- }
185
-
186
- // clear references to sockets and rooms
187
- this.sockets = null;
188
- this.rooms = null;
189
-
190
- // clear all listeners
191
- this._stringListeners.clear();
192
- this._binaryListeners.clear();
193
-
194
- // clear "native" listeners
195
- [this.binaryWs, this.ws].forEach((socket) => {
196
- [
197
- 'close',
198
- 'error',
199
- 'message',
200
- 'open',
201
- 'ping',
202
- 'pong',
203
- 'unexpected-response',
204
- 'upgrade',
205
- ].forEach(eventName => {
206
- socket.removeAllListeners(eventName);
207
- });
208
- });
209
-
210
- // clear binarySocket as this is called from the string one.
211
- this.binaryWs.terminate();
212
- this.ws.terminate();
213
128
  }
214
129
 
215
130
  /**
216
- * @param {boolean} binary - Emit to either the string or binary socket.
131
+ * Dipatch an event to the listeners of the given channel.
217
132
  * @param {string} channel - Channel name.
218
133
  * @param {...*} args - Content of the message.
219
- * @private
220
134
  */
221
- _emit(binary, channel, ...args) {
222
- const listeners = binary ? this._binaryListeners : this._stringListeners;
223
-
224
- if (listeners.has(channel)) {
225
- const callbacks = listeners.get(channel);
135
+ #dispatchEvent(channel, ...args) {
136
+ if (this.#listeners.has(channel)) {
137
+ const callbacks = this.#listeners.get(channel);
226
138
  callbacks.forEach(callback => callback(...args));
227
139
  }
228
140
  }
229
141
 
230
142
  /**
231
- * @param {Function[]} listeners - List of listeners, either for the string or binary socket.
232
- * @param {string} channel - Channel name.
233
- * @param {Function} callback - The function to be added to the listeners.
234
- * @private
235
- */
236
- _addListener(listeners, channel, callback) {
237
- if (!listeners.has(channel)) {
238
- listeners.set(channel, new Set());
239
- }
240
-
241
- const callbacks = listeners.get(channel);
242
- callbacks.add(callback);
243
- }
244
-
245
- /**
246
- * @param {Function[]} listeners - List of listeners, either for the string or binary socket.
247
- * @param {string} channel - Channel name.
248
- * @param {Function} callback - The function to be removed from the listeners.
249
- * @private
250
- */
251
- _removeListener(listeners, channel, callback) {
252
- if (listeners.has(channel)) {
253
- const callbacks = listeners.get(channel);
254
- callbacks.delete(callback);
255
-
256
- if (callbacks.size === 0) {
257
- listeners.delete(channel);
258
- }
259
- }
260
- }
261
-
262
- /**
263
- * @param {Function[]} listeners - List of listeners, either for the string or binary socket.
264
- * @param {string} [channel=null] - Channel name of the listeners to remove. If null
265
- * all the listeners are cleared.
266
- * @private
143
+ * Removes all listeners and immediately close the web socket.
144
+ *
145
+ * Is automatically called when socket is closed on the client side or when
146
+ * Server is stopped
267
147
  */
268
- _removeAllListeners(listeners, channel) {
269
- if (channel === null) {
270
- listeners.clear();
271
- } else if (listeners.has(channel)) {
272
- listeners.delete(channel);
273
- }
148
+ [kSocketTerminate]() {
149
+ // clear ping / pong interval
150
+ clearInterval(this.#heartbeatId);
151
+ // remove socket from all rooms
152
+ this.#sockets[kSocketsRemoveFromAllRooms](this);
153
+ // clear references to sockets
154
+ this.#sockets = null;
155
+ // clear all listeners
156
+ this.#listeners.clear();
157
+ // clear native listeners
158
+ [
159
+ 'close',
160
+ 'error',
161
+ 'message',
162
+ 'open',
163
+ 'ping',
164
+ 'pong',
165
+ 'unexpected-response',
166
+ 'upgrade',
167
+ ].forEach(eventName => this.#socket.removeAllListeners(eventName));
168
+ // terminate the socket
169
+ this.#socket.terminate();
274
170
  }
275
171
 
276
172
  /**
277
- * Add the socket to a room
173
+ * Reference to the @link{server.Sockets} instance.
278
174
  *
279
- * @param {string} roomId - Id of the room.
175
+ * Allows for broadcasting from a given socket instance.
176
+ *
177
+ * @type {server.Sockets}
178
+ * @example
179
+ * socket.sockets.broadcast('my-room', this, 'update-value', 1);
280
180
  */
281
- addToRoom(roomId) {
282
- if (!this.rooms.has(roomId)) {
283
- this.rooms.set(roomId, new Set());
284
- }
285
-
286
- const room = this.rooms.get(roomId);
287
- room.add(this);
181
+ get sockets() {
182
+ return this.#sockets;
288
183
  }
289
184
 
290
- /**
291
- * Remove the socket from a room
292
- *
293
- * @param {string} roomId - Id of the room.
294
- */
295
- removeFromRoom(roomId) {
296
- if (this.rooms.has(roomId)) {
297
- const room = this.rooms.get(roomId);
298
- room.delete(this);
299
- }
185
+ get readyState() {
186
+ return this.#socket.readyState;
300
187
  }
301
188
 
302
189
  /**
@@ -309,14 +196,15 @@ class Socket {
309
196
  send(channel, ...args) {
310
197
  const msg = packStringMessage(channel, ...args);
311
198
 
312
- // 0 CONNECTING Socket has been created. The connection is not yet open.
313
- // 1 OPEN The connection is open and ready to communicate.
314
- // 2 CLOSING The connection is in the process of closing.
315
- // 3 CLOSED The connection is closed or couldn't be opened.
316
- if (this.ws.readyState === 1) {
317
- this.ws.send(msg, (err) => {
199
+ // ## Status codes:
200
+ // CONNECTING = 0; Socket has been created. The connection is not yet open.
201
+ // OPEN = 1; The connection is open and ready to communicate.
202
+ // CLOSING = 2; The connection is in the process of closing.
203
+ // CLOSED = 3; The connection is closed or couldn't be opened.
204
+ if (this.#socket.readyState === 1) {
205
+ this.#socket.send(msg, (err) => {
318
206
  if (err) {
319
- console.error('error sending msg:', channel, args, err);
207
+ console.error('[Socket] error sending msg:', channel, args, err.message);
320
208
  }
321
209
  });
322
210
  }
@@ -326,12 +214,17 @@ class Socket {
326
214
  * Listen messages with JSON compatible data types on a given channel.
327
215
  *
328
216
  * @param {string} channel - Channel name.
329
- * @param {Function} callback - Callback to execute when a message is received,
330
- * arguments of the callback function will match the arguments sent using the
217
+ * @param {Function} callback - Callback to execute when a message is received.
218
+ * Arguments of the callback function will match the arguments sent using the
331
219
  * {@link server.Socket#send} method.
332
220
  */
333
221
  addListener(channel, callback) {
334
- this._addListener(this._stringListeners, channel, callback);
222
+ if (!this.#listeners.has(channel)) {
223
+ this.#listeners.set(channel, new Set());
224
+ }
225
+
226
+ const callbacks = this.#listeners.get(channel);
227
+ callbacks.add(callback);
335
228
  }
336
229
 
337
230
  /**
@@ -341,7 +234,14 @@ class Socket {
341
234
  * @param {Function} callback - Callback to remove.
342
235
  */
343
236
  removeListener(channel, callback) {
344
- this._removeListener(this._stringListeners, channel, callback);
237
+ if (this.#listeners.has(channel)) {
238
+ const callbacks = this.#listeners.get(channel);
239
+ callbacks.delete(callback);
240
+
241
+ if (callbacks.size === 0) {
242
+ this.#listeners.delete(channel);
243
+ }
244
+ }
345
245
  }
346
246
 
347
247
  /**
@@ -350,53 +250,29 @@ class Socket {
350
250
  * @param {string} channel - Channel name.
351
251
  */
352
252
  removeAllListeners(channel = null) {
353
- this._removeAllListeners(this._stringListeners, channel);
354
- }
355
-
356
- /**
357
- * Send binary messages on a given channel.
358
- *
359
- * @param {string} channel - Channel name.
360
- * @param {TypedArray} typedArray - Binary data to be sent.
361
- */
362
- sendBinary(channel, typedArray) {
363
- const msg = packBinaryMessage(channel, typedArray);
364
-
365
- this.binaryWs.send(msg, (err) => {
366
- if (err) {
367
- console.error('error sending msg:', channel, typedArray);
368
- }
369
- });
253
+ if (channel === null) {
254
+ this.#listeners.clear();
255
+ } else if (this.#listeners.has(channel)) {
256
+ this.#listeners.delete(channel);
257
+ }
370
258
  }
371
259
 
372
260
  /**
373
- * Listen binary messages on a given channel
374
- *
375
- * @param {string} channel - Channel name.
376
- * @param {Function} callback - Callback to execute when a message is received.
261
+ * Add the socket to a room
262
+ * @param {string} roomId - Id of the room.
377
263
  */
378
- addBinaryListener(channel, callback) {
379
- this._addListener(this._binaryListeners, channel, callback);
264
+ addToRoom(roomId) {
265
+ this.sockets.addToRoom(this, roomId);
380
266
  }
381
267
 
382
268
  /**
383
- * Remove a listener of binary compatible messages from a given channel
384
- *
385
- * @param {string} channel - Channel name.
386
- * @param {Function} callback - Callback to remove.
269
+ * Remove the socket from a room
270
+ * @param {string} roomId - Id of the room.
387
271
  */
388
- removeBinaryListener(channel, callback) {
389
- this._removeListener(this._binaryListeners, channel, callback);
272
+ removeFromRoom(roomId) {
273
+ this.sockets.removeFromRoom(this, roomId);
390
274
  }
391
275
 
392
- /**
393
- * Remove all listeners of binary compatible messages on a given channel
394
- *
395
- * @param {string} channel - Channel of the message.
396
- */
397
- removeAllBinaryListeners(channel = null) {
398
- this._removeAllListeners(this._binaryListeners, channel);
399
- }
400
276
  }
401
277
 
402
278
  export default Socket;