@soundworks/core 4.0.0-alpha.7 → 5.0.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 (144) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +16 -50
  3. package/package.json +26 -44
  4. package/src/client/Client.js +306 -256
  5. package/src/client/{Context.js → ClientContext.js} +63 -54
  6. package/src/client/ClientContextManager.js +115 -0
  7. package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
  8. package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
  9. package/src/client/ClientSocket.js +270 -0
  10. package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
  11. package/src/client/index.js +36 -39
  12. package/src/common/BasePlugin.js +63 -58
  13. package/src/common/BasePluginManager.js +191 -142
  14. package/src/common/BaseStateManager.js +636 -143
  15. package/src/common/BatchedTransport.js +73 -0
  16. package/src/common/ParameterBag.js +90 -93
  17. package/src/common/PromiseStore.js +54 -0
  18. package/src/common/SharedState.js +821 -0
  19. package/src/common/SharedStateCollection.js +492 -0
  20. package/src/common/constants.js +14 -2
  21. package/src/common/logger.js +64 -68
  22. package/src/common/shared-state-types.js +82 -0
  23. package/src/common/sockets-utils.js +0 -64
  24. package/src/common/version.js +2 -0
  25. package/src/server/Server.js +559 -821
  26. package/src/server/ServerClient.js +74 -0
  27. package/src/server/{Context.js → ServerContext.js} +96 -77
  28. package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
  29. package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
  30. package/src/server/ServerPluginManager.js +177 -0
  31. package/src/server/ServerSocket.js +256 -0
  32. package/src/server/ServerSockets.js +150 -0
  33. package/src/server/ServerStateManager.js +706 -0
  34. package/src/server/SharedStatePrivate.js +277 -0
  35. package/src/server/audit-network-latency.worker.js +19 -4
  36. package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
  37. package/src/server/create-http-server.js +127 -0
  38. package/src/server/crypto.js +8 -7
  39. package/src/server/index.js +60 -17
  40. package/types/client/Client.d.ts +95 -70
  41. package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
  42. package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
  43. package/types/client/ClientPlugin.d.ts +51 -0
  44. package/types/client/ClientPluginManager.d.ts +112 -0
  45. package/types/client/ClientSocket.d.ts +76 -0
  46. package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
  47. package/types/client/index.d.ts +33 -6
  48. package/types/common/BasePlugin.d.ts +64 -53
  49. package/types/common/BasePluginManager.d.ts +84 -39
  50. package/types/common/BaseStateManager.d.ts +227 -49
  51. package/types/common/BatchedTransport.d.ts +17 -0
  52. package/types/common/ParameterBag.d.ts +73 -76
  53. package/types/common/PromiseStore.d.ts +11 -0
  54. package/types/common/SharedState.d.ts +291 -0
  55. package/types/common/SharedStateCollection.d.ts +311 -0
  56. package/types/common/constants.d.ts +10 -3
  57. package/types/common/logger.d.ts +8 -6
  58. package/types/common/shared-state-types.d.ts +95 -0
  59. package/types/common/sockets-utils.d.ts +0 -5
  60. package/types/common/version.d.ts +2 -0
  61. package/types/server/Server.d.ts +215 -136
  62. package/types/server/ServerClient.d.ts +45 -0
  63. package/types/server/ServerContext.d.ts +209 -0
  64. package/types/server/ServerContextManager.d.ts +75 -0
  65. package/types/server/ServerPlugin.d.ts +57 -0
  66. package/types/server/ServerPluginManager.d.ts +115 -0
  67. package/types/server/ServerSocket.d.ts +69 -0
  68. package/types/server/ServerSockets.d.ts +42 -0
  69. package/types/server/ServerStateManager.d.ts +284 -0
  70. package/types/server/SharedStatePrivate.d.ts +23 -0
  71. package/types/server/audit-network-latency.worker.d.ts +2 -2
  72. package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
  73. package/types/server/create-http-server.d.ts +5 -0
  74. package/types/server/crypto.d.ts +4 -0
  75. package/types/server/index.d.ts +51 -3
  76. package/.eslintrc +0 -15
  77. package/.github/workflows/gh-pages.yml +0 -31
  78. package/.github/workflows/unit-tests.yml +0 -18
  79. package/src/client/ContextManager.js +0 -111
  80. package/src/client/SharedState.js +0 -84
  81. package/src/client/SharedStateCollection.js +0 -35
  82. package/src/client/Socket.js +0 -402
  83. package/src/common/BaseSharedState.js +0 -517
  84. package/src/common/BaseSharedStateCollection.js +0 -179
  85. package/src/common/SharedStatePrivate.js +0 -192
  86. package/src/common/promise-store.js +0 -25
  87. package/src/server/Client.js +0 -55
  88. package/src/server/PluginManager.js +0 -170
  89. package/src/server/SharedState.js +0 -85
  90. package/src/server/SharedStateCollection.js +0 -36
  91. package/src/server/Socket.js +0 -396
  92. package/src/server/Sockets.js +0 -224
  93. package/src/server/StateManager.js +0 -620
  94. package/tsconfig.json +0 -21
  95. package/types/client/Client.d.ts.map +0 -1
  96. package/types/client/Context.d.ts.map +0 -1
  97. package/types/client/ContextManager.d.ts.map +0 -1
  98. package/types/client/Plugin.d.ts +0 -53
  99. package/types/client/Plugin.d.ts.map +0 -1
  100. package/types/client/PluginManager.d.ts +0 -96
  101. package/types/client/PluginManager.d.ts.map +0 -1
  102. package/types/client/SharedState.d.ts +0 -85
  103. package/types/client/SharedState.d.ts.map +0 -1
  104. package/types/client/Socket.d.ts +0 -138
  105. package/types/client/Socket.d.ts.map +0 -1
  106. package/types/client/StateManager.d.ts.map +0 -1
  107. package/types/client/index.d.ts.map +0 -1
  108. package/types/common/BasePlugin.d.ts.map +0 -1
  109. package/types/common/BasePluginManager.d.ts.map +0 -1
  110. package/types/common/BaseSharedState.d.ts +0 -173
  111. package/types/common/BaseSharedState.d.ts.map +0 -1
  112. package/types/common/BaseStateManager.d.ts.map +0 -1
  113. package/types/common/ParameterBag.d.ts.map +0 -1
  114. package/types/common/SharedStatePrivate.d.ts +0 -20
  115. package/types/common/SharedStatePrivate.d.ts.map +0 -1
  116. package/types/common/constants.d.ts.map +0 -1
  117. package/types/common/logger.d.ts.map +0 -1
  118. package/types/common/promise-store.d.ts +0 -4
  119. package/types/common/promise-store.d.ts.map +0 -1
  120. package/types/common/sockets-utils.d.ts.map +0 -1
  121. package/types/common/utils.d.ts +0 -5
  122. package/types/common/utils.d.ts.map +0 -1
  123. package/types/server/Client.d.ts +0 -40
  124. package/types/server/Client.d.ts.map +0 -1
  125. package/types/server/Context.d.ts +0 -59
  126. package/types/server/Context.d.ts.map +0 -1
  127. package/types/server/ContextManager.d.ts +0 -69
  128. package/types/server/ContextManager.d.ts.map +0 -1
  129. package/types/server/Plugin.d.ts +0 -70
  130. package/types/server/Plugin.d.ts.map +0 -1
  131. package/types/server/PluginManager.d.ts +0 -91
  132. package/types/server/PluginManager.d.ts.map +0 -1
  133. package/types/server/Server.d.ts.map +0 -1
  134. package/types/server/SharedState.d.ts +0 -86
  135. package/types/server/SharedState.d.ts.map +0 -1
  136. package/types/server/Socket.d.ts +0 -128
  137. package/types/server/Socket.d.ts.map +0 -1
  138. package/types/server/Sockets.d.ts +0 -73
  139. package/types/server/Sockets.d.ts.map +0 -1
  140. package/types/server/StateManager.d.ts +0 -190
  141. package/types/server/StateManager.d.ts.map +0 -1
  142. package/types/server/audit-network-latency.worker.d.ts.map +0 -1
  143. package/types/server/audit-schema.d.ts.map +0 -1
  144. package/types/server/index.d.ts.map +0 -1
