@soundworks/core 3.6.0 → 3.7.0

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/server/Server.js CHANGED
@@ -11,20 +11,26 @@ var _http = _interopRequireDefault(require("http"));
11
11
 
12
12
  var _https = _interopRequireDefault(require("https"));
13
13
 
14
- var _lodash = _interopRequireDefault(require("lodash.merge"));
15
-
16
14
  var _path = _interopRequireDefault(require("path"));
17
15
 
18
- var _pem = _interopRequireDefault(require("pem"));
19
-
20
16
  var _os = _interopRequireDefault(require("os"));
21
17
 
22
- var _polka = _interopRequireDefault(require("polka"));
18
+ var _crypto = require("crypto");
23
19
 
24
- var _serveStatic = _interopRequireDefault(require("serve-static"));
20
+ var _chalk = _interopRequireDefault(require("chalk"));
25
21
 
26
22
  var _compression = _interopRequireDefault(require("compression"));
27
23
 
24
+ var _keyv = _interopRequireDefault(require("keyv"));
25
+
26
+ var _keyvFile = _interopRequireDefault(require("keyv-file"));
27
+
28
+ var _lodash = _interopRequireDefault(require("lodash.merge"));
29
+
30
+ var _pem = _interopRequireDefault(require("pem"));
31
+
32
+ var _polka = _interopRequireDefault(require("polka"));
33
+
28
34
  var _Client = _interopRequireDefault(require("./Client.js"));
29
35
 
30
36
  var _PluginManager = _interopRequireDefault(require("./PluginManager.js"));
@@ -33,17 +39,11 @@ var _Sockets = _interopRequireDefault(require("./Sockets.js"));
33
39
 
34
40
  var _SharedStateManagerServer = _interopRequireDefault(require("../common/SharedStateManagerServer.js"));
35
41
 
36
- var _logger = _interopRequireDefault(require("./utils/logger.js"));
37
-
38
- var _keyv = _interopRequireDefault(require("keyv"));
39
-
40
- var _keyvFile = _interopRequireDefault(require("keyv-file"));
42
+ var _logger = _interopRequireDefault(require("../common/logger.js"));
41
43
 
42
44
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
43
45
 
44
46
  let _dbNamespaces = new Set();
45
-
46
- let _dbStore = null;
47
47
  /**
48
48
  * Server side entry point for a `soundworks` application.
49
49
  *
@@ -62,6 +62,7 @@ let _dbStore = null;
62
62
  * soundworks.server.start();
63
63
  */
64
64
 
