@soundworks/core 4.0.0-alpha.1 → 4.0.0-alpha.10

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.
@@ -1,5 +1,6 @@
1
1
  import EventEmitter from 'node:events';
2
2
 
3
+ import { idGenerator, isString, isPlainObject } from '@ircam/sc-utils';
3
4
  import clonedeep from 'lodash.clonedeep';
4
5
 
5
6
  import BaseStateManager from '../common/BaseStateManager.js';
@@ -21,17 +22,229 @@ import {
21
22
  DELETE_SCHEMA,
22
23
  PRIVATE_STATES,
23
24
  } from '../common/constants.js';
24
- import { idGenerator } from '../common/utils.js';
25
+
25
26
 
26
27
  const generateStateId = idGenerator();
27
28
  const generateRemoteId = idGenerator();
28
29
 
30
+ /**
31
+ * @typedef {object} server.StateManager~schema
32
+ *
33
+ * Description of a schema to be registered by the {@link server.StateManager#registerSchema}
34
+ *
35
+ * A schema is the blueprint, or definition from which shared states can be created.
36
+ *
37
+ * It consists of a set of key / value pairs where the key is the name of
38
+ * the parameter, and the value is an object describing the parameter.
39
+ *
40
+ * The value can be of any of the foolowing types:
41
+ * - {@link server.StateManager~schemaBooleanDefinition}
42
+ * - {@link server.StateManager~schemaStringDefinition}
43
+ * - {@link server.StateManager~schemaIntegerDefinition}
44
+ * - {@link server.StateManager~schemaFloatDefinition}
45
+ * - {@link server.StateManager~schemaEnumDefinition}
46
+ * - {@link server.StateManager~schemaAnyDefinition}
47
+ *
48
+ * @example
49
+ * const mySchema = {
50
+ * triggerSound: {
51
+ * type: 'boolean',
52
+ * event: true,
53
+ * },
54
+ * volume: {
55
+ * type: 'float'
56
+ * default: 0,
57
+ * min: -80,
58
+ * max: 6,
59
+ * }
60
+ * };
61
+ *
62
+ * server.stateManager.registerSchema('my-schema-name', mySchema);
63
+ */
64
+ /**
65
+ * Describe a {@link server.StateManager~schema} entry of "boolean" type.
66
+ *
67
+ * @typedef {object} server.StateManager~schemaBooleanDefinition
68
+ * @property {string} type='boolean' - Define a boolean parameter.
69
+ * @property {boolean} default - Default value of the parameter.
70
+ * @property {boolean} [nullable=false] - Define if the parameter is nullable. If
71
+ * set to `true` the parameter `default` is set to `null`.
72
+ * @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
73
+ * set its value back to `null` after propagation. When `true`, `nullable` is
74
+ * automatically set to `true` and `default` to `null`.
75
+ * @property {boolean} [filterChange=true] - Setting this option to `false` forces
76
+ * the propagation of a parameter even when its value do not change. It
77
+ * offers a kind of middle ground between the default bahavior (e.g. where
78
+ * only changed values are propagated) and the behavior of the `event` option
79
+ * (which has no state per se). As such, setting this options to `false` if
80
+ * `event=true` does not make sens.
81
+ * @property {boolean} [immediate=false] - Setting this option to `true` will
82
+ * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
83
+ * state that generate the update (i.e. calling `set`), before propagating the
84
+ * change on the network. This option can be usefull in cases the network
85
+ * would introduce a noticeable latency on the client. If for some reason
86
+ * the value is overriden server-side (e.g. in an `updateHook`) the listeners
87
+ * will be called again on when the "real" / final value will be received.
88
+ * @property {object} [metas={}] - Optionnal metadata of the parameter.
89
+ */
90
+ /**
91
+ * Describe a {@link server.StateManager~schema} entry of "string" type.
92
+ *
93
+ * @typedef {object} server.StateManager~schemaStringDefinition
94
+ * @property {string} type='string' - Define a boolean parameter.
95
+ * @property {string} default - Default value of the parameter.
96
+ * @property {boolean} [nullable=false] - Define if the parameter is nullable. If
97
+ * set to `true` the parameter `default` is set to `null`.
98
+ * @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
99
+ * set its value back to `null` after propagation. When `true`, `nullable` is
100
+ * automatically set to `true` and `default` to `null`.
101
+ * @property {boolean} [filterChange=true] - Setting this option to `false` forces
102
+ * the propagation of a parameter even when its value do not change. It
103
+ * offers a kind of middle ground between the default bahavior (e.g. where
104
+ * only changed values are propagated) and the behavior of the `event` option
105
+ * (which has no state per se). As such, setting this options to `false` if
106
+ * `event=true` does not make sens.
107
+ * @property {boolean} [immediate=false] - Setting this option to `true` will
108
+ * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
109
+ * state that generate the update (i.e. calling `set`), before propagating the
110
+ * change on the network. This option can be usefull in cases the network
111
+ * would introduce a noticeable latency on the client. If for some reason
112
+ * the value is overriden server-side (e.g. in an `updateHook`) the listeners
113
+ * will be called again on when the "real" / final value will be received.
114
+ * @property {object} [metas={}] - Optionnal metadata of the parameter.
115
+ */
116
+ /**
117
+ * Describe a {@link server.StateManager~schema} entry of "integer" type.
118
+ *
119
+ * @typedef {object} server.StateManager~schemaIntegerDefinition
120
+ * @property {string} type='integer' - Define a boolean parameter.
121
+ * @property {number} default - Default value of the parameter.
122
+ * @property {number} [min=-Infinity] - Minimum value of the parameter.
123
+ * @property {number} [max=+Infinity] - Maximum value of the parameter.
124
+ * @property {boolean} [nullable=false] - Define if the parameter is nullable. If
125
+ * set to `true` the parameter `default` is set to `null`.
126
+ * @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
127
+ * set its value back to `null` after propagation. When `true`, `nullable` is
128
+ * automatically set to `true` and `default` to `null`.
129
+ * @property {boolean} [filterChange=true] - Setting this option to `false` forces
130
+ * the propagation of a parameter even when its value do not change. It
131
+ * offers a kind of middle ground between the default bahavior (e.g. where
132
+ * only changed values are propagated) and the behavior of the `event` option
133
+ * (which has no state per se). As such, setting this options to `false` if
134
+ * `event=true` does not make sens.
135
+ * @property {boolean} [immediate=false] - Setting this option to `true` will
136
+ * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
137
+ * state that generate the update (i.e. calling `set`), before propagating the
138
+ * change on the network. This option can be usefull in cases the network
139
+ * would introduce a noticeable latency on the client. If for some reason
140
+ * the value is overriden server-side (e.g. in an `updateHook`) the listeners
141
+ * will be called again on when the "real" / final value will be received.
142
+ * @property {object} [metas={}] - Optionnal metadata of the parameter.
143
+ */
144
+ /**
145
+ * Describe a {@link server.StateManager~schema} entry of "float" type.
146
+ *
147
+ * @typedef {object} server.StateManager~schemaFloatDefinition
148
+ * @property {string} [type='float'] - Float parameter.
149
+ * @property {number} default - Default value.
150
+ * @property {number} [min=-Infinity] - Minimum value.
151
+ * @property {number} [max=+Infinity] - Maximum value.
152
+ * @property {boolean} [nullable=false] - Define if the parameter is nullable. If
153
+ * set to `true` the parameter `default` is set to `null`.
154
+ * @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
155
+ * set its value back to `null` after propagation. When `true`, `nullable` is
156
+ * automatically set to `true` and `default` to `null`.
157
+ * @property {boolean} [filterChange=true] - Setting this option to `false` forces
158
+ * the propagation of a parameter even when its value do not change. It
159
+ * offers a kind of middle ground between the default bahavior (e.g. where
160
+ * only changed values are propagated) and the behavior of the `event` option
161
+ * (which has no state per se). As such, setting this options to `false` if
162
+ * `event=true` does not make sens.
163
+ * @property {boolean} [immediate=false] - Setting this option to `true` will
164
+ * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
165
+ * state that generate the update (i.e. calling `set`), before propagating the
166
+ * change on the network. This option can be usefull in cases the network
167
+ * would introduce a noticeable latency on the client. If for some reason
168
+ * the value is overriden server-side (e.g. in an `updateHook`) the listeners
169
+ * will be called again on when the "real" / final value will be received.
170
+ * @property {object} [metas={}] - Optionnal metadata of the parameter.
171
+ */
172
+ /**
173
+ * Describe a {@link server.StateManager~schema} entry of "enum" type.
174
+ *
175
+ * @typedef {object} server.StateManager~schemaEnumDefinition
176
+ * @property {string} [type='enum'] - Enum parameter.
177
+ * @property {string} default - Default value of the parameter.
178
+ * @property {Array} list - Possible values of the parameter.
179
+ * @property {boolean} [nullable=false] - Define if the parameter is nullable. If
180
+ * set to `true` the parameter `default` is set to `null`.
181
+ * @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
182
+ * set its value back to `null` after propagation. When `true`, `nullable` is
183
+ * automatically set to `true` and `default` to `null`.
184
+ * @property {boolean} [filterChange=true] - Setting this option to `false` forces
185
+ * the propagation of a parameter even when its value do not change. It
186
+ * offers a kind of middle ground between the default bahavior (e.g. where
187
+ * only changed values are propagated) and the behavior of the `event` option
188
+ * (which has no state per se). As such, setting this options to `false` if
189
+ * `event=true` does not make sens.
190
+ * @property {boolean} [immediate=false] - Setting this option to `true` will
191
+ * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
192
+ * state that generate the update (i.e. calling `set`), before propagating the
193
+ * change on the network. This option can be usefull in cases the network
194
+ * would introduce a noticeable latency on the client. If for some reason
195
+ * the value is overriden server-side (e.g. in an `updateHook`) the listeners
196
+ * will be called again on when the "real" / final value will be received.
197
+ * @property {object} [metas={}] - Optionnal metadata of the parameter.
198
+ */
199
+ /**
200
+ * Describe a {@link server.StateManager~schema} entry of "any" type.
201
+ *
202
+ * Note that the `any` type always return a shallow copy of the state internal
203
+ * value. Mutating the returned value will therefore not modify the internal state.
204
+ *
205
+ * @typedef {object} server.StateManager~schemaAnyDefinition
206
+ * @property {string} [type='any'] - Parameter of any type.
207
+ * @property {*} default - Default value of the parameter.
208
+ * @property {boolean} [nullable=false] - Define if the parameter is nullable. If
209
+ * set to `true` the parameter `default` is set to `null`.
210
+ * @property {boolean} [event=false] - Define if the parameter is a volatile, e.g.
211
+ * set its value back to `null` after propagation. When `true`, `nullable` is
212
+ * automatically set to `true` and `default` to `null`.
213
+ * @property {boolean} [filterChange=true] - Setting this option to `false` forces
214
+ * the propagation of a parameter even when its value do not change. It
215
+ * offers a kind of middle ground between the default bahavior (e.g. where
216
+ * only changed values are propagated) and the behavior of the `event` option
217
+ * (which has no state per se). As such, setting this options to `false` if
218
+ * `event=true` does not make sens.
219
+ * @property {boolean} [immediate=false] - Setting this option to `true` will
220
+ * trigger any change (e.g. call the `onUpdate` listeners) immediately on the
221
+ * state that generate the update (i.e. calling `set`), before propagating the
222
+ * change on the network. This option can be usefull in cases the network
223
+ * would introduce a noticeable latency on the client. If for some reason
224
+ * the value is overriden server-side (e.g. in an `updateHook`) the listeners
225
+ * will be called again on when the "real" / final value will be received.
226
+ * @property {object} [metas={}] - Optionnal metadata of the parameter.
227
+ */
228
+
29
229
  /**
30
230
  * @callback server.StateManager~ObserveCallback
31
231
  * @async
32
- * @param {String} schemaName - name of the schema
33
- * @param {Number} stateId - id of the state
34
- * @param {Number} nodeId - id of the node that created the state
232
+ * @param {string} schemaName - name of the schema
233
+ * @param {number} stateId - id of the state
234
+ * @param {number} nodeId - id of the node that created the state
235
+ */
236
+
237
+ /**
238
+ * @callback server.StateManager~updateHook
239
+ * @async
240
+ *
241
+ * @param {object} updates - Update object as given on a set callback, or
242
+ * result of the previous hook
243
+ * @param {object} currentValues - Current values of the state.
244
+ * @param {object} [context=null] - Optionnal context passed by the creator
245
+ * of the update.
246
+ *
247
+ * @return {object} The "real" updates to be applied on the state.
35
248
  */
