@soundworks/core 3.6.0 → 4.0.0-alpha.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 (125) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +57 -26
  5. package/src/client/Client.js +391 -0
  6. package/src/client/Context.js +225 -0
  7. package/src/client/ContextManager.js +111 -0
  8. package/src/client/Plugin.js +66 -0
  9. package/src/client/PluginManager.js +123 -0
  10. package/src/client/SharedState.js +84 -0
  11. package/src/client/Socket.js +389 -0
  12. package/src/client/StateManager.js +67 -0
  13. package/src/client/index.js +77 -0
  14. package/src/common/BasePlugin.js +163 -0
  15. package/src/common/BasePluginManager.js +227 -0
  16. package/src/common/BaseSharedState.js +473 -0
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +82 -122
  19. package/{common → src/common}/SharedStatePrivate.js +56 -64
  20. package/src/common/constants.js +51 -0
  21. package/src/common/logger.js +154 -0
  22. package/src/common/promise-store.js +25 -0
  23. package/src/common/sockets-utils.js +73 -0
  24. package/src/common/utils.js +22 -0
  25. package/src/server/Client.js +50 -0
  26. package/src/server/Context.js +99 -0
  27. package/src/server/ContextManager.js +266 -0
  28. package/src/server/Plugin.js +77 -0
  29. package/src/server/PluginManager.js +150 -0
  30. package/src/server/Server.js +849 -0
  31. package/src/server/SharedState.js +86 -0
  32. package/{server → src/server}/Socket.js +119 -94
  33. package/{server → src/server}/Sockets.js +73 -61
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +183 -131
  35. package/src/server/audit-network-latency.worker.js +57 -0
  36. package/src/server/audit-schema.js +42 -0
  37. package/src/server/index.js +45 -0
  38. package/tsconfig.json +21 -0
  39. package/types/client/Client.d.ts +194 -0
  40. package/types/client/Client.d.ts.map +1 -0
  41. package/types/client/Context.d.ts +139 -0
  42. package/types/client/Context.d.ts.map +1 -0
  43. package/types/client/ContextManager.d.ts +45 -0
  44. package/types/client/ContextManager.d.ts.map +1 -0
  45. package/types/client/Plugin.d.ts +53 -0
  46. package/types/client/Plugin.d.ts.map +1 -0
  47. package/types/client/PluginManager.d.ts +96 -0
  48. package/types/client/PluginManager.d.ts.map +1 -0
  49. package/types/client/SharedState.d.ts +85 -0
  50. package/types/client/SharedState.d.ts.map +1 -0
  51. package/types/client/Socket.d.ts +138 -0
  52. package/types/client/Socket.d.ts.map +1 -0
  53. package/types/client/StateManager.d.ts +72 -0
  54. package/types/client/StateManager.d.ts.map +1 -0
  55. package/types/client/index.d.ts +11 -0
  56. package/types/client/index.d.ts.map +1 -0
  57. package/types/common/BasePlugin.d.ts +145 -0
  58. package/types/common/BasePlugin.d.ts.map +1 -0
  59. package/types/common/BasePluginManager.d.ts +88 -0
  60. package/types/common/BasePluginManager.d.ts.map +1 -0
  61. package/types/common/BaseSharedState.d.ts +173 -0
  62. package/types/common/BaseSharedState.d.ts.map +1 -0
  63. package/types/common/BaseStateManager.d.ts +70 -0
  64. package/types/common/BaseStateManager.d.ts.map +1 -0
  65. package/types/common/ParameterBag.d.ts +143 -0
  66. package/types/common/ParameterBag.d.ts.map +1 -0
  67. package/types/common/SharedStatePrivate.d.ts +20 -0
  68. package/types/common/SharedStatePrivate.d.ts.map +1 -0
  69. package/types/common/constants.d.ts +35 -0
  70. package/types/common/constants.d.ts.map +1 -0
  71. package/types/common/logger.d.ts +16 -0
  72. package/types/common/logger.d.ts.map +1 -0
  73. package/types/common/promise-store.d.ts +4 -0
  74. package/types/common/promise-store.d.ts.map +1 -0
  75. package/types/common/sockets-utils.d.ts +9 -0
  76. package/types/common/sockets-utils.d.ts.map +1 -0
  77. package/types/common/utils.d.ts +5 -0
  78. package/types/common/utils.d.ts.map +1 -0
  79. package/types/server/Client.d.ts +40 -0
  80. package/types/server/Client.d.ts.map +1 -0
  81. package/types/server/Context.d.ts +59 -0
  82. package/types/server/Context.d.ts.map +1 -0
  83. package/types/server/ContextManager.d.ts +69 -0
  84. package/types/server/ContextManager.d.ts.map +1 -0
  85. package/types/server/Plugin.d.ts +70 -0
  86. package/types/server/Plugin.d.ts.map +1 -0
  87. package/types/server/PluginManager.d.ts +91 -0
  88. package/types/server/PluginManager.d.ts.map +1 -0
  89. package/types/server/Server.d.ts +223 -0
  90. package/types/server/Server.d.ts.map +1 -0
  91. package/types/server/SharedState.d.ts +86 -0
  92. package/types/server/SharedState.d.ts.map +1 -0
  93. package/types/server/Socket.d.ts +128 -0
  94. package/types/server/Socket.d.ts.map +1 -0
  95. package/types/server/Sockets.d.ts +73 -0
  96. package/types/server/Sockets.d.ts.map +1 -0
  97. package/types/server/StateManager.d.ts +190 -0
  98. package/types/server/StateManager.d.ts.map +1 -0
  99. package/types/server/audit-network-latency.worker.d.ts +2 -0
  100. package/types/server/audit-network-latency.worker.d.ts.map +1 -0
  101. package/types/server/audit-schema.d.ts +27 -0
  102. package/types/server/audit-schema.d.ts.map +1 -0
  103. package/types/server/index.d.ts +7 -0
  104. package/types/server/index.d.ts.map +1 -0
  105. package/client/AbstractExperience.js +0 -54
  106. package/client/AbstractPlugin.js +0 -230
  107. package/client/Client.js +0 -190
  108. package/client/PluginManager.js +0 -267
  109. package/client/Socket.js +0 -338
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedState.js +0 -480
  113. package/common/SharedStateManagerClient.js +0 -194
  114. package/common/Signal.js +0 -40
  115. package/common/SignalAll.js +0 -44
  116. package/common/shared-state-utils.js +0 -90
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -211
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -177
  122. package/server/Server.js +0 -746
  123. package/server/index.js +0 -49
  124. package/server/utils/logger.js +0 -134
  125. package/server.js +0 -28
