@soundworks/core 5.2.1 → 5.3.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/ParameterBag.js +1 -0
- package/src/common/SharedState.js +55 -37
- 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/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
|
}
|
|
@@ -465,31 +465,31 @@ class SharedState {
|
|
|
465
465
|
|
|
466
466
|
const newValues = {};
|
|
467
467
|
const oldValues = {};
|
|
468
|
-
const
|
|
469
|
-
const
|
|
468
|
+
const locallyResolvedParam = {};
|
|
469
|
+
const networkPropagatedParams = {};
|
|
470
470
|
let hasLocalParam = false;
|
|
471
|
-
let
|
|
472
|
-
let
|
|
473
|
-
let
|
|
471
|
+
let hasRegularSharedParam = false;
|
|
472
|
+
let hasNonAcknowledgedParams = false;
|
|
473
|
+
let syncedOnUpdate = false;
|
|
474
474
|
|
|
475
475
|
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
|
|
476
|
+
// Try to coerce value now, so that eventual errors are triggered early
|
|
477
|
+
// on the node requesting the update and not only on the server side.
|
|
479
478
|
try {
|
|
479
|
+
// This also throws if name does not exists
|
|
480
480
|
this.#parameters.coerceValue(name, updates[name]);
|
|
481
481
|
} catch (err) {
|
|
482
482
|
throw new TypeError(`Cannot execute 'set' on SharedState (${this.#className}): ${err.message}`);
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
//
|
|
485
|
+
// Make sure that given name is in filter white list (if any)
|
|
486
486
|
if (this.#filter !== null) {
|
|
487
487
|
if (!this.#filter.includes(name)) {
|
|
488
488
|
throw new DOMException(`Cannot execute 'set' on SharedState (${this.#className}): Parameter '${name}' is not in white list`, 'NotSupportedError');
|
|
489
489
|
}
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
-
// `immediate`
|
|
492
|
+
// ### `immediate` modifier behavior
|
|
493
493
|
//
|
|
494
494
|
// If immediate=true
|
|
495
495
|
// - call listeners if value changed
|
|
@@ -501,7 +501,7 @@ class SharedState {
|
|
|
501
501
|
// - if the node is initiator of the update (UPDATE_RESPONSE), (re-)check
|
|
502
502
|
// to prevent execute the listeners twice
|
|
503
503
|
|
|
504
|
-
// `local`
|
|
504
|
+
// ### `local` modifier behavior
|
|
505
505
|
//
|
|
506
506
|
// - If the `updates` object only contains local variables, we can call the
|
|
507
507
|
// update listeners and return a fulfilled promise immediately
|
|
@@ -510,9 +510,19 @@ class SharedState {
|
|
|
510
510
|
// response, call update listeners with server response and resolve promise with
|
|
511
511
|
// the full payload, i.e. reintegrating the local params in the resolve payload
|
|
512
512
|
|
|
513
|
-
|
|
513
|
+
// ### `acknowledge` modifier behavior
|
|
514
|
+
//
|
|
515
|
+
// Same as immediate, but no feedback from server
|
|
516
|
+
|
|
517
|
+
const {
|
|
518
|
+
local,
|
|
519
|
+
immediate,
|
|
520
|
+
filterChange,
|
|
521
|
+
event,
|
|
522
|
+
acknowledge,
|
|
523
|
+
} = this.#parameters.getDescription(name);
|
|
514
524
|
|
|
515
|
-
if (immediate || local) {
|
|
525
|
+
if (immediate || local || acknowledge === false) {
|
|
516
526
|
const oldValue = this.#parameters.get(name);
|
|
517
527
|
const [newValue, changed] = this.#parameters.set(name, updates[name]);
|
|
518
528
|
|
|
@@ -520,46 +530,50 @@ class SharedState {
|
|
|
520
530
|
if (changed || filterChange === false || event) {
|
|
521
531
|
oldValues[name] = oldValue;
|
|
522
532
|
newValues[name] = newValue;
|
|
523
|
-
|
|
533
|
+
syncedOnUpdate = true;
|
|
524
534
|
}
|
|
525
535
|
}
|
|
526
536
|
|
|
527
|
-
// define params that must go through network or not
|
|
528
537
|
if (local) {
|
|
538
|
+
// these don't go to the network
|
|
529
539
|
hasLocalParam = true;
|
|
530
|
-
|
|
531
|
-
|
|
540
|
+
locallyResolvedParam[name] = this.#parameters.get(name);
|
|
541
|
+
} else if (acknowledge === false) {
|
|
542
|
+
// these go to the network but don't receive an ack
|
|
543
|
+
// they are both locally forwarded and network propagated then
|
|
544
|
+
hasNonAcknowledgedParams = true;
|
|
545
|
+
locallyResolvedParam[name] = this.#parameters.get(name);
|
|
546
|
+
networkPropagatedParams[name] = updates[name];
|
|
532
547
|
} else {
|
|
533
|
-
// note that immediate are shared
|
|
534
|
-
|
|
535
|
-
|
|
548
|
+
// note that immediate params are shared and acknowledged
|
|
549
|
+
hasRegularSharedParam = true;
|
|
550
|
+
networkPropagatedParams[name] = updates[name];
|
|
536
551
|
}
|
|
537
552
|
}
|
|
538
553
|
|
|
539
|
-
//
|
|
540
|
-
if (
|
|
554
|
+
// params that trigger a synced onUpdate call: immediate, local, non-acknowledged
|
|
555
|
+
if (syncedOnUpdate) {
|
|
541
556
|
this.#onUpdateCallbacks.forEach(listener => listener(newValues, oldValues));
|
|
542
557
|
}
|
|
543
558
|
|
|
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
|
-
}
|
|
559
|
+
// if we only have local params, we can resolve immediately
|
|
560
|
+
if (hasLocalParam && !hasNonAcknowledgedParams && !hasRegularSharedParam) {
|
|
561
|
+
return Promise.resolve(locallyResolvedParam);
|
|
552
562
|
}
|
|
553
563
|
|
|
554
|
-
// override updates to be shared on network without local params
|
|
555
|
-
updates = sharedParams;
|
|
556
|
-
|
|
557
564
|
// go through server-side normal behavior
|
|
558
565
|
const { id: reqId, promise } = this[kSharedStatePromiseStore].createPromise();
|
|
559
|
-
|
|
560
|
-
|
|
566
|
+
// store local params that should be re-merged w/ regular ones
|
|
567
|
+
if (Object.keys(locallyResolvedParam).length > 0) {
|
|
568
|
+
this[kSharedStatePromiseStore].associateResolveData(reqId, locallyResolvedParam);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#instanceId}`, reqId, networkPropagatedParams);
|
|
572
|
+
|
|
573
|
+
// if we don't have regular shared params, we can resolve now
|
|
574
|
+
if (!hasRegularSharedParam) {
|
|
575
|
+
this[kSharedStatePromiseStore].resolve(reqId, {});
|
|
561
576
|
}
|
|
562
|
-
this.#client.transport.emit(`${UPDATE_REQUEST}-${this.#id}-${this.#instanceId}`, reqId, updates);
|
|
563
577
|
|
|
564
578
|
return promise;
|
|
565
579
|
}
|
|
@@ -746,7 +760,7 @@ class SharedState {
|
|
|
746
760
|
|
|
747
761
|
return this.detach();
|
|
748
762
|
} else {
|
|
749
|
-
throw new DOMException(`Cannot execute 'delete' on SharedState (${this.#className}):
|
|
763
|
+
throw new DOMException(`Cannot execute 'delete' on SharedState (${this.#className}): This instance is attached and not owned. Use 'SharedState#detach' instead`, 'NotSupportedError');
|
|
750
764
|
}
|
|
751
765
|
}
|
|
752
766
|
|
|
@@ -814,6 +828,10 @@ class SharedState {
|
|
|
814
828
|
* @param {Function} callback - Callback to execute when the state is deleted.
|
|
815
829
|
*/
|
|
816
830
|
onDelete(callback) {
|
|
831
|
+
if (!this.#isOwner) {
|
|
832
|
+
throw new DOMException(`Cannot execute 'onDelete' on SharedState (${this.#className}): This instance is attached and not owned. Use 'SharedState#onDetach' instead`, 'NotSupportedError');
|
|
833
|
+
}
|
|
834
|
+
|
|
817
835
|
this.#onDeleteCallbacks.add(callback);
|
|
818
836
|
return () => this.#onDeleteCallbacks.delete(callback);
|
|
819
837
|
}
|
|
@@ -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.3.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 {
|
|
@@ -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.3.0";
|
|
2
2
|
export default _default;
|