@soundworks/core 4.0.0-alpha.0 → 4.0.0-alpha.2
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/README.md +26 -16
- package/package.json +2 -2
- package/src/client/Client.js +37 -3
- package/src/client/Context.js +37 -10
- package/src/client/Plugin.js +2 -1
- package/src/client/PluginManager.js +26 -17
- package/src/client/Socket.js +44 -39
- package/src/client/index.js +0 -33
- package/src/common/BasePluginManager.js +12 -3
- package/src/common/ParameterBag.js +7 -196
- package/src/server/Context.js +162 -18
- package/src/server/ContextManager.js +4 -5
- package/src/server/Plugin.js +2 -1
- package/src/server/PluginManager.js +26 -6
- package/src/server/Server.js +185 -69
- package/src/server/Socket.js +105 -62
- package/src/server/Sockets.js +27 -22
- package/src/server/StateManager.js +223 -24
- package/src/server/index.js +0 -20
package/src/server/Server.js
CHANGED
|
@@ -31,6 +31,27 @@ import {
|
|
|
31
31
|
|
|
32
32
|
let _dbNamespaces = new Set();
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Configuration object for the server.
|
|
36
|
+
*
|
|
37
|
+
* @typedef ServerConfig
|
|
38
|
+
* @memberof server
|
|
39
|
+
* @type {object}
|
|
40
|
+
* @property {object} [app] - Application configration object.
|
|
41
|
+
* @property {object} app.clients - Definition of the application clients.
|
|
42
|
+
* @property {string} [app.name=''] - Name of the application.
|
|
43
|
+
* @property {string} [app.author=''] - Name of the author.
|
|
44
|
+
* @property {object} [env] - Environment configration object.
|
|
45
|
+
* @property {boolean} env.port - Port on which the server is listening.
|
|
46
|
+
* @property {boolean} env.useHttps - Define is the server run in http or in https.
|
|
47
|
+
* @property {boolean} [env.httpsInfos={}] - Path to cert files for https.
|
|
48
|
+
* @property {boolean} env.serverAddress - Domain name or IP of the server.
|
|
49
|
+
* Mandatory if node clients are defined
|
|
50
|
+
* @property {string} [env.websockets={}] - Configuration options for websockets.
|
|
51
|
+
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/** @private */
|
|
34
55
|
const DEFAULT_CONFIG = {
|
|
35
56
|
env: {
|
|
36
57
|
type: 'development',
|
|
@@ -74,50 +95,88 @@ function setTerminalTitle(server) {
|
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
/**
|
|
77
|
-
* Server
|
|
78
|
-
*
|
|
79
|
-
* This object hosts configuration informations, as well as methods to
|
|
80
|
-
* initialize and start the application. It is also responsible for creating
|
|
81
|
-
* the static file (http) server as well as the socket server.
|
|
98
|
+
* The `Server` class is the main entry point for the server-side of a soundworks
|
|
99
|
+
* application.
|
|
82
100
|
*
|
|
83
|
-
* @
|
|
101
|
+
* The `Server` instance allows to access soundworks components such as {@link server.StateManager},
|
|
102
|
+
* {@link server.PluginManager},{@link server.Socket} or {@link server.ContextManager}.
|
|
103
|
+
* Its is also responsible for handling the initialization lifecycles of the different
|
|
104
|
+
* soundworks components.
|
|
84
105
|
*
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
106
|
+
* ```
|
|
87
107
|
* import { Server } from '@soundworks/core/server';
|
|
108
|
+
*
|
|
88
109
|
* const server = new Server({
|
|
89
110
|
* app: {
|
|
90
111
|
* name: 'my-example-app',
|
|
91
|
-
* clients: {
|
|
112
|
+
* clients: {
|
|
113
|
+
* player: { target: 'browser', default: true },
|
|
114
|
+
* controller: { target: 'browser' },
|
|
115
|
+
* thing: { target: 'node' }
|
|
116
|
+
* },
|
|
92
117
|
* },
|
|
93
118
|
* env: {
|
|
94
|
-
* port:
|
|
119
|
+
* port: 8000,
|
|
95
120
|
* },
|
|
96
121
|
* });
|
|
97
|
-
*
|
|
122
|
+
*
|
|
98
123
|
* await server.start();
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* According to the clients definitions provided in `config.app.clients`, the
|
|
127
|
+
* server will automatically create a dedicated route for each browser client role.
|
|
128
|
+
* For example, given the config object of the example above that defines two
|
|
129
|
+
* different client roles for browser targets (i.e. `player` and `controller`):
|
|
130
|
+
*
|
|
131
|
+
* ```
|
|
132
|
+
* config.app.clients = {
|
|
133
|
+
* player: { target: 'browser', default: true },
|
|
134
|
+
* controller: { target: 'browser' },
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* The server will listen to the following URLs:
|
|
139
|
+
* - `http://127.0.0.1:8000/` for the `player` role, which is defined as the default client.
|
|
140
|
+
* - `http://127.0.0.1:8000/controller` for the `controller` role.
|
|
141
|
+
*
|
|
142
|
+
* @memberof server
|
|
99
143
|
*/
|
|
100
144
|
class Server {
|
|
145
|
+
/**
|
|
146
|
+
* @param {server.ServerConfig} config - Configuration object for the server.
|
|
147
|
+
* @throws
|
|
148
|
+
* - If `config.app.clients` is empty.
|
|
149
|
+
* - If a `node` client is defined but `config.env.serverAddress` is not defined.
|
|
150
|
+
* - if `config.env.useHttps` is `true` and `config.env.httpsInfos` is not `null`
|
|
151
|
+
* (which generates self signed certificated), `config.env.httpsInfos.cert` and
|
|
152
|
+
* `config.env.httpsInfos.key` should point to valid cert files.
|
|
153
|
+
*/
|
|
101
154
|
constructor(config) {
|
|
102
155
|
if (!isPlainObject(config)) {
|
|
103
156
|
throw new Error(`[soundworks:Server] Invalid argument for Server constructor, config should be an object`);
|
|
104
157
|
}
|
|
105
158
|
/**
|
|
106
|
-
*
|
|
107
|
-
* Defaults to:
|
|
159
|
+
* Given config object merged with the following defaults:
|
|
108
160
|
* ```
|
|
109
161
|
* {
|
|
110
162
|
* env: {
|
|
111
163
|
* type: 'development',
|
|
112
164
|
* port: 8000,
|
|
113
|
-
*
|
|
165
|
+
* serverAddress: null,
|
|
166
|
+
* subpath: '',
|
|
167
|
+
* websockets: {
|
|
168
|
+
* path: 'socket',
|
|
169
|
+
* pingInterval: 5000,
|
|
170
|
+
* },
|
|
114
171
|
* useHttps: false,
|
|
115
172
|
* httpsInfos: null,
|
|
173
|
+
* crossOriginIsolated: true,
|
|
174
|
+
* verbose: true,
|
|
116
175
|
* },
|
|
117
176
|
* app: {
|
|
118
177
|
* name: 'soundworks',
|
|
119
178
|
* clients: {},
|
|
120
|
-
* }
|
|
179
|
+
* }
|
|
121
180
|
* }
|
|
122
181
|
* ```
|
|
123
182
|
*/
|
|
@@ -161,67 +220,90 @@ class Server {
|
|
|
161
220
|
}
|
|
162
221
|
|
|
163
222
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
223
|
+
* Instance of the express router.
|
|
224
|
+
*
|
|
225
|
+
* The router can be used to open new route, for example to expose a directory
|
|
226
|
+
* of static assets (in default soundworks applications only the `public` is exposed).
|
|
227
|
+
*
|
|
228
|
+
* @see {@link https://github.com/expressjs/express}
|
|
229
|
+
* @example
|
|
230
|
+
* import { Server } from '@soundworks/core/server.js';
|
|
231
|
+
* import express from 'express';
|
|
232
|
+
*
|
|
233
|
+
* // create the soundworks server instance
|
|
234
|
+
* const server = new Server(config);
|
|
235
|
+
*
|
|
236
|
+
* // expose assets located in the `soundfiles` directory on the network
|
|
237
|
+
* server.router.use('/soundfiles', express.static('soundfiles')));
|
|
166
238
|
*/
|
|
167
239
|
this.router = express();
|
|
168
240
|
// compression (must be set before serve-static)
|
|
169
241
|
this.router.use(compression());
|
|
170
242
|
|
|
171
243
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
244
|
+
* Raw Node.js `http` or `https` instance
|
|
245
|
+
*
|
|
246
|
+
* @see {@link https://nodejs.org/api/http.html}
|
|
247
|
+
* @see {@link https://nodejs.org/api/https.html}
|
|
174
248
|
*/
|
|
175
249
|
this.httpServer = null;
|
|
176
250
|
|
|
177
251
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* @private
|
|
181
|
-
*/
|
|
182
|
-
this.db = this.createNamespacedDb('core');
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* The {@link server.Sockets} instance. A small wrapper around
|
|
186
|
-
* [`ws`](https://github.com/websockets/ws) server.
|
|
252
|
+
* Instance of the {@link server.Sockets} class.
|
|
253
|
+
*
|
|
187
254
|
* @see {@link server.Sockets}
|
|
188
255
|
* @type {server.Sockets}
|
|
189
256
|
*/
|
|
190
257
|
this.sockets = new Sockets();
|
|
191
258
|
|
|
192
259
|
/**
|
|
193
|
-
*
|
|
260
|
+
* Instance of the {@link server.PluginManager} class.
|
|
261
|
+
*
|
|
194
262
|
* @see {@link server.PluginManager}
|
|
195
263
|
* @type {server.PluginManager}
|
|
196
264
|
*/
|
|
197
265
|
this.pluginManager = new PluginManager(this);
|
|
198
266
|
|
|
199
267
|
/**
|
|
200
|
-
*
|
|
268
|
+
* Instance of the {@link server.StateManager} class.
|
|
269
|
+
*
|
|
201
270
|
* @see {@link server.StateManager}
|
|
202
271
|
* @type {server.StateManager}
|
|
203
272
|
*/
|
|
204
273
|
this.stateManager = new StateManager();
|
|
205
274
|
|
|
206
275
|
/**
|
|
207
|
-
*
|
|
276
|
+
* Instance of the {@link server.ContextManager} class.
|
|
208
277
|
*
|
|
278
|
+
* @see {@link server.ContextManager}
|
|
279
|
+
* @type {server.ContextManager}
|
|
209
280
|
*/
|
|
210
281
|
this.contextManager = new ContextManager(this);
|
|
211
282
|
|
|
212
283
|
/**
|
|
213
|
-
* If https is required,
|
|
214
|
-
*
|
|
284
|
+
* If `https` is required, hold informations about the certificates, e.g. if
|
|
285
|
+
* self-signed, the dates of validity of the certificates, etc.
|
|
215
286
|
*/
|
|
216
287
|
this.httpsInfos = null;
|
|
217
288
|
|
|
218
289
|
/**
|
|
219
|
-
*
|
|
290
|
+
* Status of the server, 'idle', 'inited', 'started' or 'errored'.
|
|
291
|
+
*
|
|
292
|
+
* @type {string}
|
|
220
293
|
*/
|
|
221
294
|
this.status = 'idle';
|
|
222
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Simple key / value database with Promise based Map API store on filesystem,
|
|
298
|
+
* basically a tiny wrapper around the `kvey` package.
|
|
299
|
+
*
|
|
300
|
+
* @private
|
|
301
|
+
* @see {@link https://github.com/lukechilds/keyv}
|
|
302
|
+
*/
|
|
303
|
+
this.db = this.createNamespacedDb('core');
|
|
304
|
+
|
|
223
305
|
/** @private */
|
|
224
|
-
this.
|
|
306
|
+
this._applicationTemplateOptions = {
|
|
225
307
|
templateEngine: null,
|
|
226
308
|
templatePath: null,
|
|
227
309
|
clientConfigFunction: null,
|
|
@@ -232,7 +314,7 @@ class Server {
|
|
|
232
314
|
/** @private */
|
|
233
315
|
this._auditState = null;
|
|
234
316
|
|
|
235
|
-
//
|
|
317
|
+
// register audit state schema
|
|
236
318
|
this.stateManager.registerSchema(AUDIT_STATE_NAME, auditSchema);
|
|
237
319
|
|
|
238
320
|
logger.configure(this.config.env.verbose);
|
|
@@ -240,18 +322,21 @@ class Server {
|
|
|
240
322
|
}
|
|
241
323
|
|
|
242
324
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
325
|
+
* The `init` method is part of the initialization lifecycle of the `soundworks`
|
|
326
|
+
* server. Most of the time, the `init` method will be implicitly called by the
|
|
327
|
+
* {@link server.Server#start} method.
|
|
328
|
+
*
|
|
329
|
+
* In some situations you might want to call this method manually, in such cases
|
|
330
|
+
* the method should be called before the {@link server.Server#start} method.
|
|
246
331
|
*
|
|
247
332
|
* What it does:
|
|
248
333
|
* - create the audit state
|
|
249
334
|
* - prepapre http(s) server and routing according to the informations
|
|
250
335
|
* declared in `config.app.clients`
|
|
251
|
-
* -
|
|
336
|
+
* - initialize all registered plugins
|
|
252
337
|
*
|
|
253
|
-
* After `await server.init()
|
|
254
|
-
*
|
|
338
|
+
* After `await server.init()` is fulfilled, the {@link server.Server#stateManager}
|
|
339
|
+
* and all registered plugins can be safely used.
|
|
255
340
|
*
|
|
256
341
|
* @example
|
|
257
342
|
* const server = new Server(config);
|
|
@@ -421,9 +506,9 @@ Invalid certificate files, please check your:
|
|
|
421
506
|
}
|
|
422
507
|
|
|
423
508
|
if (!nodeOnly) {
|
|
424
|
-
if (this.
|
|
425
|
-
|| this.
|
|
426
|
-
|| this.
|
|
509
|
+
if (this._applicationTemplateOptions.templateEngine === null
|
|
510
|
+
|| this._applicationTemplateOptions.templatePath === null
|
|
511
|
+
|| this._applicationTemplateOptions.clientConfigFunction === null
|
|
427
512
|
) {
|
|
428
513
|
throw new Error('[soundworks:Server] A browser client has been found in "config.app.clients" but configuration for html templating is missing. You should probably call `server.setDefaultTemplateConfig()` if you use the soundworks-template and/or refer (at your own risks) to the documentation of `setCustomTemplateConfig()`');
|
|
429
514
|
}
|
|
@@ -464,17 +549,23 @@ Invalid certificate files, please check your:
|
|
|
464
549
|
}
|
|
465
550
|
|
|
466
551
|
/**
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
552
|
+
* The `start` method is part of the initialization lifecycle of the `soundworks`
|
|
553
|
+
* server. The `start` method will implicitly call the {@link server.Server#init}
|
|
554
|
+
* method if it has not been called manually.
|
|
470
555
|
*
|
|
471
556
|
* What it does:
|
|
472
|
-
* -
|
|
473
|
-
*
|
|
474
|
-
* - start
|
|
475
|
-
*
|
|
557
|
+
* - implicitely call {@link server.Server#init} if not done manually
|
|
558
|
+
* - launch the HTTP and WebSocket servers
|
|
559
|
+
* - start all created contexts. To this end, you will have to call `server.init`
|
|
560
|
+
* manually and instantiate the contexts between `server.init()` and `server.start()`
|
|
561
|
+
*
|
|
562
|
+
* After `await server.start()` the server is ready to accept incoming connections
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* import { Server } from '@soundworks/core/server.js'
|
|
476
566
|
*
|
|
477
|
-
*
|
|
567
|
+
* const server = new Server(config);
|
|
568
|
+
* await server.start();
|
|
478
569
|
*/
|
|
479
570
|
async start() {
|
|
480
571
|
if (this.status === 'idle') {
|
|
@@ -562,10 +653,22 @@ Invalid certificate files, please check your:
|
|
|
562
653
|
});
|
|
563
654
|
}
|
|
564
655
|
|
|
565
|
-
// @todo - handle gracefull close of the server (but define what it means first...)
|
|
566
656
|
/**
|
|
567
|
-
*
|
|
568
|
-
*
|
|
657
|
+
* Stops all started contexts, plugins, close all the socket connections and
|
|
658
|
+
* the http(s) server.
|
|
659
|
+
*
|
|
660
|
+
* In most situations, you might not need to call this method. However, it can
|
|
661
|
+
* be usefull for unit testing or similar situations where you want to create
|
|
662
|
+
* and delete several servers in the same process.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* import { Server } from '@soundworks/core/server.js'
|
|
666
|
+
*
|
|
667
|
+
* const server = new Server(config);
|
|
668
|
+
* await server.start();
|
|
669
|
+
*
|
|
670
|
+
* await new Promise(resolve => setTimeout(resolve, 1000));
|
|
671
|
+
* await server.stop();
|
|
569
672
|
*/
|
|
570
673
|
async stop() {
|
|
571
674
|
if (this.status !== 'started') {
|
|
@@ -608,7 +711,7 @@ Invalid certificate files, please check your:
|
|
|
608
711
|
templatePath,
|
|
609
712
|
templateEngine,
|
|
610
713
|
clientConfigFunction,
|
|
611
|
-
} = this.
|
|
714
|
+
} = this._applicationTemplateOptions;
|
|
612
715
|
|
|
613
716
|
const clientTmpl = path.join(templatePath, `${role}.tmpl`);
|
|
614
717
|
const defaultTmpl = path.join(templatePath, `default.tmpl`);
|
|
@@ -783,8 +886,8 @@ Invalid certificate files, please check your:
|
|
|
783
886
|
}
|
|
784
887
|
|
|
785
888
|
/**
|
|
786
|
-
* Configure the server to work
|
|
787
|
-
*
|
|
889
|
+
* Configure the server to work _out-of-the-box_ within the soundworks application
|
|
890
|
+
* template provided by `@soundworks/create.
|
|
788
891
|
*
|
|
789
892
|
* - uses [template-literal](https://www.npmjs.com/package/template-literal) package
|
|
790
893
|
* as html templateEngine
|
|
@@ -796,9 +899,12 @@ Invalid certificate files, please check your:
|
|
|
796
899
|
* Also expose two public directory:
|
|
797
900
|
* - the `public` directory which is exposed behind the root path
|
|
798
901
|
* - the `./.build/public` directory which is exposed behind the `build` path
|
|
902
|
+
*
|
|
903
|
+
* _Note: except in very rare cases (so rare that they are quite difficult to imagine),
|
|
904
|
+
* you should rely on these defaults._
|
|
799
905
|
*/
|
|
800
|
-
|
|
801
|
-
this.
|
|
906
|
+
useDefaultApplicationTemplate() {
|
|
907
|
+
this._applicationTemplateOptions = {
|
|
802
908
|
templateEngine: { compile },
|
|
803
909
|
templatePath: path.join('.build', 'server', 'tmpl'),
|
|
804
910
|
clientConfigFunction: (role, config, _httpRequest) => {
|
|
@@ -822,20 +928,30 @@ Invalid certificate files, please check your:
|
|
|
822
928
|
}
|
|
823
929
|
|
|
824
930
|
/**
|
|
825
|
-
* Define
|
|
826
|
-
* This method is for very advanced use-cases and
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* If you end up using this, please contact me to explain your use-case :)
|
|
931
|
+
* Define custom template path, template engine, and clientConfig function.
|
|
932
|
+
* This method is proposed for very advanced use-cases and should very probably
|
|
933
|
+
* be improved. If you consider using this for some reason, please get in touch
|
|
934
|
+
* first to explain your use-case :)
|
|
830
935
|
*/
|
|
831
|
-
|
|
832
|
-
Object.assign(this.
|
|
936
|
+
setCustomApplicationTemplateOptions(options) {
|
|
937
|
+
Object.assign(this._applicationTemplateOptions, options);
|
|
833
938
|
}
|
|
834
939
|
|
|
835
940
|
/**
|
|
836
|
-
*
|
|
941
|
+
* Attach and retrieve the global audit state of the application.
|
|
837
942
|
*
|
|
943
|
+
* The audit state is a {@link server.SharedState} instance that keeps track of
|
|
944
|
+
* global informations about the application such as, the number of connected
|
|
945
|
+
* clients, network latency estimation, etc.
|
|
946
|
+
*
|
|
947
|
+
* The audit state is created by the server on start up.
|
|
948
|
+
*
|
|
949
|
+
* @returns {Promise<server.SharedState>}
|
|
838
950
|
* @throws Will throw if called before `server.init()`
|
|
951
|
+
* @see {@link server.SharedState}
|
|
952
|
+
* @example
|
|
953
|
+
* const auditState = await server.getAuditState();
|
|
954
|
+
* auditState.onUpdate(() => console.log(auditState.getValues()), true);
|
|
839
955
|
*/
|
|
840
956
|
async getAuditState() {
|
|
841
957
|
if (this.status === 'idle') {
|