65
+
65
66
  class Server {
66
67
  constructor() {
67
68
  /**
@@ -139,8 +140,13 @@ class Server {
139
140
  * @param {String}
140
141
  */
141
142
 
142
- this.templateDirectory = null; // private stuff
143
+ this.templateDirectory = null;
144
+ /**
145
+ * If https is required, will contain informations about the certificates
146
+ * (self-signed, validity dates, etc.)
147
+ */
143
148
 
149
+ this.httpsInfos = null;
144
150
  /**
145
151
  * Required activities that must be started. Only used in Experience
146
152
  * and Plugin - do not expose.
@@ -148,13 +154,6 @@ class Server {
148
154
  */
149
155
 
150
156
  this.activities = new Set();
151
- /**
152
- * Key and certificates (may be generated and self-signed) for https server.
153
- * @todo - put in config...
154
- * @private
155
- */
156
-
157
- this._httpsInfos = null;
158
157
  /**
159
158
  * Mapping between a `clientType` and its related activities.
160
159
  * @private
@@ -224,7 +223,7 @@ class Server {
224
223
  */
225
224
 
226
225
 
227
- async init(config, clientConfigFunction = (clientType, serverConfig, httpRequest) => ({
226
+ async init(config, clientConfigFunction = (clientType, _serverConfig, _httpRequest) => ({
228
227
  clientType
229
228
  })) {
230
229
  const defaultConfig = {
@@ -310,216 +309,248 @@ class Server {
310
309
 
311
310
 
312
311
  async start() {
313
- try {
314
- // ------------------------------------------------------------
315
- // init acitvities
316
- // ------------------------------------------------------------
317
- // we should at least have one activity registered for each client here
318
- // i.e. the Experience so it's ok
319
- this.activities.forEach(activity => {
320
- activity.clientTypes.forEach(clientType => {
321
- if (!this._clientTypeActivitiesMap[clientType]) {
322
- this._clientTypeActivitiesMap[clientType] = new Set();
323
- }
312
+ // ------------------------------------------------------------
313
+ // init acitvities
314
+ // ------------------------------------------------------------
315
+ // we should at least have one activity registered for each client here
316
+ // i.e. the Experience so it's ok
317
+ this.activities.forEach(activity => {
318
+ activity.clientTypes.forEach(clientType => {
319
+ if (!(clientType in this.config.app.clients)) {
320
+ throw new Error(`[soundworks:core] no config found for client type ${clientType}`);
321
+ }
324
322
 
325
- this._clientTypeActivitiesMap[clientType].add(activity);
326
- });
327
- }); // start http server
323
+ if (!this._clientTypeActivitiesMap[clientType]) {
324
+ this._clientTypeActivitiesMap[clientType] = new Set();
325
+ }
328
326
 
329
- const useHttps = this.config.env.useHttps || false;
330
- return Promise.resolve() // ------------------------------------------------------------
331
- // create HTTP(S) SERVER
332
- // ------------------------------------------------------------
333
- .then(() => {
334
- // create http server
335
- if (!useHttps) {
336
- const httpServer = _http.default.createServer();
327
+ this._clientTypeActivitiesMap[clientType].add(activity);
328
+ });
329
+ }); // start http server
330
+
331
+ const useHttps = this.config.env.useHttps || false;
332
+ return Promise.resolve() // ------------------------------------------------------------
333
+ // create HTTP(S) SERVER
334
+ // ------------------------------------------------------------
335
+ .then(async () => {
336
+ // create http server
337
+ if (!useHttps) {
338
+ const httpServer = _http.default.createServer();
339
+
340
+ return Promise.resolve(httpServer);
341
+ } else {
342
+ const httpsInfos = this.config.env.httpsInfos;
337
343
 
338
- return Promise.resolve(httpServer);
339
- } else {
340
- const httpsInfos = this.config.env.httpsInfos;
344
+ if (httpsInfos.key && httpsInfos.cert) {
345
+ // use given certificate
346
+ try {
347
+ const key = _fs.default.readFileSync(httpsInfos.key);
341
348
 
342
- if (httpsInfos.key && httpsInfos.cert) {
343
- // use given certificate
344
- try {
345
- const key = _fs.default.readFileSync(httpsInfos.key);
349
+ const cert = _fs.default.readFileSync(httpsInfos.cert); // this fails with self-signed certificates for whatever reason...
346
350
 
347
- const cert = _fs.default.readFileSync(httpsInfos.cert);
348
351
 
349
- this._httpsInfos = {
350
- key,
351
- cert
352
- };
352
+ const x509 = new _crypto.X509Certificate(cert);
353
+ this.httpsInfos = {
354
+ selfSigned: false,
355
+ CN: x509.subject.split('=')[1],
356
+ altNames: x509.subjectAltName.split(',').map(e => e.trim().split(':')[1]),
357
+ validFrom: x509.validFrom,
358
+ validTo: x509.validTo
359
+ };
353
360
 
354
- const httpsServer = _https.default.createServer(this._httpsInfos);
361
+ const httpsServer = _https.default.createServer({
362
+ key,
363
+ cert
364
+ });
355
365
 
356
- return Promise.resolve(httpsServer);
357
- } catch (err) {
358
- console.error(`Invalid certificate files, please check your:
366
+ return Promise.resolve(httpsServer);
367
+ } catch (err) {
368
+ _logger.default.error(`
369
+ Invalid certificate files, please check your:
359
370
  - key file: ${httpsInfos.key}
360
371
  - cert file: ${httpsInfos.cert}
361
- `);
362
- throw err;
363
- }
364
- } else {
365
- return new Promise(async (resolve, reject) => {
366
- const key = await this.db.get('httpsKey');
367
- const cert = await this.db.get('httpsCert');
372
+ `);
368
373
 
369
- if (key && cert) {
370
- this._httpsInfos = {
371
- key,
372
- cert
373
- };
374
+ throw err;
375
+ }
376
+ } else {
377
+ const key = await this.db.get('httpsKey');
378
+ const cert = await this.db.get('httpsCert');
374
379
 
375
- const httpsServer = _https.default.createServer(this._httpsInfos);
380
+ if (key && cert) {
381
+ this.httpsInfos = {
382
+ selfSigned: true
383
+ };
376
384
 
377
- resolve(httpsServer);
378
- } else {
379
- // generate certificate on the fly (for development purposes)
380
- _pem.default.createCertificate({
381
- days: 1,
382
- selfSigned: true
383
- }, async (err, keys) => {
384
- if (err) {
385
- return console.error(err.stack);
386
- }
385
+ const httpsServer = _https.default.createServer({
386
+ key,
387
+ cert
388
+ });
389
+
390
+ return Promise.resolve(httpsServer);
391
+ } else {
392
+ return new Promise(resolve => {
393
+ // generate certificate on the fly (for development purposes)
394
+ _pem.default.createCertificate({
395
+ days: 1,
396
+ selfSigned: true
397
+ }, async (err, keys) => {
398
+ if (err) {
399
+ _logger.default.error(err.stack);
400
+
401
+ return;
402
+ }
387
403
 
388
- this._httpsInfos = {
389
- key: keys.serviceKey,
390
- cert: keys.certificate
391
- };
392
- await this.db.set('httpsKey', this._httpsInfos.key);
393
- await this.db.set('httpsCert', this._httpsInfos.cert);
404
+ const key = keys.serviceKey;
405
+ const cert = keys.certificate;
406
+ this.httpsInfos = {
407
+ selfSigned: true
408
+ }; // we store the generated cert so that we don't have to re-accept
409
+ // the cert each time the server restarts
394
410
 
395
- const httpsServer = _https.default.createServer(this._httpsInfos);
411
+ await this.db.set('httpsKey', key);
412
+ await this.db.set('httpsCert', cert);
396
413
 
397
- resolve(httpsServer);
414
+ const httpsServer = _https.default.createServer({
415
+ key,
416
+ cert
398
417
  });
399
- }
418
+
419
+ resolve(httpsServer);
420
+ });
400
421
  });
401
422
  }
402
423
  }
403
- }).then(httpServer => {
404
- this.httpServer = httpServer;
405
- this.router.server = httpServer;
406
- return Promise.resolve();
407
- }).then(() => {
408
- let nodeOnly = true; // do not throw if no browser clients are defined, very usefull for
409
- // cleaning tests in particular
410
-
411
- for (let clientType in this.config.app.clients) {
412
- if (this.config.app.clients[clientType].target === 'browser') {
413
- nodeOnly = false;
414
- }
424
+ }
425
+ }).then(httpServer => {
426
+ this.httpServer = httpServer;
427
+ this.router.server = httpServer;
428
+ return Promise.resolve();
429
+ }).then(() => {
430
+ let nodeOnly = true; // do not throw if no browser clients are defined, very usefull for
431
+ // cleaning tests in particular
432
+
433
+ for (let clientType in this.config.app.clients) {
434
+ if (this.config.app.clients[clientType].target === 'browser') {
435
+ nodeOnly = false;
415
436
  }
437
+ }
416
438
 
417
- if (!nodeOnly) {
418
- if (this.templateEngine === null) {
419
- throw new Error('Undefined "server.templateEngine": please provide a valid template engine');
420
- }
439
+ if (!nodeOnly) {
440
+ if (this.templateEngine === null) {
441
+ throw new Error('Undefined "server.templateEngine": please provide a valid template engine');
442
+ }
421
443
 
422
- if (this.templateDirectory === null) {
423
- throw new Error('Undefined "server.templateDirectory": please provide a valid template directory');
424
- }
425
- } // ------------------------------------------------------------
426
- // INIT ROUTING
427
- // ------------------------------------------------------------
444
+ if (this.templateDirectory === null) {
445
+ throw new Error('Undefined "server.templateDirectory": please provide a valid template directory');
446
+ }
447
+ } // ------------------------------------------------------------
448
+ // INIT ROUTING
449
+ // ------------------------------------------------------------
428
450
 
429
451
 
430
- _logger.default.title(`configured clients and routing`);
452
+ _logger.default.title(`configured clients and routing`);
431
453
 
432
- const routes = [];
433
- let defaultClientType = null;
454
+ const routes = [];
455
+ let defaultClientType = null;
434
456
 
435
- for (let clientType in this.config.app.clients) {
436
- if (this.config.app.clients[clientType].default === true) {
437
- defaultClientType = clientType;
438
- }
439
- } // we must open default route last
457
+ for (let clientType in this.config.app.clients) {
458
+ if (this.config.app.clients[clientType].default === true) {
459
+ defaultClientType = clientType;
460
+ }
461
+ } // we must open default route last
440
462
 
441
463
 
442
- for (let clientType in this._clientTypeActivitiesMap) {
443
- // do nothing if client type is not registered in config
444
- if (!(clientType in this.config.app.clients)) {
445
- continue;
446
- }
464
+ for (let clientType in this._clientTypeActivitiesMap) {
465
+ // do nothing if client type is not registered in config
466
+ if (!(clientType in this.config.app.clients)) {
467
+ continue;
468
+ }
447
469
 
448
- if (clientType !== defaultClientType) {
449
- const clientTarget = this.config.app.clients[clientType].target;
470
+ if (clientType !== defaultClientType) {
471
+ const clientTarget = this.config.app.clients[clientType].target;
450
472
 
451
- const path = this._openClientRoute(clientType, clientTarget, this.router);
473
+ const path = this._openClientRoute(clientType, clientTarget, this.router);
452
474
 
453
- routes.push({
454
- clientType,
455
- path
456
- });
457
- }
458
- } // open default route last
475
+ routes.push({
476
+ clientType,
477
+ path
478
+ });
479
+ }
480
+ } // open default route last
459
481
 
460
482
 
461
- for (let clientType in this._clientTypeActivitiesMap) {
462
- // do nothing if client type is not registered in config
463
- if (!(clientType in this.config.app.clients)) {
464
- continue;
465
- }
483
+ for (let clientType in this._clientTypeActivitiesMap) {
484
+ // do nothing if client type is not registered in config
485
+ if (!(clientType in this.config.app.clients)) {
486
+ continue;
487
+ }
466
488
 
467
- if (clientType === defaultClientType) {
468
- const clientTarget = this.config.app.clients[clientType].target;
489
+ if (clientType === defaultClientType) {
490
+ const clientTarget = this.config.app.clients[clientType].target;
469
491
 
470
- const path = this._openClientRoute(clientType, clientTarget, this.router, true);
492
+ const path = this._openClientRoute(clientType, clientTarget, this.router, true);
471
493
 
472
- routes.unshift({
473
- clientType,
474
- path
475
- });
476
- }
494
+ routes.unshift({
495
+ clientType,
496
+ path
497
+ });
477
498
  }
499
+ }
478
500
 
479
- _logger.default.clientConfigAndRouting(routes, this.config);
480
-
481
- return Promise.resolve();
482
- }).then(() => {
483
- // ------------------------------------------------------------
484
- // START SOCKET SERVER
485
- // ------------------------------------------------------------
486
- this.sockets.start(this.httpServer, this.config.env.websockets, (clientType, socket) => this._onSocketConnection(clientType, socket));
487
- return Promise.resolve();
488
- }).then(async () => {
489
- // ------------------------------------------------------------
490
- // START SERVICE MANAGER
491
- // ------------------------------------------------------------
492
- return this.pluginManager.start();
493
- }).then(() => {
494
- // ------------------------------------------------------------
495
- // START HTTP SERVER
496
- // ------------------------------------------------------------
497
- return new Promise((resolve, reject) => {
498
- const port = this.config.env.port;
499
- const useHttps = this.config.env.useHttps || false;
500
- const protocol = useHttps ? 'https' : 'http';
501
-
502
- const ifaces = _os.default.networkInterfaces();
503
-
504
- this.router.listen(port, async () => {
505
- _logger.default.title(`${protocol} server listening on`);
506
-
507
- Object.keys(ifaces).forEach(dev => {
508
- ifaces[dev].forEach(details => {
509
- if (details.family === 'IPv4') {
510
- _logger.default.ip(protocol, details.address, port);
511
- }
512
- });
501
+ _logger.default.clientConfigAndRouting(routes, this.config);
502
+
503
+ return Promise.resolve();
504
+ }).then(() => {
505
+ // ------------------------------------------------------------
506
+ // START SOCKET SERVER
507
+ // ------------------------------------------------------------
508
+ this.sockets.start(this.httpServer, this.config.env.websockets, (clientType, socket) => this._onSocketConnection(clientType, socket));
509
+ return Promise.resolve();
510
+ }).then(async () => {
511
+ // ------------------------------------------------------------
512
+ // START SERVICE MANAGER
513
+ // ------------------------------------------------------------
514
+ return this.pluginManager.start();
515
+ }).then(() => {
516
+ // ------------------------------------------------------------
517
+ // START HTTP SERVER
518
+ // ------------------------------------------------------------
519
+ return new Promise(resolve => {
520
+ const port = this.config.env.port;
521
+ const useHttps = this.config.env.useHttps || false;
522
+ const protocol = useHttps ? 'https' : 'http';
523
+
524
+ const ifaces = _os.default.networkInterfaces();
525
+
526
+ this.router.listen(port, async () => {
527
+ _logger.default.title(`${protocol} server listening on`);
528
+
529
+ Object.keys(ifaces).forEach(dev => {
530
+ ifaces[dev].forEach(details => {
531
+ if (details.family === 'IPv4') {
532
+ _logger.default.ip(protocol, details.address, port);
533
+ }
513
534
  });
514
- await this._emit('started');
515
- resolve();
516
535
  });
536
+
537
+ if (this.httpsInfos !== null) {
538
+ _logger.default.title(`https certificates infos`);
539
+
540
+ if (this.httpsInfos.selfSigned) {
541
+ _logger.default.log(` self-signed: ${this.httpsInfos.selfSigned ? 'true' : 'false'}`);
542
+ } else {
543
+ _logger.default.log(` valid from: ${this.httpsInfos.validFrom}`);
544
+
545
+ _logger.default.log(` valid to: ${this.httpsInfos.validTo}`);
546
+ }
547
+ }
548
+
549
+ await this._emit('started');
550
+ resolve();
517
551
  });
518
552
  });
519
- return Promise.resolve();
520
- } catch (err) {
521
- console.error(err);
522
- }
553
+ });
523
554
  } // @todo - handle gracefull close of the server
524
555
 
525
556
 
@@ -619,39 +650,47 @@ class Server {
619
650
  const activities = this._clientTypeActivitiesMap[clientType];
620
651
 
621
652
  if (activities == undefined) {
622
- throw new Error(`No activity registered for "${clientType}" clients, make sure an Experience has been initialized for this client (see src/server/index.js)`);
653
+ _logger.default.error(`[soundworks:core] No activity registered for "${clientType}", make sure an Experience has been created for this client`);
623
654
  }
624
655
 
625
656
  socket.addListener('close', () => {
626
657
  // clean sockets
627
658
  socket.terminate(); // remove client from activities
628
659
 
629
- activities.forEach(activity => activity.disconnect(client)); // destroy client
660
+ if (activities) {
661
+ activities.forEach(activity => activity.disconnect(client));
662
+ } // destroy client
663
+
630
664
 
631
665
  client.destroy();
632
666
  });
633
667
  socket.addListener('s:client:handshake', data => {
634
- // in development, if plugin required client-side but not server-side,
635
- // complain properly client-side.
636
- if (this.config.env.type !== 'production') {
637
- // check coherence between client-side and server-side plugin requirements
638
- const clientRequiredPlugins = data.requiredPlugins || [];
639
- const serverRequiredPlugins = this.pluginManager.getRequiredPlugins(clientType);
640
- const missingPlugins = [];
641
- clientRequiredPlugins.forEach(pluginId => {
642
- if (serverRequiredPlugins.indexOf(pluginId) === -1) {
643
- missingPlugins.push(pluginId);
644
- }
645
- });
646
-
647
- if (missingPlugins.length > 0) {
648
- const err = {
649
- type: 'plugins',
650
- data: missingPlugins
651
- };
652
- socket.send('s:client:error', err);
653
- return;
668
+ // client should throw error if no activity registered server-side (cf. #16)
669
+ if (activities === undefined) {
670
+ const err = {
671
+ type: 'no-activity'
672
+ };
673
+ socket.send('s:client:error', err);
674
+ return;
675
+ } // check coherence between client-side and server-side plugin requirements
676
+
677
+
678
+ const clientRequiredPlugins = data.requiredPlugins || [];
679
+ const serverRequiredPlugins = this.pluginManager.getRequiredPlugins(clientType);
680
+ const missingPlugins = [];
681
+ clientRequiredPlugins.forEach(pluginId => {
682
+ if (serverRequiredPlugins.indexOf(pluginId) === -1) {
683
+ missingPlugins.push(pluginId);
654
684
  }
685
+ });
686
+
687
+ if (missingPlugins.length > 0) {
688
+ const err = {
689
+ type: 'plugins',
690
+ data: missingPlugins
691
+ };
692
+ socket.send('s:client:error', err);
693
+ return;
655
694
  }
656
695
 
657
696
  activities.forEach(activity => activity.connect(client));
@@ -700,7 +739,7 @@ class Server {
700
739
  namespace,
701
740
  store
702
741
  });
703
- db.on('error', err => console.log(chalk.red('[soundworks:core] db ${namespace} error:'), err));
742
+ db.on('error', err => _logger.default.error('[soundworks:core] db ${namespace} error: ${err}'));
704
743
  return db;
705
744
  }
706
745
 
package/server/Socket.js CHANGED
@@ -7,14 +7,12 @@ exports.default = void 0;
7
7
 
8
8
  var _socketsEncoderDecoder = require("../common/sockets-encoder-decoder.js");
9
9
 
10
- const noop = () => {};
11
-
12
- let counter = 0;
13
- const CONNECTING = 0;
14
- const OPEN = 1;
15
- const CLOSING = 2;
16
- const CLOSED = 3;
17
- const READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
10
+ const noop = () => {}; // const CONNECTING = 0;
11
+ // const OPEN = 1;
12
+ // const CLOSING = 2;
13
+ // const CLOSED = 3;
14
+ // const READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
15
+
18
16
  /**
19
17
  * Simple wrapper with simple pubsub system built on top of `ws` sockets.
20
18
  * The abstraction contains two different socket:
@@ -27,6 +25,7 @@ const READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
27
25
  * @memberof server
28
26
  */
29
27
 
28
+
30
29
  class Socket {
31
30
  constructor(ws, binaryWs, rooms, sockets, options = {}) {
32
31
  /**
@@ -115,7 +114,7 @@ class Socket {
115
114
  this._isAlive = false;
116
115
  this.ws.ping(noop);
117
116
  }, this.config.pingInterval);
118
- this.ws.addListener('error', err => {// console.log(this.clientId, err);
117
+ this.ws.addListener('error', _err => {// console.log(this.clientId, err);
119
118
  });
120
119
  }
121
120
  /**
@@ -128,7 +127,7 @@ class Socket {
128
127
  terminate() {
129
128
  clearInterval(this._intervalId); // clean rooms
130
129
 
131
- for (let [key, room] of this.rooms) {
130
+ for (let [_key, room] of this.rooms) {
132
131
  room.delete(this);
133
132
  } // clear references to sockets and rooms
134
133
 
package/server/Sockets.js CHANGED
@@ -181,6 +181,5 @@ class Sockets {
181
181
 
182
182
  }
183
183
 
184
- ;
185
184
  var _default = Sockets;
186
185
  exports.default = _default;