36
249
 
37
250
  /**
@@ -114,10 +327,10 @@ class StateManager extends BaseStateManager {
114
327
  *
115
328
  * This is automatically handled by the {@link server.Server} when a client connects.
116
329
  *
117
- * @param {Number} nodeId - Id of the client node, as given in
330
+ * @param {number} nodeId - Id of the client node, as given in
118
331
  * {@link client.StateManager}
119
- * @param {Object} transport - Tranpsort mecanism to communicate with the
120
- * client. Should implement a basic EventEmitter API.
332
+ * @param {object} transport - Transport mecanism to communicate with the
333
+ * client. Must implement a basic EventEmitter API.
121
334
  *
122
335
  * @private
123
336
  */
@@ -146,9 +359,11 @@ class StateManager extends BaseStateManager {
146
359
  client.transport.emit(CREATE_RESPONSE, reqId, stateId, remoteId, schemaName, sendedSchema, currentValues);
147
360
 
148
361
  this._observers.forEach(observer => {
149
- observer.transport.emit(OBSERVE_NOTIFICATION, [schemaName, stateId, nodeId]);
362
+ if (observer.id !== nodeId) {
363
+ observer.transport.emit(OBSERVE_NOTIFICATION, schemaName, stateId, nodeId);
364
+ }
150
365
  });
151
- } catch(err) {
366
+ } catch (err) {
152
367
  client.transport.emit(CREATE_ERROR, reqId, err.message);
153
368
  console.error(err.message);
154
369
  }
