@soundworks/core 4.0.0-alpha.9 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +14 -29
- package/package.json +24 -42
- package/src/client/Client.js +306 -256
- package/src/client/{Context.js → ClientContext.js} +63 -54
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
- package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
- package/src/client/ClientSocket.js +270 -0
- package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
- package/src/client/index.js +36 -39
- package/src/common/BasePlugin.js +63 -58
- package/src/common/BasePluginManager.js +191 -142
- package/src/common/BaseStateManager.js +635 -145
- package/src/common/BatchedTransport.js +73 -0
- package/src/common/ParameterBag.js +90 -93
- package/src/common/PromiseStore.js +54 -0
- package/src/common/SharedState.js +821 -0
- package/src/common/SharedStateCollection.js +492 -0
- package/src/common/constants.js +14 -2
- package/src/common/logger.js +64 -68
- package/src/common/shared-state-types.js +82 -0
- package/src/common/sockets-utils.js +0 -64
- package/src/common/version.js +2 -0
- package/src/server/Server.js +552 -824
- package/src/server/ServerClient.js +74 -0
- package/src/server/{Context.js → ServerContext.js} +96 -77
- package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
- package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
- package/src/server/ServerPluginManager.js +177 -0
- package/src/server/ServerSocket.js +256 -0
- package/src/server/ServerSockets.js +150 -0
- package/src/server/ServerStateManager.js +706 -0
- package/src/server/SharedStatePrivate.js +277 -0
- package/src/server/audit-network-latency.worker.js +19 -4
- package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
- package/src/server/create-http-server.js +127 -0
- package/src/server/crypto.js +8 -7
- package/src/server/index.js +60 -17
- package/types/client/Client.d.ts +95 -70
- package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/ClientPlugin.d.ts +51 -0
- package/types/client/ClientPluginManager.d.ts +112 -0
- package/types/client/ClientSocket.d.ts +76 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
- package/types/client/index.d.ts +33 -6
- package/types/common/BasePlugin.d.ts +64 -53
- package/types/common/BasePluginManager.d.ts +84 -39
- package/types/common/BaseStateManager.d.ts +227 -49
- package/types/common/BatchedTransport.d.ts +17 -0
- package/types/common/ParameterBag.d.ts +73 -76
- package/types/common/PromiseStore.d.ts +11 -0
- package/types/common/SharedState.d.ts +291 -0
- package/types/common/SharedStateCollection.d.ts +311 -0
- package/types/common/constants.d.ts +10 -3
- package/types/common/logger.d.ts +8 -6
- package/types/common/shared-state-types.d.ts +95 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +2 -0
- package/types/server/Server.d.ts +215 -136
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/ServerContext.d.ts +209 -0
- package/types/server/ServerContextManager.d.ts +75 -0
- package/types/server/ServerPlugin.d.ts +57 -0
- package/types/server/ServerPluginManager.d.ts +115 -0
- package/types/server/ServerSocket.d.ts +69 -0
- package/types/server/ServerSockets.d.ts +42 -0
- package/types/server/ServerStateManager.d.ts +284 -0
- package/types/server/SharedStatePrivate.d.ts +23 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -2
- package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
- package/types/server/create-http-server.d.ts +5 -0
- package/types/server/crypto.d.ts +4 -0
- package/types/server/index.d.ts +51 -3
- package/.eslintrc +0 -15
- package/.github/workflows/gh-pages.yml +0 -31
- package/.github/workflows/unit-tests.yml +0 -18
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -35
- package/src/client/Socket.js +0 -402
- package/src/common/BaseSharedState.js +0 -583
- package/src/common/BaseSharedStateCollection.js +0 -179
- package/src/common/SharedStatePrivate.js +0 -192
- package/src/common/promise-store.js +0 -25
- package/src/server/Client.js +0 -55
- package/src/server/PluginManager.js +0 -170
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -36
- package/src/server/Socket.js +0 -396
- package/src/server/Sockets.js +0 -224
- package/src/server/StateManager.js +0 -620
- package/tsconfig.json +0 -21
- package/types/client/Client.d.ts.map +0 -1
- package/types/client/Context.d.ts.map +0 -1
- package/types/client/ContextManager.d.ts.map +0 -1
- package/types/client/Plugin.d.ts +0 -53
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts +0 -96
- package/types/client/PluginManager.d.ts.map +0 -1
- package/types/client/SharedState.d.ts +0 -85
- package/types/client/SharedState.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -138
- package/types/client/Socket.d.ts.map +0 -1
- package/types/client/StateManager.d.ts.map +0 -1
- package/types/client/index.d.ts.map +0 -1
- package/types/common/BasePlugin.d.ts.map +0 -1
- package/types/common/BasePluginManager.d.ts.map +0 -1
- package/types/common/BaseSharedState.d.ts +0 -173
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -20
- package/types/common/SharedStatePrivate.d.ts.map +0 -1
- package/types/common/constants.d.ts.map +0 -1
- package/types/common/logger.d.ts.map +0 -1
- package/types/common/promise-store.d.ts +0 -4
- package/types/common/promise-store.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/utils.d.ts +0 -5
- package/types/common/utils.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -40
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts +0 -59
- package/types/server/Context.d.ts.map +0 -1
- package/types/server/ContextManager.d.ts +0 -69
- package/types/server/ContextManager.d.ts.map +0 -1
- package/types/server/Plugin.d.ts +0 -70
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts +0 -91
- package/types/server/PluginManager.d.ts.map +0 -1
- package/types/server/Server.d.ts.map +0 -1
- package/types/server/SharedState.d.ts +0 -86
- package/types/server/SharedState.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -128
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -73
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -190
- package/types/server/StateManager.d.ts.map +0 -1
- package/types/server/audit-network-latency.worker.d.ts.map +0 -1
- package/types/server/audit-schema.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @private
|
|
3
|
-
*/
|
|
4
|
-
class BaseSharedStateCollection {
|
|
5
|
-
constructor(stateManager, schemaName) {
|
|
6
|
-
this._stateManager = stateManager;
|
|
7
|
-
this._schemaName = schemaName;
|
|
8
|
-
this._states = [];
|
|
9
|
-
this._onUpdateCallbacks = new Set();
|
|
10
|
-
this._onAttachCallbacks = new Set();
|
|
11
|
-
this._onDetachCallbacks = new Set();
|
|
12
|
-
this._unobserve = null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async _init() {
|
|
16
|
-
this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId, nodeId) => {
|
|
17
|
-
const state = await this._stateManager.attach(schemaName, stateId);
|
|
18
|
-
|
|
19
|
-
this._states.push(state);
|
|
20
|
-
|
|
21
|
-
state.onDetach(() => {
|
|
22
|
-
const index = this._states.indexOf(state);
|
|
23
|
-
this._states.splice(index, 1);
|
|
24
|
-
|
|
25
|
-
this._onDetachCallbacks.forEach(callback => callback(state));
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
state.onUpdate((newValues, oldValues, context) => {
|
|
29
|
-
Array.from(this._onUpdateCallbacks).forEach(callback => {
|
|
30
|
-
callback(state, newValues, oldValues, context);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
this._onAttachCallbacks.forEach(callback => callback(state));
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Size of the collection
|
|
40
|
-
* @type {number}
|
|
41
|
-
*/
|
|
42
|
-
get length() {
|
|
43
|
-
return this._states.length;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Detach from the collection, i.e. detach all underlying shared states.
|
|
48
|
-
* @type {number}
|
|
49
|
-
*/
|
|
50
|
-
async detach() {
|
|
51
|
-
this._unobserve();
|
|
52
|
-
|
|
53
|
-
const promises = Array.from(this._states).map(state => state.detach());
|
|
54
|
-
await Promise.all(promises);
|
|
55
|
-
|
|
56
|
-
this._onUpdateCallbacks.clear();
|
|
57
|
-
this._onDetachCallbacks.clear();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Return the current values of all the states in the collection.
|
|
62
|
-
* @return {Object[]}
|
|
63
|
-
*/
|
|
64
|
-
getValues() {
|
|
65
|
-
return this._states.map(state => state.getValues());
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Return the current param value of all the states in the collection.
|
|
70
|
-
*
|
|
71
|
-
* @param {String} name - Name of the parameter
|
|
72
|
-
* @return {any[]}
|
|
73
|
-
*/
|
|
74
|
-
get(name) {
|
|
75
|
-
return this._states.map(state => state.get(name));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Subscribe to any state update of the collection.
|
|
80
|
-
*
|
|
81
|
-
* @param {server.SharedStateCollection~onUpdateCallback|client.SharedStateCollection~onUpdateCallback}
|
|
82
|
-
* callback - Callback to execute when an update is applied on a state.
|
|
83
|
-
* @param {Boolean} [executeListener=false] - Execute the callback immediately
|
|
84
|
-
* for all underlying states with current state values. (`oldValues` will be
|
|
85
|
-
* set to `{}`, and `context` to `null`)
|
|
86
|
-
* @returns {Function} - Function that delete the registered listener.
|
|
87
|
-
*/
|
|
88
|
-
onUpdate(callback, executeListener = false) {
|
|
89
|
-
this._onUpdateCallbacks.add(callback);
|
|
90
|
-
|
|
91
|
-
if (executeListener === true) {
|
|
92
|
-
this._states.forEach(state => {
|
|
93
|
-
const currentValues = state.getValues();
|
|
94
|
-
const oldValues = {};
|
|
95
|
-
const context = null;
|
|
96
|
-
|
|
97
|
-
callback(state, currentValues, oldValues, context);
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return () => this._onUpdateCallbacks.delete(callback);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Register a function to execute when a state is added to the collection.
|
|
106
|
-
*
|
|
107
|
-
* @param {Function} callback - callback to execute when a state is added to
|
|
108
|
-
* the collection.
|
|
109
|
-
*/
|
|
110
|
-
onAttach(callback) {
|
|
111
|
-
this._onAttachCallbacks.add(callback);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Register a function to execute when a state is removed from the collection.
|
|
116
|
-
*
|
|
117
|
-
* @param {Function} callback - callback to execute when a state is removed
|
|
118
|
-
* from the collection.
|
|
119
|
-
*/
|
|
120
|
-
onDetach(callback) {
|
|
121
|
-
this._onDetachCallbacks.add(callback);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Execute the given function once for each states of the collection (see `Array.forEach`).
|
|
126
|
-
*
|
|
127
|
-
* @param {Function} func - A function to execute for each element in the array.
|
|
128
|
-
* Its return value is discarded.
|
|
129
|
-
*/
|
|
130
|
-
forEach(func) {
|
|
131
|
-
this._states.forEach(func);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Creates a new array populated with the results of calling a provided function
|
|
136
|
-
* on every state of the collection (see `Array.map`).
|
|
137
|
-
*
|
|
138
|
-
* @param {Function} func - A function to execute for each element in the array.
|
|
139
|
-
* Its return value is added as a single element in the new array.
|
|
140
|
-
*/
|
|
141
|
-
map(func) {
|
|
142
|
-
return this._states.map(func);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Creates a shallow copy of a portion of the collection, filtered down to just
|
|
147
|
-
* the estates that pass the test implemented by the provided function (see `Array.filter`).
|
|
148
|
-
*
|
|
149
|
-
* @param {Function} func - A function to execute for each element in the array.
|
|
150
|
-
* It should return a truthy to keep the element in the resulting array, and a f
|
|
151
|
-
* alsy value otherwise.
|
|
152
|
-
*/
|
|
153
|
-
filter(func) {
|
|
154
|
-
return this._states.filter(func);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Sort the elements of the collection in place (see `Array.sort`).
|
|
159
|
-
*
|
|
160
|
-
* @param {Function} func - Function that defines the sort order.
|
|
161
|
-
*/
|
|
162
|
-
sort(func) {
|
|
163
|
-
this._states.sort(func);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Returns the first element of the collection that satisfies the provided testing
|
|
168
|
-
* function. If no values satisfy the testing function, undefined is returned.
|
|
169
|
-
*
|
|
170
|
-
* @param {Function} func - Function to execute for each element in the array.
|
|
171
|
-
* It should return a truthy value to indicate a matching element has been found.
|
|
172
|
-
* @return {}
|
|
173
|
-
*/
|
|
174
|
-
find(func) {
|
|
175
|
-
return this._states.find(func);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default BaseSharedStateCollection;
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import ParameterBag from '../common/ParameterBag.js';
|
|
2
|
-
import {
|
|
3
|
-
DELETE_REQUEST,
|
|
4
|
-
DELETE_RESPONSE,
|
|
5
|
-
DELETE_NOTIFICATION,
|
|
6
|
-
DETACH_REQUEST,
|
|
7
|
-
DETACH_RESPONSE,
|
|
8
|
-
UPDATE_REQUEST,
|
|
9
|
-
UPDATE_RESPONSE,
|
|
10
|
-
UPDATE_ABORT,
|
|
11
|
-
UPDATE_NOTIFICATION,
|
|
12
|
-
} from '../common/constants.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The "real" state, this instance is kept private by the server.StateManager.
|
|
16
|
-
* It cannot be accessed without a SharedState proxy
|
|
17
|
-
* @private
|
|
18
|
-
*/
|
|
19
|
-
class SharedStatePrivate {
|
|
20
|
-
constructor(id, schemaName, schema, manager, initValues = {}) {
|
|
21
|
-
this.id = id;
|
|
22
|
-
this.schemaName = schemaName;
|
|
23
|
-
|
|
24
|
-
this._manager = manager;
|
|
25
|
-
this._parameters = new ParameterBag(schema, initValues);
|
|
26
|
-
this._attachedClients = new Map(); // other peers interested in watching / controlling the state
|
|
27
|
-
|
|
28
|
-
this._creatorRemoteId = null;
|
|
29
|
-
this._creatorId = null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
_attachClient(remoteId, client, isOwner = false) {
|
|
33
|
-
this._attachedClients.set(remoteId, client);
|
|
34
|
-
|
|
35
|
-
if (isOwner) {
|
|
36
|
-
this._creatorRemoteId = remoteId;
|
|
37
|
-
this._creatorId = client.id;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// attach client listeners
|
|
41
|
-
client.transport.addListener(`${UPDATE_REQUEST}-${this.id}-${remoteId}`, async (reqId, updates, context) => {
|
|
42
|
-
// apply registered hooks
|
|
43
|
-
const hooks = this._manager._hooksBySchemaName.get(this.schemaName);
|
|
44
|
-
const values = this._parameters.getValues();
|
|
45
|
-
let hookAborted = false;
|
|
46
|
-
|
|
47
|
-
// cf. https://github.com/collective-soundworks/soundworks/issues/45
|
|
48
|
-
for (let hook of hooks.values()) {
|
|
49
|
-
const result = await hook(updates, values, context);
|
|
50
|
-
|
|
51
|
-
if (result === null) { // explicit abort if hook returns null
|
|
52
|
-
hookAborted = true;
|
|
53
|
-
break;
|
|
54
|
-
} else if (result === undefined) { // implicit continue if hook returns undefined
|
|
55
|
-
continue;
|
|
56
|
-
} else { // the hook returned an updates object
|
|
57
|
-
updates = result;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (hookAborted === false) {
|
|
62
|
-
const filteredUpdates = {};
|
|
63
|
-
let hasUpdates = false;
|
|
64
|
-
|
|
65
|
-
for (let name in updates) {
|
|
66
|
-
// from v3.1.0 - the `filteredUpdates` check is made using 'fast-deep-equal'
|
|
67
|
-
// cf. https://github.com/epoberezkin/fast-deep-equal
|
|
68
|
-
// therefore unchanged objects are not considered changed
|
|
69
|
-
// nor propagated anymore.
|
|
70
|
-
// until v3.0.4 - we checked the `schema[name].type === 'any'`, to always consider
|
|
71
|
-
// objects as dirty, because if the state is attached locally, we
|
|
72
|
-
// compare the Object instances instead of their values.
|
|
73
|
-
// @note - this should be made more robust but how?
|
|
74
|
-
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
75
|
-
|
|
76
|
-
// if `filterChange` is set to `false` we don't check if the value
|
|
77
|
-
// has been changed or not, it is always propagated to client states
|
|
78
|
-
const { event, filterChange } = this._parameters.getSchema(name);
|
|
79
|
-
|
|
80
|
-
// if event type reset internal value to null
|
|
81
|
-
if (event) {
|
|
82
|
-
this._parameters.set(name, null);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if ((filterChange && changed) || !filterChange) {
|
|
86
|
-
filteredUpdates[name] = newValue;
|
|
87
|
-
hasUpdates = true;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (hasUpdates) {
|
|
92
|
-
// send response to requester
|
|
93
|
-
// client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
|
|
94
|
-
|
|
95
|
-
// @note: we propagate server-side last, because as the server transport
|
|
96
|
-
// is synchronous it can break ordering if a subscription function makes
|
|
97
|
-
// itself an update in reaction to an update, therefore network messages
|
|
98
|
-
// order would be broken,
|
|
99
|
-
|
|
100
|
-
// we need to handle cases where:
|
|
101
|
-
// client state (client.id: 2) sends a request
|
|
102
|
-
// server attached state (client.id: -1) spot a problem and overrides the value
|
|
103
|
-
// we want the remote client (id: 2) to receive in the right order:
|
|
104
|
-
// * 1. the value it requested,
|
|
105
|
-
// * 2. the value overriden by the server-side attached state (id: -1)
|
|
106
|
-
|
|
107
|
-
// this problem could be solved properly with a reducer system:
|
|
108
|
-
// if (dirty) {
|
|
109
|
-
// -> call (async) reducer
|
|
110
|
-
// -> get values from reducer
|
|
111
|
-
//. -> dispatch to everybody
|
|
112
|
-
// }
|
|
113
|
-
|
|
114
|
-
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
115
|
-
// propagate notification to all other attached clients except server
|
|
116
|
-
if (remoteId !== peerRemoteId && peer.id !== -1) {
|
|
117
|
-
peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (client.id !== -1) {
|
|
122
|
-
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
126
|
-
// propagate notification to server
|
|
127
|
-
if (remoteId !== peerRemoteId && peer.id === -1) {
|
|
128
|
-
peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (client.id === -1) {
|
|
133
|
-
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
// propagate back to the requester that the update has been aborted
|
|
137
|
-
// ignore all other attached clients.
|
|
138
|
-
client.transport.emit(`${UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context);
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
// retrieve values from inner state (also handle immediate approriately)
|
|
142
|
-
const oldValues = {};
|
|
143
|
-
|
|
144
|
-
for (let name in updates) {
|
|
145
|
-
oldValues[name] = this._parameters.get(name);
|
|
146
|
-
}
|
|
147
|
-
// aborted by hook (updates have been overriden to {})
|
|
148
|
-
client.transport.emit(`${UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, oldValues, context);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (isOwner) {
|
|
153
|
-
// delete only if creator
|
|
154
|
-
client.transport.addListener(`${DELETE_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
|
|
155
|
-
this._manager._serverStatesById.delete(this.id);
|
|
156
|
-
// --------------------------------------------------------------------
|
|
157
|
-
// WARNING - MAKE SURE WE DON'T HAVE PROBLEM W/ THAT
|
|
158
|
-
// --------------------------------------------------------------------
|
|
159
|
-
// @todo - propagate server-side last, because if a subscription function sends a
|
|
160
|
-
// message to a client, network messages order are kept coherent
|
|
161
|
-
// this._subscriptions.forEach(func => func(updated));
|
|
162
|
-
for (let [remoteId, attached] of this._attachedClients.entries()) {
|
|
163
|
-
this._detachClient(remoteId, attached);
|
|
164
|
-
|
|
165
|
-
if (remoteId === this._creatorRemoteId) {
|
|
166
|
-
attached.transport.emit(`${DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
167
|
-
} else {
|
|
168
|
-
attached.transport.emit(`${DELETE_NOTIFICATION}-${this.id}-${remoteId}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
} else {
|
|
173
|
-
// detach only if not creator
|
|
174
|
-
client.transport.addListener(`${DETACH_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
|
|
175
|
-
this._detachClient(remoteId, client);
|
|
176
|
-
client.transport.emit(`${DETACH_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
_detachClient(remoteId, client) {
|
|
182
|
-
this._attachedClients.delete(remoteId);
|
|
183
|
-
// delete listeners
|
|
184
|
-
client.transport.removeAllListeners(`${UPDATE_REQUEST}-${this.id}-${remoteId}`);
|
|
185
|
-
client.transport.removeAllListeners(`${DELETE_REQUEST}-${this.id}-${remoteId}`);
|
|
186
|
-
client.transport.removeAllListeners(`${DETACH_REQUEST}-${this.id}-${remoteId}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default SharedStatePrivate;
|
|
191
|
-
|
|
192
|
-
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { idGenerator } from '@ircam/sc-utils';
|
|
2
|
-
|
|
3
|
-
const generateRequestId = idGenerator();
|
|
4
|
-
const requestPromises = new Map();
|
|
5
|
-
|
|
6
|
-
export function storeRequestPromise(resolve, reject) {
|
|
7
|
-
const reqId = generateRequestId.next().value;
|
|
8
|
-
requestPromises.set(reqId, { resolve, reject });
|
|
9
|
-
|
|
10
|
-
return reqId;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function resolveRequest(reqId, data) {
|
|
14
|
-
const { resolve } = requestPromises.get(reqId);
|
|
15
|
-
requestPromises.delete(reqId);
|
|
16
|
-
|
|
17
|
-
resolve(data);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function rejectRequest(reqId, data) {
|
|
21
|
-
const { reject } = requestPromises.get(reqId);
|
|
22
|
-
requestPromises.delete(reqId);
|
|
23
|
-
|
|
24
|
-
reject(data);
|
|
25
|
-
}
|
package/src/server/Client.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { idGenerator } from '@ircam/sc-utils';
|
|
2
|
-
import { v4 as uuid } from 'uuid';
|
|
3
|
-
|
|
4
|
-
const generateId = idGenerator();
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Server-side representation of a `soundworks` client.
|
|
8
|
-
*
|
|
9
|
-
* @memberof server
|
|
10
|
-
* @hideconstructor
|
|
11
|
-
* @see {@link client.Client}
|
|
12
|
-
*/
|
|
13
|
-
class Client {
|
|
14
|
-
/**
|
|
15
|
-
* @param {String} role - Role of the client
|
|
16
|
-
* @param {server.Socket} socket - Socket connection with the client
|
|
17
|
-
*/
|
|
18
|
-
constructor(role, socket) {
|
|
19
|
-
/**
|
|
20
|
-
* Client role, as specified in client side config {@link client.Client}.
|
|
21
|
-
*
|
|
22
|
-
* @type {String}
|
|
23
|
-
*/
|
|
24
|
-
this.role = role;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Session Id (incremented positive number).
|
|
28
|
-
*
|
|
29
|
-
* @type {Number}
|
|
30
|
-
*/
|
|
31
|
-
this.id = generateId.next().value;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Unique session Id (uuidv4).
|
|
35
|
-
*
|
|
36
|
-
* @type {String}
|
|
37
|
-
*/
|
|
38
|
-
this.uuid = uuid();
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Socket connection with the remote client {@link client.Client}.
|
|
42
|
-
*
|
|
43
|
-
* @type {server.Socket}
|
|
44
|
-
*/
|
|
45
|
-
this.socket = socket;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Is set in server._onSocketConnection
|
|
49
|
-
* @private
|
|
50
|
-
*/
|
|
51
|
-
this.token = null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export default Client;
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import BasePluginManager from '../common/BasePluginManager.js';
|
|
2
|
-
import Plugin from './Plugin.js';
|
|
3
|
-
import Server from './Server.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Callback executed when a plugin internal state is updated.
|
|
7
|
-
*
|
|
8
|
-
* @callback server.PluginManager~onStateChangeCallback
|
|
9
|
-
* @param {object<server.Plugin#id, server.Plugin>} fullState - List of all plugins.
|
|
10
|
-
* @param {server.Plugin|null} initiator - Plugin that initiated the update or `null`
|
|
11
|
-
* if the change was initiated by the state manager (i.e. when the initialization
|
|
12
|
-
* of the plugins starts).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Delete the registered {@link server.PluginManager~onStateChangeCallback}.
|
|
17
|
-
*
|
|
18
|
-
* @callback server.PluginManager~deleteOnStateChangeCallback
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The `PluginManager` allows to register and retrieve `soundworks` plugins.
|
|
23
|
-
*
|
|
24
|
-
* Plugins should always be registered both client-side and server-side,
|
|
25
|
-
* and before {@link client.Client#start} or {@link server.Server#start}
|
|
26
|
-
* to be properly initialized.
|
|
27
|
-
*
|
|
28
|
-
* In some sitautions, you might want to register the same plugin factory several times
|
|
29
|
-
* using different ids (e.g. for watching several parts of the file system, etc.).
|
|
30
|
-
*
|
|
31
|
-
* Refer to the plugins' documentation for more precise examples, and the specific API
|
|
32
|
-
* they expose. See [https://soundworks.dev/guide/ecosystem](https://soundworks.dev/guide/ecosystem)
|
|
33
|
-
* for more informations on the available plugins.
|
|
34
|
-
*
|
|
35
|
-
* ```
|
|
36
|
-
* // client-side
|
|
37
|
-
* import { Client } from '@soundworks/core/client.js';
|
|
38
|
-
* import syncPlugin from '@soundworks/plugin-sync/client.js';
|
|
39
|
-
*
|
|
40
|
-
* const client = new Client(config);
|
|
41
|
-
* // register the plugin before `client.start()`
|
|
42
|
-
* client.pluginManager.register('sync', syncPlugin);
|
|
43
|
-
*
|
|
44
|
-
* await client.start();
|
|
45
|
-
*
|
|
46
|
-
* const sync = await client.pluginManager.get('sync');
|
|
47
|
-
*
|
|
48
|
-
* setInterval(() => {
|
|
49
|
-
* // log the estimated global synced clock alongside the local clock.
|
|
50
|
-
* console.log(sync.getSyncTime(), sync.getLocalTime());
|
|
51
|
-
* }, 1000);
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* ```
|
|
55
|
-
* // server-side
|
|
56
|
-
* import { Server } from '@soundworks/core/server.js';
|
|
57
|
-
* import syncPlugin from '@soundworks/plugin-sync/server.js';
|
|
58
|
-
*
|
|
59
|
-
* const server = new Server(config);
|
|
60
|
-
* // register the plugin before `server.start()`
|
|
61
|
-
* server.pluginManager.register('sync', syncPlugin);
|
|
62
|
-
*
|
|
63
|
-
* await server.start();
|
|
64
|
-
*
|
|
65
|
-
* const sync = await server.pluginManager.get('sync');
|
|
66
|
-
*
|
|
67
|
-
* setInterval(() => {
|
|
68
|
-
* // log the estimated global synced clock alongside the local clock.
|
|
69
|
-
* console.log(sync.getSyncTime(), sync.getLocalTime());
|
|
70
|
-
* }, 1000);
|
|
71
|
-
* ```
|
|
72
|
-
*
|
|
73
|
-
* @memberof server
|
|
74
|
-
* @extends BasePluginManager
|
|
75
|
-
* @inheritdoc
|
|
76
|
-
* @hideconstructor
|
|
77
|
-
*/
|
|
78
|
-
class PluginManager extends BasePluginManager {
|
|
79
|
-
constructor(server) {
|
|
80
|
-
if (!(server instanceof Server)) {
|
|
81
|
-
throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(server)" should receive an instance of "soundworks.Server" as argument`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
super(server);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
register(id, factory = null, options = {}, deps = []) {
|
|
88
|
-
const ctor = factory(Plugin);
|
|
89
|
-
|
|
90
|
-
if (!(ctor.prototype instanceof Plugin)) {
|
|
91
|
-
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" second argument should be a factory function returning a class extending the "Plugin" base class`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
super.register(id, ctor, options, deps);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Retrieve an fully started instance of a registered plugin.
|
|
99
|
-
*
|
|
100
|
-
* Be aware that the `get` method resolves only when the plugin is fully 'started',
|
|
101
|
-
* which is what we want 99.99% of the time. As such, and to prevent the application
|
|
102
|
-
* to be stuck in some kind of Promise dead lock, this method will throw if
|
|
103
|
-
* used before `server.init()` (or `server.start()`).
|
|
104
|
-
*
|
|
105
|
-
* To handle the remaining 0.01% cases and access plugin instances during their
|
|
106
|
-
* initialization (e.g. to display initialization screen, etc.), you should rely
|
|
107
|
-
* on the `onStateChange` method.
|
|
108
|
-
*
|
|
109
|
-
* _Note: the async API is designed to enable the dynamic creation of plugins
|
|
110
|
-
* (hopefully without brealing changes) in a future release._
|
|
111
|
-
*
|
|
112
|
-
* @param {server.Plugin#id} id - Id of the plugin as defined when registered.
|
|
113
|
-
* @returns {server.Plugin}
|
|
114
|
-
* @see {@link server.PluginManager#onStateChange}
|
|
115
|
-
*
|
|
116
|
-
* @private
|
|
117
|
-
*/
|
|
118
|
-
async get(id) {
|
|
119
|
-
if (this.status !== 'started') {
|
|
120
|
-
throw new Error(`[soundworks.PluginManager] Cannot get plugin before "server.init()"`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return super.unsafeGet(id);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// server only methods
|
|
127
|
-
|
|
128
|
-
/** private */
|
|
129
|
-
checkRegisteredPlugins(registeredPlugins) {
|
|
130
|
-
let missingPlugins = [];
|
|
131
|
-
|
|
132
|
-
for (let id of registeredPlugins) {
|
|
133
|
-
if (!this._instances.has(id)) {
|
|
134
|
-
missingPlugins.push(id);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (missingPlugins.length > 0) {
|
|
139
|
-
throw new Error(`Invalid plugin list, the following plugins registered client-side: [${missingPlugins.join(', ')}] have not been registered server-side. Registered server-side plugins are: [${Array.from(this._instances.keys()).join(', ')}].`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** @private */
|
|
144
|
-
async addClient(client, registeredPlugins = []) {
|
|
145
|
-
let promises = [];
|
|
146
|
-
|
|
147
|
-
for (let pluginName of registeredPlugins) {
|
|
148
|
-
const plugin = await this.get(pluginName);
|
|
149
|
-
const promise = plugin.addClient(client);
|
|
150
|
-
promises.push(promise);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
await Promise.all(promises);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** @private */
|
|
157
|
-
async removeClient(client) {
|
|
158
|
-
let promises = [];
|
|
159
|
-
|
|
160
|
-
for (let plugin of this._instances.values()) {
|
|
161
|
-
if (plugin.clients.has(client)) {
|
|
162
|
-
promises.push(plugin.removeClient(client));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
await Promise.all(promises);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export default PluginManager;
|