@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.
@@ -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 side entry point for a `soundworks` application.
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
- * @memberof server
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
- * @param {server.ServerConfig} config
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: { myClient: { target: 'node' } },
112
+ * clients: {
113
+ * player: { target: 'browser', default: true },
114
+ * controller: { target: 'browser' },
115
+ * thing: { target: 'node' }
116
+ * },
92
117
  * },
93
118
  * env: {
94
- * port: 8888,
119
+ * port: 8000,
95
120
  * },
96
121
  * });
97
- * await server.init();
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
- * Configuration informations.
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
- * subfolder: '',
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
- * Router. Internally use polka.
165
- * (cf. {@link https://github.com/lukeed/polka})
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
- * Http(s) server instance. The node `http` or `https` module instance
173
- * (cf. {@link https://nodejs.org/api/http.html})
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
- * Key / value storage with Promise based Map API
179
- * basically a wrapper around kvey (cf. {@link https://github.com/lukechilds/keyv})
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
- * The {@link server.PluginManager} instance.
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
- * The {@link server.StateManager} instance.
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
- * The {@link server.ContextManager} instance.
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, will contain informations about the certificates
214
- * (self-signed, validity dates, etc.)
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
- * Current status of the server ['idle', 'inited', 'started']
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._applicationTemplateConfig = {
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
- // create audit state
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
- * Method to be called before `start` in the initialization lifecycle of the
244
- * soundworks server. Note that if `init()`` is not explicitely called, `start()`
245
- * will call it implicitely.
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
- * - starts all registered plugins
336
+ * - initialize all registered plugins
252
337
  *
253
- * After `await server.init()`, you can safely use the StateManager, as well
254
- * as any registered Plugins.
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._applicationTemplateConfig.templateEngine === null
425
- || this._applicationTemplateConfig.templatePath === null
426
- || this._applicationTemplateConfig.clientConfigFunction === null
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
- * Method to be called when `init` step is done in the initialization
468
- * lifecycle of the soundworks server. If `server.init()` has not been called
469
- * explicitely, `server.start()` will call it automatically.
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
- * - starts all registered contexts (context are automatically registered
473
- * when instantiated)
474
- * - start the web socket server
475
- * - launch the HTTP server on given port
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
- * After `await server.start()` the server is ready to accept incoming connexions
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
- * Stop the server, close all existing WebSocket connections.
568
- * Mainly usefull for test.
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._applicationTemplateConfig;
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 out-of-the box with the soundworks-template
787
- * directory tree structure.
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
- setDefaultTemplateConfig() {
801
- this._applicationTemplateConfig = {
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 your own template path, template engine, and clientConfig function.
826
- * This method is for very advanced use-cases and only be used if you know what
827
- * you are doing. As such its behavior could probably be improved a lot...
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
- setCustomTemplateConfig(options) {
832
- Object.assign(this._applicationTemplateConfig, options);
936
+ setCustomApplicationTemplateOptions(options) {
937
+ Object.assign(this._applicationTemplateOptions, options);
833
938
  }
834
939
 
835
940
  /**
836
- * Get the global audit state of the application.
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') {