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