@@ -1,517 +0,0 @@
1
- import { isPlainObject } from '@ircam/sc-utils';
2
- import ParameterBag from './ParameterBag.js';
3
- import {
4
- DELETE_REQUEST,
5
- DELETE_RESPONSE,
6
- DELETE_ERROR,
7
- DELETE_NOTIFICATION,
8
- DETACH_REQUEST,
9
- DETACH_RESPONSE,
10
- DETACH_ERROR,
11
- UPDATE_REQUEST,
12
- UPDATE_RESPONSE,
13
- UPDATE_ABORT,
14
- UPDATE_NOTIFICATION,
15
- } from './constants.js';
16
- import {
17
- storeRequestPromise,
18
- resolveRequest,
19
- rejectRequest,
20
- } from './promise-store.js';
21
-
22
- class BaseSharedState {
23
- constructor(id, remoteId, schemaName, schema, client, isOwner, manager, initValues = {}) {
24
- this.id = id;
25
- this.remoteId = remoteId;
26
- this.schemaName = schemaName;
27
-
28
- /** @private */
29
- this._isOwner = isOwner; // may be the server or any client
30
- /** @private */
31
- this._client = client;
32
- /** @private */
33
- this._manager = manager;
34
-
35
- try {
36
- /** @private */
37
- this._parameters = new ParameterBag(schema, initValues);
38
- } catch (err) {
39
- console.error(err.stack);
40
-
41
- throw new Error(`Error creating or attaching state "${schemaName}" w/ values:\n
42
- ${JSON.stringify(initValues, null, 2)}`);
43
- }
44
-
45
- /** @private */
46
- this._onUpdateCallbacks = new Set();
47
- /** @private */
48
- this._onDetachCallbacks = new Set();
49
- /** @private */
50
- this._onDeleteCallbacks = new Set();
51
-
52
- // add listener for state updates
53
- this._client.transport.addListener(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
54
- const updated = await this._commit(updates, context, true, true);
55
- resolveRequest(reqId, updated);
56
- });
57
-
58
- // retrieve values but do not propagate to subscriptions
59
- this._client.transport.addListener(`${UPDATE_ABORT}-${this.id}-${this.remoteId}`, async (reqId, updates, context) => {
60
- const updated = await this._commit(updates, context, false, true);
61
- resolveRequest(reqId, updated);
62
- });
63
-
64
- this._client.transport.addListener(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`, async (updates, context) => {
65
- // https://github.com/collective-soundworks/soundworks/issues/18
66
- //
67
- // # note: 2002-10-03
68
- //
69
- // `setTimeout(async () => this._commit(updates, context, true, false));`
70
- // appears to be the only way to push the update commit in the next event
71
- // cycle so that `attach` can resolve before the update notification is
72
- // actually dispatched. The alternative:
73
- // `Promise.resolve().then(() => this._commit(updates, context, true, false))``
74
- // does not behave as expected...
75
- //
76
- // However this breaks the reliability of:
77
- // ```
78
- // /* given a0, a1 and a2 being 3 similar attached states */
79
- // await state.set({ int: i });
80
- //
81
- // assert.equal(a0.get('int'), i);
82
- // assert.equal(a1.get('int'), i);
83
- // assert.equal(a2.get('int'), i);
84
- // ```
85
- // which is far more important than the edge case reported in the issue
86
- // therefore this wont be fixed for now
87
- this._commit(updates, context, true, false);
88
- });
89
-
90
- // ---------------------------------------------
91
- // DELETE initiated by creator, or schema deleted
92
- // ---------------------------------------------
93
- this._client.transport.addListener(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`, () => {
94
- this._manager._statesById.delete(this.id);
95
- this._clearTransport();
96
-
97
- this._onDetachCallbacks.forEach(callback => callback());
98
- this._onDeleteCallbacks.forEach(callback => callback());
99
-
100
- this._clearDetach();
101
- });
102
-
103
-
104
- if (this._isOwner) {
105
- // ---------------------------------------------
106
- // DELETE (can only delete if creator)
107
- // ---------------------------------------------
108
- this._client.transport.addListener(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`, (reqId) => {
109
- this._manager._statesById.delete(this.id);
110
- this._clearTransport();
111
-
112
- this._onDetachCallbacks.forEach(callback => callback());
113
- this._onDeleteCallbacks.forEach(callback => callback());
114
-
115
- this._clearDetach();
116
- resolveRequest(reqId, this);
117
- });
118
-
119
- this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
120
- rejectRequest(reqId, msg);
121
- });
122
-
123
- } else {
124
- // ---------------------------------------------
125
- // DETACH
126
- // ---------------------------------------------
127
- this._client.transport.addListener(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`, (reqId) => {
128
- this._manager._statesById.delete(this.id);
129
- this._clearTransport();
130
-
131
- this._onDetachCallbacks.forEach(func => func());
132
-
133
- this._clearDetach();
134
- resolveRequest(reqId, this);
135
- });
136
-
137
- // the state does not exists anymore in the server (should not happen)
138
- this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
139
- rejectRequest(reqId, msg);
140
- });
141
- }
142
- }
143
-
144
- /** @private */
145
- _clearDetach() {
146
- this._onDetachCallbacks.clear();
147
- this._onDeleteCallbacks.clear();
148
-
149
- // Monkey patch detach so it throws if called twice. Doing nothing blocks
150
- // the process on a second `detach` call as the Promise never resolves
151
- this.detach = () => {
152
- throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
153
- };
154
- }
155
-
156
- /** @private */
157
- _clearTransport() {
158
- // remove listeners
159
- this._client.transport.removeAllListeners(`${UPDATE_RESPONSE}-${this.id}-${this.remoteId}`);
160
- this._client.transport.removeAllListeners(`${UPDATE_NOTIFICATION}-${this.id}-${this.remoteId}`);
161
- this._client.transport.removeAllListeners(`${DELETE_RESPONSE}-${this.id}-${this.remoteId}`);
162
- this._client.transport.removeAllListeners(`${DELETE_NOTIFICATION}-${this.id}-${this.remoteId}`);
163
- this._client.transport.removeAllListeners(`${DETACH_RESPONSE}-${this.id}-${this.remoteId}`);
164
- }
165
-
166
- /** @private */
167
- async _commit(updates, context, propagate = true, initiator = false) {
168
- const newValues = {};
169
- const oldValues = {};
170
-
171
- for (let name in updates) {
172
- const { immediate, event } = this._parameters.getSchema(name);
173
- // @note 20211209 - we had an issue here server-side, because if the value
174
- // is an object or an array, the reference is shared by everybody, therefore
175
- // `changed` is always false and the new value is never propagated...
176
- // FIXED - `state.get` now returns a deep copy when `type` is `any`
177
- const oldValue = this._parameters.get(name);
178
- const [newValue, changed] = this._parameters.set(name, updates[name]);
179
-
180
- // handle immediate stuff
181
- if (initiator && immediate) {
182
- // @note - we don't need to check filterChange here because the value
183
- // has been updated in parameters on the `set` side so can rely on `changed`
184
- // to avoid retrigger listeners.
185
- // If the value has been overriden by the server, `changed` will true
186
- // anyway so it should behave correctly.
187
- if (!changed || event) {
188
- continue;
189
- }
190
- }
191
-
192
- newValues[name] = newValue;
193
- oldValues[name] = oldValue;
194
- }
195
-
196
- // if the `UPDATE_REQUEST` as been aborted by the server, do not propagate
197
- let promises = [];
198
-
199
- if (propagate && Object.keys(newValues).length > 0) {
200
- this._onUpdateCallbacks.forEach(listener => {
201
- promises.push(listener(newValues, oldValues, context));
202
- });
203
- }
204
-
205
- // on a given client, `await state.set(update)` resolves after all
206
- // update callbacks have themselves resolved
207
- await Promise.all(promises);
208
-
209
- // reset events to null after propagation of all listeners
210
- for (let name in newValues) {
211
- const { event } = this._parameters.getSchema(name);
212
-
213
- if (event) {
214
- this._parameters.set(name, null);
215
- }
216
- }
217
-
218
- return newValues;
219
- }
220
-
221
- /**
222
- * Update values of the state.
223
- *
224
- * The returned `Promise` resolves on the applied updates, when all the `onUpdate`
225
- * callbacks have resolved themselves, i.e.:
226
- *
227
- * ```js
228
- * server.stateManager.registerSchema('test', {
229
- * myBool: { type: 'boolean', default: false },
230
- * });
231
- * const a = await server.stateManager.create('a');
232
- * let asyncCallbackCalled = false;
233
- *
234
- * a.onUpdate(updates => {
235
- * return new Promise(resolve => {
236
- * setTimeout(() => {
237
- * asyncCallbackCalled = true;
238
- * resolve();
239
- * }, 100);
240
- * });
241
- * });
242
- *
243
- * const updates = await a.set({ myBool: true });
244
- * assert.equal(asyncCallbackCalled, true);
245
- * assert.deepEqual(updates, { myBool: true });
246
- * ```
247
- *
248
- * @param {object} updates - key / value pairs of updates to apply to the state.
249
- * @param {mixed} [context=null] - optionnal contextual object that will be propagated
250
- * alongside the updates of the state. The context is valid only for the
251
- * current call and will be passed as third argument to all update listeners.
252
- * @returns {Promise<Object>} A promise to the (coerced) updates.
253
- * @example
254
- * const state = await client.state.attach('globals');
255
- * const updates = await state.set({ myParam: Math.random() });
256
- */
257
- async set(updates, context = null) {
258
- if (!isPlainObject(updates)) {
259
- throw new ReferenceError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as first parameter`);
260
- }
261
-
262
- if (context !== null && !isPlainObject(context)) {
263
- throw new ReferenceError(`[SharedState] State "${this.schemaName}": state.set(updates[, context]) should receive an object as second parameter`);
264
- }
265
-
266
- // handle immediate option
267
- const immediateNewValues = {};
268
- const immediateOldValues = {};
269
- let propagateNow = false;
270
-
271
- for (let name in updates) {
272
- // try to coerce value early, so that eventual errors are triggered early
273
- // on the node requesting the update
274
- const _ = this._parameters.coerceValue(name, updates[name]);
275
-
276
- // @note: general idea...
277
- // if immediate=true
278
- // - call listeners if value changed
279
- // - go through normal server path
280
- // - retrigger only if response from server is different from current value
281
- // if immediate=true && (filterChange=false || event=true)
282
- // - call listeners with value regarless it changed
283
- // - go through normal server path
284
- // - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
285
- // to prevent execute the listeners twice
286
-
287
- const { immediate, filterChange, event } = this._parameters.getSchema(name);
288
-
289
- if (immediate) {
290
- const oldValue = this._parameters.get(name);
291
- const [newValue, changed] = this._parameters.set(name, updates[name]);
292
-
293
- if (changed || filterChange === false || event) {
294
- immediateOldValues[name] = oldValue;
295
- immediateNewValues[name] = newValue;
296
- propagateNow = true;
297
- }
298
- }
299
- }
300
-
301
- if (propagateNow) {
302
- this._onUpdateCallbacks.forEach(listener => listener(immediateNewValues, immediateOldValues, context));
303
- }
304
-
305
- // go through server-side normal behavior
306
- return new Promise((resolve, reject) => {
307
- const reqId = storeRequestPromise(resolve, reject);
308
- this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);
309
- });
310
- }
311
-
312
- /**
313
- * Get the value of a parameter of the state. If the parameter is of `any` type,
314
- * a deep copy is returned.
315
- *
316
- * @param {string} name - Name of the param.
317
- * @throws Throws if `name` does not correspond to an existing field
318
- * of the state.
319
- * @return {mixed}
320
- * @example
321
- * const value = state.get('paramName');
322
- */
323
- get(name) {
324
- return this._parameters.get(name);
325
- }
326
-
327
- /**
328
- * Similar to `get` but returns a reference to the underlying value in case of
329
- * `any` type. May be usefull if the underlying value is big (e.g. sensors
330
- * recordings, etc.) and deep cloning expensive. Be aware that if changes are
331
- * made on the returned object, the state of your application will become
332
- * inconsistent.
333
- *
334
- * @param {string} name - Name of the param.
335
- * @throws Throws if `name` does not correspond to an existing field
336
- * of the state.
337
- * @return {mixed}
338
- * @example
339
- * const value = state.getUnsafe('paramName');
340
- */
341
- getUnsafe(name) {
342
- return this._parameters.getUnsafe(name);
343
- }
344
-
345
- /**
346
- * Get all the key / value pairs of the state. If a parameter is of `any`
347
- * type, a deep copy is made.
348
- *
349
- * @return {object}
350
- * @example
351
- * const values = state.getValues();
352
- */
353
- getValues() {
354
- return this._parameters.getValues();
355
- }
356
-
357
- /**
358
- * Get all the key / value pairs of the state. If a parameter is of `any`
359
- * type, a deep copy is made.
360
- * Similar to `getValues` but returns a reference to the underlying value in
361
- * case of `any` type. May be usefull if the underlying value is big (e.g.
362
- * sensors recordings, etc.) and deep cloning expensive. Be aware that if
363
- * changes are made on the returned object, the state of your application will
364
- * become inconsistent.
365
- *
366
- * @return {object}
367
- * @example
368
- * const values = state.getValues();
369
- */
370
- getValuesUnsafe() {
371
- return this._parameters.getValuesUnsafe();
372
- }
373
-
374
- /**
375
- * Get the schema from which the state has been created.
376
- *
377
- * @param {string} [name=null] - If given, returns only the definition corresponding
378
- * to the given param name.
379
- * @throws Throws if `name` does not correspond to an existing field
380
- * of the state.
381
- * @return {object}
382
- * @example
383
- * const schema = state.getSchema();
384
- */
385
- getSchema(name = null) {
386
- return this._parameters.getSchema(name);
387
- }
388
-
389
- /**
390
- * Get the values with which the state has been created. May defer from the
391
- * default values declared in the schema.
392
- *
393
- * @return {object}
394
- * @example
395
- * const initValues = state.getInitValues();
396
- */
397
- getInitValues() {
398
- return this._parameters.getInitValues();
399
- }
400
-
401
- /**
402
- * Get the default values as declared in the schema.
403
- *
404
- * @return {object}
405
- * @example
406
- * const defaults = state.getDefaults();
407
- */
408
- getDefaults() {
409
- return this._parameters.getDefaults();
410
- }
411
-
412
- /**
413
- * Detach from the state. If the client is the creator of the state, the state
414
- * is deleted and all attached nodes get notified
415
- *
416
- * @example
417
- * const state = await client.state.attach('globals');
418
- * // later
419
- * await state.detach();
420
- */
421
- async detach() {
422
- this._onUpdateCallbacks.clear();
423
-
424
- if (this._isOwner) {
425
- return new Promise((resolve, reject) => {
426
- const reqId = storeRequestPromise(resolve, reject);
427
- this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);
428
- });
429
- } else {
430
- return new Promise((resolve, reject) => {
431
- const reqId = storeRequestPromise(resolve, reject);
432
- this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);
433
- });
434
- }
435
- }
436
-
437
- /**
438
- * Delete the state. Only the creator/owner of the state can use this method.
439
- * All nodes attached to the state will be deteched, triggering the `onDetach`
440
- * callback. The creator of the state will also have its `onDelete` callback
441
- * triggered.
442
- *
443
- * @throws Throws if the method is called by a node which is not the owner of
444
- * the state.
445
- * @example
446
- * const state = await client.state.create('my-schema-name');
447
- * // later
448
- * await state.delete();
449
- */
450
- async delete() {
451
- if (this._isOwner) {
452
- return this.detach();
453
- } else {
454
- throw new Error(`[SharedState] Cannot delete state "${this.schemaName}", only the owner of the state (i.e. the node that created it) can delete the state. Use "detach" instead.`);
455
- }
456
- }
457
-
458
- /**
459
- * Subscribe to state updates.
460
- *
461
- * @param {client.SharedState~onUpdateCallback|server.SharedState~onUpdateCallback} callback
462
- * Callback to execute when an update is applied on the state.
463
- * @param {Boolean} [executeListener=false] - Execute the callback immediately
464
- * with current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
465
- * @returns {client.SharedState~deleteOnUpdateCallback|server.SharedState~deleteOnUpdateCallback}
466
- * @example
467
- * const unsubscribe = state.onUpdate(async (newValues, oldValues, context) => {
468
- * for (let [key, value] of Object.entries(newValues)) {
469
- * switch (key) {
470
- * // do something
471
- * }
472
- * }
473
- * });
474
- *
475
- * // later
476
- * unsubscribe();
477
- */
478
- onUpdate(listener, executeListener = false) {
479
- this._onUpdateCallbacks.add(listener);
480
-
481
- if (executeListener === true) {
482
- const currentValues = this.getValues();
483
- const oldValues = {};
484
- const context = null;
485
- listener(currentValues, oldValues, context);
486
- }
487
-
488
- return () => {
489
- this._onUpdateCallbacks.delete(listener);
490
- };
491
- }
492
-
493
- /**
494
- * Register a function to execute when detaching from the state
495
- *
496
- * @param {Function} callback - Callback to execute when detaching from the state.
497
- * Whether the client as called `detach`, or the state has been deleted by its
498
- * creator.
499
- */
500
- onDetach(callback) {
501
- this._onDetachCallbacks.add(callback);
502
- return () => this._onDetachCallbacks.delete(callback);
503
- }
504
-
505
- /**
506
- * Register a function to execute when the state is deleted. Only called if the
507
- * node was the creator of the state. Is called after `onDetach`
508
- *
509
- * @param {Function} callback - Callback to execute when the state is deleted.
510
- */
511
- onDelete(callback) {
512
- this._onDeleteCallbacks.add(callback);
513
- return () => this._onDeleteCallbacks.delete(callback);
514
- }
515
- }
516
-
517
- export default BaseSharedState;
@@ -1,179 +0,0 @@
1
- /**
2
- * @private
3
- */
4
- class BaseSharedStateCollection {
5
- constructor(stateManager, schemaName) {
6
- this._stateManager = stateManager;
7
- this._schemaName = schemaName;
8
- this._states = [];
9
- this._onUpdateCallbacks = new Set();
10
- this._onAttachCallbacks = new Set();
11
- this._onDetachCallbacks = new Set();
12
- this._unobserve = null;
13
- }
14
-
15
- async _init() {
16
- this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId, nodeId) => {
17
- const state = await this._stateManager.attach(schemaName, stateId);
18
-
19
- this._states.push(state);
20
-
21
- state.onDetach(() => {
22
- const index = this._states.indexOf(state);
23
- this._states.splice(index, 1);
24
-
25
- this._onDetachCallbacks.forEach(callback => callback(state));
26
- });
27
-
28
- state.onUpdate((newValues, oldValues, context) => {
29
- Array.from(this._onUpdateCallbacks).forEach(callback => {
30
- callback(state, newValues, oldValues, context);
31
- });
32
- });
33
-
34
- this._onAttachCallbacks.forEach(callback => callback(state));
35
- });
36
- }
37
-
38
- /**
39
- * Size of the collection
40
- * @type {number}
41
- */
42
- get length() {
43
- return this._states.length;
44
- }
45
-
46
- /**
47
- * Detach from the collection, i.e. detach all underlying shared states.
48
- * @type {number}
49
- */
50
- async detach() {
51
- this._unobserve();
52
-
53
- const promises = Array.from(this._states).map(state => state.detach());
54
- await Promise.all(promises);
55
-
56
- this._onUpdateCallbacks.clear();
57
- this._onDetachCallbacks.clear();
58
- }
59
-
60
- /**
61
- * Return the current values of all the states in the collection.
62
- * @return {Object[]}
63
- */
64
- getValues() {
65
- return this._states.map(state => state.getValues());
66
- }
67
-
68
- /**
69
- * Return the current param value of all the states in the collection.
70
- *
71
- * @param {String} name - Name of the parameter
72
- * @return {any[]}
73
- */
74
- get(name) {
75
- return this._states.map(state => state.get(name));
76
- }
77
-
78
- /**
79
- * Subscribe to any state update of the collection.
80
- *
81
- * @param {server.SharedStateCollection~onUpdateCallback|client.SharedStateCollection~onUpdateCallback}
82
- * callback - Callback to execute when an update is applied on a state.
83
- * @param {Boolean} [executeListener=false] - Execute the callback immediately
84
- * for all underlying states with current state values. (`oldValues` will be
85
- * set to `{}`, and `context` to `null`)
86
- * @returns {Function} - Function that delete the registered listener.
87
- */
88
- onUpdate(callback, executeListener = false) {
89
- this._onUpdateCallbacks.add(callback);
90
-
91
- if (executeListener === true) {
92
- this._states.forEach(state => {
93
- const currentValues = state.getValues();
94
- const oldValues = {};
95
- const context = null;
96
-
97
- callback(state, currentValues, oldValues, context);
98
- });
99
- }
100
-
101
- return () => this._onUpdateCallbacks.delete(callback);
102
- }
103
-
104
- /**
105
- * Register a function to execute when a state is added to the collection.
106
- *
107
- * @param {Function} callback - callback to execute when a state is added to
108
- * the collection.
109
- */
110
- onAttach(callback) {
111
- this._onAttachCallbacks.add(callback);
112
- }
113
-
114
- /**
115
- * Register a function to execute when a state is removed from the collection.
116
- *
117
- * @param {Function} callback - callback to execute when a state is removed
118
- * from the collection.
119
- */
120
- onDetach(callback) {
121
- this._onDetachCallbacks.add(callback);
122
- }
123
-
124
- /**
125
- * Execute the given function once for each states of the collection (see `Array.forEach`).
126
- *
127
- * @param {Function} func - A function to execute for each element in the array.
128
- * Its return value is discarded.
129
- */
130
- forEach(func) {
131
- this._states.forEach(func);
132
- }
133
-
134
- /**
135
- * Creates a new array populated with the results of calling a provided function
136
- * on every state of the collection (see `Array.map`).
137
- *
138
- * @param {Function} func - A function to execute for each element in the array.
139
- * Its return value is added as a single element in the new array.
140
- */
141
- map(func) {
142
- return this._states.map(func);
143
- }
144
-
145
- /**
146
- * Creates a shallow copy of a portion of the collection, filtered down to just
147
- * the estates that pass the test implemented by the provided function (see `Array.filter`).
148
- *
149
- * @param {Function} func - A function to execute for each element in the array.
150
- * It should return a truthy to keep the element in the resulting array, and a f
151
- * alsy value otherwise.
152
- */
153
- filter(func) {
154
- return this._states.filter(func);
155
- }
156
-
157
- /**
158
- * Sort the elements of the collection in place (see `Array.sort`).
159
- *
160
- * @param {Function} func - Function that defines the sort order.
161
- */
162
- sort(func) {
163
- this._states.sort(func);
164
- }
165
-
166
- /**
167
- * Returns the first element of the collection that satisfies the provided testing
168
- * function. If no values satisfy the testing function, undefined is returned.
169
- *
170
- * @param {Function} func - Function to execute for each element in the array.
171
- * It should return a truthy value to indicate a matching element has been found.
172
- * @return {}
173
- */
174
- find(func) {
175
- return this._states.find(func);
176
- }
177
- }
178
-
179
- export default BaseSharedStateCollection;