@soundworks/core 5.5.0 → 5.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +6 -0
  3. package/package.json +17 -11
  4. package/src/client/Client.js +7 -4
  5. package/src/client/ClientSocket.js +2 -3
  6. package/src/common/BasePlugin.js +2 -2
  7. package/src/common/BasePluginManager.js +2 -2
  8. package/src/common/BaseStateManager.js +253 -221
  9. package/src/common/ParameterBag.js +14 -14
  10. package/src/common/SharedState.js +405 -247
  11. package/src/common/SharedStateCollection.js +82 -32
  12. package/src/common/constants.js +2 -0
  13. package/src/common/isomorphic/colored-logger.browser.js +17 -0
  14. package/src/common/isomorphic/colored-logger.node.js +19 -0
  15. package/src/common/isomorphic/web-socket.browser.js +1 -0
  16. package/src/common/isomorphic/web-socket.node.js +3 -0
  17. package/src/common/logs/gated-logger.js +30 -0
  18. package/src/common/logs/warnings.js +36 -0
  19. package/src/common/shared-state-utils.js +102 -0
  20. package/src/common/version.js +1 -1
  21. package/src/server/Server.js +23 -35
  22. package/src/server/ServerClient.js +7 -3
  23. package/src/server/ServerStateManager.js +242 -227
  24. package/src/server/SharedStatePrivate.js +105 -68
  25. package/src/server/create-http-server.js +81 -39
  26. package/src/server/crypto.js +2 -3
  27. package/types/client/Client.d.ts.map +1 -1
  28. package/types/client/ClientSocket.d.ts.map +1 -1
  29. package/types/common/BaseStateManager.d.ts +34 -47
  30. package/types/common/BaseStateManager.d.ts.map +1 -1
  31. package/types/common/ParameterBag.d.ts +28 -6
  32. package/types/common/ParameterBag.d.ts.map +1 -1
  33. package/types/common/SharedState.d.ts +55 -29
  34. package/types/common/SharedState.d.ts.map +1 -1
  35. package/types/common/SharedStateCollection.d.ts +26 -8
  36. package/types/common/SharedStateCollection.d.ts.map +1 -1
  37. package/types/common/constants.d.ts +1 -0
  38. package/types/common/constants.d.ts.map +1 -1
  39. package/types/common/isomorphic/colored-logger.browser.d.ts +8 -0
  40. package/types/common/isomorphic/colored-logger.browser.d.ts.map +1 -0
  41. package/types/common/isomorphic/colored-logger.node.d.ts +8 -0
  42. package/types/common/isomorphic/colored-logger.node.d.ts.map +1 -0
  43. package/types/common/isomorphic/web-socket.browser.d.ts +10 -0
  44. package/types/common/isomorphic/web-socket.browser.d.ts.map +1 -0
  45. package/types/common/isomorphic/web-socket.node.d.ts +2 -0
  46. package/types/common/isomorphic/web-socket.node.d.ts.map +1 -0
  47. package/types/common/logs/gated-logger.d.ts +9 -0
  48. package/types/common/logs/gated-logger.d.ts.map +1 -0
  49. package/types/common/logs/warnings.d.ts +7 -0
  50. package/types/common/logs/warnings.d.ts.map +1 -0
  51. package/types/common/shared-state-utils.d.ts +12 -0
  52. package/types/common/shared-state-utils.d.ts.map +1 -0
  53. package/types/common/version.d.ts +1 -1
  54. package/types/server/Server.d.ts.map +1 -1
  55. package/types/server/ServerClient.d.ts.map +1 -1
  56. package/types/server/ServerStateManager.d.ts +10 -10
  57. package/types/server/ServerStateManager.d.ts.map +1 -1
  58. package/types/server/SharedStatePrivate.d.ts +2 -3
  59. package/types/server/SharedStatePrivate.d.ts.map +1 -1
  60. package/types/server/create-http-server.d.ts +1 -0
  61. package/types/server/create-http-server.d.ts.map +1 -1
  62. package/types/server/crypto.d.ts.map +1 -1
  63. package/src/common/logger.js +0 -150
  64. package/types/common/logger.d.ts +0 -19
  65. package/types/common/logger.d.ts.map +0 -1
