@soundworks/core 4.0.0-alpha.2 → 4.0.0-alpha.21

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,4 +1,4 @@
1
- import cloneDeep from 'lodash.clonedeep';
1
+ import cloneDeep from 'lodash/cloneDeep.js';
2
2
  import equal from 'fast-deep-equal';
3
3
 
4
4
  export const sharedOptions = {
@@ -17,7 +17,7 @@ export const types = {
17
17
  },
18
18
  coerceFunction: (name, def, value) => {
19
19
  if (typeof value !== 'boolean') {
20
- throw new TypeError(`[stateManager] Invalid value for boolean param "${name}": ${value}`);
20
+ throw new TypeError(`[SharedState] Invalid value "${value}" for boolean parameter "${name}"`);
21
21
  }
22
22
 
23
23
  return value;
@@ -30,7 +30,7 @@ export const types = {
30
30
  },
31
31
  coerceFunction: (name, def, value) => {
32
32
  if (typeof value !== 'string') {
33
- throw new TypeError(`[stateManager] Invalid value for string param "${name}": ${value}`);
33
+ throw new TypeError(`[SharedState] Invalid value "${value}" for string parameter "${name}"`);
34
34
  }
35
35
 
36
36
  return value;
@@ -63,7 +63,7 @@ export const types = {
63
63
  },
64
64
  coerceFunction: (name, def, value) => {
65
65
  if (!(typeof value === 'number' && Math.floor(value) === value)) {
66
- throw new TypeError(`[stateManager] Invalid value for integer param "${name}": ${value}`);
66
+ throw new TypeError(`[SharedState] Invalid value "${value}" for integer parameter "${name}"`);
67
67
  }
68
68
 
69
69
  return Math.max(def.min, Math.min(def.max, value));
@@ -96,7 +96,7 @@ export const types = {
96
96
  },
97
97
  coerceFunction: (name, def, value) => {
98
98
  if (typeof value !== 'number' || value !== value) { // reject NaN
99
- throw new TypeError(`[stateManager] Invalid value for float param "${name}": ${value}`);
99
+ throw new TypeError(`[SharedState] Invalid value "${value}" for float parameter "${name}"`);
100
100
  }
101
101
 
102
102
  return Math.max(def.min, Math.min(def.max, value));
@@ -109,7 +109,7 @@ export const types = {
109
109
  },
110
110
  coerceFunction: (name, def, value) => {
111
111
  if (def.list.indexOf(value) === -1) {
112
- throw new TypeError(`[stateManager] Invalid value for enum param "${name}": ${value}`);
112
+ throw new TypeError(`[SharedState] Invalid value "${value}" for enum parameter "${name}"`);
113
113
  }
114
114
 
115
115
 
@@ -139,11 +139,11 @@ class ParameterBag {
139
139
  const def = schema[name];
140
140
 
141
141
  if (!Object.prototype.hasOwnProperty.call(def, 'type')) {
142
- throw new TypeError(`[stateManager] Invalid schema definition - param "${name}": "type" key is required`);
142
+ throw new TypeError(`[StateManager.registerSchema] Invalid schema definition - param "${name}": "type" key is required`);
143
143
  }
144
144
 
145
145
  if (!Object.prototype.hasOwnProperty.call(types, def.type)) {
146
- throw new TypeError(`[stateManager] Invalid schema definition - param "${name}": "{ type: '${def.type}' }" does not exists`);
146
+ throw new TypeError(`[StateManager.registerSchema] Invalid schema definition - param "${name}": "{ type: '${def.type}' }" does not exists`);
147
147
  }
148
148
 
149
149
  const required = types[def.type].required;
@@ -152,7 +152,7 @@ class ParameterBag {
152
152
  if (def.event === true && key === 'default') {
153
153
  // do nothing, default is always null for `event` params
154
154
  } else if (!Object.prototype.hasOwnProperty.call(def, key)) {
155
- throw new TypeError(`[stateManager] Invalid schema definition - param "${name}" (type "${def.type}"): "${key}" key is required`);
155
+ throw new TypeError(`[StateManager.registerSchema] Invalid schema definition - param "${name}" (type "${def.type}"): "${key}" key is required`);
156
156
  }
157
157
  });
158
158
  }
@@ -160,7 +160,7 @@ class ParameterBag {
160
160
 
161
161
  constructor(schema, initValues = {}) {
162
162
  if (!schema) {
163
- throw new Error(`[stateManager] schema is mandatory`);
163
+ throw new Error(`[ParameterBag] schema is mandatory`);
164
164
  }
165
165
 
166
166
  schema = cloneDeep(schema);
@@ -193,7 +193,7 @@ class ParameterBag {
193
193
  // make shure initValues make sens according to the given schema
194
194
  for (let name in initValues) {
195
195
  if (!Object.prototype.hasOwnProperty.call(schema, name)) {
196
- throw new ReferenceError(`[stateManager] init value defined for undefined param "${name}"`);
196
+ throw new ReferenceError(`[StateManager.create] init value defined for undefined param "${name}"`);
197
197
  }
198
198
  }
199
199
 
@@ -226,7 +226,6 @@ class ParameterBag {
226
226
 
227
227
  this._schema[name].initValue = initValue;
228
228
  this._values[name] = initValue;
229
-
230
229
  }
231
230
  }
232
231
 
@@ -241,7 +240,8 @@ class ParameterBag {
241
240
  }
242
241
 
243
242
  /**
244
- * Return values of all parameters as a flat object.
243
+ * Return values of all parameters as a flat object. If a parameter is of `any`
244
+ * type, a deep copy is made.
245
245
  *
246
246
  * @return {object}
247
247
  */
@@ -255,6 +255,25 @@ class ParameterBag {
255
255
  return values;
256
256
  }
257
257
 
258
+ /**
259
+ * Return values of all parameters as a flat object. Similar to `getValues` but
260
+ * returns a reference to the underlying value in case of `any` type. May be
261
+ * usefull if the underlying value is big (e.g. sensors recordings, etc.) and
262
+ * deep cloning expensive. Be aware that if changes are made on the returned
263
+ * object, the state of your application will become inconsistent.
264
+ *
265
+ * @return {object}
266
+ */
267
+ getValuesUnsafe() {
268
+ let values = {};
269
+
270
+ for (let name in this._values) {
271
+ values[name] = this.getUnsafe(name);
272
+ }
273
+
274
+ return values;
275
+ }
276
+
258
277
  /**
259
278
  * Return the value of the given parameter. If the parameter is of `any` type,
260
279
  * a deep copy is returned.
@@ -264,10 +283,12 @@ class ParameterBag {
264
283
  */
265
284
  get(name) {
266
285
  if (!this.has(name)) {
267
- throw new ReferenceError(`[stateManager] Cannot get value of undefined parameter "${name}"`);
286
+ throw new ReferenceError(`[SharedState] Cannot get value of undefined parameter "${name}"`);
268
287
  }
269
288
 
270
289
  if (this._schema[name].type === 'any') {
290
+ // we return a deep copy of the object as we don't want the client code to
291
+ // be able to modify our underlying data.
271
292
  return cloneDeep(this._values[name]);
272
293
  } else {
273
294
  return this._values[name];
@@ -275,35 +296,72 @@ class ParameterBag {
275
296
  }
276
297
 
277
298
  /**
278
- * Set the value of a parameter. If the value of the parameter is updated
279
- * (aka if previous value is different from new value) all registered
280
- * callbacks are registered.
299
+ * Similar to `get` but returns a reference to the underlying value in case of
300
+ * `any` type. May be usefull if the underlying value is big (e.g. sensors
301
+ * recordings, etc.) and deep cloning expensive. Be aware that if changes are
302
+ * made on the returned object, the state of your application will become
303
+ * inconsistent.
281
304
  *
282
305
  * @param {string} name - Name of the parameter.
306
+ * @return {Mixed} - Value of the parameter.
307
+ */
308
+ getUnsafe(name) {
309
+ if (!this.has(name)) {
310
+ throw new ReferenceError(`[SharedState] Cannot get value of undefined parameter "${name}"`);
311
+ }
312
+
313
+ return this._values[name];
314
+ }
315
+
316
+ /**
317
+ * Check that the value is valid according to the schema and return it coerced
318
+ * to the schema definition
319
+ *
320
+ * @param {String} name - Name of the parameter.
283
321
  * @param {Mixed} value - Value of the parameter.
284
- * @param {boolean} [forcePropagation=false] - if true, propagate value even
285
- * if the value has not changed.
286
- * @return {Array} - [new value, updated flag].
287
322
  */
288
- set(name, value) {
323
+ coerceValue(name, value) {
289
324
  if (!this.has(name)) {
290
- throw new ReferenceError(`[stateManager] Cannot set value of undefined parameter "${name}"`);
325
+ throw new ReferenceError(`[SharedState] Cannot set value of undefined parameter "${name}"`);
291
326
  }
292
327
 
293
328
  const def = this._schema[name];
294
- const { coerceFunction } = types[def.type];
295
329
 
296
330
  if (value === null && def.nullable === false) {
297
- throw new TypeError(`[stateManager] Invalid value for ${def.type} param "${name}": value is null and param is not nullable`);
331
+ throw new TypeError(`[SharedState] Invalid value for ${def.type} param "${name}": value is null and param is not nullable`);
298
332
  } else if (value === null && def.nullable === true) {
299
333
  value = null;
300
334
  } else {
335
+ const { coerceFunction } = types[def.type];
301
336
  value = coerceFunction(name, def, value);
302
337
  }
303
338
 
339
+ return value;
340
+ }
341
+
342
+ /**
343
+ * Set the value of a parameter. If the value of the parameter is updated
344
+ * (aka if previous value is different from new value) all registered
345
+ * callbacks are registered.
346
+ *
347
+ * @param {string} name - Name of the parameter.
348
+ * @param {Mixed} value - Value of the parameter.
349
+ * @return {Array} - [new value, updated flag].
350
+ */
351
+ set(name, value) {
352
+ value = this.coerceValue(name, value);
304
353
  const currentValue = this._values[name];
305
354
  const updated = !equal(currentValue, value);
306
355
 
356
+ // we store a deep copy of the object as we don't want the client to be able
357
+ // to modify our underlying data, which leads to unexpected behavior where the
358
+ // deep equal check to returns true, and therefore the update is not triggered.
359
+ // @see tests/common.state-manager.spec.js
360
+ // 'should copy stored value for "any" type to have a predictable behavior'
361
+ if (this._schema[name].type === 'any') {
362
+ value = cloneDeep(value);
363
+ }
364
+
307
365
  this._values[name] = value;
308
366
 
309
367
  // return tuple so that the state manager can handle the `filterChange` option
@@ -335,30 +393,34 @@ class ParameterBag {
335
393
  getSchema(name = null) {
336
394
  if (name === null) {
337
395
  return this._schema;
338
- } else {
339
- if (!this.has(name)) {
340
- throw new ReferenceError(`[stateManager] Cannot get schema description of undefined parameter "${name}"`);
341
- }
396
+ }
342
397
 
343
- return this._schema[name];
398
+ if (!this.has(name)) {
399
+ throw new ReferenceError(`[SharedState] Cannot get schema description of undefined parameter "${name}"`);
344
400
  }
401
+
402
+ return this._schema[name];
345
403
  }
346
404
 
347
405
  // return the default value, if initValue has been given, return init values
348
406
  getInitValues() {
349
407
  const initValues = {};
408
+
350
409
  for (let [name, def] of Object.entries(this._schema)) {
351
410
  initValues[name] = def.initValue;
352
411
  }
412
+
353
413
  return initValues;
354
414
  }
355
415
 
356
416
  // return the default value, if initValue has been given, return init values
357
417
  getDefaults() {
358
418
  const defaults = {};
419
+
359
420
  for (let [name, def] of Object.entries(this._schema)) {
360
- defaults[name] = def.defaults;
421
+ defaults[name] = def.default;
361
422
  }
423
+
362
424
  return defaults;
363
425
  }
364
426
  }
@@ -0,0 +1,53 @@
1
+ import { idGenerator } from '@ircam/sc-utils';
2
+
3
+ export default class PromiseStore {
4
+ constructor(name) {
5
+ this.name = name;
6
+ this.store = new Map();
7
+ this.generateRequestId = idGenerator();
8
+ }
9
+
10
+ add(resolve, reject, type, localParams) {
11
+ const reqId = this.generateRequestId.next().value;
12
+ this.store.set(reqId, { resolve, reject, type, localParams });
13
+
14
+ return reqId;
15
+ }
16
+
17
+ resolve(reqId, data) {
18
+ if (this.store.has(reqId)) {
19
+ const { resolve, localParams } = this.store.get(reqId);
20
+ this.store.delete(reqId);
21
+
22
+ // re-merge local params into network response
23
+ if (localParams !== undefined) {
24
+ Object.assign(data, localParams);
25
+ }
26
+
27
+ resolve(data);
28
+ } else {
29
+ throw new Error(`[${this.name}] cannot resolve request id (${reqId}), id does not exist`);
30
+ }
31
+ }
32
+
33
+ reject(reqId, msg) {
34
+ if (this.store.has(reqId)) {
35
+ const { reject } = this.store.get(reqId);
36
+ this.store.delete(reqId);
37
+
38
+ reject(new Error(msg));
39
+ } else {
40
+ throw new Error(`[${this.name}] cannot reject request id (${reqId}), id does not exist`);
41
+ }
42
+ }
43
+
44
+ // reject all pending request
45
+ flush() {
46
+ for (let [_reqId, entry] of this.store) {
47
+ const { reject, type } = entry;
48
+ reject(new Error(`[${this.name}] Discard promise "${type}", cannot resolve`));
49
+ }
50
+
51
+ this.store.clear();
52
+ }
53
+ }
@@ -11,9 +11,29 @@ import {
11
11
  UPDATE_NOTIFICATION,
12
12
  } from '../common/constants.js';
13
13
 
14
+ /**
15
+ * Filter update object according to filter list.
16
+ * Return identity is filter is null
17
+ * @param {object} updates
18
+ * @param {array|null} filter
19
+ */
20
+ function filterUpdates(updates, filter) {
21
+ if (filter === null) {
22
+ return updates;
23
+ }
24
+
25
+ return filter.reduce((acc, key) => {
26
+ if (key in updates) {
27
+ acc[key] = updates[key];
28
+ }
29
+ return acc;
30
+ }, {});
31
+ }
32
+
14
33
  /**
15
34
  * The "real" state, this instance is kept private by the server.StateManager.
16
- * It cannot be accessed without a SharedState proxy
35
+ * It can only be accessed through a SharedState proxy.
36
+ *
17
37
  * @private
18
38
  */
19
39
  class SharedStatePrivate {
@@ -29,8 +49,9 @@ class SharedStatePrivate {
29
49
  this._creatorId = null;
30
50
  }
31
51
 
32
- _attachClient(remoteId, client, isOwner = false) {
33
- this._attachedClients.set(remoteId, client);
52
+ _attachClient(remoteId, client, isOwner, filter) {
53
+ const clientInfos = { client, isOwner, filter };
54
+ this._attachedClients.set(remoteId, clientInfos);
34
55
 
35
56
  if (isOwner) {
36
57
  this._creatorRemoteId = remoteId;
@@ -59,20 +80,11 @@ class SharedStatePrivate {
59
80
  }
60
81
 
61
82
  if (hookAborted === false) {
62
- const filteredUpdates = {};
83
+ let acknowledgedUpdates = {};
63
84
  let hasUpdates = false;
64
85
 
65
86
  for (let name in updates) {
66
- // from v3.1.0 - the `filteredUpdates` check is made using 'fast-deep-equal'
67
- // cf. https://github.com/epoberezkin/fast-deep-equal
68
- // therefore unchanged objects are not considered changed
69
- // nor propagated anymore.
70
- // until v3.0.4 - we checked the `schema[name].type === 'any'`, to always consider
71
- // objects as dirty, because if the state is attached locally, we
72
- // compare the Object instances instead of their values.
73
- // @note - this should be made more robust but how?
74
87
  const [newValue, changed] = this._parameters.set(name, updates[name]);
75
-
76
88
  // if `filterChange` is set to `false` we don't check if the value
77
89
  // has been changed or not, it is always propagated to client states
78
90
  const { event, filterChange } = this._parameters.getSchema(name);
@@ -83,55 +95,83 @@ class SharedStatePrivate {
83
95
  }
84
96
 
85
97
  if ((filterChange && changed) || !filterChange) {
86
- filteredUpdates[name] = newValue;
98
+ acknowledgedUpdates[name] = newValue;
87
99
  hasUpdates = true;
88
100
  }
89
101
  }
90
102
 
91
103
  if (hasUpdates) {
92
- // send response to requester
93
- // client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
94
-
95
- // @note: we propagate server-side last, because as the server transport
96
- // is synchronous it can break ordering if a subscription function makes
97
- // itself an update in reaction to an update, therefore network messages
98
- // order would be broken,
99
-
100
- // we need to handle cases where:
101
- // client state (client.id: 2) sends a request
102
- // server attached state (client.id: -1) spot a problem and overrides the value
103
- // we want the remote client (id: 2) to receive in the right order:
104
+ // Normal case
105
+ //
106
+ // We need to handle cases where:
107
+ // - client state (client.id: 2) sends a request
108
+ // - server attached state (client.id: -1) spot a problem and overrides the value
109
+ // We want the remote client (id: 2) to receive in the right order:
104
110
  // * 1. the value it requested,
105
111
  // * 2. the value overriden by the server-side attached state (id: -1)
112
+ //
113
+ // such problem is now better solved with the the upateHook system, none
114
+ // nonetheway we don't want to introduce inconsistencies here
115
+ //
116
+ // Then we propagate server-side last, because as the server transport
117
+ // is synchronous it can break ordering if a subscription function makes
118
+ // itself an update in reaction to an update. Propagating to server last
119
+ // alllows to maintain network messages order consistent.
120
+ //
121
+ // @note - remoteId correspond to unique remote state id
106
122
 
107
- // this problem could be solved properly with a reducer system:
108
- // if (dirty) {
109
- // -> call (async) reducer
110
- // -> get values from reducer
111
- //. -> dispatch to everybody
112
- // }
123
+ // propagate RESPONSE to the client that originates the request if not the server
124
+ if (client.id !== -1) {
125
+ // no need to filter updates on requested, is blocked on client-side
126
+ client.transport.emit(
127
+ `${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
128
+ reqId, acknowledgedUpdates, context,
129
+ );
130
+ }
131
+
132
+ // propagate NOTIFICATION to all attached clients except server
133
+ for (let [peerRemoteId, clientInfos] of this._attachedClients) {
134
+ const peer = clientInfos.client;
113
135
 
114
- for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
115
- // propagate notification to all other attached clients except server
116
136
  if (remoteId !== peerRemoteId && peer.id !== -1) {
117
- peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
137
+ const filter = clientInfos.filter;
138
+ const filteredUpdates = filterUpdates(acknowledgedUpdates, filter);
139
+
140
+ // propagate only if there something left after applying the filter
141
+ if (Object.keys(filteredUpdates).length > 0) {
142
+ peer.transport.emit(
143
+ `${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`,
144
+ filteredUpdates, context,
145
+ );
146
+ }
118
147
  }
119
148
  }
120
149
 
121
- if (client.id !== -1) {
122
- client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
150
+ // propagate RESPONSE to server if it is the requester
151
+ if (client.id === -1) {
152
+ // no need to filter updates on requested, is blocked on client-side
153
+ client.transport.emit(
154
+ `${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
155
+ reqId, acknowledgedUpdates, context,
156
+ );
123
157
  }
124
158
 
125
- for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
126
- // propagate notification to server
159
+ // propagate NOTIFICATION to other state attached on the server
160
+ for (let [peerRemoteId, clientInfos] of this._attachedClients) {
161
+ const peer = clientInfos.client;
162
+
127
163
  if (remoteId !== peerRemoteId && peer.id === -1) {
128
- peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
164
+ const filter = clientInfos.filter;
165
+ const filteredUpdates = filterUpdates(acknowledgedUpdates, filter);
166
+
167
+ if (Object.keys(filteredUpdates).length > 0) {
168
+ peer.transport.emit(
169
+ `${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`,
170
+ filteredUpdates, context,
171
+ );
172
+ }
129
173
  }
130
174
  }
131
-
132
- if (client.id === -1) {
133
- client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
134
- }
135
175
  } else {
136
176
  // propagate back to the requester that the update has been aborted
137
177
  // ignore all other attached clients.
@@ -152,14 +192,15 @@ class SharedStatePrivate {
152
192
  if (isOwner) {
153
193
  // delete only if creator
154
194
  client.transport.addListener(`${DELETE_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
155
- this._manager._serverStatesById.delete(this.id);
195
+ this._manager._sharedStatePrivateById.delete(this.id);
156
196
  // --------------------------------------------------------------------
157
197
  // WARNING - MAKE SURE WE DON'T HAVE PROBLEM W/ THAT
158
198
  // --------------------------------------------------------------------
159
199
  // @todo - propagate server-side last, because if a subscription function sends a
160
200
  // message to a client, network messages order are kept coherent
161
201
  // this._subscriptions.forEach(func => func(updated));
162
- for (let [remoteId, attached] of this._attachedClients.entries()) {
202
+ for (let [remoteId, clientInfos] of this._attachedClients) {
203
+ const attached = clientInfos.client;
163
204
  this._detachClient(remoteId, attached);
164
205
 
165
206
  if (remoteId === this._creatorRemoteId) {
@@ -1,6 +1,14 @@
1
1
  // id of the server when owner of a state
2
2
  export const SERVER_ID = -1;
3
3
 
4
+ export const PING_INTERVAL = 10 * 1000;
5
+ export const PING_LATENCY_TOLERANCE = 1000;
6
+ export const PING_MESSAGE = 'h:ping';
7
+ export const PONG_MESSAGE = 'h:pong';
8
+
9
+ // batched transport channel
10
+ export const BATCHED_TRANSPORT_CHANNEL = 'b:t';
11
+
4
12
  // shared states channels
5
13
  export const CREATE_REQUEST = 's:c:req';
6
14
  export const CREATE_RESPONSE = 's:c:res';
@@ -21,6 +29,7 @@ export const DETACH_ERROR = 's:dt:err';
21
29
 
22
30
  export const OBSERVE_REQUEST = 's:o:req';
23
31
  export const OBSERVE_RESPONSE = 's:o:res';
32
+ export const OBSERVE_ERROR = 's:o:err';
24
33
  export const OBSERVE_NOTIFICATION = 's:o:not';
25
34
 
26
35
  export const UNOBSERVE_NOTIFICATION = 's:uo:not';
@@ -32,6 +41,10 @@ export const UPDATE_NOTIFICATION = 's:u:not';
32
41
 
33
42
  export const DELETE_SCHEMA = 's:d:s';
34
43
 
44
+ export const GET_SCHEMA_REQUEST = 's:s:req';
45
+ export const GET_SCHEMA_RESPONSE = 's:s:res';
46
+ export const GET_SCHEMA_ERROR = 's:s:err';
47
+
35
48
  // context channels
36
49
  export const CONTEXT_ENTER_REQUEST = 'c:en:req';
37
50
  export const CONTEXT_ENTER_RESPONSE = 'c:en:res';
@@ -47,5 +60,5 @@ export const CLIENT_HANDSHAKE_RESPONSE = 'cl:h:res';
47
60
  export const CLIENT_HANDSHAKE_ERROR = 'cl:h:err';
48
61
 
49
62
  // audit state schema name
50
- export const AUDIT_STATE_NAME = 's:c:audit';
63
+ export const AUDIT_STATE_NAME = 'p:s:audit';
51
64
  export const PRIVATE_STATES = [AUDIT_STATE_NAME];
@@ -1,8 +1,7 @@
1
1
  import 'fast-text-encoding';
2
- import root from 'window-or-global';
3
2
 
4
- const encoder = new root.TextEncoder('utf-8');
5
- const decoder = new root.TextDecoder('utf-8');
3
+ const encoder = new TextEncoder('utf-8');
4
+ const decoder = new TextDecoder('utf-8');
6
5
 
7
6
  const types = [
8
7
  'Int8Array',
@@ -57,7 +56,7 @@ export function unpackBinaryMessage(buffer /* arraybuffer */) {
57
56
  const channel = decoder.decode(channelBuffer);
58
57
  const type = types[typeIndex];
59
58
  // slice (copy) the underlying ArrayBuffer to create a clean TypedArray
60
- const data = new root[type](buffer.slice(startOffset));
59
+ const data = new globalThis[type](buffer.slice(startOffset));
61
60
 
62
61
  return [channel, data];
63
62
  }
@@ -1,7 +1,6 @@
1
+ import { idGenerator } from '@ircam/sc-utils';
1
2
  import { v4 as uuid } from 'uuid';
2
3
 
3
- import { idGenerator } from '../common/utils.js';
4
-
5
4
  const generateId = idGenerator();
6
5
 
7
6
  /**
@@ -44,6 +43,12 @@ class Client {
44
43
  * @type {server.Socket}
45
44
  */
46
45
  this.socket = socket;
46
+
47
+ /**
48
+ * Is set in server._onSocketConnection
49
+ * @private
50
+ */
51
+ this.token = null;
47
52
  }
48
53
  }
49
54
 
@@ -114,7 +114,7 @@ class ContextManager {
114
114
  this._contextStartPromises.set(contextName, startPromise);
115
115
  await startPromise;
116
116
  context.status = 'started';
117
- } catch(err) {
117
+ } catch (err) {
118
118
  context.status = 'errored';
119
119
  throw new Error(err);
120
120
  }
@@ -144,7 +144,7 @@ class ContextManager {
144
144
  let context;
145
145
  try {
146
146
  context = await this.get(contextName);
147
- } catch(err) {
147
+ } catch (err) {
148
148
  client.socket.send(
149
149
  CONTEXT_ENTER_ERROR,
150
150
  reqId,
@@ -185,7 +185,7 @@ class ContextManager {
185
185
  await context.enter(client);
186
186
 
187
187
  client.socket.send(CONTEXT_ENTER_RESPONSE, reqId, contextName);
188
- } catch(err) {
188
+ } catch (err) {
189
189
  client.socket.send(CONTEXT_ENTER_ERROR, reqId, contextName, err.message);
190
190
  }
191
191
  });
@@ -207,7 +207,7 @@ class ContextManager {
207
207
  try {
208
208
  await context.exit(client);
209
209
  client.socket.send(CONTEXT_EXIT_RESPONSE, reqId, contextName);
210
- } catch(err) {
210
+ } catch (err) {
211
211
  client.socket.send(CONTEXT_EXIT_ERROR, reqId, contextName, err.message);
212
212
  }
213
213
  } else {
@@ -78,7 +78,7 @@ import Server from './Server.js';
78
78
  class PluginManager extends BasePluginManager {
79
79
  constructor(server) {
80
80
  if (!(server instanceof Server)) {
81
- throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(server)" should receive an instance of "soundworks.Server as argument"`);
81
+ throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(server)" should receive an instance of "soundworks.Server" as argument`);
82
82
  }
83
83
 
84
84
  super(server);