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

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.
@@ -0,0 +1,66 @@
1
+ import { BATCHED_TRANSPORT_CHANNEL } from './constants.js';
2
+
3
+ /**
4
+ * This class proxies transports given the SharedStateManager to batch messages
5
+ * @private
6
+ */
7
+ class BatchedTransport {
8
+ constructor(transport) {
9
+ this._transport = transport;
10
+ this._stack = [];
11
+ this._listeners = new Map();
12
+ this._sending = false;
13
+
14
+ this._transport.addListener(BATCHED_TRANSPORT_CHANNEL, stack => {
15
+ stack.forEach(entry => {
16
+ const [channel, args] = entry;
17
+ // server side the transport is the same EventEmitter instance
18
+ // for both state manager server and client, so channel might not exist
19
+ // one side or the other.
20
+ if (this._listeners.has(channel)) {
21
+ const callbacks = this._listeners.get(channel);
22
+ callbacks.forEach(callback => callback(...args));
23
+ }
24
+ });
25
+ });
26
+ }
27
+
28
+ addListener(channel, callback) {
29
+ if (!this._listeners.has(channel)) {
30
+ this._listeners.set(channel, new Set());
31
+ }
32
+
33
+ const callbacks = this._listeners.get(channel);
34
+ callbacks.add(callback);
35
+ }
36
+
37
+ async emit(channel, ...args) {
38
+ this._stack.push([channel, args]);
39
+
40
+ if (!this._sending) {
41
+ this._sending = true;
42
+ this._sending = await false;
43
+ const stack = this._stack;
44
+ this._stack = [];
45
+ this._transport.emit(BATCHED_TRANSPORT_CHANNEL, stack);
46
+ }
47
+ }
48
+
49
+ removeListener(channel, callback) {
50
+ if (!this._listeners.has(channel)) {
51
+ const callbacks = this._listeners.get(channel);
52
+ callbacks.delete(callback);
53
+ }
54
+ }
55
+
56
+ removeAllListeners(channel = null) {
57
+ if (channel === null) {
58
+ this._listeners.clear();
59
+ } else if (this._listeners.has(channel)) {
60
+ const callbacks = this._listeners.get(channel);
61
+ callbacks.clear();
62
+ }
63
+ }
64
+ }
65
+
66
+ export default BatchedTransport;
@@ -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,48 @@
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) {
11
+ const reqId = this.generateRequestId.next().value;
12
+ this.store.set(reqId, { resolve, reject, type });
13
+
14
+ return reqId;
15
+ }
16
+
17
+ resolve(reqId, data) {
18
+ if (this.store.has(reqId)) {
19
+ const { resolve } = this.store.get(reqId);
20
+ this.store.delete(reqId);
21
+
22
+ resolve(data);
23
+ } else {
24
+ throw new Error(`[${this.name}] cannot resolve request id (${reqId}), id does not exist`);
25
+ }
26
+ }
27
+
28
+ reject(reqId, msg) {
29
+ if (this.store.has(reqId)) {
30
+ const { reject } = this.store.get(reqId);
31
+ this.store.delete(reqId);
32
+
33
+ reject(new Error(msg));
34
+ } else {
35
+ throw new Error(`[${this.name}] cannot reject request id (${reqId}), id does not exist`);
36
+ }
37
+ }
38
+
39
+ // reject all pendeing request
40
+ flush() {
41
+ for (let [_reqId, entry] of this.store) {
42
+ const { reject, type } = entry;
43
+ reject(new Error(`[${this.name}] Discard promise "${type}", cannot resolve`));
44
+ }
45
+
46
+ this.store.clear();
47
+ }
48
+ }
@@ -13,7 +13,8 @@ import {
13
13
 
14
14
  /**
15
15
  * The "real" state, this instance is kept private by the server.StateManager.
16
- * It cannot be accessed without a SharedState proxy
16
+ * It can only be accessed through a SharedState proxy.
17
+ *
17
18
  * @private
18
19
  */
19
20
  class SharedStatePrivate {
@@ -30,7 +31,8 @@ class SharedStatePrivate {
30
31
  }
31
32
 
32
33
  _attachClient(remoteId, client, isOwner = false) {
33
- this._attachedClients.set(remoteId, client);
34
+ const clientInfos = { client, isOwner };
35
+ this._attachedClients.set(remoteId, clientInfos);
34
36
 
35
37
  if (isOwner) {
36
38
  this._creatorRemoteId = remoteId;
@@ -63,16 +65,7 @@ class SharedStatePrivate {
63
65
  let hasUpdates = false;
64
66
 
65
67
  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
68
  const [newValue, changed] = this._parameters.set(name, updates[name]);
75
-
76
69
  // if `filterChange` is set to `false` we don't check if the value
77
70
  // has been changed or not, it is always propagated to client states
78
71
  const { event, filterChange } = this._parameters.getSchema(name);
@@ -89,49 +82,64 @@ class SharedStatePrivate {
89
82
  }
90
83
 
91
84
  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:
85
+ // Normal case
86
+ //
87
+ // We need to handle cases where:
88
+ // - client state (client.id: 2) sends a request
89
+ // - server attached state (client.id: -1) spot a problem and overrides the value
90
+ // We want the remote client (id: 2) to receive in the right order:
104
91
  // * 1. the value it requested,
105
92
  // * 2. the value overriden by the server-side attached state (id: -1)
93
+ //
94
+ // such problem is now better solved with the the upateHook system, none
95
+ // nonetheway we don't want to introduce inconsistencies here
96
+ //
97
+ // Then we propagate server-side last, because as the server transport
98
+ // is synchronous it can break ordering if a subscription function makes
99
+ // itself an update in reaction to an update. Propagating to server last
100
+ // alllows to maintain network messages order consistent.
101
+ //
102
+ // @note - remoteId correspond to unique remote state id
103
+
104
+ // propagate RESPONSE to the client that originates the request if not the server
105
+ if (client.id !== -1) {
106
+ client.transport.emit(
107
+ `${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
108
+ reqId, filteredUpdates, context,
109
+ );
110
+ }
106
111
 
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
- // }
112
+ // propagate NOTIFICATION to all other attached clients except server
113
+ for (let [peerRemoteId, clientInfos] of this._attachedClients) {
114
+ const peer = clientInfos.client;
113
115
 
114
- for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
115
- // propagate notification to all other attached clients except server
116
116
  if (remoteId !== peerRemoteId && peer.id !== -1) {
117
- peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
117
+ peer.transport.emit(
118
+ `${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`,
119
+ filteredUpdates, context,
120
+ );
118
121
  }
119
122
  }
120
123
 
121
- if (client.id !== -1) {
122
- client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
124
+ // propagate RESPONSE to server if it is the requester
125
+ if (client.id === -1) {
126
+ client.transport.emit(
127
+ `${UPDATE_RESPONSE}-${this.id}-${remoteId}`,
128
+ reqId, filteredUpdates, context,
129
+ );
123
130
  }
124
131
 
125
- for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
126
- // propagate notification to server
132
+ // propagate NOTIFICATION other attached state that belongs to server
133
+ for (let [peerRemoteId, clientInfos] of this._attachedClients) {
134
+ const peer = clientInfos.client;
135
+
127
136
  if (remoteId !== peerRemoteId && peer.id === -1) {
128
- peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
137
+ peer.transport.emit(
138
+ `${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`,
139
+ filteredUpdates, context,
140
+ );
129
141
  }
130
142
  }
131
-
132
- if (client.id === -1) {
133
- client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
134
- }
135
143
  } else {
136
144
  // propagate back to the requester that the update has been aborted
137
145
  // ignore all other attached clients.
@@ -152,14 +160,15 @@ class SharedStatePrivate {
152
160
  if (isOwner) {
153
161
  // delete only if creator
154
162
  client.transport.addListener(`${DELETE_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
155
- this._manager._serverStatesById.delete(this.id);
163
+ this._manager._sharedStatePrivateById.delete(this.id);
156
164
  // --------------------------------------------------------------------
157
165
  // WARNING - MAKE SURE WE DON'T HAVE PROBLEM W/ THAT
158
166
  // --------------------------------------------------------------------
159
167
  // @todo - propagate server-side last, because if a subscription function sends a
160
168
  // message to a client, network messages order are kept coherent
161
169
  // this._subscriptions.forEach(func => func(updated));
162
- for (let [remoteId, attached] of this._attachedClients.entries()) {
170
+ for (let [remoteId, clientInfos] of this._attachedClients) {
171
+ const attached = clientInfos.client;
163
172
  this._detachClient(remoteId, attached);
164
173
 
165
174
  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);