@@ -215,7 +430,7 @@ class StateManager extends BaseStateManager {
215
430
  const { schemaName, id, _creatorId } = state;
216
431
  // only track application states
217
432
  // (e.g. do not propagate infos about audit state)
218
- if (!PRIVATE_STATES.includes(schemaName)) {
433
+ if (!PRIVATE_STATES.includes(schemaName) && _creatorId !== client.id) {
219
434
  statesInfos.push([schemaName, id, _creatorId]);
220
435
  }
221
436
  });
@@ -237,7 +452,7 @@ class StateManager extends BaseStateManager {
237
452
  *
238
453
  * This is automatically handled by the {@link server.Server} when a client disconnects.
239
454
  *
240
- * @param {Number} nodeId - Id of the client node, as given in
455
+ * @param {number} nodeId - Id of the client node, as given in
241
456
  * {@link client.StateManager}
242
457
  *
243
458
  * @private
@@ -280,7 +495,7 @@ class StateManager extends BaseStateManager {
280
495
  * Register a schema from which shared states (cf. {@link common.SharedState})
281
496
  * can be instanciated.
282
497
  *
283
- * @param {String} schemaName - Name of the schema.
498
+ * @param {string} schemaName - Name of the schema.
284
499
  * @param {server.StateManager~schema} schema - Data structure
285
500
  * describing the states that will be created from this schema.
286
501
  *
@@ -302,10 +517,18 @@ class StateManager extends BaseStateManager {
302
517
  * })
303
518
  */
304
519
  registerSchema(schemaName, schema) {
520
+ if (!isString(schemaName)) {
521
+ throw new Error(`[stateManager.registerSchema] Invalid schema name "${schemaName}", should be a string`);
522
+ }
523
+
305
524
  if (this._schemas.has(schemaName)) {
306
525
  throw new Error(`[stateManager.registerSchema] cannot register schema with name: "${schemaName}", schema name already exists`);
307
526
  }
308
527
 
528
+ if (!isPlainObject(schema)) {
529
+ throw new Error(`[stateManager.registerSchema] Invalid schema, should be an object`);
530
+ }
531
+
309
532
  ParameterBag.validateSchema(schema);
310
533
 
311
534
  this._schemas.set(schemaName, clonedeep(schema));
@@ -315,11 +538,12 @@ class StateManager extends BaseStateManager {
315
538
 
316
539
  /**
317
540
  * Delete a schema and all associated states.
541
+ *
318
542
  * When a schema is deleted, all states created from this schema are deleted
319
543
  * as well, therefore all attached clients are detached and the `onDetach`
320
544
  * and `onDelete` callbacks are called on the related states.
321
545
  *
322
- * @param {String} schemaName - Name of the schema.
546
+ * @param {string} schemaName - Name of the schema.
323
547
  */
324
548
  deleteSchema(schemaName) {
325
549
  // @note: deleting schema
@@ -344,19 +568,6 @@ class StateManager extends BaseStateManager {
344
568
  this._hooksBySchemaName.delete(schemaName);
345
569
  }
346
570
 
347
- /**
348
- * @callback server.StateManager~updateHook
349
- * @async
350
- *
351
- * @param {Object} updates - Update object as given on a set callback, or
352
- * result of the previous hook
353
- * @param {Object} currentValues - Current values of the state.
354
- * @param {Object} [context=null] - Optionnal context passed by the creator
355
- * of the update.
356
- *
357
- * @return {Object} The "real" updates to be applied on the state.
358
- */
359
-
360
571
  /**
361
572
  * Register a function for a given schema (e.g. will be applied on all states
362
573
  * created from this schema) that will be executed before the update values
@@ -369,7 +580,7 @@ class StateManager extends BaseStateManager {
369
580
  * server-side regarless the node on which `set` has been called and before
370
581
  * the "actual" update of the state (e.g. before the call of `onUpdate`).
371
582
  *
372
- * @param {String} schemaName - Kind of states on which applying the hook.
583
+ * @param {string} schemaName - Kind of states on which applying the hook.
373
584
  * @param {server.StateManager~updateHook} updateHook - Function
374
585
  * called between the `set` call and the actual update.
375
586
  *
@@ -404,7 +615,6 @@ class StateManager extends BaseStateManager {
404
615
 
405
616
  return () => hooks.delete(updateHook);
406
617
  }
407
-
408
618
  }
409
619
 
410
620
  export default StateManager;
@@ -0,0 +1,38 @@
1
+ import crypto from 'node:crypto';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+
4
+ // adapted from https://dev.to/jobizil/encrypt-and-decrypt-data-in-nodejs-using-aes-256-cbc-2l6d
5
+ // generate a new key and init vector each time the server restart, we don't need
6
+ // any persistency here
7
+ const secretKey = uuidv4();
8
+ const secretIv = uuidv4();
9
+ const encryptionMethod = 'aes-256-cbc';
10
+
11
+ const key = crypto
12
+ .createHash('sha512')
13
+ .update(secretKey)
14
+ .digest('hex')
15
+ .substring(0, 32);
16
+
17
+ const encryptionIV = crypto
18
+ .createHash('sha512')
19
+ .update(secretIv)
20
+ .digest('hex')
21
+ .substring(0, 16);
22
+
23
+
24
+ export function encryptData(obj) {
25
+ const data = JSON.stringify(obj);
26
+ const cipher = crypto.createCipheriv(encryptionMethod, key, encryptionIV)
27
+ return Buffer.from(
28
+ cipher.update(data, 'utf8', 'hex') + cipher.final('hex')
29
+ ).toString('base64')
30
+ }
31
+
32
+ export function decryptData(encryptedData) {
33
+ const buff = Buffer.from(encryptedData, 'base64')
34
+ const decipher = crypto.createDecipheriv(encryptionMethod, key, encryptionIV)
35
+
36
+ const decrypted = `${decipher.update(buff.toString('utf8'), 'hex', 'utf8')}${decipher.final('utf8')}`
37
+ return JSON.parse(decrypted);
38
+ }
@@ -1,23 +1,3 @@
1
- /**
2
- * Configuration object for the server.
3
- *
4
- * @typedef ServerConfig
5
- * @memberof server
6
- * @type {object}
7
- * @property {object} [app] - Application configration object.
8
- * @property {object} app.clients - Definition of the application clients.
9
- * @property {string} [app.name=''] - Name of the application.
10
- * @property {string} [app.author=''] - Name of the author.
11
- * @property {object} [env] - Environment configration object.
12
- * @property {boolean} env.port - Port on which the server is listening.
13
- * @property {boolean} env.useHttps - Define is the server run in http or in https.
14
- * @property {boolean} [env.httpsInfos={}] - Path to cert files for https.
15
- * @property {boolean} env.serverAddress - Domain name or IP of the server.
16
- * Mandatory if node clients are defined
17
- * @property {string} [env.websockets={}] - Configuration options for websockets.
18
- * @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
19
- */
20
-
21
1
  /**
22
2
  * Server-side part of the *soundworks* framework.
23
3
  *
@@ -1,22 +0,0 @@
1
- // https://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser
2
- export const isBrowser = new Function('try {return this===window;}catch(e){ return false;}');
3
-
4
- export function* idGenerator() {
5
- for (let i = 0; true; i++) {
6
- if (i === Number.MAX_SAFE_INTEGER) {
7
- i = 0;
8
- }
9
-
10
- yield i;
11
- }
12
- }
13
-
14
- export function isString(val) {
15
- return (typeof val === 'string' || val instanceof String);
16
- }
17
-
18
- export function isFunction(func) {
19
- return Object.prototype.toString.call(func) == '[object Function]' ||
20
- Object.prototype.toString.call(func) == '[object AsyncFunction]';
21
- }
22
-