@soundworks/core 5.2.1 → 5.4.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.
- package/package.json +1 -1
- package/src/client/Client.js +12 -0
- package/src/common/BaseStateManager.js +11 -0
- package/src/common/ParameterBag.js +1 -0
- package/src/common/SharedState.js +70 -40
- package/src/common/shared-state-types.js +14 -6
- package/src/common/version.js +1 -1
- package/src/server/Server.js +12 -0
- package/src/server/SharedStatePrivate.js +64 -34
- package/types/common/BaseStateManager.d.ts +8 -0
- package/types/common/ParameterBag.d.ts +1 -0
- package/types/common/shared-state-types.d.ts +20 -9
- package/types/common/version.d.ts +1 -1
package/package.json
CHANGED
package/src/client/Client.js
CHANGED
|
@@ -314,6 +314,10 @@ class Client {
|
|
|
314
314
|
* await client.start();
|
|
315
315
|
*/
|
|
316
316
|
async init() {
|
|
317
|
+
if (this.#status === 'inited') {
|
|
318
|
+
throw new DOMException(`Cannot execute 'init' twice on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
319
|
+
}
|
|
320
|
+
|
|
317
321
|
if (this.#status !== 'idle') {
|
|
318
322
|
throw new DOMException(`Cannot execute 'init' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
319
323
|
}
|
|
@@ -391,6 +395,10 @@ class Client {
|
|
|
391
395
|
await this.init();
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
if (this.#status === 'started') {
|
|
399
|
+
throw new DOMException(`Cannot execute 'start' twice on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
400
|
+
}
|
|
401
|
+
|
|
394
402
|
if (this.#status !== 'inited') {
|
|
395
403
|
throw new DOMException(`Cannot execute 'start' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
396
404
|
}
|
|
@@ -416,6 +424,10 @@ class Client {
|
|
|
416
424
|
* await client.stop();
|
|
417
425
|
*/
|
|
418
426
|
async stop() {
|
|
427
|
+
if (this.#status === 'stopped') {
|
|
428
|
+
throw new DOMException(`Cannot execute 'stop' twice on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
429
|
+
}
|
|
430
|
+
|
|
419
431
|
if (this.#status !== 'started') {
|
|
420
432
|
throw new DOMException(`Cannot execute 'stop' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
421
433
|
}
|
|
@@ -230,6 +230,17 @@ class BaseStateManager {
|
|
|
230
230
|
this.#status = 'inited';
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Validates the given class description.
|
|
235
|
+
*
|
|
236
|
+
* @throws Throws if the class description is invalid
|
|
237
|
+
* @param {*} classDescription
|
|
238
|
+
* @returns undefined
|
|
239
|
+
*/
|
|
240
|
+
validateClassDescription(classDescription) {
|
|
241
|
+
return ParameterBag.validateDescription(classDescription);
|
|
242
|
+
}
|
|
243
|
+
|
|
233
244
|
/**
|
|
234
245
|
* Return a class description from a given class name
|
|
235
246
|
*
|
|
@@ -214,11 +214,19 @@ class SharedState {
|
|
|
214
214
|
this.#clearTransport();
|
|
215
215
|
|
|
216
216
|
for (let callback of this.#onDetachCallbacks) {
|
|
217
|
-
|
|
217
|
+
try {
|
|
218
|
+
await callback();
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.log(err.message);
|
|
221
|
+
}
|
|
218
222
|
}
|
|
219
223
|
|
|
220
224
|
for (let callback of this.#onDeleteCallbacks) {
|
|
221
|
-
|
|
225
|
+
try {
|
|
226
|
+
await callback();
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.log(err.message);
|
|
229
|
+
}
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
this.#onDetachCallbacks.clear();
|
|
@@ -240,7 +248,11 @@ class SharedState {
|
|
|
240
248
|
this.#clearTransport();
|
|
241
249
|
|
|
242
250
|
for (let callback of this.#onDetachCallbacks) {
|
|
243
|
-
|
|
251
|
+
try {
|
|
252
|
+
await callback();
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.log(err.message);
|
|
255
|
+
}
|
|
244
256
|
}
|
|
245
257
|
|
|
246
258
|
this.#onDetachCallbacks.clear();
|
|
@@ -465,31 +477,31 @@ class SharedState {
|
|
|
465
477
|
|
|
466
478
|
const newValues = {};
|
|
467
479
|
const oldValues = {};
|
|
468
|
-
const
|
|
469
|
-
const
|
|
480
|
+
const locallyResolvedParam = {};
|
|
481
|
+
const networkPropagatedParams = {};
|
|
470
482
|
let hasLocalParam = false;
|
|
471
|
-
let
|
|
472
|
-
let
|
|
473
|
-
let
|
|
483
|
+
let hasRegularSharedParam = false;
|
|
484
|
+
let hasNonAcknowledgedParams = false;
|
|
485
|
+
let syncedOnUpdate = false;
|
|
474
486
|
|
|
475
487
|
for (let name in updates) {
|
|
476
|
-
// Try to coerce value
|
|
477
|
-
// on the node requesting the update
|
|
478
|
-
// This throws if name does not exists
|
|
488
|
+
// Try to coerce value now, so that eventual errors are triggered early
|
|
489
|
+
// on the node requesting the update and not only on the server side.
|
|
479
490
|
try {
|
|
491
|
+
// This also throws if name does not exists
|
|
480
492
|
this.#parameters.coerceValue(name, updates[name]);
|
|
481
493
|
} catch (err) {
|
|
482
494
|
throw new TypeError(`Cannot execute 'set' on SharedState (${this.#className}): ${err.message}`);
|
|
483
495
|
}
|
|
484
496
|
|
|
485
|
-
//
|
|
497
|
+
// Make sure that given name is in filter white list (if any)
|
|
486
498
|
if (this.#filter !== null) {
|
|
487
499
|
if (!this.#filter.includes(name)) {
|
|
488
500
|
throw new DOMException(`Cannot execute 'set' on SharedState (${this.#className}): Parameter '${name}' is not in white list`, 'NotSupportedError');
|
|
489
501
|
}
|
|
490
502
|
}
|
|
491
503
|
|
|
492
|
-
// `immediate`
|
|
504
|
+
// ### `immediate` modifier behavior
|
|
493
505
|
//
|
|
494
506
|
// If immediate=true
|
|
495
507
|
// - call listeners if value changed
|
|
@@ -501,7 +513,7 @@ class SharedState {
|
|
|
501
513
|
// - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
|
|
502
514
|
// to prevent execute the listeners twice
|
|
503
515
|
|
|
504
|
-
// `local`
|
|
516
|
+
// ### `local` modifier behavior
|
|
505
517
|
//
|
|
506
518
|
// - If the `updates` object only contains local variables, we can call the
|
|
507
519
|
// update listeners and return a fulfilled promise immediately
|
|
@@ -510,9 +522,19 @@ class SharedState {
|
|
|
510
522
|
// response, call update listeners with server response and resolve promise with
|
|
511
523
|
// the full payload, i.e. reintegrating the local params in the resolve payload
|
|
512
524
|
|
|
513
|
-
|
|
525
|
+
// ### `acknowledge` modifier behavior
|
|
526
|
+
//
|
|
527
|
+
// Same as immediate, but no feedback from server
|
|
514
528
|
|
|
515
|
-
|
|
529
|
+
const {
|
|
530
|
+
local,
|
|
531
|
+
immediate,
|
|
532
|
+
filterChange,
|
|
533
|
+
event,
|
|
534
|
+
acknowledge,
|
|
535
|
+
} = this.#parameters.getDescription(name);
|
|
536
|
+
|
|
537
|
+
if (immediate || local || acknowledge === false) {
|
|
516
538
|
const oldValue = this.#parameters.get(name);
|
|
517
539
|
const [newValue, changed] = this.#parameters.set(name, updates[name]);
|
|
518
540
|
|
|
@@ -520,46 +542,50 @@ class SharedState {
|
|
|
520
542
|
if (changed || filterChange === false || event) {
|
|
521
543
|
oldValues[name] = oldValue;
|
|
522
544
|
newValues[name] = newValue;
|
|
523
|
-
|
|
545
|
+
syncedOnUpdate = true;
|
|
524
546
|
}
|
|
525
547
|
}
|
|
526
548
|
|
|
527
|
-
// define params that must go through network or not
|
|
528
549
|
if (local) {
|
|
550
|
+
// these don't go to the network
|
|
529
551
|
hasLocalParam = true;
|
|
530
|
-
|
|
531
|
-
|
|
552
|
+
locallyResolvedParam[name] = this.#parameters.get(name);
|
|
553
|
+
} else if (acknowledge === false) {
|
|
554
|
+
// these go to the network but don't receive an ack
|
|
555
|
+
// they are both locally forwarded and network propagated then
|
|
556
|
+
hasNonAcknowledgedParams = true;
|
|
557
|
+
locallyResolvedParam[name] = this.#parameters.get(name);
|
|
558
|
+
networkPropagatedParams[name] = updates[name];
|
|
532
559
|
} else {
|
|
533
|
-
// note that immediate are shared
|
|
534
|
-
|
|
535
|
-
|
|
560
|
+
// note that immediate params are shared and acknowledged
|
|
561
|
+
hasRegularSharedParam = true;
|
|
562
|
+
networkPropagatedParams[name] = updates[name];
|
|
536
563
|
}
|
|
537
564
|
}
|
|
538
565
|
|
|
539
|
-
//
|
|
540
|
-
if (
|
|
566
|
+
// params that trigger a synced onUpdate call: immediate, local, non-acknowledged
|
|
567
|
+
if (syncedOnUpdate) {
|
|
541
568
|
this.#onUpdateCallbacks.forEach(listener => listener(newValues, oldValues));
|
|
542
569
|
}
|
|
543
570
|
|
|
544
|
-
//
|
|
545
|
-
if (hasLocalParam) {
|
|
546
|
-
|
|
547
|
-
return Promise.resolve(localParams);
|
|
548
|
-
} else {
|
|
549
|
-
// store local params to fulfill promise with all values, see PromiseStore
|
|
550
|
-
forwardParams = localParams;
|
|
551
|
-
}
|
|
571
|
+
// if we only have local params, we can resolve immediately
|
|
572
|
+
if (hasLocalParam && !hasNonAcknowledgedParams && !hasRegularSharedParam) {
|
|
573
|
+
return Promise.resolve(locallyResolvedParam);
|
|
552
574
|
}
|
|
553
575
|
|
|
554
|
-
// override updates to be shared on network without local params
|
|
555
|
-
updates = sharedParams;
|
|
556
|
-
|
|
557
576
|
// go through server-side normal behavior
|
|
558
577
|
const { id: reqId, promise } = this[kSharedStatePromiseStore].createPromise();
|
|
559
|
-
|
|
560
|
-
|
|
578
|
+
// store local params that should be re-merged w/ regular ones
|
|
579
|
+
if (Object.keys(locallyResolvedParam).length > 0) {
|
|
580
|
+
this[kSharedStatePromiseStore].associateResolveData(reqId, locallyResolvedParam);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#instanceId}`, reqId, networkPropagatedParams);
|
|
584
|
+
|
|
585
|
+
// if we don't have regular shared params, we can resolve now
|
|
586
|
+
if (!hasRegularSharedParam) {
|
|
587
|
+
this[kSharedStatePromiseStore].resolve(reqId, {});
|
|
561
588
|
}
|
|
562
|
-
this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#instanceId}`, reqId, updates);
|
|
563
589
|
|
|
564
590
|
return promise;
|
|
565
591
|
}
|
|
@@ -746,7 +772,7 @@ class SharedState {
|
|
|
746
772
|
|
|
747
773
|
return this.detach();
|
|
748
774
|
} else {
|
|
749
|
-
throw new DOMException(`Cannot execute 'delete' on SharedState (${this.#className}):
|
|
775
|
+
throw new DOMException(`Cannot execute 'delete' on SharedState (${this.#className}): This instance is attached and not owned. Use 'SharedState#detach' instead`, 'NotSupportedError');
|
|
750
776
|
}
|
|
751
777
|
}
|
|
752
778
|
|
|
@@ -814,6 +840,10 @@ class SharedState {
|
|
|
814
840
|
* @param {Function} callback - Callback to execute when the state is deleted.
|
|
815
841
|
*/
|
|
816
842
|
onDelete(callback) {
|
|
843
|
+
if (!this.#isOwner) {
|
|
844
|
+
throw new DOMException(`Cannot execute 'onDelete' on SharedState (${this.#className}): This instance is attached and not owned. Use 'SharedState#onDetach' instead`, 'NotSupportedError');
|
|
845
|
+
}
|
|
846
|
+
|
|
817
847
|
this.#onDeleteCallbacks.add(callback);
|
|
818
848
|
return () => this.#onDeleteCallbacks.delete(callback);
|
|
819
849
|
}
|
|
@@ -52,25 +52,33 @@
|
|
|
52
52
|
* @property {boolean} [event=false] - Define if the parameter is a volatile, i.e.
|
|
53
53
|
* its value only exists on an update and is set back to `null` after propagation.
|
|
54
54
|
* When `true`, `nullable` is automatically set to `true` and `default` to `null`.
|
|
55
|
+
* @property {boolean} [required=false] - When set to true, the parameter must be
|
|
56
|
+
* provided at the creation of the shared state.
|
|
55
57
|
* @property {boolean} [filterChange=true] - When set to `false`, an update will
|
|
56
58
|
* trigger the propagation of a parameter even when its value didn't change.
|
|
57
|
-
* This
|
|
59
|
+
* This modifier provides a sort of middle ground between the default behavior
|
|
58
60
|
* (e.g. where only changed values are propagated) and the behavior of the `event`
|
|
59
61
|
* option (which has no state per se). Hence, setting this options to `false` if
|
|
60
62
|
* `event=true` makes no sens.
|
|
61
63
|
* @property {boolean} [immediate=false] - When set to `true`, an update will
|
|
62
64
|
* trigger the update listeners immediately on the node that generated the update,
|
|
63
|
-
* before propagating the change on the network.
|
|
64
|
-
*
|
|
65
|
-
* latency on the client.
|
|
65
|
+
* before propagating the change on the network. This modifier is useful in cases
|
|
66
|
+
* the network would introduce a noticeable latency on the client.
|
|
66
67
|
* If for some reason the value is overridden server-side (e.g. in an `updateHook`)
|
|
67
68
|
* the listeners will be called again when the "real" value is received.
|
|
68
|
-
*
|
|
69
|
-
*
|
|
69
|
+
* Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
|
|
70
|
+
* according to the `set` call.
|
|
70
71
|
* @property {boolean} [local=false] - When set to true, the parameter is never
|
|
71
72
|
* propagated on the network (hence it is no longer a shared parameter :). This
|
|
72
73
|
* is useful to declare some common parameter (e.g. some interface state) that
|
|
73
74
|
* don't need to be shared but to stay in the shared state API paradigm.
|
|
75
|
+
* Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
|
|
76
|
+
* according to the `set` call.
|
|
77
|
+
* @property {boolean} [acknowledge=true] - When set to false, the acknowledgement
|
|
78
|
+
* is never sent back to the shared state that initiated an update. This can be
|
|
79
|
+
* useful to e.g. stream values to the network, but can lead to invalid state.
|
|
80
|
+
* Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
|
|
81
|
+
* according to the `set` call.
|
|
74
82
|
* @property {number} [min=-Number.MIN_VALUE] - Minimum value of the parameter. Only applies
|
|
75
83
|
* for `integer` and `float` types.
|
|
76
84
|
* @property {number} [max=Number.MAX_VALUE] - Maximum value of the parameter. Only applies
|
package/src/common/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// [WARNING] This file is generated, do not edit
|
|
2
|
-
export default '5.
|
|
2
|
+
export default '5.4.0';
|
package/src/server/Server.js
CHANGED
|
@@ -482,6 +482,10 @@ class Server {
|
|
|
482
482
|
* await server.start(); // init is called implicitly
|
|
483
483
|
*/
|
|
484
484
|
async init() {
|
|
485
|
+
if (this.#status === 'inited') {
|
|
486
|
+
throw new DOMException(`Cannot execute 'init' twice on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
487
|
+
}
|
|
488
|
+
|
|
485
489
|
if (this.#status !== 'idle') {
|
|
486
490
|
throw new DOMException(`Cannot execute 'init' on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
487
491
|
}
|
|
@@ -547,6 +551,10 @@ class Server {
|
|
|
547
551
|
await this.init();
|
|
548
552
|
}
|
|
549
553
|
|
|
554
|
+
if (this.#status === 'started') {
|
|
555
|
+
throw new DOMException(`Cannot execute 'start' twice on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
556
|
+
}
|
|
557
|
+
|
|
550
558
|
if (this.#status !== 'inited') {
|
|
551
559
|
throw new DOMException(`Cannot execute 'start' on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
552
560
|
}
|
|
@@ -601,6 +609,10 @@ class Server {
|
|
|
601
609
|
* await server.stop();
|
|
602
610
|
*/
|
|
603
611
|
async stop() {
|
|
612
|
+
if (this.#status === 'stopped') {
|
|
613
|
+
throw new DOMException(`Cannot execute 'stop' twice on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
614
|
+
}
|
|
615
|
+
|
|
604
616
|
if (this.#status !== 'started') {
|
|
605
617
|
throw new DOMException(`Cannot execute 'stop' on Server: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
606
618
|
}
|
|
@@ -16,18 +16,18 @@ import {
|
|
|
16
16
|
} from '../server/ServerStateManager.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Filter update object according to
|
|
19
|
+
* Filter update object according to a given white list.
|
|
20
20
|
* Return identity is filter is null
|
|
21
21
|
* @param {object} updates
|
|
22
|
-
* @param {array|null}
|
|
22
|
+
* @param {array|null} whiteList
|
|
23
23
|
* @private
|
|
24
24
|
*/
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
25
|
+
function whiteListFilterUpdates(updates, whiteList) {
|
|
26
|
+
if (whiteList === null) {
|
|
27
27
|
return updates;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
return
|
|
30
|
+
return whiteList.reduce((acc, key) => {
|
|
31
31
|
if (key in updates) {
|
|
32
32
|
acc[key] = updates[key];
|
|
33
33
|
}
|
|
@@ -35,6 +35,29 @@ function filterUpdates(updates, filter) {
|
|
|
35
35
|
}, {});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Filter update object according to a given black list.
|
|
40
|
+
* Return identity is filter is null
|
|
41
|
+
* @param {object} updates
|
|
42
|
+
* @param {array|null} filter
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
function blackListFilterUpdates(updates, blackList) {
|
|
46
|
+
if (blackList === null) {
|
|
47
|
+
return updates;
|
|
48
|
+
}
|
|
49
|
+
// create a shallow copy so we can have multiple filters applied on same source
|
|
50
|
+
const filtered = Object.assign({}, updates);
|
|
51
|
+
|
|
52
|
+
blackList.forEach(key => {
|
|
53
|
+
if (key in updates) {
|
|
54
|
+
delete filtered[key];
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return filtered;
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
export const kSharedStatePrivateAttachClient = Symbol('soundworks:shared-state-private-attach-client');
|
|
39
62
|
export const kSharedStatePrivateDetachClient = Symbol('soundworks:shared-state-private-detach-client');
|
|
40
63
|
export const kSharedStatePrivateGetValues = Symbol('soundworks:shared-state-private-get-values');
|
|
@@ -122,13 +145,13 @@ class SharedStatePrivate {
|
|
|
122
145
|
|
|
123
146
|
if (hookAborted === false) {
|
|
124
147
|
let acknowledgedUpdates = {};
|
|
125
|
-
let
|
|
148
|
+
let acknowledgeFilter = [];
|
|
126
149
|
|
|
127
150
|
for (let name in updates) {
|
|
128
151
|
const [newValue, changed] = this.#parameters.set(name, updates[name]);
|
|
129
152
|
// if `filterChange` is set to `false` we don't check if the value
|
|
130
153
|
// has been changed or not, it is always propagated to client states
|
|
131
|
-
const { event, filterChange } = this.#parameters.getDescription(name);
|
|
154
|
+
const { event, filterChange, acknowledge } = this.#parameters.getDescription(name);
|
|
132
155
|
|
|
133
156
|
// if event type reset internal value to null
|
|
134
157
|
if (event) {
|
|
@@ -137,11 +160,14 @@ class SharedStatePrivate {
|
|
|
137
160
|
|
|
138
161
|
if ((filterChange && changed) || !filterChange) {
|
|
139
162
|
acknowledgedUpdates[name] = newValue;
|
|
140
|
-
|
|
163
|
+
|
|
164
|
+
if (acknowledge === false) {
|
|
165
|
+
acknowledgeFilter.push(name);
|
|
166
|
+
}
|
|
141
167
|
}
|
|
142
168
|
}
|
|
143
169
|
|
|
144
|
-
if (
|
|
170
|
+
if (Object.keys(acknowledgedUpdates).length > 0) {
|
|
145
171
|
// Normal case
|
|
146
172
|
//
|
|
147
173
|
// We need to handle cases where:
|
|
@@ -161,25 +187,29 @@ class SharedStatePrivate {
|
|
|
161
187
|
//
|
|
162
188
|
// @note - instanceId correspond to unique remote state id
|
|
163
189
|
|
|
190
|
+
// - Apply acknowledge filter on `acknowledgedUpdates`
|
|
191
|
+
// - We don't need to apply the regular filter on update request, they are blocked client-side
|
|
192
|
+
const requesterFilteredUpdates = blackListFilterUpdates(acknowledgedUpdates, acknowledgeFilter);
|
|
193
|
+
|
|
164
194
|
// propagate RESPONSE to the client that originates the request if not the server
|
|
165
195
|
if (client.id !== -1) {
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
196
|
+
// propagate only if there something left after applying the acknowledge filter
|
|
197
|
+
if (Object.keys(requesterFilteredUpdates).length > 0) {
|
|
198
|
+
client.transport.emit(
|
|
199
|
+
`${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
|
|
200
|
+
reqId,
|
|
201
|
+
requesterFilteredUpdates,
|
|
202
|
+
);
|
|
203
|
+
};
|
|
172
204
|
}
|
|
173
205
|
|
|
174
|
-
// propagate NOTIFICATION to all
|
|
206
|
+
// propagate NOTIFICATION to all peer states except on server-side
|
|
175
207
|
for (let [peerInstanceId, clientInfos] of this.#attachedClients) {
|
|
176
|
-
const peer = clientInfos
|
|
208
|
+
const { client: peer, filter } = clientInfos;
|
|
177
209
|
|
|
178
210
|
if (instanceId !== peerInstanceId && peer.id !== -1) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// propagate only if there something left after applying the filter
|
|
211
|
+
const filteredUpdates = whiteListFilterUpdates(acknowledgedUpdates, filter);
|
|
212
|
+
// propagate only if there something left after applying the white list filter
|
|
183
213
|
if (Object.keys(filteredUpdates).length > 0) {
|
|
184
214
|
peer.transport.emit(
|
|
185
215
|
`${UPDATE_NOTIFICATION}-${this.id}-${peerInstanceId}`,
|
|
@@ -191,22 +221,23 @@ class SharedStatePrivate {
|
|
|
191
221
|
|
|
192
222
|
// propagate RESPONSE to server if it is the requester
|
|
193
223
|
if (client.id === -1) {
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
224
|
+
// propagate only if there something left after applying the acknowledge filter
|
|
225
|
+
if (Object.keys(requesterFilteredUpdates).length > 0) {
|
|
226
|
+
client.transport.emit(
|
|
227
|
+
`${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
|
|
228
|
+
reqId,
|
|
229
|
+
requesterFilteredUpdates,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
200
232
|
}
|
|
201
233
|
|
|
202
|
-
// propagate NOTIFICATION to
|
|
234
|
+
// propagate NOTIFICATION to all peer states on the server-side
|
|
203
235
|
for (let [peerInstanceId, clientInfos] of this.#attachedClients) {
|
|
204
|
-
const peer = clientInfos
|
|
236
|
+
const { client: peer, filter } = clientInfos;
|
|
205
237
|
|
|
206
238
|
if (instanceId !== peerInstanceId && peer.id === -1) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
239
|
+
const filteredUpdates = whiteListFilterUpdates(acknowledgedUpdates, filter);
|
|
240
|
+
// propagate only if there something left after applying the white list filter
|
|
210
241
|
if (Object.keys(filteredUpdates).length > 0) {
|
|
211
242
|
peer.transport.emit(
|
|
212
243
|
`${UPDATE_NOTIFICATION}-${this.id}-${peerInstanceId}`,
|
|
@@ -216,8 +247,7 @@ class SharedStatePrivate {
|
|
|
216
247
|
}
|
|
217
248
|
}
|
|
218
249
|
} else {
|
|
219
|
-
// propagate back to the requester that the update has been aborted
|
|
220
|
-
// ignore all other attached clients.
|
|
250
|
+
// propagate back to the requester that the update has been aborted, ignore all peers
|
|
221
251
|
client.transport.emit(`${UPDATE_ABORT}-${this.id}-${instanceId}`, reqId, updates);
|
|
222
252
|
}
|
|
223
253
|
} else {
|
|
@@ -28,6 +28,14 @@ export type stateManagerDeleteObserveCallback = () => any;
|
|
|
28
28
|
*/
|
|
29
29
|
/** @private */
|
|
30
30
|
declare class BaseStateManager {
|
|
31
|
+
/**
|
|
32
|
+
* Validates the given class description.
|
|
33
|
+
*
|
|
34
|
+
* @throws Throws if the class description is invalid
|
|
35
|
+
* @param {*} classDescription
|
|
36
|
+
* @returns undefined
|
|
37
|
+
*/
|
|
38
|
+
validateClassDescription(classDescription: any): void;
|
|
31
39
|
/**
|
|
32
40
|
* Return a class description from a given class name
|
|
33
41
|
*
|
|
@@ -42,10 +42,15 @@ type SharedStateParameterDescription = {
|
|
|
42
42
|
* When `true`, `nullable` is automatically set to `true` and `default` to `null`.
|
|
43
43
|
*/
|
|
44
44
|
event?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* - When set to true, the parameter must be
|
|
47
|
+
* provided at the creation of the shared state.
|
|
48
|
+
*/
|
|
49
|
+
required?: boolean;
|
|
45
50
|
/**
|
|
46
51
|
* - When set to `false`, an update will
|
|
47
52
|
* trigger the propagation of a parameter even when its value didn't change.
|
|
48
|
-
* This
|
|
53
|
+
* This modifier provides a sort of middle ground between the default behavior
|
|
49
54
|
* (e.g. where only changed values are propagated) and the behavior of the `event`
|
|
50
55
|
* option (which has no state per se). Hence, setting this options to `false` if
|
|
51
56
|
* `event=true` makes no sens.
|
|
@@ -54,25 +59,31 @@ type SharedStateParameterDescription = {
|
|
|
54
59
|
/**
|
|
55
60
|
* - When set to `true`, an update will
|
|
56
61
|
* trigger the update listeners immediately on the node that generated the update,
|
|
57
|
-
* before propagating the change on the network.
|
|
58
|
-
*
|
|
59
|
-
* latency on the client.
|
|
62
|
+
* before propagating the change on the network. This modifier is useful in cases
|
|
63
|
+
* the network would introduce a noticeable latency on the client.
|
|
60
64
|
* If for some reason the value is overridden server-side (e.g. in an `updateHook`)
|
|
61
65
|
* the listeners will be called again when the "real" value is received.
|
|
66
|
+
* Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
|
|
67
|
+
* according to the `set` call.
|
|
62
68
|
*/
|
|
63
69
|
immediate?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* - When set to true, the parameter must be
|
|
66
|
-
* provided in the initialization values when the state is created.
|
|
67
|
-
*/
|
|
68
|
-
required?: boolean;
|
|
69
70
|
/**
|
|
70
71
|
* - When set to true, the parameter is never
|
|
71
72
|
* propagated on the network (hence it is no longer a shared parameter :). This
|
|
72
73
|
* is useful to declare some common parameter (e.g. some interface state) that
|
|
73
74
|
* don't need to be shared but to stay in the shared state API paradigm.
|
|
75
|
+
* Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
|
|
76
|
+
* according to the `set` call.
|
|
74
77
|
*/
|
|
75
78
|
local?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* - When set to false, the acknowledgement
|
|
81
|
+
* is never sent back to the shared state that initiated an update. This can be
|
|
82
|
+
* useful to e.g. stream values to the network, but can lead to invalid state.
|
|
83
|
+
* Setting this modifier to `true` will trigger the `onUpdate` callback synchronously
|
|
84
|
+
* according to the `set` call.
|
|
85
|
+
*/
|
|
86
|
+
acknowledge?: boolean;
|
|
76
87
|
/**
|
|
77
88
|
* - Minimum value of the parameter. Only applies
|
|
78
89
|
* for `integer` and `float` types.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "5.
|
|
1
|
+
declare const _default: "5.4.0";
|
|
2
2
|
export default _default;
|