@@ -1,4 +1,9 @@
1
- import { isString, isFunction, isPlainObject } from '@ircam/sc-utils';
1
+ import {
2
+ isString,
3
+ isFunction,
4
+ isPlainObject,
5
+ } from '@ircam/sc-utils';
6
+
2
7
  import SharedState from './SharedState.js';
3
8
  import SharedStateCollection, {
4
9
  kSharedStateCollectionInit,
@@ -18,18 +23,19 @@ import {
18
23
  OBSERVE_ERROR,
19
24
  OBSERVE_NOTIFICATION,
20
25
  UNOBSERVE_NOTIFICATION,
21
- // DELETE_SHARED_STATE_CLASS,
22
26
  GET_CLASS_DESCRIPTION_REQUEST,
23
27
  GET_CLASS_DESCRIPTION_RESPONSE,
24
28
  GET_CLASS_DESCRIPTION_ERROR,
25
29
  } from './constants.js';
26
- import logger from './logger.js';
30
+ import warnings from './logs/warnings.js';
27
31
 
28
32
  export const kStateManagerInit = Symbol('soundworks:state-manager-init');
29
33
  export const kStateManagerDeleteState = Symbol('soundworks:state-manager-delete-state');
34
+ export const kPendingSharedStateConstructionData = Symbol('soundworks:state-manager-pending-shared-state-construction-data');
30
35
  // for testing purposes
31
36
  export const kStateManagerClient = Symbol('soundworks:state-manager-client');
32
37
 
38
+
33
39
  /**
34
40
  * Callback executed when a state is created on the network.
35
41
  *
@@ -61,21 +67,6 @@ class BaseStateManager {
61
67
 
62
68
  }
63
69
 
64
- /** @private */
65
- #filterObserve(observedClassName, className, creatorId, options) {
66
- let filter = true;
67
-
68
- if (observedClassName === null || observedClassName === className) {
69
- filter = false;
70
- }
71
- // filter state created by client if excludeLocal is true
72
- if (options.excludeLocal === true && creatorId === this[kStateManagerClient].id) {
73
- filter = true;
74
- }
75
-
76
- return filter;
77
- }
78
-
79
70
  /** @private */
