@soundworks/core 4.0.0-alpha.28 → 4.0.0-alpha.29
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/.jsdoc +3 -1
- package/CHANGELOG.md +11 -5
- package/package.json +5 -5
- package/src/client/Client.js +78 -83
- package/src/client/{Context.js → ClientContext.js} +48 -38
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +22 -18
- package/src/client/{PluginManager.js → ClientPluginManager.js} +39 -35
- package/src/client/{Socket.js → ClientSocket.js} +54 -65
- package/src/client/{StateManager.js → ClientStateManager.js} +7 -8
- package/src/client/index.js +17 -41
- package/src/common/BasePlugin.js +56 -51
- package/src/common/BasePluginManager.js +175 -139
- package/src/common/BaseStateManager.js +316 -120
- package/src/common/BatchedTransport.js +24 -24
- package/src/common/ParameterBag.js +18 -6
- package/src/common/PromiseStore.js +1 -0
- package/src/common/{BaseSharedState.js → SharedState.js} +271 -199
- package/src/common/{BaseSharedStateCollection.js → SharedStateCollection.js} +92 -61
- package/src/common/logger.js +5 -0
- package/src/common/shared-state-types.js +76 -0
- package/src/common/version.js +1 -1
- package/src/server/Server.js +391 -354
- package/src/server/ServerClient.js +73 -0
- package/src/server/{Context.js → ServerContext.js} +85 -66
- package/src/server/{ContextManager.js → ServerContextManager.js} +98 -85
- package/src/server/{Plugin.js → ServerPlugin.js} +10 -24
- package/src/server/{PluginManager.js → ServerPluginManager.js} +76 -69
- package/src/server/{Socket.js → ServerSocket.js} +19 -18
- package/src/server/{Sockets.js → ServerSockets.js} +36 -27
- package/src/server/ServerStateManager.js +515 -0
- package/src/{common → server}/SharedStatePrivate.js +61 -26
- package/src/server/audit-schema.js +1 -1
- package/src/server/crypto.js +2 -0
- package/src/server/index.js +18 -21
- package/types/client/Client.d.ts +74 -95
- package/types/client/{Context.d.ts → ClientContext.d.ts} +25 -26
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/{Plugin.d.ts → ClientPlugin.d.ts} +17 -20
- package/types/client/{PluginManager.d.ts → ClientPluginManager.d.ts} +13 -36
- package/types/client/ClientSocket.d.ts +74 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +11 -15
- package/types/client/index.d.ts +26 -2
- package/types/common/BasePlugin.d.ts +52 -46
- package/types/common/BasePluginManager.d.ts +68 -44
- package/types/common/BaseStateManager.d.ts +207 -97
- package/types/common/BatchedTransport.d.ts +1 -5
- package/types/common/ParameterBag.d.ts +16 -18
- package/types/common/PromiseStore.d.ts +1 -1
- package/types/common/SharedState.d.ts +297 -0
- package/types/common/{BaseSharedStateCollection.d.ts → SharedStateCollection.d.ts} +34 -18
- package/types/common/constants.d.ts +0 -2
- package/types/common/logger.d.ts +2 -1
- package/types/common/shared-state-types.d.ts +83 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +1 -2
- package/types/server/Server.d.ts +78 -132
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/{Context.d.ts → ServerContext.d.ts} +53 -43
- package/types/server/ServerContextManager.d.ts +80 -0
- package/types/server/{Plugin.d.ts → ServerPlugin.d.ts} +19 -33
- package/types/server/{PluginManager.d.ts → ServerPluginManager.d.ts} +39 -37
- package/types/server/ServerSocket.d.ts +85 -0
- package/types/server/ServerSockets.d.ts +50 -0
- package/types/server/ServerStateManager.d.ts +189 -0
- package/types/server/SharedStatePrivate.d.ts +21 -0
- package/types/server/audit-network-latency.worker.d.ts +1 -2
- package/types/server/audit-schema.d.ts +0 -1
- package/types/server/crypto.d.ts +2 -1
- package/types/server/index.d.ts +24 -2
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -38
- package/src/server/Client.js +0 -55
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -39
- package/src/server/StateManager.js +0 -696
- 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.map +0 -1
- 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/SharedStateCollection.d.ts +0 -41
- package/types/client/SharedStateCollection.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -149
- 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 -244
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseSharedStateCollection.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/BatchedTransport.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/PromiseStore.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -21
- 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/sockets-utils.d.ts.map +0 -1
- package/types/common/version.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -45
- package/types/server/Client.d.ts.map +0 -1
- 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.map +0 -1
- 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/SharedStateCollection.d.ts +0 -41
- package/types/server/SharedStateCollection.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -164
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -80
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -405
- 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/crypto.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import BasePlugin from '../common/BasePlugin.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Callback executed when the plugin state is updated.
|
|
5
|
-
*
|
|
6
|
-
* @callback server.Plugin~onStateChangeCallback
|
|
7
|
-
* @param {server.Plugin#state} state - Current state of the plugin.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Delete the registered {@link server.Plugin~onStateChangeCallback}.
|
|
12
|
-
*
|
|
13
|
-
* @callback server.Plugin~deleteOnStateChangeCallback
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
3
|
/**
|
|
17
4
|
* Base class to extend in order to create the server-side counterpart of a
|
|
18
5
|
* `soundworks` plugin.
|
|
@@ -28,18 +15,17 @@ import BasePlugin from '../common/BasePlugin.js';
|
|
|
28
15
|
*
|
|
29
16
|
* _Creating new plugins should be considered an advanced usage._
|
|
30
17
|
*
|
|
31
|
-
* @memberof server
|
|
32
18
|
* @extends BasePlugin
|
|
33
19
|
* @inheritdoc
|
|
34
20
|
*/
|
|
35
|
-
class
|
|
21
|
+
class ServerPlugin extends BasePlugin {
|
|
36
22
|
#server = null;
|
|
37
23
|
#clients = new Set();
|
|
38
24
|
|
|
39
25
|
/**
|
|
40
|
-
* @param {
|
|
26
|
+
* @param {Server} server - The soundworks server instance.
|
|
41
27
|
* @param {string} id - User defined id of the plugin as defined in
|
|
42
|
-
* {@link
|
|
28
|
+
* {@link ServerPluginManager#register}.
|
|
43
29
|
*/
|
|
44
30
|
constructor(server, id) {
|
|
45
31
|
super(id);
|
|
@@ -48,8 +34,8 @@ class Plugin extends BasePlugin {
|
|
|
48
34
|
|
|
49
35
|
/**
|
|
50
36
|
* Instance of soundworks server.
|
|
51
|
-
* @type {
|
|
52
|
-
* @see {@link
|
|
37
|
+
* @type {Server}
|
|
38
|
+
* @see {@link Server}
|
|
53
39
|
*/
|
|
54
40
|
get server() {
|
|
55
41
|
return this.#server;
|
|
@@ -57,8 +43,8 @@ class Plugin extends BasePlugin {
|
|
|
57
43
|
|
|
58
44
|
/**
|
|
59
45
|
* Set of the clients registered in the plugin.
|
|
60
|
-
* @type {Set<
|
|
61
|
-
* @see {@link
|
|
46
|
+
* @type {Set<ServerClient>}
|
|
47
|
+
* @see {@link ServerClient}
|
|
62
48
|
*/
|
|
63
49
|
get clients() {
|
|
64
50
|
return this.#clients;
|
|
@@ -69,7 +55,7 @@ class Plugin extends BasePlugin {
|
|
|
69
55
|
* connects to the application. Override this method if you need to perform
|
|
70
56
|
* some particular logic (e.g. creating a shared state) for each clients.
|
|
71
57
|
*
|
|
72
|
-
* @param {
|
|
58
|
+
* @param {ServerClient} client
|
|
73
59
|
*/
|
|
74
60
|
async addClient(client) {
|
|
75
61
|
this.#clients.add(client);
|
|
@@ -80,11 +66,11 @@ class Plugin extends BasePlugin {
|
|
|
80
66
|
* disconnects from the application. Override this method if you need to perform
|
|
81
67
|
* some particular logic (e.g. creating a shared state) for each clients.
|
|
82
68
|
*
|
|
83
|
-
* @param {
|
|
69
|
+
* @param {ServerClient} client
|
|
84
70
|
*/
|
|
85
71
|
async removeClient(client) {
|
|
86
72
|
this.#clients.delete(client);
|
|
87
73
|
}
|
|
88
74
|
}
|
|
89
75
|
|
|
90
|
-
export default
|
|
76
|
+
export default ServerPlugin;
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
import BasePluginManager
|
|
2
|
-
|
|
1
|
+
import BasePluginManager, {
|
|
2
|
+
kPluginManagerInstances,
|
|
3
|
+
} from '../common/BasePluginManager.js';
|
|
4
|
+
import ServerPlugin from './ServerPlugin.js';
|
|
3
5
|
import Server from './Server.js';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* @callback server.PluginManager~onStateChangeCallback
|
|
9
|
-
* @param {object<server.Plugin#id, server.Plugin>} fullState - List of all plugins.
|
|
10
|
-
* @param {server.Plugin|null} initiator - Plugin that initiated the update or `null`
|
|
11
|
-
* if the change was initiated by the state manager (i.e. when the initialization
|
|
12
|
-
* of the plugins starts).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Delete the registered {@link server.PluginManager~onStateChangeCallback}.
|
|
17
|
-
*
|
|
18
|
-
* @callback server.PluginManager~deleteOnStateChangeCallback
|
|
19
|
-
*/
|
|
7
|
+
export const kServerPluginManagerCheckRegisteredPlugins = Symbol('soundworks:server-plugin-manager-check-registered-plugins');
|
|
8
|
+
export const kServerPluginManagerAddClient = Symbol('soundworks:server-plugin-manager-add-client');
|
|
9
|
+
export const kServerPluginManagerRemoveClient = Symbol('soundworks:server-plugin-manager-remove-client');
|
|
20
10
|
|
|
21
11
|
/**
|
|
22
12
|
* The `PluginManager` allows to register and retrieve `soundworks` plugins.
|
|
23
13
|
*
|
|
24
14
|
* Plugins should always be registered both client-side and server-side,
|
|
25
|
-
* and before {@link
|
|
15
|
+
* and before {@link Client#start} or {@link Server#start}
|
|
26
16
|
* to be properly initialized.
|
|
27
17
|
*
|
|
28
18
|
* In some sitautions, you might want to register the same plugin factory several times
|
|
@@ -70,12 +60,11 @@ import Server from './Server.js';
|
|
|
70
60
|
* }, 1000);
|
|
71
61
|
* ```
|
|
72
62
|
*
|
|
73
|
-
* @memberof server
|
|
74
63
|
* @extends BasePluginManager
|
|
75
64
|
* @inheritdoc
|
|
76
65
|
* @hideconstructor
|
|
77
66
|
*/
|
|
78
|
-
class
|
|
67
|
+
class ServerPluginManager extends BasePluginManager {
|
|
79
68
|
constructor(server) {
|
|
80
69
|
if (!(server instanceof Server)) {
|
|
81
70
|
throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(server)" should receive an instance of "soundworks.Server" as argument`);
|
|
@@ -84,64 +73,23 @@ class PluginManager extends BasePluginManager {
|
|
|
84
73
|
super(server);
|
|
85
74
|
}
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!(ctor.prototype instanceof Plugin)) {
|
|
91
|
-
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" second argument should be a factory function returning a class extending the "Plugin" base class`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
super.register(id, ctor, options, deps);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Retrieve an fully started instance of a registered plugin.
|
|
99
|
-
*
|
|
100
|
-
* Be aware that the `get` method resolves only when the plugin is fully 'started',
|
|
101
|
-
* which is what we want 99.99% of the time. As such, and to prevent the application
|
|
102
|
-
* to be stuck in some kind of Promise dead lock, this method will throw if
|
|
103
|
-
* used before `server.init()` (or `server.start()`).
|
|
104
|
-
*
|
|
105
|
-
* To handle the remaining 0.01% cases and access plugin instances during their
|
|
106
|
-
* initialization (e.g. to display initialization screen, etc.), you should rely
|
|
107
|
-
* on the `onStateChange` method.
|
|
108
|
-
*
|
|
109
|
-
* _Note: the async API is designed to enable the dynamic creation of plugins
|
|
110
|
-
* (hopefully without brealing changes) in a future release._
|
|
111
|
-
*
|
|
112
|
-
* @param {server.Plugin#id} id - Id of the plugin as defined when registered.
|
|
113
|
-
* @returns {server.Plugin}
|
|
114
|
-
* @see {@link server.PluginManager#onStateChange}
|
|
115
|
-
*
|
|
116
|
-
* @private
|
|
117
|
-
*/
|
|
118
|
-
async get(id) {
|
|
119
|
-
if (this.status !== 'started') {
|
|
120
|
-
throw new Error(`[soundworks.PluginManager] Cannot get plugin before "server.init()"`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return super.unsafeGet(id);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// server only methods
|
|
127
|
-
|
|
128
|
-
/** private */
|
|
129
|
-
checkRegisteredPlugins(registeredPlugins) {
|
|
76
|
+
/** @private */
|
|
77
|
+
[kServerPluginManagerCheckRegisteredPlugins](registeredPlugins) {
|
|
130
78
|
let missingPlugins = [];
|
|
131
79
|
|
|
132
80
|
for (let id of registeredPlugins) {
|
|
133
|
-
if (!this.
|
|
81
|
+
if (!this[kPluginManagerInstances].has(id)) {
|
|
134
82
|
missingPlugins.push(id);
|
|
135
83
|
}
|
|
136
84
|
}
|
|
137
85
|
|
|
138
86
|
if (missingPlugins.length > 0) {
|
|
139
|
-
throw new Error(`Invalid plugin list, the following plugins registered client-side: [${missingPlugins.join(', ')}] have not been registered server-side. Registered server-side plugins are: [${Array.from(this.
|
|
87
|
+
throw new Error(`Invalid plugin list, the following plugins registered client-side: [${missingPlugins.join(', ')}] have not been registered server-side. Registered server-side plugins are: [${Array.from(this[kPluginManagerInstances].keys()).join(', ')}].`);
|
|
140
88
|
}
|
|
141
89
|
}
|
|
142
90
|
|
|
143
91
|
/** @private */
|
|
144
|
-
async
|
|
92
|
+
async [kServerPluginManagerAddClient](client, registeredPlugins = []) {
|
|
145
93
|
let promises = [];
|
|
146
94
|
|
|
147
95
|
for (let pluginName of registeredPlugins) {
|
|
@@ -154,10 +102,10 @@ class PluginManager extends BasePluginManager {
|
|
|
154
102
|
}
|
|
155
103
|
|
|
156
104
|
/** @private */
|
|
157
|
-
async
|
|
105
|
+
async [kServerPluginManagerRemoveClient](client) {
|
|
158
106
|
let promises = [];
|
|
159
107
|
|
|
160
|
-
for (let plugin of this.
|
|
108
|
+
for (let plugin of this[kPluginManagerInstances].values()) {
|
|
161
109
|
if (plugin.clients.has(client)) {
|
|
162
110
|
promises.push(plugin.removeClient(client));
|
|
163
111
|
}
|
|
@@ -165,6 +113,65 @@ class PluginManager extends BasePluginManager {
|
|
|
165
113
|
|
|
166
114
|
await Promise.all(promises);
|
|
167
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Register a plugin into the manager.
|
|
119
|
+
*
|
|
120
|
+
* _A plugin must always be registered both on client-side and on server-side_
|
|
121
|
+
*
|
|
122
|
+
* Refer to the plugin documentation to check its options and proper way of
|
|
123
|
+
* registering it.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} id - Unique id of the plugin. Enables the registration of the
|
|
126
|
+
* same plugin factory under different ids.
|
|
127
|
+
* @param {Function} factory - Factory function that returns the Plugin class.
|
|
128
|
+
* @param {object} [options={}] - Options to configure the plugin.
|
|
129
|
+
* @param {array} [deps=[]] - List of plugins' names the plugin depends on, i.e.
|
|
130
|
+
* the plugin initialization will start only after the plugins it depends on are
|
|
131
|
+
* fully started themselves.
|
|
132
|
+
* @see {@link ClientPluginManager#register}
|
|
133
|
+
* @see {@link ServerPluginManager#register}
|
|
134
|
+
* @example
|
|
135
|
+
* // client-side
|
|
136
|
+
* client.pluginManager.register('user-defined-id', pluginFactory);
|
|
137
|
+
* // server-side
|
|
138
|
+
* server.pluginManager.register('user-defined-id', pluginFactory);
|
|
139
|
+
*/
|
|
140
|
+
register(id, factory = null, options = {}, deps = []) {
|
|
141
|
+
const ctor = factory(ServerPlugin);
|
|
142
|
+
|
|
143
|
+
if (!(ctor.prototype instanceof ServerPlugin)) {
|
|
144
|
+
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" second argument should be a factory function returning a class extending the "ServerPlugin" base class`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
super.register(id, ctor, options, deps);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Retrieve a fully started instance of a registered plugin.
|
|
152
|
+
*
|
|
153
|
+
* Be aware that the `get` method resolves only when the plugin is fully 'started',
|
|
154
|
+
* which is what we want 99.99% of the time. As such, and to prevent the application
|
|
155
|
+
* to be stuck in some kind of Promise dead lock, this method will throw if
|
|
156
|
+
* used before `server.init()` (or `server.start()`).
|
|
157
|
+
*
|
|
158
|
+
* To handle the remaining 0.01% cases and access plugin instances during their
|
|
159
|
+
* initialization (e.g. to display initialization screen, etc.), you should rely
|
|
160
|
+
* on the `onStateChange` method.
|
|
161
|
+
*
|
|
162
|
+
* _Note: the async API is designed to enable the dynamic creation of plugins
|
|
163
|
+
* (hopefully without brealing changes) in a future release._
|
|
164
|
+
*
|
|
165
|
+
* @param {ServerPlugin#id} id - Id of the plugin as defined when registered.
|
|
166
|
+
* @returns {ServerPlugin}
|
|
167
|
+
*/
|
|
168
|
+
async get(id) {
|
|
169
|
+
if (this.status !== 'started') {
|
|
170
|
+
throw new Error(`[soundworks.PluginManager] Cannot get plugin before "server.init()"`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return super.getUnsafe(id);
|
|
174
|
+
}
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
export default
|
|
177
|
+
export default ServerPluginManager;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
kSocketsLatencyStatsWorker,
|
|
13
13
|
kSocketsDebugPreventHeartBeat,
|
|
14
14
|
kSocketsRemoveFromAllRooms,
|
|
15
|
-
} from './
|
|
15
|
+
} from './ServerSockets.js';
|
|
16
16
|
|
|
17
17
|
export const kSocketClientId = Symbol('soundworks:socket-client-id');
|
|
18
18
|
export const kSocketTerminate = Symbol('soundworks:socket-terminate');
|
|
@@ -21,22 +21,15 @@ export const kSocketTerminate = Symbol('soundworks:socket-terminate');
|
|
|
21
21
|
* Simple publish / subscribe wrapper built on top of the
|
|
22
22
|
* [ws](https://github.com/websockets/ws) library.
|
|
23
23
|
*
|
|
24
|
-
* An instance of {@link
|
|
25
|
-
* when it connects (see {@link
|
|
24
|
+
* An instance of {@link ServerSocket} is automatically created per client
|
|
25
|
+
* when it connects (see {@link SeverClient#socket}).
|
|
26
26
|
*
|
|
27
|
-
* _Important: In most cases, you should consider using a {@link
|
|
27
|
+
* _Important: In most cases, you should consider using a {@link SharedState}
|
|
28
28
|
* rather than directly using the Socket instance._
|
|
29
29
|
*
|
|
30
|
-
* The Socket class contains two different WebSockets:
|
|
31
|
-
* - a socket configured with `binaryType = 'blob'` for JSON compatible data
|
|
32
|
-
* types (i.e. string, number, boolean, object, array and null).
|
|
33
|
-
* - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming
|
|
34
|
-
* of binary data.
|
|
35
|
-
*
|
|
36
|
-
* @memberof server
|
|
37
30
|
* @hideconstructor
|
|
38
31
|
*/
|
|
39
|
-
class
|
|
32
|
+
class ServerSocket {
|
|
40
33
|
#socket = null;
|
|
41
34
|
#sockets = null;
|
|
42
35
|
#listeners = new Map();
|
|
@@ -53,7 +46,7 @@ class Socket {
|
|
|
53
46
|
* Reference to the sockets object, is mainly dedicated to allow
|
|
54
47
|
* broadcasting from a given socket instance.
|
|
55
48
|
*
|
|
56
|
-
* @type {
|
|
49
|
+
* @type {ServerSockets}
|
|
57
50
|
* @example
|
|
58
51
|
* socket.sockets.broadcast('my-room', this, 'update-value', 1);
|
|
59
52
|
*/
|
|
@@ -94,7 +87,7 @@ class Socket {
|
|
|
94
87
|
// client is busy, e.g. when loading large sound files so let's just warn
|
|
95
88
|
// until we gather more feedback
|
|
96
89
|
// cf. https://making.close.com/posts/reliable-websockets/
|
|
97
|
-
console.warn(`[
|
|
90
|
+
console.warn(`[ServerSocket] client (id: ${this[kSocketClientId]}) did not respond to ping message in time (missed: ${heartbeatMissed}, interval: ${PING_INTERVAL})`);
|
|
98
91
|
// this.#dispatchEvent('close');
|
|
99
92
|
// return;
|
|
100
93
|
}
|
|
@@ -144,6 +137,8 @@ class Socket {
|
|
|
144
137
|
*
|
|
145
138
|
* Is automatically called when socket is closed on the client side or when
|
|
146
139
|
* Server is stopped
|
|
140
|
+
*
|
|
141
|
+
* @private
|
|
147
142
|
*/
|
|
148
143
|
[kSocketTerminate]() {
|
|
149
144
|
// clear ping / pong interval
|
|
@@ -170,11 +165,11 @@ class Socket {
|
|
|
170
165
|
}
|
|
171
166
|
|
|
172
167
|
/**
|
|
173
|
-
* Reference to the @link{
|
|
168
|
+
* Reference to the @link{ServerSockets} instance.
|
|
174
169
|
*
|
|
175
170
|
* Allows for broadcasting from a given socket instance.
|
|
176
171
|
*
|
|
177
|
-
* @type {
|
|
172
|
+
* @type {ServerSockets}
|
|
178
173
|
* @example
|
|
179
174
|
* socket.sockets.broadcast('my-room', this, 'update-value', 1);
|
|
180
175
|
*/
|
|
@@ -182,6 +177,12 @@ class Socket {
|
|
|
182
177
|
return this.#sockets;
|
|
183
178
|
}
|
|
184
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Reay state of the underlying socket instance.
|
|
182
|
+
*
|
|
183
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState}
|
|
184
|
+
* @type {number}
|
|
185
|
+
*/
|
|
185
186
|
get readyState() {
|
|
186
187
|
return this.#socket.readyState;
|
|
187
188
|
}
|
|
@@ -216,7 +217,7 @@ class Socket {
|
|
|
216
217
|
* @param {string} channel - Channel name.
|
|
217
218
|
* @param {Function} callback - Callback to execute when a message is received.
|
|
218
219
|
* Arguments of the callback function will match the arguments sent using the
|
|
219
|
-
* {@link
|
|
220
|
+
* {@link ServerSocket#send} method.
|
|
220
221
|
*/
|
|
221
222
|
addListener(channel, callback) {
|
|
222
223
|
if (!this.#listeners.has(channel)) {
|
|
@@ -275,4 +276,4 @@ class Socket {
|
|
|
275
276
|
|
|
276
277
|
}
|
|
277
278
|
|
|
278
|
-
export default
|
|
279
|
+
export default ServerSocket;
|
|
@@ -8,30 +8,39 @@ import {
|
|
|
8
8
|
WebSocketServer,
|
|
9
9
|
} from 'ws';
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import {
|
|
12
|
+
kServerOnSocketConnection,
|
|
13
|
+
kServerIsProtectedRole,
|
|
14
|
+
} from './Server.js';
|
|
15
|
+
import ServerSocket, {
|
|
12
16
|
kSocketTerminate,
|
|
13
|
-
} from './
|
|
17
|
+
} from './ServerSocket.js';
|
|
14
18
|
|
|
15
19
|
// @note - fs.readFileSync creates some cwd() issues...
|
|
16
20
|
import networkLatencyWorker from './audit-network-latency.worker.js';
|
|
17
21
|
|
|
22
|
+
export const kSocketsStart = Symbol('soundworks:sockets-start');
|
|
23
|
+
export const kSocketsStop = Symbol('soundworks:sockets-stop');
|
|
24
|
+
|
|
18
25
|
export const kSocketsRemoveFromAllRooms = Symbol('soundworks:sockets-remove-from-all-rooms');
|
|
19
26
|
export const kSocketsLatencyStatsWorker = Symbol('soundworks:sockets-latency-stats-worker');
|
|
20
27
|
export const kSocketsDebugPreventHeartBeat = Symbol('soundworks:sockets-debug-prevent-heartbeat');
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
|
-
* Manage all {@link
|
|
24
|
-
*
|
|
25
|
-
* _Important: In most cases, you should consider using a {@link client.SharedState}
|
|
26
|
-
* rather than directly using the Socket instance._
|
|
30
|
+
* Manage all {@link ServerSocket} instances.
|
|
27
31
|
*
|
|
28
|
-
* @
|
|
32
|
+
* _Important: In most cases, you should consider using a {@link SharedState}
|
|
33
|
+
* rather than directly using the ServerSocket instance._
|
|
29
34
|
*/
|
|
30
|
-
class
|
|
35
|
+
class ServerSockets {
|
|
36
|
+
#server = null;
|
|
37
|
+
#config = null;
|
|
31
38
|
#wsServer = null;
|
|
32
39
|
#rooms = new Map();
|
|
33
40
|
|
|
34
|
-
constructor() {
|
|
41
|
+
constructor(server, config) {
|
|
42
|
+
this.#server = server;
|
|
43
|
+
this.#config = config;
|
|
35
44
|
// Init special `'*'` room which stores all current connections.
|
|
36
45
|
this.#rooms.set('*', new Set());
|
|
37
46
|
|
|
@@ -43,15 +52,14 @@ class Sockets {
|
|
|
43
52
|
* Initialize sockets, all sockets are added to two rooms by default:
|
|
44
53
|
* - to the room corresponding to the client `role`
|
|
45
54
|
* - to the '*' room that holds all connected sockets
|
|
46
|
-
*
|
|
47
55
|
* @private
|
|
48
56
|
*/
|
|
49
|
-
async
|
|
57
|
+
async [kSocketsStart]() {
|
|
50
58
|
// Audit for network latency estimation, the worker is written in cjs so that we
|
|
51
59
|
// can make builds for Max, move back to modules once Max support modules
|
|
52
60
|
this[kSocketsLatencyStatsWorker] = new Worker(networkLatencyWorker, { eval: true });
|
|
53
61
|
|
|
54
|
-
const auditState = await server.getAuditState();
|
|
62
|
+
const auditState = await this.#server.getAuditState();
|
|
55
63
|
|
|
56
64
|
auditState.onUpdate(updates => {
|
|
57
65
|
if ('averageNetworkLatencyWindow' in updates || 'averageNetworkLatencyPeriod' in updates) {
|
|
@@ -74,27 +82,27 @@ class Sockets {
|
|
|
74
82
|
// Init ws server
|
|
75
83
|
this.#wsServer = new WebSocketServer({
|
|
76
84
|
noServer: true,
|
|
77
|
-
path: `/${config.path}`,
|
|
85
|
+
path: `/${this.#config.path}`,
|
|
78
86
|
});
|
|
79
87
|
|
|
80
88
|
this.#wsServer.on('connection', (ws, req) => {
|
|
81
89
|
const { role, token } = querystring.parse(req.url.split('?')[1]);
|
|
82
|
-
const socket = new
|
|
90
|
+
const socket = new ServerSocket(ws, this);
|
|
83
91
|
|
|
84
92
|
socket.addToRoom('*');
|
|
85
93
|
socket.addToRoom(role);
|
|
86
94
|
|
|
87
|
-
|
|
95
|
+
this.#server[kServerOnSocketConnection](role, socket, token);
|
|
88
96
|
});
|
|
89
97
|
|
|
90
|
-
// Prevent socket with protected role to connect
|
|
91
|
-
server.httpServer.on('upgrade', async (req, socket, head) => {
|
|
98
|
+
// Prevent socket with protected role to connect if token is invalid
|
|
99
|
+
this.#server.httpServer.on('upgrade', async (req, socket, head) => {
|
|
92
100
|
const { role, token } = querystring.parse(req.url.split('?')[1]);
|
|
93
101
|
|
|
94
|
-
if (server
|
|
102
|
+
if (this.#server[kServerIsProtectedRole](role)) {
|
|
95
103
|
// we don't have any IP in the upgrade request object,
|
|
96
104
|
// so we just check the connection token is pending and valid
|
|
97
|
-
if (!server
|
|
105
|
+
if (!this.#server[kServerIsValidConnectionToken](token)) {
|
|
98
106
|
socket.destroy('not allowed');
|
|
99
107
|
}
|
|
100
108
|
}
|
|
@@ -109,7 +117,7 @@ class Sockets {
|
|
|
109
117
|
* Terminate all existing sockets.
|
|
110
118
|
* @private
|
|
111
119
|
*/
|
|
112
|
-
|
|
120
|
+
[kSocketsStop]() {
|
|
113
121
|
// terminate stat worker thread
|
|
114
122
|
this[kSocketsLatencyStatsWorker].terminate();
|
|
115
123
|
// clean sockets
|
|
@@ -119,6 +127,7 @@ class Sockets {
|
|
|
119
127
|
|
|
120
128
|
/**
|
|
121
129
|
* Remove given socket from all rooms.
|
|
130
|
+
* @private
|
|
122
131
|
*/
|
|
123
132
|
[kSocketsRemoveFromAllRooms](socket) {
|
|
124
133
|
for (let [_, room] of this.#rooms) {
|
|
@@ -129,9 +138,9 @@ class Sockets {
|
|
|
129
138
|
/**
|
|
130
139
|
* Add a socket to a room.
|
|
131
140
|
*
|
|
132
|
-
* _Note that in most cases, you should use a
|
|
141
|
+
* _Note that in most cases, you should use a {@link SharedState} instead_
|
|
133
142
|
*
|
|
134
|
-
* @param {
|
|
143
|
+
* @param {ServerSocket} socket - Socket to add to the room.
|
|
135
144
|
* @param {String} roomId - Id of the room.
|
|
136
145
|
*/
|
|
137
146
|
addToRoom(socket, roomId) {
|
|
@@ -146,9 +155,9 @@ class Sockets {
|
|
|
146
155
|
/**
|
|
147
156
|
* Remove a socket from a room.
|
|
148
157
|
*
|
|
149
|
-
* _Note that in most cases, you should use a
|
|
158
|
+
* _Note that in most cases, you should use a {@link SharedState} instead_
|
|
150
159
|
*
|
|
151
|
-
* @param {
|
|
160
|
+
* @param {ServerSocket} socket - Socket to remove from the room.
|
|
152
161
|
* @param {String} roomId - Id of the room.
|
|
153
162
|
*/
|
|
154
163
|
removeFromRoom(socket, roomId) {
|
|
@@ -162,11 +171,11 @@ class Sockets {
|
|
|
162
171
|
* Send a message to all clients os given room(s). If no room is specified,
|
|
163
172
|
* the message is sent to all clients.
|
|
164
173
|
*
|
|
165
|
-
* _Note that in most cases, you should use a
|
|
174
|
+
* _Note that in most cases, you should use a {@link SharedState} instead_
|
|
166
175
|
*
|
|
167
176
|
* @param {String|Array} roomsIds - Ids of the rooms that must receive
|
|
168
177
|
* the message. If `null` the message is sent to all clients.
|
|
169
|
-
* @param {
|
|
178
|
+
* @param {ServerSocket} excludeSocket - Optionnal socket to ignore when
|
|
170
179
|
* broadcasting the message, typically the client at the origin of the message.
|
|
171
180
|
* @param {String} channel - Channel name.
|
|
172
181
|
* @param {...*} args - Payload of the message. As many arguments as needed, of
|
|
@@ -198,4 +207,4 @@ class Sockets {
|
|
|
198
207
|
}
|
|
199
208
|
}
|
|
200
209
|
|
|
201
|
-
export default
|
|
210
|
+
export default ServerSockets;
|