@@ -1,480 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _ParameterBag = _interopRequireDefault(require("./ParameterBag.js"));
9
-
10
- var _sharedStateUtils = require("./shared-state-utils.js");
11
-
12
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
-
14
- // -----------------------------------------------------------
15
- // define a common namespace...
16
- // -----------------------------------------------------------
17
-
18
- /**
19
- * Representation of a shared state. The class and its instances are the same
20
- * client-side and server-side.
21
- *
22
- * @memberof common
23
- *
24
- * @see {client.SharedStateManagerClient}
25
- * @see {server.SharedStateManagerServer}
26
- */
27
- class SharedState {
28
- constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues = {}) {
29
- this.id = id;
30
- this.remoteId = remoteId;
31
- this.schemaName = schemaName;
32
- this._isOwner = isOwner; // may be the server or any client
33
-
34
- this._client = client;
35
- this._manager = manager;
36
-
37
- try {
38
- this._parameters = new _ParameterBag.default(schema, initValues);
39
- } catch (err) {
40
- console.error(err.stack);
41
- throw new Error(`Error creating or attaching state "${schemaName}" w/ values:\n
42
- ${JSON.stringify(initValues, null, 2)}`);
43
- }
44
-
45
- this._subscriptions = new Set();
46
- this._onDetachCallbacks = new Set();
47
- this._onDeleteCallbacks = new Set(); // add listener for state updates
48
-
49
- client.transport.addListener(`${_sharedStateUtils.UPDATE_RESPONSE}-${id}-${this.remoteId}`, async (reqId, updates, context) => {
50
- const updated = await this._commit(updates, context, true, true);
51
- (0, _sharedStateUtils.resolveRequest)(reqId, updated);
52
- }); // retrieve values but do not propagate to subscriptions
53
-
54
- client.transport.addListener(`${_sharedStateUtils.UPDATE_ABORT}-${id}-${this.remoteId}`, async (reqId, updates, context) => {
55
- const updated = await this._commit(updates, context, false, true);
56
- (0, _sharedStateUtils.resolveRequest)(reqId, updated);
57
- });
58
- client.transport.addListener(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${id}-${this.remoteId}`, async (updates, context) => {
59
- // cf. https://github.com/collective-soundworks/soundworks/issues/18
60
- await this._commit(updates, context, true, false);
61
- }); // ---------------------------------------------
62
- // DELETE initiated by creator, or schema deleted
63
- // ---------------------------------------------
64
-
65
- client.transport.addListener(`${_sharedStateUtils.DELETE_NOTIFICATION}-${id}-${this.remoteId}`, () => {
66
- this._manager._statesById.delete(this.id);
67
-
68
- this._clearTransport();
69
-
70
- this._onDetachCallbacks.forEach(callback => callback());
71
-
72
- this._onDeleteCallbacks.forEach(callback => callback());
73
-
74
- this._clearDetach();
75
- });
76
-
77
- if (this._isOwner) {
78
- // ---------------------------------------------
79
- // DELETE (can only delete if creator)
80
- // ---------------------------------------------
81
- client.transport.addListener(`${_sharedStateUtils.DELETE_RESPONSE}-${id}-${this.remoteId}`, reqId => {
82
- this._manager._statesById.delete(this.id);
83
-
84
- this._clearTransport();
85
-
86
- this._onDetachCallbacks.forEach(callback => callback());
87
-
88
- this._onDeleteCallbacks.forEach(callback => callback());
89
-
90
- this._clearDetach();
91
-
92
- (0, _sharedStateUtils.resolveRequest)(reqId, this);
93
- });
94
- client.transport.addListener(`${_sharedStateUtils.DELETE_ERROR}-${id}`, (reqId, msg) => {
95
- (0, _sharedStateUtils.rejectRequest)(reqId, msg);
96
- });
97
- } else {
98
- // ---------------------------------------------
99
- // DETACH
100
- // ---------------------------------------------
101
- client.transport.addListener(`${_sharedStateUtils.DETACH_RESPONSE}-${id}-${this.remoteId}`, reqId => {
102
- this._manager._statesById.delete(this.id);
103
-
104
- this._clearTransport();
105
-
106
- this._onDetachCallbacks.forEach(func => func());
107
-
108
- this._clearDetach();
109
-
110
- (0, _sharedStateUtils.resolveRequest)(reqId, this);
111
- }); // the state does not exists anymore in the server (should not happen)
112
-
113
- client.transport.addListener(`${_sharedStateUtils.DETACH_ERROR}-${id}`, (reqId, msg) => {
114
- (0, _sharedStateUtils.rejectRequest)(reqId, msg);
115
- });
116
- }
117
- }
118
-
119
- _clearDetach() {
120
- this._onDetachCallbacks.clear();
121
-
122
- this._onDeleteCallbacks.clear(); // Monkey patch detach so it throws if called twice. Doing nothing blocks
123
- // the process on a second `detach` call as the Promise never resolves
124
-
125
-
126
- this.detach = () => {
127
- throw new Error(`[stateManager] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
128
- };
129
- }
130
-
131
- _clearTransport() {
132
- // remove listeners
133
- this._client.transport.removeAllListeners(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
134
-
135
- this._client.transport.removeAllListeners(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
136
-
137
- this._client.transport.removeAllListeners(`${_sharedStateUtils.DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
138
-
139
- this._client.transport.removeAllListeners(`${_sharedStateUtils.DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
140
-
141
- this._client.transport.removeAllListeners(`${_sharedStateUtils.DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
142
- }
143
- /** @private */
144
-
145
-
146
- async _commit(updates, context, propagate = true, initiator = false) {
147
- const newValues = {};
148
- const oldValues = {};
149
-
150
- for (let name in updates) {
151
- const {
152
- immediate,
153
- filterChange,
154
- event
155
- } = this._parameters.getSchema(name); // @note 20211209 - we had an issue here server-side, because if the value
156
- // is an object or an array, the reference is shared by everybody, therefore
157
- // `changed` is always false and the new value is never propagated...
158
- // FIXED - `state.get` now returns a deep copy when `type` is `any`
159
-
160
-
161
- const oldValue = this._parameters.get(name);
162
-
163
- const [newValue, changed] = this._parameters.set(name, updates[name]); // handle immediate stuff
164
-
165
-
166
- if (initiator && immediate) {
167
- // @note - we don't need to check filterChange here because the value
168
- // has been updated in parameters on the `set` side so can rely on `changed`
169
- // to avoid retrigger listeners.
170
- // If the value has been overriden by the server, `changed` will true
171
- // anyway so it should behave correctly.
172
- if (!changed || event) {
173
- continue;
174
- }
175
- }
176
-
177
- newValues[name] = newValue;
178
- oldValues[name] = oldValue;
179
- } // if the `UPDATE_REQUEST` as been aborted by the server, do not propagate
180
-
181
-
182
- let promises = [];
183
-
184
- if (propagate && Object.keys(newValues).length > 0) {
185
- this._subscriptions.forEach(listener => {
186
- promises.push(listener(newValues, oldValues, context));
187
- });
188
- }
189
-
190
- await Promise.all(promises); // reset events to null after propagation of all listeners
191
-
192
- for (let name in newValues) {
193
- const {
194
- event
195
- } = this._parameters.getSchema(name);
196
-
197
- if (event) {
198
- this._parameters.set(name, null);
199
- }
200
- }
201
-
202
- return newValues;
203
- }
204
- /**
205
- * Updates values of the state. Wait for all subscriptions to be resolved
206
- * before resolving itself, i.e.:
207
- *
208
- * ```js
209
- * const a = await server.stateManager.create('a');
210
- * let asyncCallbackCalled = false;
211
- *
212
- * a.subscribe(updates => {
213
- * return new Promise(resolve => {
214
- * setTimeout(() => {
215
- * asyncCallbackCalled = true;
216
- * resolve();
217
- * }, 100);
218
- * });
219
- * });
220
- *
221
- * await a.set({ bool: true });
222
- * assert.equal(asyncCallbackCalled, true);
223
- * ```
224
- *
225
- * @async
226
- * @param {Object} updates - key / value pairs of updates to apply to the state.
227
- * @param {Mixed} [context=null] - optionnal context that will be propagated
228
- * alongside the updates of the state. The context is valid only for the
229
- * current call and will be passed as third argument to any subscribe listeners.
230
- * @return {Promise<Object>} A promise to the (coerced) updates.
231
- *
232
- * @see {common.SharedState~subscribeCallback}
233
- */
234
-
235
-
236
- async set(updates, context = null) {
237
- // handle immediate option
238
- const immediateNewValues = {};
239
- const immediateOldValues = {};
240
- let propagateNow = false;
241
-
242
- for (let name in updates) {
243
- // throw early (client-side and not only server-side) if parameter is undefined
244
- if (!this._parameters.has(name)) {
245
- throw new ReferenceError(`[stateManager] Cannot set value of undefined parameter "${name}"`);
246
- } // @note: general idea...
247
- // if immediate=true
248
- // - call listeners if value changed
249
- // - go through normal server path
250
- // - retrigger only if response from server is different from current value
251
- // if immediate=true && (filterChange=false || event=true)
252
- // - call listeners with value regarless it changed
253
- // - go through normal server path
254
- // - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
255
- // to prevent execute the listeners twice
256
-
257
-
258
- const {
259
- immediate,
260
- filterChange,
261
- event
262
- } = this._parameters.getSchema(name);
263
-
264
- if (immediate) {
265
- const oldValue = this._parameters.get(name);
266
-
267
- const [newValue, changed] = this._parameters.set(name, updates[name]);
268
-
269
- if (changed || filterChange === false || event) {
270
- immediateOldValues[name] = oldValue;
271
- immediateNewValues[name] = newValue;
272
- propagateNow = true;
273
- }
274
- }
275
- }
276
-
277
- if (propagateNow) {
278
- this._subscriptions.forEach(listener => listener(immediateNewValues, immediateOldValues, context));
279
- } // go through server-side normal behavior
280
-
281
-
282
- return new Promise((resolve, reject) => {
283
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
284
-
285
- this._client.transport.emit(`${_sharedStateUtils.UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
286
- });
287
- }
288
- /**
289
- * Get a value of the state by its name
290
- *
291
- * @param {String} name - Name of the param. Throws an error if the name is invalid.
292
- * @return {Mixed}
293
- */
294
-
295
-
296
- get(name) {
297
- return this._parameters.get(name);
298
- }
299
- /**
300
- * Get a all the key / value pairs of the state.
301
- *
302
- * @return {Object}
303
- */
304
-
305
-
306
- getValues() {
307
- return this._parameters.getValues();
308
- }
309
- /**
310
- * Get the schema that describes the state.
311
- *
312
- * @param {String} [name=null] - if given, returns only the definition
313
- * of the given param name. Throws an error if the name is invalid.
314
- * @return {Object}
315
- */
316
-
317
-
318
- getSchema(name = null) {
319
- return this._parameters.getSchema(name);
320
- }
321
- /**
322
- * Get the values with which the state has been initialized.
323
- *
324
- * @return {Object}
325
- */
326
-
327
-
328
- getInitValues() {
329
- return this._parameters.getInitValues();
330
- }
331
- /**
332
- * Get the default values that has been declared in the schema.
333
- *
334
- * @return {Object}
335
- */
336
-
337
-
338
- getDefaults() {
339
- return this._parameters.getDefaults();
340
- }
341
- /**
342
- * @callback common.SharedState~subscribeCallback
343
- * @param {Object} newValues - key / value pairs of the updates that have been
344
- * applied to the state.
345
- * @param {Object} oldValues - key / value pairs of the related params before
346
- * the updates has been applied to the state.
347
- * @param {Mixed} [context=null] - Optionnal context data that has been passed
348
- * with the updates in the `set` call.
349
- *
350
- * @example
351
- * state.subscribe(async (newValues, oldValues[, context=null]) => {
352
- * for (let [key, value] of Object.entries(newValues)) {
353
- * switch (key) {
354
- * // do something
355
- * }
356
- * }
357
- * });
358
- *
359
- * @see {common.SharedState#set}
360
- * @see {common.SharedState#subscribe}
361
- */
362
-
363
- /**
364
- * Subscribe to state updates
365
- *
366
- * @param {common.SharedState~subscribeCallback} callback - callback to execute
367
- * when an update is applied on the state.
368
- * @param {Boolean} [executeListener=false] - execute the given listener with
369
- * current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
370
- *
371
- * @example
372
- * state.subscribe(async (newValues, oldValues) => {
373
- * for (let [key, value] of Object.entries(newValues)) {
374
- * switch (key) {
375
- * // do something
376
- * }
377
- * }
378
- * });
379
- */
380
-
381
-
382
- subscribe(listener, executeListener = false) {
383
- this._subscriptions.add(listener);
384
-
385
- if (executeListener === true) {
386
- const currentValues = this.getValues();
387
- const oldValues = {};
388
- const context = null;
389
- listener(currentValues, oldValues, context);
390
- }
391
-
392
- return () => {
393
- this._subscriptions.delete(listener);
394
- };
395
- }
396
- /**
397
- * Detach from the state. If the client is the creator of the state, the state
398
- * is deleted and all attached nodes get notified
399
- *
400
- * @async
401
- * @see {common.SharedState#onDetach}
402
- * @see {common.SharedState#onDelete}
403
- * @see {client.SharedStateManagerClient#create}
404
- * @see {server.SharedStateManagerClient#create}
405
- * @see {client.SharedStateManagerClient#attach}
406
- * @see {server.SharedStateManagerClient#attach}
407
- */
408
-
409
-
410
- async detach() {
411
- this._subscriptions.clear();
412
-
413
- if (this._isOwner) {
414
- return new Promise((resolve, reject) => {
415
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
416
-
417
- this._client.transport.emit(`${_sharedStateUtils.DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
418
- });
419
- } else {
420
- return new Promise((resolve, reject) => {
421
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
422
-
423
- this._client.transport.emit(`${_sharedStateUtils.DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
424
- });
425
- }
426
- }
427
- /**
428
- * Delete the state. Only the creator/owner of the state (i.e. a state created using
429
- * `create`) can use this method. If a non-owner call this method (i.e. a
430
- * state created using `attach`), an error will be thrown.
431
- *
432
- * @async
433
- * @see {common.SharedState#onDetach}
434
- * @see {common.SharedState#onDelete}
435
- * @see {client.SharedStateManagerClient#create}
436
- * @see {server.SharedStateManagerClient#create}
437
- * @see {client.SharedStateManagerClient#attach}
438
- * @see {server.SharedStateManagerClient#attach}
439
- */
440
-
441
-
442
- async delete() {
443
- if (this._isOwner) {
444
- return this.detach();
445
- } else {
446
- throw new Error(`[stateManager] can delete state "${this.schemaName}", only owner of the state (i.e. the node that "create[d]" it) can delete it`);
447
- }
448
- }
449
- /**
450
- * Register a function to execute when detaching from the state
451
- *
452
- * @param {Function} callback - callback to execute when detaching from the state.
453
- * wether the client as called `detach`, or the state has been deleted by its
454
- * creator.
455
- */
456
-
457
-
458
- onDetach(callback) {
459
- this._onDetachCallbacks.add(callback);
460
-
461
- return () => this._onDetachCallbacks.delete(callback);
462
- }
463
- /**
464
- * Register a function to execute when the state is deleted. Only called if the
465
- * node was the creator of the state. Is called after `onDetach`
466
- *
467
- * @param {Function} callback - callback to execute when the state is deleted.
468
- */
469
-
470
-
471
- onDelete(callback) {
472
- this._onDeleteCallbacks.add(callback);
473
-
474
- return () => this._onDeleteCallbacks.delete(callback);
475
- }
476
-
477
- }
478
-
479
- var _default = SharedState;
480
- exports.default = _default;
@@ -1,194 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _SharedState = _interopRequireDefault(require("./SharedState.js"));
9
-
10
- var _sharedStateUtils = require("./shared-state-utils.js");
11
-
12
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
-
14
- /**
15
- * Component dedicated at managing distributed states, accessible through {@link common.SharedState} instances, among the application.
16
- *
17
- * An instance of `SharedStateManagerClient` is automatically created by the
18
- * `soundworks.Client` at initialization (cf. {@link client.Client#stateManager}).
19
- *
20
- * Tutorial: [https://collective-soundworks.github.io/tutorials/state-manager.html](https://collective-soundworks.github.io/tutorials/state-manager.html)
21
- *
22
- * @memberof client
23
- * @see {@link common.SharedState}
24
- * @see {@link server.SharedStateManagerServer}
25
- * @see {@link client.Client#stateManager}
26
- */
27
- class SharedStateManagerClient {
28
- /**
29
- * @param {Number} id - Id of the node.
30
- * @param {Object} transport - Transport to use for synchronizing the state.
31
- * Must implement a basic EventEmitter API.
32
- */
33
- constructor(id, transport) {
34
- this.client = {
35
- id,
36
- transport
37
- };
38
- this._statesById = new Map();
39
- this._observeListeners = new Set();
40
- this._cachedSchemas = new Map();
41
- this._observeRequestCallbacks = new Map(); // ---------------------------------------------
42
- // CREATE
43
- // ---------------------------------------------
44
-
45
- this.client.transport.addListener(_sharedStateUtils.CREATE_RESPONSE, (reqId, stateId, remoteId, schemaName, schema, initValues) => {
46
- // cache schema when first dealing it to save some bandwidth
47
- if (!this._cachedSchemas.has(schemaName)) {
48
- this._cachedSchemas.set(schemaName, schema);
49
- }
50
-
51
- schema = this._cachedSchemas.get(schemaName);
52
- const state = new _SharedState.default(stateId, remoteId, schemaName, schema, this.client, true, this, initValues);
53
-
54
- this._statesById.set(state.id, state);
55
-
56
- (0, _sharedStateUtils.resolveRequest)(reqId, state);
57
- });
58
- this.client.transport.addListener(_sharedStateUtils.CREATE_ERROR, (reqId, msg) => {
59
- (0, _sharedStateUtils.rejectRequest)(reqId, msg);
60
- }); // ---------------------------------------------
61
- // ATTACH (when creator, is attached by default)
62
- // ---------------------------------------------
63
-
64
- this.client.transport.addListener(_sharedStateUtils.ATTACH_RESPONSE, (reqId, stateId, remoteId, schemaName, schema, currentValues) => {
65
- // cache schema when first dealing with it to save some bandwidth
66
- // note: when we make the schemas dynamic at some point
67
- // the server should be able to invalidate the schema and send
68
- // it again despite the caching.
69
- if (!this._cachedSchemas.has(schemaName)) {
70
- this._cachedSchemas.set(schemaName, schema);
71
- }
72
-
73
- schema = this._cachedSchemas.get(schemaName);
74
- const state = new _SharedState.default(stateId, remoteId, schemaName, schema, this.client, false, this, currentValues);
75
-
76
- this._statesById.set(state.id, state);
77
-
78
- (0, _sharedStateUtils.resolveRequest)(reqId, state);
79
- });
80
- this.client.transport.addListener(_sharedStateUtils.ATTACH_ERROR, (reqId, msg) => {
81
- (0, _sharedStateUtils.rejectRequest)(reqId, msg);
82
- }); // ---------------------------------------------
83
- // OBSERVE PEERS (be notified when a state is created, lazy)
84
- // ---------------------------------------------
85
-
86
- this.client.transport.addListener(_sharedStateUtils.OBSERVE_RESPONSE, (reqId, ...list) => {
87
- // retrieve the callback that have been stored in observe to make sure
88
- // we don't call another callback that may have been registered earlier.
89
- const callback = this._observeRequestCallbacks.get(reqId);
90
-
91
- this._observeRequestCallbacks.delete(reqId);
92
-
93
- list.forEach(([schemaName, stateId, nodeId]) => {
94
- callback(schemaName, stateId, nodeId);
95
- });
96
- (0, _sharedStateUtils.resolveRequest)(reqId, list);
97
- });
98
- this.client.transport.addListener(_sharedStateUtils.OBSERVE_NOTIFICATION, (...list) => {
99
- list.forEach(([schemaName, stateId, nodeId]) => {
100
- this._observeListeners.forEach(callback => callback(schemaName, stateId, nodeId));
101
- });
102
- }); // ---------------------------------------------
103
- // Clear cache when schema is deleted
104
- // ---------------------------------------------
105
-
106
- this.client.transport.addListener(_sharedStateUtils.DELETE_SCHEMA, schemaName => {
107
- this._cachedSchemas.delete(schemaName);
108
- });
109
- }
110
- /**
111
- * Create a {@link common.SharedState} instance from a previsouly registered schema.
112
- *
113
- * @see {@link common.SharedState}
114
- *
115
- * @param {String} schemaName - Name of the schema as given on registration
116
- * (cf. ServerStateManager)
117
- * @param {Object} [initValues={}] - Default values for the state.
118
- *
119
- * @return {@link common.SharedState}
120
- */
121
-
122
-
123
- async create(schemaName, initValues = {}) {
124
- return new Promise((resolve, reject) => {
125
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
126
- const requireSchema = this._cachedSchemas.has(schemaName) ? false : true;
127
- this.client.transport.emit(_sharedStateUtils.CREATE_REQUEST, reqId, schemaName, requireSchema, initValues);
128
- });
129
- }
130
- /**
131
- * Attach to an existing {@link common.SharedState} instance.
132
- *
133
- * @see {@link common.SharedState}
134
- *
135
- * @param {String} schemaName - Name of the schema as given on registration
136
- * (cf. ServerStateManager)
137
- * @param {Object} [stateId=null] - Id of the state to attach to. If `null`,
138
- * attach to the first state found with the given schema name (usefull for
139
- * globally shared states owned by the server).
140
- *
141
- * @return {@link common.SharedState}
142
- */
143
-
144
-
145
- async attach(schemaName, stateId = null) {
146
- return new Promise((resolve, reject) => {
147
- // @todo - add a timeout
148
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject);
149
- const requireSchema = this._cachedSchemas.has(schemaName) ? false : true;
150
- this.client.transport.emit(_sharedStateUtils.ATTACH_REQUEST, reqId, schemaName, stateId, requireSchema);
151
- });
152
- }
153
- /**
154
- * @callback client.SharedStateManagerClient~observeCallback
155
- * @async
156
- * @param {String} schemaName - name of the schema
157
- * @param {Number} stateId - id of the state
158
- * @param {Number} nodeId - id of the node that created the state
159
- */
160
-
161
- /**
162
- * Observe all the {@link common.SharedState} instances that are created on the network.
163
- *
164
- * @param {client.SharedStateManagerClient~observeCallback} callback - Function
165
- * to be called when a new state is created on the network.
166
- *
167
- * @example
168
- * this.client.stateManager.observe(async (schemaName, stateId, nodeId) => {
169
- * if (schemaName === 'something') {
170
- * const state = await this.client.stateManager.attach(schemaName, stateId);
171
- * console.log(state.getValues());
172
- * }
173
- * });
174
- */
175
-
176
-
177
- observe(callback) {
178
- this._observeListeners.add(callback); // store function
179
-
180
-
181
- new Promise((resolve, reject) => {
182
- const reqId = (0, _sharedStateUtils.storeRequestPromise)(resolve, reject); // store the callback to be executed on the response
183
-
184
- this._observeRequestCallbacks.set(reqId, callback);
185
-
186
- this.client.transport.emit(_sharedStateUtils.OBSERVE_REQUEST, reqId);
187
- });
188
- return () => this._observeListeners.delete(callback);
189
- }
190
-
191
- }
192
-
193
- var _default = SharedStateManagerClient;
194
- exports.default = _default;