80
71
  [kStateManagerDeleteState](stateId) {
81
72
  this.#statesById.delete(stateId);
@@ -93,116 +84,18 @@ class BaseStateManager {
93
84
  const batchedTransport = new BatchedTransport(transport);
94
85
  this[kStateManagerClient] = { id, transport: batchedTransport };
95
86
 
96
- // ---------------------------------------------
97
- // CREATE
98
- // ---------------------------------------------
99
- this[kStateManagerClient].transport.addListener(
100
- CREATE_RESPONSE,
101
- (reqId, stateId, instanceId, className, classDescription, initValues) => {
102
- const state = new SharedState({
103
- manager: this,
104
- className,
105
- classDescription,
106
- stateId,
107
- instanceId,
108
- isOwner: true,
109
- initValues,
110
- filter: null, // owner cannot filter parameters
111
- });
112
-
113
- this.#statesById.set(state.id, state);
114
- this.#promiseStore.resolve(reqId, state);
115
- },
116
- );
117
-
118
- this[kStateManagerClient].transport.addListener(CREATE_ERROR, (reqId, msg) => {
119
- msg = `Cannot execute 'create' on BaseStateManager: ${msg}`;
120
- this.#promiseStore.reject(reqId, msg);
121
- });
122
-
123
- // ---------------------------------------------
124
- // ATTACH (when creator, is attached by default)
125
- // ---------------------------------------------
126
- this[kStateManagerClient].transport.addListener(
127
- ATTACH_RESPONSE,
128
- (reqId, stateId, instanceId, className, classDescription, currentValues, filter) => {
129
- const state = new SharedState({
130
- manager: this,
131
- className,
132
- classDescription,
133
- stateId,
134
- instanceId,
135
- isOwner: false,
136
- initValues: currentValues,
137
- filter,
138
- });
139
-
140
- this.#statesById.set(state.id, state);
141
- this.#promiseStore.resolve(reqId, state);
142
- },
143
- );
144
-
145
- this[kStateManagerClient].transport.addListener(ATTACH_ERROR, (reqId, msg) => {
146
- msg = `Cannot execute 'attach' on BaseStateManager: ${msg}`;
147
- this.#promiseStore.reject(reqId, msg);
148
- });
87
+ this[kStateManagerClient].transport.addListener(CREATE_RESPONSE, this.#onCreateResponse);
88
+ this[kStateManagerClient].transport.addListener(CREATE_ERROR, this.#onCreateError);
149
89
 
150
- // ---------------------------------------------
151
- // OBSERVE PEERS (be notified when a state is created, lazy)
152
- // ---------------------------------------------
153
- this[kStateManagerClient].transport.addListener(OBSERVE_RESPONSE, async (reqId, ...list) => {
154
- // retrieve the callback that have been stored in `observe()` to make sure
155
- // we don't call another callback that may have been registered earlier.
156
- const observeInfos = this.#observeRequestCallbacks.get(reqId);
157
- const [observedClassName, callback, options] = observeInfos;
158
- // move observeInfos from `_observeRequestCallbacks` to `_observeListeners`
159
- // to guarantee future order of execution
160
- this.#observeRequestCallbacks.delete(reqId);
161
- this.#observeListeners.add(observeInfos);
90
+ this[kStateManagerClient].transport.addListener(ATTACH_RESPONSE, this.#onAttachResponse);
91
+ this[kStateManagerClient].transport.addListener(ATTACH_ERROR, this.#onAttachError);
162
92
 
163
- const promises = list.map(([className, stateId, nodeId]) => {
164
- const filter = this.#filterObserve(observedClassName, className, nodeId, options);
93
+ this[kStateManagerClient].transport.addListener(OBSERVE_RESPONSE, this.#onObserveResponse);
94
+ this[kStateManagerClient].transport.addListener(OBSERVE_ERROR, this.#onObserveError);
95
+ this[kStateManagerClient].transport.addListener(OBSERVE_NOTIFICATION, this.#onObserveNotification);
165
96
 
166
- if (!filter) {
167
- return callback(className, stateId, nodeId);
168
- } else {
169
- return Promise.resolve();
170
- }
171
- });
172
-
173
- await Promise.all(promises);
174
-
175
- const unsubscribe = () => {
176
- this.#observeListeners.delete(observeInfos);
177
- // no more listeners, we can stop receiving notifications from the server
178
- if (this.#observeListeners.size === 0) {
179
- this[kStateManagerClient].transport.emit(UNOBSERVE_NOTIFICATION);
180
- }
181
- };
182
-
183
- this.#promiseStore.resolve(reqId, unsubscribe);
184
- });
185
-
186
- // Observe error occur if observed class name does not exists
187
- this[kStateManagerClient].transport.addListener(OBSERVE_ERROR, (reqId, msg) => {
188
- msg = `Cannot execute 'observe' on BaseStateManager: ${msg}`;
189
- this.#observeRequestCallbacks.delete(reqId);
190
- this.#promiseStore.reject(reqId, msg);
191
- });
192
-
193
- this[kStateManagerClient].transport.addListener(
194
- OBSERVE_NOTIFICATION,
195
- (className, stateId, nodeId) => {
196
- this.#observeListeners.forEach(observeInfos => {
197
- const [observedClassName, callback, options] = observeInfos;
198
- const filter = this.#filterObserve(observedClassName, className, nodeId, options);
199
-
200
- if (!filter) {
201
- callback(className, stateId, nodeId);
202
- }
203
- });
204
- },
205
- );
97
+ this[kStateManagerClient].transport.addListener(GET_CLASS_DESCRIPTION_RESPONSE, this.#onGetClassDescriptionResponse);
98
+ this[kStateManagerClient].transport.addListener(GET_CLASS_DESCRIPTION_ERROR, this.#onGetClassDescriptionError);
206
99
 
207
100
  // ---------------------------------------------
208
101
  // note 2025-05-05: caching of class descriptions has been removed because it
@@ -211,25 +104,32 @@ class BaseStateManager {
211
104
  // ---------------------------------------------
212
105
  // this[kStateManagerClient].transport.addListener(DELETE_SHARED_STATE_CLASS, _className => {});
213
106
 
214
- // ---------------------------------------------
215
- // Get class description
216
- // ---------------------------------------------
217
- this[kStateManagerClient].transport.addListener(
218
- GET_CLASS_DESCRIPTION_RESPONSE,
219
- (reqId, _className, classDescription) => {
220
- const fullDescription = ParameterBag.getFullDescription(classDescription);
221
- this.#promiseStore.resolve(reqId, fullDescription);
222
- },
223
- );
224
-
225
- this[kStateManagerClient].transport.addListener(GET_CLASS_DESCRIPTION_ERROR, (reqId, msg) => {
226
- msg = `Cannot execute 'getClassDescription' on BaseStateManager: ${msg}`;
227
- this.#promiseStore.reject(reqId, msg);
228
- });
229
-
230
107
  this.#status = 'inited';
231
108
  }
232
109
 
110
+ // This pattern allows to have a clean SharedState constructor signature for which
111
+ // which will provide a cleaner user facing API when derived.
112
+ // cf. instantiation pattern from AudioWorkletProcessor
113
+ // <https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-instantiation>
114
+ #buildSharedState(options) {
115
+ globalThis[kPendingSharedStateConstructionData] = {
116
+ manager: this,
117
+ className: options.className,
118
+ classDescription: options.classDescription,
119
+ stateId: options.stateId,
120
+ instanceId: options.instanceId,
121
+ isOwner: options.isOwner,
122
+ initValues: options.initValues,
123
+ filter: options.filter,
124
+ };
125
+
126
+ const state = new SharedState();
127
+ // cleanup global scope
128
+ delete globalThis[kPendingSharedStateConstructionData];
129
+
130
+ return state;
131
+ }
132
+
233
133
  /**
234
134
  * Validates the given class description.
235
135
  *
@@ -261,13 +161,15 @@ class BaseStateManager {
261
161
  return promise;
262
162
  }
263
163
 
264
- /**
265
- * @deprecated Use {@link BaseStateManager#getClassDescription} instead.
266
- */
267
- async getSchema(className) {
268
- logger.deprecated('BaseStateManager#getSchema', 'BaseStateManager#getClassDescription', '4.0.0-alpha.29');
269
- return this.getClassDescription(className);
270
- }
164
+ #onGetClassDescriptionResponse = (reqId, _className, classDescription) => {
165
+ const fullDescription = ParameterBag.getFullDescription(classDescription);
166
+ this.#promiseStore.resolve(reqId, fullDescription);
167
+ };
168
+
169
+ #onGetClassDescriptionError = (reqId, msg) => {
170
+ msg = `Cannot execute 'getClassDescription' on BaseStateManager: ${msg}`;
171
+ this.#promiseStore.reject(reqId, msg);
172
+ };
271
173
 
272
174
  /**
273
175
  * Create a {@link SharedState} instance from a registered class.
@@ -289,6 +191,26 @@ class BaseStateManager {
289
191
  return promise;
290
192
  }
291
193
 
194
+ #onCreateResponse = (reqId, stateId, instanceId, className, classDescription, initValues) => {
195
+ const state = this.#buildSharedState({
196
+ className,
197
+ classDescription,
198
+ stateId,
199
+ instanceId,
200
+ isOwner: true,
201
+ initValues,
202
+ filter: null, // owner cannot filter parameters
203
+ });
204
+
205
+ this.#statesById.set(state.id, state);
206
+ this.#promiseStore.resolve(reqId, state);
207
+ };
208
+
209
+ #onCreateError = (reqId, msg) => {
210
+ msg = `Cannot execute 'create' on BaseStateManager: ${msg}`;
211
+ this.#promiseStore.reject(reqId, msg);
212
+ };
213
+
292
214
  /**
293
215
  * Attach to an existing {@link SharedState} instance.
294
216
  *
@@ -315,7 +237,9 @@ class BaseStateManager {
315
237
  *
316
238
  * @overload
317
239
  * @param {SharedStateClassName} className - Name of the class.
318
- * @param {string[]} filter - List of parameters of interest
240
+ * @param {Object} [options={}]
241
+ * @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
242
+ * @param {string[]} [options.blackList] - Black list of parameter names to ignore.
319
243
  * @returns {Promise<SharedState>}
320
244
  *
321
245
  * @example
@@ -327,7 +251,9 @@ class BaseStateManager {
327
251
  * @overload
328
252
  * @param {SharedStateClassName} className - Name of the class.
329
253
  * @param {number} stateId - Id of the state
330
- * @param {string[]} filter - List of parameters of interest
254
+ * @param {object} [options={}]
255
+ * @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
256
+ * @param {string[]} [options.blackList] - Black list of parameter names to ignore.
331
257
  * @returns {Promise<SharedState>}
332
258
  *
333
259
  * @example
@@ -339,21 +265,22 @@ class BaseStateManager {
339
265
  * Alternative signatures:
340
266
  * - `stateManager.attach(className)`
341
267
  * - `stateManager.attach(className, stateId)`
342
- * - `stateManager.attach(className, filter)`
343
- * - `stateManager.attach(className, stateId, filter)`
268
+ * - `stateManager.attach(className, options)`
269
+ * - `stateManager.attach(className, stateId, options)`
344
270
  *
345
271
  * @param {SharedStateClassName} className - Name of the class.
346
- * @param {number|string[]} [stateIdOrFilter] - Id of the state to attach to. If `null`,
272
+ * @param {number} [stateId] - Id of the state to attach to. If `null`,
347
273
  * attach to the first state found with the given class name (useful for
348
274
  * globally shared states owned by the server).
349
- * @param {string[]} [filter] - List of parameters of interest in the
350
- * returned state. If set to `null`, no filter is applied.
275
+ * @param {object} [options={}]
276
+ * @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
277
+ * @param {string[]} [options.blackList] - Black list of parameter names to ignore.
351
278
  * @returns {Promise<SharedState>}
352
279
  *
353
280
  * @example
354
281
  * const state = await client.stateManager.attach('my-class');
355
282
  */
356
- async attach(className, stateIdOrFilter = null, filter = null) {
283
+ async attach(className, stateIdOrOptions = null, options = null) {
357
284
  if (this.#status !== 'inited') {
358
285
  throw new DOMException(`Cannot execute 'attach' on BaseStateManager: BaseStateManager is not inited`, 'InvalidStateError');
359
286
  }
@@ -365,38 +292,79 @@ class BaseStateManager {
365
292
  }
366
293
 
367
294
  if (arguments.length === 2) {
368
- if (stateIdOrFilter === null) {
295
+ if (stateIdOrOptions === null) {
296
+ stateId = null;
297
+ options = null;
298
+ } else if (Number.isFinite(stateIdOrOptions)) {
299
+ stateId = stateIdOrOptions;
300
+ options = null;
301
+ } else if (Array.isArray(stateIdOrOptions)) {
302
+ // backward compatibility for legacy filter array argument
303
+ warnings.deprecated(
304
+ 'argument `filter: string[]` of BaseStateManager#attach',
305
+ 'argument `options { whiteList: string[] }`',
306
+ '5.6.0',
307
+ );
369
308
  stateId = null;
370
- filter = null;
371
- } else if (Number.isFinite(stateIdOrFilter)) {
372
- stateId = stateIdOrFilter;
373
- filter = null;
374
- } else if (Array.isArray(stateIdOrFilter)) {
309
+ options = { whiteList: stateIdOrOptions };
310
+ } else if (isPlainObject(stateIdOrOptions)) {
375
311
  stateId = null;
376
- filter = stateIdOrFilter;
312
+ options = stateIdOrOptions;
377
313
  } else {
378
- throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 2 must be either null, a number or an array`);
314
+ throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 2 must be either null, a number or an object`);
379
315
  }
380
316
  }
381
317
 
382
318
  if (arguments.length === 3) {
383
- stateId = stateIdOrFilter;
319
+ stateId = stateIdOrOptions;
384
320
 
385
321
  if (stateId !== null && !Number.isFinite(stateId)) {
386
322
  throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 2 must be either null or a number`);
387
323
  }
388
324
 
389
- if (filter !== null && !Array.isArray(filter)) {
390
- throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 3 must be either null or an array`);
325
+ if (options !== null) {
326
+ if (Array.isArray(options)) {
327
+ // backward compatibility for legacy filter array argument
328
+ warnings.deprecated(
329
+ 'argument `filter: string[]` of BaseStateManager#attach',
330
+ 'argument `options { whiteList: string[] }`',
331
+ '5.6.0',
332
+ );
333
+ options = { whiteList: options };
334
+ } else if (!isPlainObject(options)) {
335
+ throw new TypeError(`Cannot execute 'attach' on BaseStateManager: argument 3 must be either null or an object`);
336
+ }
337
+ // keep option object as is
391
338
  }
392
339
  }
393
340
 
394
341
  const { id: reqId, promise } = this.#promiseStore.createPromise();
395
- this[kStateManagerClient].transport.emit(ATTACH_REQUEST, reqId, className, stateId, filter);
342
+ this[kStateManagerClient].transport.emit(ATTACH_REQUEST, reqId, className, stateId, options);
396
343
 
397
344
  return promise;
398
345
  }
399
346
 
347
+ #onAttachResponse = (reqId, stateId, instanceId, className, classDescription, currentValues, filter) => {
348
+ const state = this.#buildSharedState({
349
+ manager: this,
350
+ className,
351
+ classDescription,
352
+ stateId,
353
+ instanceId,
354
+ isOwner: false,
355
+ initValues: currentValues,
356
+ filter,
357
+ });
358
+
359
+ this.#statesById.set(state.id, state);
360
+ this.#promiseStore.resolve(reqId, state);
361
+ };
362
+
363
+ #onAttachError = (reqId, msg) => {
364
+ msg = `Cannot execute 'attach' on BaseStateManager: ${msg}`;
365
+ this.#promiseStore.reject(reqId, msg);
366
+ };
367
+
400
368
  /**
401
369
  * Observe all the {@link SharedState} instances that are created on the network.
402
370
  *
@@ -432,7 +400,7 @@ class BaseStateManager {
432
400
  * @param {stateManagerObserveCallback} callback - Function to execute when a
433
401
  * new {@link SharedState} is created on the network.
434
402
  * @param {object} options - Options.
435
- * @param {boolean} options.excludeLocal=false - If set to true, exclude states
403
+ * @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
436
404
  * created by the same node from the collection.
437
405
  * @example
438
406
  * client.stateManager.observe(async (className, stateId) => {
@@ -451,7 +419,7 @@ class BaseStateManager {
451
419
  * @param {stateManagerObserveCallback} callback - Function to execute when a
452
420
  * new {@link SharedState} is created on the network.
453
421
  * @param {object} options - Options.
454
- * @param {boolean} options.excludeLocal=false - If set to true, exclude states
422
+ * @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
455
423
  * created by the same node from the collection.
456
424
  * @example
457
425
  * client.stateManager.observe('my-shared-state-class', async (className, stateId) => {
@@ -602,6 +570,74 @@ class BaseStateManager {
602
570
  return promise;
603
571
  }
604
572
 
573
+ /** @private */
574
+ #filterObserve(observedClassName, className, ownerId, options) {
575
+ let filter = true;
576
+
577
+ if (observedClassName === null || observedClassName === className) {
578
+ filter = false;
579
+ }
580
+ // filter states created by this client if excludeLocal is true
581
+ if (options.excludeLocal === true && ownerId === this[kStateManagerClient].id) {
582
+ filter = true;
583
+ }
584
+
585
+ return filter;
586
+ }
587
+
588
+ /** @private */
589
+ #onObserveResponse = async (reqId, ...list) => {
590
+ // retrieve the callback that have been stored in `observe()` to make sure
591
+ // we don't call another callback that may have been registered earlier.
592
+ const observeInfos = this.#observeRequestCallbacks.get(reqId);
593
+ const [observedClassName, callback, options] = observeInfos;
594
+ // move observeInfos from `_observeRequestCallbacks` to `_observeListeners`
595
+ // to guarantee future order of execution
596
+ this.#observeRequestCallbacks.delete(reqId);
597
+ this.#observeListeners.add(observeInfos);
598
+
599
+ const promises = list.map(([className, stateId, ownerId]) => {
600
+ const filter = this.#filterObserve(observedClassName, className, ownerId, options);
601
+
602
+ if (!filter) {
603
+ return callback(className, stateId, ownerId);
604
+ } else {
605
+ return Promise.resolve();
606
+ }
607
+ });
608
+
609
+ await Promise.all(promises);
610
+
611
+ const unsubscribe = () => {
612
+ this.#observeListeners.delete(observeInfos);
613
+ // no more listeners, we can stop receiving notifications from the server
614
+ if (this.#observeListeners.size === 0) {
615
+ this[kStateManagerClient].transport.emit(UNOBSERVE_NOTIFICATION);
616
+ }
617
+ };
618
+
619
+ this.#promiseStore.resolve(reqId, unsubscribe);
620
+ };
621
+
622
+ /** @private */
623
+ #onObserveError = (reqId, msg) => {
624
+ msg = `Cannot execute 'observe' on BaseStateManager: ${msg}`;
625
+ this.#observeRequestCallbacks.delete(reqId);
626
+ this.#promiseStore.reject(reqId, msg);
627
+ };
628
+
629
+ /** @private */
630
+ #onObserveNotification = (className, stateId, ownerId) => {
631
+ this.#observeListeners.forEach(observeInfos => {
632
+ const [observedClassName, callback, options] = observeInfos;
633
+ const filter = this.#filterObserve(observedClassName, className, ownerId, options);
634
+
635
+ if (!filter) {
636
+ callback(className, stateId, ownerId);
637
+ }
638
+ });
639
+ };
640
+
605
641
  /**
606
642
  * Returns a collection of all the states created from a given shared state class.
607
643
  *
@@ -617,62 +653,38 @@ class BaseStateManager {
617
653
  *
618
654
  * @overload
619
655
  * @param {SharedStateClassName} className - Name of the shared state class.
620
- * @param {SharedStateParameterName[]} filter - Filter parameter of interest for each
621
- * state of the collection.
622
- * @returns {Promise<SharedStateCollection>}
623
- *
624
- * @example
625
- * const collection = await client.stateManager.getCollection(className, ['my-param']);
626
- */
627
- /**
628
- * Returns a collection of all the states created from a given shared state class.
629
- *
630
- * @overload
631
- * @param {SharedStateClassName} className - Name of the shared state class.
632
- * @param {object} options - Options.
633
- * @param {boolean} options.excludeLocal=false - If set to true, exclude states
634
- * created by the same node from the collection.
635
- * @returns {Promise<SharedStateCollection>}
636
- *
637
- * @example
638
- * const collection = await client.stateManager.getCollection(className, { excludeLocal: true });
639
- */
640
- /**
641
- * Returns a collection of all the states created from a given shared state class.
642
- *
643
- * @overload
644
- * @param {SharedStateClassName} className - Name of the shared state class.
645
- * @param {SharedStateParameterName[]} filter - Filter parameter of interest for each
646
- * state of the collection.
647
- * @param {object} options - Options.
648
- * @param {boolean} options.excludeLocal=false - If set to true, exclude states
656
+ * @param {object} [options={}] - Options.
657
+ * @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
649
658
  * created by the same node from the collection.
659
+ * @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
660
+ * @param {string[]} [options.blackList] - Black list of parameter names to ignore.
650
661
  * @returns {Promise<SharedStateCollection>}
651
662
  *
652
663
  * @example
653
- * const collection = await client.stateManager.getCollection(className, ['my-param'], { excludeLocal: true });
664
+ * const collection = await client.stateManager.getCollection(className, {
665
+ * blackList: ['my-param'],
666
+ * excludeLocal: true,
667
+ * });
654
668
  */
