@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { idGenerator } from '@ircam/sc-utils';
|
|
2
|
+
import { v4 as uuid } from 'uuid';
|
|
3
|
+
|
|
4
|
+
const generateId = idGenerator();
|
|
5
|
+
|
|
6
|
+
export const kServerClientToken = Symbol('soundworks:server-client-token');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Server-side representation of a `soundworks` client.
|
|
10
|
+
*
|
|
11
|
+
* @hideconstructor
|
|
12
|
+
* @see {@link Client}
|
|
13
|
+
*/
|
|
14
|
+
class ServerClient {
|
|
15
|
+
#role = null;
|
|
16
|
+
#id = null;
|
|
17
|
+
#uuid = null;
|
|
18
|
+
#socket = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {String} role - Role of the client
|
|
22
|
+
* @param {ServerSocket} socket - Socket connection with the client
|
|
23
|
+
*/
|
|
24
|
+
constructor(role, socket) {
|
|
25
|
+
this.#role = role;
|
|
26
|
+
this.#id = generateId.next().value;
|
|
27
|
+
this.#uuid = uuid();
|
|
28
|
+
this.#socket = socket;
|
|
29
|
+
/**
|
|
30
|
+
* Is set in server[kServerOnSocketConnection]
|
|
31
|
+
* @private
|
|
32
|
+
*/
|
|
33
|
+
this[kServerClientToken] = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Client role, as specified in client side config {@link Client}.
|
|
38
|
+
*
|
|
39
|
+
* @type {String}
|
|
40
|
+
*/
|
|
41
|
+
get role() {
|
|
42
|
+
return this.#role;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Session Id (incremented positive number).
|
|
47
|
+
*
|
|
48
|
+
* @type {Number}
|
|
49
|
+
*/
|
|
50
|
+
get id() {
|
|
51
|
+
return this.#id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Unique session Id (uuidv4).
|
|
56
|
+
*
|
|
57
|
+
* @type {String}
|
|
58
|
+
*/
|
|
59
|
+
get uuid() {
|
|
60
|
+
return this.#uuid;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Socket connection with the remote {@link Client}.
|
|
65
|
+
*
|
|
66
|
+
* @type {ServerSocket}
|
|
67
|
+
*/
|
|
68
|
+
get socket() {
|
|
69
|
+
return this.#socket;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default ServerClient;
|
|
@@ -1,31 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ServerClient from './ServerClient.js';
|
|
2
2
|
import Server from './Server.js';
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
kServerContextManagerRegister,
|
|
6
|
+
} from './ServerContextManager.js';
|
|
7
|
+
|
|
8
|
+
export const kServerContextStatus = Symbol('soundworks:server-context-status');
|
|
9
|
+
|
|
4
10
|
/**
|
|
5
11
|
* Base class to extend in order to implment the optionnal server-side counterpart
|
|
6
|
-
* of a {@link
|
|
12
|
+
* of a {@link ClientContext}. If not defined, a default context will be created
|
|
7
13
|
* and used by the server.
|
|
8
14
|
*
|
|
9
15
|
* In the `soundworks` paradigm, a client has a "role" (e.g. _player_, _controller_)
|
|
10
|
-
* see {@link
|
|
16
|
+
* see {@link Client#role}) and can be in different "contexts" (e.g. different
|
|
11
17
|
* part of the experience such as sections of a music piece, etc.). The
|
|
12
|
-
* {@link
|
|
18
|
+
* {@link ClientContext} and optionnal {@link ServerContext} abstractions provide
|
|
13
19
|
* a simple and unified way to model these reccuring aspects of an application.
|
|
14
20
|
*
|
|
15
|
-
* If a `
|
|
16
|
-
* {@link
|
|
17
|
-
* and {@link
|
|
18
|
-
* by the
|
|
19
|
-
* {@link
|
|
20
|
-
* show how soundwords handles (and guarantees) the order of the `enter()` steps
|
|
21
|
-
* between the client-side and the server-side parts of the context. The same goes
|
|
22
|
-
* for the `exit()` method.
|
|
21
|
+
* If a `ServerContext` is recognized as the server-side counterpart of a
|
|
22
|
+
* {@link ClientContext}, based on their respective `name` (see {@link ClientContext#name}
|
|
23
|
+
* and {@link ServerContext#name}), `soundworks` will ensure the logic defined
|
|
24
|
+
* by the ServerContext will be executed at the beginning of the
|
|
25
|
+
* {@link ClientContext#enter} and {@link ClientContext#exit} methods.
|
|
23
26
|
*
|
|
24
|
-
*
|
|
27
|
+
* The example above shows how soundwords handles (and guarantees) the order of
|
|
28
|
+
* the `enter()` steps between the client-side and the server-side parts of the
|
|
29
|
+
* context. The same goes for the `exit()` method.
|
|
30
|
+
*
|
|
31
|
+
* ```js
|
|
25
32
|
* // client-side
|
|
26
|
-
* import {
|
|
33
|
+
* import { ClientContext } from '@soundworks/core/client.js';
|
|
27
34
|
*
|
|
28
|
-
* class MyContext extends
|
|
35
|
+
* class MyContext extends ClientContext {
|
|
29
36
|
* async enter() {
|
|
30
37
|
* // 1. client side context enter() starts
|
|
31
38
|
* // server-side logic is triggered first
|
|
@@ -46,11 +53,11 @@ import Server from './Server.js';
|
|
|
46
53
|
* await myContext.enter();
|
|
47
54
|
* ```
|
|
48
55
|
*
|
|
49
|
-
* ```
|
|
56
|
+
* ```js
|
|
50
57
|
* // server-side
|
|
51
|
-
* import {
|
|
58
|
+
* import { ServerContext } from '@soundworks/core/server.js';
|
|
52
59
|
*
|
|
53
|
-
* class MyContext extends
|
|
60
|
+
* class MyContext extends ServerContext {
|
|
54
61
|
* async enter(client) {
|
|
55
62
|
* // 2. server-side context enter() starts
|
|
56
63
|
* await super.enter(client);
|
|
@@ -61,14 +68,16 @@ import Server from './Server.js';
|
|
|
61
68
|
* }
|
|
62
69
|
*
|
|
63
70
|
* // Instantiate the context
|
|
64
|
-
* const myContext = new
|
|
71
|
+
* const myContext = new MyContext(server);
|
|
65
72
|
* ```
|
|
66
|
-
*
|
|
67
|
-
* @memberof server
|
|
68
73
|
*/
|
|
69
|
-
class
|
|
74
|
+
class ServerContext {
|
|
75
|
+
#server = null;
|
|
76
|
+
#clients = new Set();
|
|
77
|
+
#roles = null;
|
|
78
|
+
|
|
70
79
|
/**
|
|
71
|
-
* @param {
|
|
80
|
+
* @param {Server} server - The soundworks server instance.
|
|
72
81
|
* @param {string|string[]} [roles=[]] - Optionnal list of client roles that can
|
|
73
82
|
* use this context. In large applications, this may be usefull to guarantee
|
|
74
83
|
* that a context can be consumed only by specific client roles, throwing an
|
|
@@ -81,52 +90,62 @@ class Context {
|
|
|
81
90
|
throw new Error(`[soundworks:Context] Invalid argument, context "${this.constructor.name}" should receive a "soundworks.Server" instance as first argument`);
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
/**
|
|
85
|
-
* soundworks server
|
|
86
|
-
* @type {server.Server}
|
|
87
|
-
* @readonly
|
|
88
|
-
*/
|
|
89
|
-
this.server = server;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* List of clients that are currently in this context
|
|
93
|
-
* @type {server.Client[]}
|
|
94
|
-
*/
|
|
95
|
-
this.clients = new Set();
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Status of the context ('idle', 'inited', 'started' or 'errored')
|
|
99
|
-
* @type {string}
|
|
100
|
-
* @readonly
|
|
101
|
-
*/
|
|
102
|
-
this.status = 'idle';
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* List of client roles that can use this context.
|
|
106
|
-
* @type {string[]}
|
|
107
|
-
* @readonly
|
|
108
|
-
*/
|
|
109
93
|
roles = Array.isArray(roles) ? roles : [roles];
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.roles = new Set(roles);
|
|
95
|
+
this.#server = server;
|
|
96
|
+
this.#roles = new Set(roles);
|
|
116
97
|
|
|
98
|
+
/** @private */
|
|
99
|
+
this[kServerContextStatus] = 'idle';
|
|
117
100
|
// register in context manager
|
|
118
|
-
this
|
|
101
|
+
this.#server.contextManager[kServerContextManagerRegister](this);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The soundworks server instance.
|
|
106
|
+
*
|
|
107
|
+
* @type {Server}
|
|
108
|
+
*/
|
|
109
|
+
get server() {
|
|
110
|
+
return this.#server;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* List of clients that are currently in this context.
|
|
115
|
+
*
|
|
116
|
+
* @type {Set<ServerClient>}
|
|
117
|
+
*/
|
|
118
|
+
get clients() {
|
|
119
|
+
return this.#clients;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* List of client roles that can use this context. No access policy if empty.
|
|
124
|
+
*
|
|
125
|
+
* @type {Set<string>}
|
|
126
|
+
*/
|
|
127
|
+
get roles() {
|
|
128
|
+
return this.#roles;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Status of the context ('idle', 'inited', 'started' or 'errored')
|
|
133
|
+
*
|
|
134
|
+
* @type {string}
|
|
135
|
+
*/
|
|
136
|
+
get status() {
|
|
137
|
+
return this[kServerContextStatus];
|
|
119
138
|
}
|
|
120
139
|
|
|
121
140
|
/**
|
|
122
141
|
* Optionnal user-defined name of the context (defaults to the class name).
|
|
123
142
|
*
|
|
124
143
|
* The context manager will match the client-side and server-side contexts based
|
|
125
|
-
* on this name. If the {@link
|
|
144
|
+
* on this name. If the {@link ServerContextManager} don't find a corresponding
|
|
126
145
|
* user-defined context with the same name, it will use a default (noop) context.
|
|
127
146
|
*
|
|
128
|
-
* @readonly
|
|
129
147
|
* @type {string}
|
|
148
|
+
*
|
|
130
149
|
* @example
|
|
131
150
|
* // server-side and client-side contexts are matched based on their respective `name`
|
|
132
151
|
* class MyContext extends Context {
|
|
@@ -141,7 +160,7 @@ class Context {
|
|
|
141
160
|
|
|
142
161
|
/**
|
|
143
162
|
* Start the context. This method is lazilly called when a client enters the
|
|
144
|
-
* context for the first time (cf. ${
|
|
163
|
+
* context for the first time (cf. ${ServerContext#enter}). If you know some
|
|
145
164
|
* some heavy and/or potentially long job has to be done when starting the context
|
|
146
165
|
* (e.g. connect to a database, parsing a long file) it may be a good practice
|
|
147
166
|
* to call it explicitely.
|
|
@@ -185,7 +204,7 @@ class Context {
|
|
|
185
204
|
*
|
|
186
205
|
* _WARNING: this method should never be called manually._
|
|
187
206
|
*
|
|
188
|
-
* @param {
|
|
207
|
+
* @param {ServerClient} client - Server-side representation of the client
|
|
189
208
|
* that enters the context.
|
|
190
209
|
* @returns {Promise} - Promise that resolves when the context is entered.
|
|
191
210
|
* @example
|
|
@@ -202,11 +221,11 @@ class Context {
|
|
|
202
221
|
* }
|
|
203
222
|
*/
|
|
204
223
|
async enter(client) {
|
|
205
|
-
if (!(client instanceof
|
|
206
|
-
throw new Error(`[soundworks.Context] Invalid argument, ${this.name} context ".enter()" method should receive a
|
|
224
|
+
if (!(client instanceof ServerClient)) {
|
|
225
|
+
throw new Error(`[soundworks.Context] Invalid argument, ${this.name} context ".enter()" method should receive a "ServerClient" instance argument`);
|
|
207
226
|
}
|
|
208
227
|
|
|
209
|
-
this
|
|
228
|
+
this.#clients.add(client);
|
|
210
229
|
}
|
|
211
230
|
|
|
212
231
|
/**
|
|
@@ -215,7 +234,7 @@ class Context {
|
|
|
215
234
|
*
|
|
216
235
|
* * _WARNING: this method should never be called manually._
|
|
217
236
|
*
|
|
218
|
-
* @param {
|
|
237
|
+
* @param {ServerClient} client - Server-side representation of the client
|
|
219
238
|
* that exits the context.
|
|
220
239
|
* @returns {Promise} - Promise that resolves when the context is exited.
|
|
221
240
|
* @example
|
|
@@ -232,12 +251,12 @@ class Context {
|
|
|
232
251
|
* }
|
|
233
252
|
*/
|
|
234
253
|
async exit(client) {
|
|
235
|
-
if (!(client instanceof
|
|
236
|
-
throw new Error(`[soundworks.Context] Invalid argument, ${this.name}.exit() should receive a
|
|
254
|
+
if (!(client instanceof ServerClient)) {
|
|
255
|
+
throw new Error(`[soundworks.Context] Invalid argument, ${this.name}.exit() should receive a "ServerClient" instance argument`);
|
|
237
256
|
}
|
|
238
257
|
|
|
239
|
-
this
|
|
258
|
+
this.#clients.delete(client);
|
|
240
259
|
}
|
|
241
260
|
}
|
|
242
261
|
|
|
243
|
-
export default
|
|
262
|
+
export default ServerContext;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ServerContext, {
|
|
2
|
+
kServerContextStatus,
|
|
3
|
+
} from './ServerContext.js';
|
|
2
4
|
import {
|
|
3
5
|
CONTEXT_ENTER_REQUEST,
|
|
4
6
|
CONTEXT_ENTER_RESPONSE,
|
|
@@ -8,15 +10,22 @@ import {
|
|
|
8
10
|
CONTEXT_EXIT_ERROR,
|
|
9
11
|
} from '../common/constants.js';
|
|
10
12
|
|
|
13
|
+
export const kServerContextManagerStart = Symbol('soundworks:server-context-manager-start');
|
|
14
|
+
export const kServerContextManagerStop = Symbol('soundworks:server-context-manager-stop');
|
|
15
|
+
export const kServerContextManagerAddClient = Symbol('soundworks:server-context-manager-add-client');
|
|
16
|
+
export const kServerContextManagerRemoveClient = Symbol('soundworks:server-context-manager-remove-client');
|
|
17
|
+
export const kServerContextManagerRegister = Symbol('soundworks:server-context-manager-register');
|
|
18
|
+
// for testing purposes
|
|
19
|
+
export const kServerContextManagerContexts = Symbol('soundworks:server-context-manager-contexts');
|
|
20
|
+
|
|
11
21
|
/**
|
|
12
22
|
* Create a dummy server side context if a proper server-side context has not
|
|
13
|
-
* been declared and registered
|
|
23
|
+
* been declared and registered. Ane DefaultContext is created per unknown
|
|
14
24
|
* contextName and associated to all known client types.
|
|
15
|
-
*
|
|
16
25
|
* @private
|
|
17
26
|
*/
|
|
18
27
|
function createNamedContextClass(contextName) {
|
|
19
|
-
return class DefaultContext extends
|
|
28
|
+
return class DefaultContext extends ServerContext {
|
|
20
29
|
get name() {
|
|
21
30
|
return contextName;
|
|
22
31
|
}
|
|
@@ -25,119 +34,110 @@ function createNamedContextClass(contextName) {
|
|
|
25
34
|
|
|
26
35
|
/** @private */
|
|
27
36
|
class ContextCollection {
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
#inner = [];
|
|
38
|
+
|
|
39
|
+
// for testing purposes
|
|
40
|
+
get length() {
|
|
41
|
+
return this.#inner.length;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
add(context) {
|
|
33
|
-
this
|
|
45
|
+
this.#inner.push(context);
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
has(name) {
|
|
37
|
-
return this
|
|
49
|
+
return this.#inner.find(c => c.name === name) !== undefined;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
get(name) {
|
|
41
|
-
return this
|
|
53
|
+
return this.#inner.find(c => c.name === name);
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
map(func) {
|
|
45
|
-
return this
|
|
57
|
+
return this.#inner.map(func);
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
filter(func) {
|
|
49
|
-
return this
|
|
61
|
+
return this.#inner.filter(func);
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
/**
|
|
54
|
-
* Manage the different server-side contexts and their lifecycle.
|
|
55
|
-
*
|
|
66
|
+
* Manage the different server-side contexts and their lifecycle.
|
|
67
|
+
*
|
|
68
|
+
* The `ServerContextManager` is automatically instantiated by the {@link Server}.
|
|
56
69
|
*
|
|
57
70
|
* _WARNING: Most of the time, you should not have to manipulate the context manager directly._
|
|
58
71
|
*
|
|
59
|
-
* @memberof server
|
|
60
72
|
* @hideconstructor
|
|
61
73
|
*/
|
|
62
|
-
class
|
|
74
|
+
class ServerContextManager {
|
|
75
|
+
#server = null;
|
|
76
|
+
|
|
77
|
+
#contextStartPromises = new Map();
|
|
78
|
+
|
|
63
79
|
/**
|
|
64
|
-
* @param {
|
|
80
|
+
* @param {Server} server - Instance of the soundworks server.
|
|
65
81
|
*/
|
|
66
82
|
constructor(server) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this._contexts = new ContextCollection();
|
|
71
|
-
/** @private */
|
|
72
|
-
this._contextStartPromises = new Map();
|
|
83
|
+
this.#server = server;
|
|
84
|
+
|
|
85
|
+
this[kServerContextManagerContexts] = new ContextCollection();
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
/**
|
|
76
89
|
* Register a context in the manager.
|
|
77
|
-
* This method is called in the {@link
|
|
90
|
+
* This method is called in the {@link ServerContext} constructor
|
|
78
91
|
*
|
|
79
|
-
* @param {
|
|
92
|
+
* @param {ServerContext} context - Context instance to register.
|
|
80
93
|
*
|
|
81
94
|
* @private
|
|
82
95
|
*/
|
|
83
|
-
|
|
96
|
+
[kServerContextManagerRegister](context) {
|
|
84
97
|
// we must await the contructor initialization end to check the name and throw
|
|
85
|
-
if (this.
|
|
86
|
-
throw new Error(`[soundworks:
|
|
98
|
+
if (this[kServerContextManagerContexts].has(context.name)) {
|
|
99
|
+
throw new Error(`[soundworks:ServerContextManager] Context "${context.name}" already registered`);
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
this.
|
|
102
|
+
this[kServerContextManagerContexts].add(context);
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* _WARNING: Most of the time, you should not have to call this method manually._
|
|
106
|
+
* Start all contexts registered before `await server.start()`.
|
|
107
|
+
* Called on {@link Server#start}
|
|
96
108
|
*
|
|
97
|
-
* @
|
|
109
|
+
* @private
|
|
98
110
|
*/
|
|
99
|
-
async
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const context = this._contexts.get(contextName);
|
|
105
|
-
|
|
106
|
-
if (this._contextStartPromises.has(contextName)) {
|
|
107
|
-
const startPromise = this._contextStartPromises.get(contextName);
|
|
108
|
-
await startPromise;
|
|
109
|
-
} else {
|
|
110
|
-
context.status = 'inited';
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
const startPromise = context.start();
|
|
114
|
-
this._contextStartPromises.set(contextName, startPromise);
|
|
115
|
-
await startPromise;
|
|
116
|
-
context.status = 'started';
|
|
117
|
-
} catch (err) {
|
|
118
|
-
context.status = 'errored';
|
|
119
|
-
throw new Error(err);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
111
|
+
async [kServerContextManagerStart]() {
|
|
112
|
+
const promises = this[kServerContextManagerContexts].map(context => this.get(context.name));
|
|
113
|
+
await Promise.all(promises);
|
|
114
|
+
}
|
|
122
115
|
|
|
123
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Stop all contexts. Called on {@link Server#stop}
|
|
118
|
+
*
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
async [kServerContextManagerStop]() {
|
|
122
|
+
const promises = this[kServerContextManagerContexts].map(context => context.stop());
|
|
123
|
+
await Promise.all(promises);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* Called when a client connects to the server (websocket handshake)
|
|
128
128
|
*
|
|
129
|
-
* @param {
|
|
129
|
+
* @param {ServerClient} client
|
|
130
130
|
*
|
|
131
131
|
* @private
|
|
132
132
|
*/
|
|
133
|
-
|
|
133
|
+
[kServerContextManagerAddClient](client) {
|
|
134
134
|
client.socket.addListener(CONTEXT_ENTER_REQUEST, async (reqId, contextName) => {
|
|
135
135
|
// if no context found, create a DefaultContext on the fly
|
|
136
|
-
if (!this.
|
|
136
|
+
if (!this[kServerContextManagerContexts].has(contextName)) {
|
|
137
137
|
// create default context for all client types
|
|
138
138
|
const ctor = createNamedContextClass(contextName);
|
|
139
139
|
// this will automatically register the context in the context manager
|
|
140
|
-
new ctor(this
|
|
140
|
+
new ctor(this.#server);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// we ensure context is started, even lazilly after server.start()
|
|
@@ -161,7 +161,7 @@ class ContextManager {
|
|
|
161
161
|
CONTEXT_ENTER_ERROR,
|
|
162
162
|
reqId,
|
|
163
163
|
contextName,
|
|
164
|
-
`[soundworks:
|
|
164
|
+
`[soundworks:ServerContextManager] Client already in context (if only one context is created .enter() has been called automatically)`,
|
|
165
165
|
);
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
@@ -171,12 +171,12 @@ class ContextManager {
|
|
|
171
171
|
context = this.defaultContext;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (!context.roles.has(client.role)) {
|
|
174
|
+
if (context.roles.size > 0 && !context.roles.has(client.role)) {
|
|
175
175
|
client.socket.send(
|
|
176
176
|
CONTEXT_ENTER_ERROR,
|
|
177
177
|
reqId,
|
|
178
178
|
contextName,
|
|
179
|
-
`[soundworks:
|
|
179
|
+
`[soundworks:ServerContextManager] Clients with role "${client.role}" are not declared as possible consumers of context "${contextName}"`,
|
|
180
180
|
);
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
@@ -191,12 +191,12 @@ class ContextManager {
|
|
|
191
191
|
});
|
|
192
192
|
|
|
193
193
|
client.socket.addListener(CONTEXT_EXIT_REQUEST, async (reqId, contextName) => {
|
|
194
|
-
if (!this.
|
|
194
|
+
if (!this[kServerContextManagerContexts].has(contextName)) {
|
|
195
195
|
client.socket.send(
|
|
196
196
|
CONTEXT_EXIT_ERROR,
|
|
197
197
|
reqId,
|
|
198
198
|
contextName,
|
|
199
|
-
`[soundworks:
|
|
199
|
+
`[soundworks:ServerContextManager] Cannot exit(), context ${contextName} does not exists`,
|
|
200
200
|
);
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
@@ -215,7 +215,7 @@ class ContextManager {
|
|
|
215
215
|
CONTEXT_EXIT_ERROR,
|
|
216
216
|
reqId,
|
|
217
217
|
contextName,
|
|
218
|
-
`[soundworks:
|
|
218
|
+
`[soundworks:ServerContextManager] Client with role "${client.role}" is not in context "${contextName}"`,
|
|
219
219
|
);
|
|
220
220
|
}
|
|
221
221
|
});
|
|
@@ -224,16 +224,16 @@ class ContextManager {
|
|
|
224
224
|
/**
|
|
225
225
|
* Called when a client connects to the server (websocket 'close' event)
|
|
226
226
|
*
|
|
227
|
-
* @param {
|
|
227
|
+
* @param {ServerClient} client
|
|
228
228
|
*
|
|
229
229
|
* @private
|
|
230
230
|
*/
|
|
231
|
-
async
|
|
231
|
+
async [kServerContextManagerRemoveClient](client) {
|
|
232
232
|
client.socket.removeAllListeners(CONTEXT_ENTER_REQUEST);
|
|
233
233
|
client.socket.removeAllListeners(CONTEXT_EXIT_REQUEST);
|
|
234
234
|
|
|
235
235
|
// exit from all contexts
|
|
236
|
-
const promises = this
|
|
236
|
+
const promises = this[kServerContextManagerContexts]
|
|
237
237
|
.filter(context => context.clients.has(client))
|
|
238
238
|
.map(context => context.exit(client));
|
|
239
239
|
|
|
@@ -241,25 +241,38 @@ class ContextManager {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
|
-
*
|
|
245
|
-
* Called on {@link server.Server#start}
|
|
244
|
+
* Retrieve a started context from its name.
|
|
246
245
|
*
|
|
247
|
-
*
|
|
248
|
-
*/
|
|
249
|
-
async start() {
|
|
250
|
-
const promises = this._contexts.map(context => this.get(context.name));
|
|
251
|
-
await Promise.all(promises);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Stop all contexts. Called on {@link server.Server#stop}
|
|
246
|
+
* _WARNING: Most of the time, you should not have to call this method manually._
|
|
256
247
|
*
|
|
257
|
-
* @
|
|
248
|
+
* @param {ServerContext#name} contextName - Name of the context.
|
|
258
249
|
*/
|
|
259
|
-
async
|
|
260
|
-
|
|
261
|
-
|
|
250
|
+
async get(contextName) {
|
|
251
|
+
if (!this[kServerContextManagerContexts].has(contextName)) {
|
|
252
|
+
throw new Error(`[soundworks:ServerContextManager] Can't get context "${contextName}", not registered`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const context = this[kServerContextManagerContexts].get(contextName);
|
|
256
|
+
|
|
257
|
+
if (this.#contextStartPromises.has(contextName)) {
|
|
258
|
+
const startPromise = this.#contextStartPromises.get(contextName);
|
|
259
|
+
await startPromise;
|
|
260
|
+
} else {
|
|
261
|
+
context[kServerContextStatus] = 'inited';
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const startPromise = context.start();
|
|
265
|
+
this.#contextStartPromises.set(contextName, startPromise);
|
|
266
|
+
await startPromise;
|
|
267
|
+
context[kServerContextStatus] = 'started';
|
|
268
|
+
} catch (err) {
|
|
269
|
+
context[kServerContextStatus] = 'errored';
|
|
270
|
+
throw new Error(err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return context;
|
|
262
275
|
}
|
|
263
276
|
}
|
|
264
277
|
|
|
265
|
-
export default
|
|
278
|
+
export default ServerContextManager;
|