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