655
669
  /**
656
670
  * Returns a collection of all the states created from a given shared state class.
657
671
  *
658
672
  * Alternative signatures:
659
673
  * - `stateManager.getCollection(className)`
660
- * - `stateManager.getCollection(className, filter)`
661
674
  * - `stateManager.getCollection(className, options)`
662
- * - `stateManager.getCollection(className, filter, options)`
663
675
  *
664
676
  * @param {SharedStateClassName} className - Name of the shared state class.
665
- * @param {array|null} [filter=null] - Filter parameter of interest for each
666
- * state of the collection. If set to `null`, no filter applied.
667
677
  * @param {object} [options={}] - Options.
668
678
  * @param {boolean} [options.excludeLocal=false] - If set to true, exclude states
669
679
  * created by the same node from the collection.
680
+ * @param {string[]} [options.whiteList] - White list of parameter names to track (as precedence over `blackList`).
681
+ * @param {string[]} [options.blackList] - Black list of parameter names to ignore.
670
682
  * @returns {Promise<SharedStateCollection>}
671
683
  *
672
684
  * @example
673
685
  * const collection = await client.stateManager.getCollection(className);
674
686
  */
675
- async getCollection(className, filterOrOptions = null, options = {}) {
687
+ async getCollection(className, options = {}) {
676
688
  if (this.#status !== 'inited') {
677
689
  throw new DOMException(`Cannot execute 'getCollection' on BaseStateManager: BaseStateManager is not inited`, 'InvalidStateError');
678
690
  }
@@ -681,36 +693,45 @@ class BaseStateManager {
681
693
  throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 1 should be a string"`);
682
694
  }
683
695
 
684
- let filter;
685
-
686
696
  if (arguments.length === 2) {
687
- if (filterOrOptions === null) {
688
- filter = null;
689
- options = null;
690
- } else if (Array.isArray(filterOrOptions)) {
691
- filter = filterOrOptions;
692
- options = {};
693
- } else if (typeof filterOrOptions === 'object') {
694
- filter = null;
695
- options = filterOrOptions;
696
- } else {
697
- throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either null, an array or an object"`);
697
+ if (Array.isArray(options)) {
698
+ // backward compatibility for legacy filter array argument
699
+ warnings.deprecated(
700
+ 'argument `filter: string[]` of BaseStateManager#getCollection',
701
+ 'argument `options { whiteList: string[] }`',
702
+ '5.6.0',
703
+ );
704
+
705
+ options = { whiteList: options };
706
+ } else if (!isPlainObject(options)) {
707
+ throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either null or an object"`);
698
708
  }
699
709
  }
700
710
 
711
+ // backward compatibility for legacy filter array argument
701
712
  if (arguments.length === 3) {
702
- filter = filterOrOptions;
713
+ // backward compatibility for legacy filter array argument
714
+ warnings.deprecated(
715
+ 'argument `filter: string[]` of BaseStateManager#getCollection',
716
+ 'argument `options { whiteList: string[] }`',
717
+ '5.6.0',
718
+ );
719
+
720
+ const filter = options;
721
+ options = arguments[2];
703
722
 
704
723
  if (filter !== null && !Array.isArray(filter)) {
705
724
  throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 2 should be either an array or null"`);
706
725
  }
707
726
 
708
- if (options === null || typeof options !== 'object') {
727
+ if (options === null || !isPlainObject(options)) {
709
728
  throw new TypeError(`Cannot execute 'getCollection' on BaseStateManager: Argument 3 should be either an object"`);
710
729
  }
730
+
731
+ options.whiteList = filter;
711
732
  }
712
733
 
713
- const collection = new SharedStateCollection(this, className, filter, options);
734
+ const collection = new SharedStateCollection(this, className, options);
714
735
 
715
736
  try {
716
737
  await collection[kSharedStateCollectionInit]();
@@ -720,6 +741,17 @@ class BaseStateManager {
720
741
 
721
742
  return collection;
722
743
  }
744
+
745
+ // ---------------------------------------------------------------------------
746
+ // DEPRECATED
747
+ // ---------------------------------------------------------------------------
748
+ /**
749
+ * @deprecated Use {@link BaseStateManager#getClassDescription} instead.
750
+ */
751
+ async getSchema(className) {
752
+ warnings.deprecated('BaseStateManager#getSchema', 'BaseStateManager#getClassDescription', '4.0.0-alpha.29');
753
+ return this.getClassDescription(className);
754
+ }
723
755
  }
724
756
 
725
757
  export default BaseStateManager;