@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.
Files changed (124) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +48 -28
  5. package/src/client/Client.js +391 -0
  6. package/src/client/Context.js +225 -0
  7. package/src/client/ContextManager.js +111 -0
  8. package/src/client/Plugin.js +66 -0
  9. package/src/client/PluginManager.js +123 -0
  10. package/src/client/SharedState.js +84 -0
  11. package/src/client/Socket.js +389 -0
  12. package/src/client/StateManager.js +67 -0
  13. package/src/client/index.js +77 -0
  14. package/src/common/BasePlugin.js +163 -0
  15. package/src/common/BasePluginManager.js +227 -0
  16. package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +74 -112
  19. package/{common → src/common}/SharedStatePrivate.js +56 -62
  20. package/src/common/constants.js +51 -0
  21. package/src/common/logger.js +154 -0
  22. package/src/common/promise-store.js +25 -0
  23. package/src/common/sockets-utils.js +73 -0
  24. package/src/common/utils.js +22 -0
  25. package/src/server/Client.js +50 -0
  26. package/src/server/Context.js +99 -0
  27. package/src/server/ContextManager.js +266 -0
  28. package/src/server/Plugin.js +77 -0
  29. package/src/server/PluginManager.js +150 -0
  30. package/src/server/Server.js +849 -0
  31. package/src/server/SharedState.js +86 -0
  32. package/{server → src/server}/Socket.js +114 -88
  33. package/{server → src/server}/Sockets.js +73 -60
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
  35. package/src/server/audit-network-latency.worker.js +57 -0
  36. package/src/server/audit-schema.js +42 -0
  37. package/src/server/index.js +45 -0
  38. package/tsconfig.json +21 -0
  39. package/types/client/Client.d.ts +194 -0
  40. package/types/client/Client.d.ts.map +1 -0
  41. package/types/client/Context.d.ts +139 -0
  42. package/types/client/Context.d.ts.map +1 -0
  43. package/types/client/ContextManager.d.ts +45 -0
  44. package/types/client/ContextManager.d.ts.map +1 -0
  45. package/types/client/Plugin.d.ts +53 -0
  46. package/types/client/Plugin.d.ts.map +1 -0
  47. package/types/client/PluginManager.d.ts +96 -0
  48. package/types/client/PluginManager.d.ts.map +1 -0
  49. package/types/client/SharedState.d.ts +85 -0
  50. package/types/client/SharedState.d.ts.map +1 -0
  51. package/types/client/Socket.d.ts +138 -0
  52. package/types/client/Socket.d.ts.map +1 -0
  53. package/types/client/StateManager.d.ts +72 -0
  54. package/types/client/StateManager.d.ts.map +1 -0
  55. package/types/client/index.d.ts +11 -0
  56. package/types/client/index.d.ts.map +1 -0
  57. package/types/common/BasePlugin.d.ts +145 -0
  58. package/types/common/BasePlugin.d.ts.map +1 -0
  59. package/types/common/BasePluginManager.d.ts +88 -0
  60. package/types/common/BasePluginManager.d.ts.map +1 -0
  61. package/types/common/BaseSharedState.d.ts +173 -0
  62. package/types/common/BaseSharedState.d.ts.map +1 -0
  63. package/types/common/BaseStateManager.d.ts +70 -0
  64. package/types/common/BaseStateManager.d.ts.map +1 -0
  65. package/types/common/ParameterBag.d.ts +143 -0
  66. package/types/common/ParameterBag.d.ts.map +1 -0
  67. package/types/common/SharedStatePrivate.d.ts +20 -0
  68. package/types/common/SharedStatePrivate.d.ts.map +1 -0
  69. package/types/common/constants.d.ts +35 -0
  70. package/types/common/constants.d.ts.map +1 -0
  71. package/types/common/logger.d.ts +16 -0
  72. package/types/common/logger.d.ts.map +1 -0
  73. package/types/common/promise-store.d.ts +4 -0
  74. package/types/common/promise-store.d.ts.map +1 -0
  75. package/types/common/sockets-utils.d.ts +9 -0
  76. package/types/common/sockets-utils.d.ts.map +1 -0
  77. package/types/common/utils.d.ts +5 -0
  78. package/types/common/utils.d.ts.map +1 -0
  79. package/types/server/Client.d.ts +40 -0
  80. package/types/server/Client.d.ts.map +1 -0
  81. package/types/server/Context.d.ts +59 -0
  82. package/types/server/Context.d.ts.map +1 -0
  83. package/types/server/ContextManager.d.ts +69 -0
  84. package/types/server/ContextManager.d.ts.map +1 -0
  85. package/types/server/Plugin.d.ts +70 -0
  86. package/types/server/Plugin.d.ts.map +1 -0
  87. package/types/server/PluginManager.d.ts +91 -0
  88. package/types/server/PluginManager.d.ts.map +1 -0
  89. package/types/server/Server.d.ts +223 -0
  90. package/types/server/Server.d.ts.map +1 -0
  91. package/types/server/SharedState.d.ts +86 -0
  92. package/types/server/SharedState.d.ts.map +1 -0
  93. package/types/server/Socket.d.ts +128 -0
  94. package/types/server/Socket.d.ts.map +1 -0
  95. package/types/server/Sockets.d.ts +73 -0
  96. package/types/server/Sockets.d.ts.map +1 -0
  97. package/types/server/StateManager.d.ts +190 -0
  98. package/types/server/StateManager.d.ts.map +1 -0
  99. package/types/server/audit-network-latency.worker.d.ts +2 -0
  100. package/types/server/audit-network-latency.worker.d.ts.map +1 -0
  101. package/types/server/audit-schema.d.ts +27 -0
  102. package/types/server/audit-schema.d.ts.map +1 -0
  103. package/types/server/index.d.ts +7 -0
  104. package/types/server/index.d.ts.map +1 -0
  105. package/client/AbstractExperience.js +0 -54
  106. package/client/AbstractPlugin.js +0 -230
  107. package/client/Client.js +0 -193
  108. package/client/PluginManager.js +0 -279
  109. package/client/Socket.js +0 -346
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedStateManagerClient.js +0 -194
  113. package/common/Signal.js +0 -40
  114. package/common/SignalAll.js +0 -44
  115. package/common/logger.js +0 -162
  116. package/common/shared-state-utils.js +0 -89
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -214
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -273
  122. package/server/Server.js +0 -785
  123. package/server/index.js +0 -49
  124. package/server.js +0 -28
@@ -1,50 +1,105 @@
1
- "use strict";
1
+ import EventEmitter from 'node:events';
2
+
3
+ import clonedeep from 'lodash.clonedeep';
4
+
5
+ import BaseStateManager from '../common/BaseStateManager.js';
6
+ import ParameterBag from '../common/ParameterBag.js';
7
+ import SharedStatePrivate from '../common/SharedStatePrivate.js';
8
+ import {
9
+ SERVER_ID,
10
+ CREATE_REQUEST,
11
+ CREATE_RESPONSE,
12
+ CREATE_ERROR,
13
+ DELETE_NOTIFICATION,
14
+ ATTACH_REQUEST,
15
+ ATTACH_RESPONSE,
16
+ ATTACH_ERROR,
17
+ OBSERVE_REQUEST,
18
+ OBSERVE_RESPONSE,
19
+ OBSERVE_NOTIFICATION,
20
+ UNOBSERVE_NOTIFICATION,
21
+ DELETE_SCHEMA,
22
+ PRIVATE_STATES,
23
+ } from '../common/constants.js';
24
+ import { idGenerator } from '../common/utils.js';
25
+
26
+ const generateStateId = idGenerator();
27
+ const generateRemoteId = idGenerator();
2
28
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _events = _interopRequireDefault(require("events"));
9
-
10
- var _ParameterBag = _interopRequireDefault(require("./ParameterBag.js"));
11
-
12
- var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
13
-
14
- var _SharedStatePrivate = _interopRequireDefault(require("./SharedStatePrivate.js"));
15
-
16
- var _SharedStateManagerClient = _interopRequireDefault(require("./SharedStateManagerClient.js"));
17
-
18
- var _sharedStateUtils = require("./shared-state-utils.js");
19
-
20
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
29
+ /**
30
+ * @callback server.StateManager~ObserveCallback
31
+ * @async
32
+ * @param {String} schemaName - name of the schema
33
+ * @param {Number} stateId - id of the state
34
+ * @param {Number} nodeId - id of the node that created the state
35
+ */
21
36
 
22
- const generateStateId = (0, _sharedStateUtils.idGenerator)();
23
- const generateRemoteId = (0, _sharedStateUtils.idGenerator)();
24
37
  /**
25
- * Component dedicated at managing distributed states among the application.
26
- * The `SharedStateManagerServer` extends the `SharedStateManagerClient` with
27
- * additionnal functionnalities to register schemas and handle clients.
38
+ * The `StateManager` allows to create new {@link server.SharedState}s, or attach
39
+ * to {@link server.SharedState}s created by other nodes (clients or server). It
40
+ * can also track all the {@link server.SharedState}s created by other nodes.
28
41
  *
29
- * An instance of `SharedStateManagerServer` is automatically created by the
30
- * `soundworks.Server` at initialization (cf. {@link server.Server#stateManager}).
42
+ * An instance of `StateManager` is automatically created by the `soundworks.Server`
43
+ * at initialization (cf. {@link server.Server#stateManager}).
31
44
  *
32
- * Tutorial: [https://collective-soundworks.github.io/tutorials/state-manager.html](https://collective-soundworks.github.io/tutorials/state-manager.html)
45
+ * Compared to the {@link client.StateManager}, the `server.StateManager` can also
46
+ * create and delete schemas, as well as register update hook that are executed when
47
+ * a state is updated.
33
48
  *
34
- * @memberof server
35
- * @extends client.SharedStateManagerClient
49
+ * See {@link server.Server#stateManager}
50
+ *
51
+ * Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
52
+ *
53
+ * ```
54
+ * // server-side
55
+ * import { Server } from '@soundworks/server/index.js';
36
56
  *
37
- * @see {@link client.SharedStateManagerClient}
38
- * @see {@link server.SharedState}
39
- * @see {@link server.Server#stateManager}
57
+ * const server = new Server(config);
58
+ * // declare and register the schema of a shared state.
59
+ * server.stateManager.registerSchema('some-global-state', {
60
+ * myRandom: {
61
+ * type: 'float',
62
+ * default: 0,
63
+ * }
64
+ * });
65
+ *
66
+ * await server.start();
67
+ *
68
+ * // create a global state server-side
69
+ * const globalState = await server.stateManager.create('some-global-state');
70
+ * // listen and react to the changes made by the clients
71
+ * globalState.onUpdate(updates => console.log(updates));
72
+ * ```
73
+ *
74
+ * ```
75
+ * // client-side
76
+ * import { Client } from '@soundworks/client.index.js';
77
+ *
78
+ * const client = new Client(config);
79
+ * await client.start();
80
+ *
81
+ * // attach to the global state created by the server
82
+ * const globalState = await client.stateManager.attach('some-global-state');
83
+ *
84
+ * // update the value of a `myRandom` parameter every seconds
85
+ * setInterval(() => {
86
+ * globalState.set({ myRandom: Math.random() });
87
+ * }, 1000);
88
+ * ```
89
+ *
90
+ * @memberof server
91
+ * @extends BaseStateManager
92
+ * @inheritdoc
93
+ * @hideconstructor
40
94
  */
41
-
42
- class SharedStateManagerServer extends _SharedStateManagerClient.default {
95
+ class StateManager extends BaseStateManager {
43
96
  constructor() {
44
97
  // acts as a client of itself locally
45
- const localClientId = -1;
46
- const localTransport = new _events.default();
98
+ const localClientId = SERVER_ID;
99
+ const localTransport = new EventEmitter();
100
+
47
101
  super(localClientId, localTransport);
102
+
48
103
  this._clientByNodeId = new Map();
49
104
  this._serverStatesById = new Map();
50
105
  this._schemas = new Map();
@@ -53,67 +108,61 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
53
108
 
54
109
  this.addClient(localClientId, localTransport);
55
110
  }
111
+
56
112
  /**
57
- * Add a client. This is automatically handle by `soundworks` when a client
58
- * connects.
113
+ * Add a client to the manager.
114
+ *
115
+ * This is automatically handled by the {@link server.Server} when a client connects.
59
116
  *
60
117
  * @param {Number} nodeId - Id of the client node, as given in
61
- * {@link client.SharedStateManagerClient}
118
+ * {@link client.StateManager}
62
119
  * @param {Object} transport - Tranpsort mecanism to communicate with the
63
120
  * client. Should implement a basic EventEmitter API.
64
121
  *
65
122
  * @private
66
123
  */
67
-
68
-
69
124
  addClient(nodeId, transport) {
70
- const client = {
71
- id: nodeId,
72
- transport
73
- };
125
+ const client = { id: nodeId, transport };
126
+ this._clientByNodeId.set(nodeId, client);
74
127
 
75
- this._clientByNodeId.set(nodeId, client); // ---------------------------------------------
128
+ // ---------------------------------------------
76
129
  // CREATE
77
130
  // ---------------------------------------------
78
-
79
-
80
- client.transport.addListener(_sharedStateUtils.CREATE_REQUEST, (reqId, schemaName, requireSchema, initValues = {}) => {
131
+ client.transport.addListener(CREATE_REQUEST, (reqId, schemaName, requireSchema, initValues = {}) => {
81
132
  if (this._schemas.has(schemaName)) {
82
133
  try {
83
134
  const schema = this._schemas.get(schemaName);
84
-
85
135
  const stateId = generateStateId.next().value;
86
- const remoteId = generateRemoteId.next().value; // id, schemaName, schema, manager, initValues = {}
87
-
88
- const state = new _SharedStatePrivate.default(stateId, schemaName, schema, this, initValues);
136
+ const remoteId = generateRemoteId.next().value;
137
+ // id, schemaName, schema, manager, initValues = {}
138
+ const state = new SharedStatePrivate(stateId, schemaName, schema, this, initValues);
89
139
 
90
140
  state._attachClient(remoteId, client, true); // attach client to the state
91
-
92
-
93
141
  this._serverStatesById.set(stateId, state);
94
142
 
95
143
  const currentValues = state._parameters.getValues();
96
-
97
144
  const sendedSchema = requireSchema ? schema : null;
98
- client.transport.emit(_sharedStateUtils.CREATE_RESPONSE, reqId, stateId, remoteId, schemaName, sendedSchema, currentValues);
145
+
146
+ client.transport.emit(CREATE_RESPONSE, reqId, stateId, remoteId, schemaName, sendedSchema, currentValues);
99
147
 
100
148
  this._observers.forEach(observer => {
101
- observer.transport.emit(_sharedStateUtils.OBSERVE_NOTIFICATION, [schemaName, stateId, nodeId]);
149
+ observer.transport.emit(OBSERVE_NOTIFICATION, [schemaName, stateId, nodeId]);
102
150
  });
103
- } catch (err) {
104
- client.transport.emit(_sharedStateUtils.CREATE_ERROR, reqId, err.message);
151
+ } catch(err) {
152
+ client.transport.emit(CREATE_ERROR, reqId, err.message);
105
153
  console.error(err.message);
106
154
  }
107
155
  } else {
108
156
  const msg = `Cannot create state "${schemaName}", schema does not exists`;
109
157
  console.error(msg);
110
- client.transport.emit(_sharedStateUtils.CREATE_ERROR, reqId, msg);
158
+ client.transport.emit(CREATE_ERROR, reqId, msg);
111
159
  }
112
- }); // ---------------------------------------------
160
+ });
161
+
162
+ // ---------------------------------------------
113
163
  // ATTACH (when creator, is attached by default)
114
164
  // ---------------------------------------------
115
-
116
- client.transport.addListener(_sharedStateUtils.ATTACH_REQUEST, (reqId, schemaName, stateId = null, requireSchema = true) => {
165
+ client.transport.addListener(ATTACH_REQUEST, (reqId, schemaName, stateId = null, requireSchema = true) => {
117
166
  if (this._schemas.has(schemaName)) {
118
167
  let state = null;
119
168
 
@@ -137,63 +186,68 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
137
186
  // several times to the same state.
138
187
  // i.e. same state -> several remote attach on the same node
139
188
  const remoteId = generateRemoteId.next().value;
140
-
141
189
  const schema = this._schemas.get(schemaName);
142
-
143
190
  const currentValues = state._parameters.getValues();
144
191
 
145
- state._attachClient(remoteId, client, false); // send schema for client-side instanciation
146
-
147
-
192
+ state._attachClient(remoteId, client, false);
193
+ // send schema for client-side instanciation
148
194
  const sendedSchema = requireSchema ? schema : null;
149
- client.transport.emit(_sharedStateUtils.ATTACH_RESPONSE, reqId, state.id, remoteId, schemaName, sendedSchema, currentValues);
195
+ client.transport.emit(ATTACH_RESPONSE, reqId, state.id, remoteId, schemaName, sendedSchema, currentValues);
150
196
  } else {
151
197
  const msg = `Cannot attach, no existing state for schema "${schemaName}" with stateId: "${stateId}"`;
152
- client.transport.emit(_sharedStateUtils.ATTACH_ERROR, reqId, msg);
198
+ client.transport.emit(ATTACH_ERROR, reqId, msg);
153
199
  console.error(msg);
154
200
  }
155
201
  } else {
156
202
  const msg = `Cannot attach, schema "${schemaName}" does not exists`;
157
- client.transport.emit(_sharedStateUtils.ATTACH_ERROR, reqId, msg);
203
+ client.transport.emit(ATTACH_ERROR, reqId, msg);
158
204
  console.error(msg);
159
205
  }
160
- }); // ---------------------------------------------
206
+ });
207
+
208
+ // ---------------------------------------------
161
209
  // OBSERVE PEERS (be notified when a state is created, lazy)
162
210
  // ---------------------------------------------
163
-
164
- client.transport.addListener(_sharedStateUtils.OBSERVE_REQUEST, reqId => {
211
+ client.transport.addListener(OBSERVE_REQUEST, reqId => {
165
212
  const statesInfos = [];
166
213
 
167
214
  this._serverStatesById.forEach(state => {
168
- const {
169
- schemaName,
170
- id,
171
- _creatorId
172
- } = state;
173
- statesInfos.push([schemaName, id, _creatorId]);
215
+ const { schemaName, id, _creatorId } = state;
216
+ // only track application states
217
+ // (e.g. do not propagate infos about audit state)
218
+ if (!PRIVATE_STATES.includes(schemaName)) {
219
+ statesInfos.push([schemaName, id, _creatorId]);
220
+ }
174
221
  });
175
222
 
176
- client.transport.emit(_sharedStateUtils.OBSERVE_RESPONSE, reqId, ...statesInfos);
177
-
223
+ // add client to observers first because if some (sync) server side
224
+ // callback throws, the client would never be added to the list
178
225
  this._observers.add(client);
226
+
227
+ client.transport.emit(OBSERVE_RESPONSE, reqId, ...statesInfos);
228
+ });
229
+
230
+ client.transport.addListener(UNOBSERVE_NOTIFICATION, () => {
231
+ this._observers.delete(client);
179
232
  });
180
233
  }
234
+
181
235
  /**
182
- * Remove a client. This is automatically handle by `soundworks` when a client
183
- * connects.
236
+ * Remove a client from the manager. Clean all created or attached states.
237
+ *
238
+ * This is automatically handled by the {@link server.Server} when a client disconnects.
184
239
  *
185
240
  * @param {Number} nodeId - Id of the client node, as given in
186
- * {@link client.SharedStateManagerClient}
241
+ * {@link client.StateManager}
187
242
  *
188
243
  * @private
189
244
  */
190
-
191
-
192
245
  removeClient(nodeId) {
193
246
  for (let [_id, state] of this._serverStatesById.entries()) {
194
- let deleteState = false; // define if the client is the creator of the state, in which case
195
- // everybody must delete it
247
+ let deleteState = false;
196
248
 
249
+ // define if the client is the creator of the state, in which case
250
+ // everybody must delete it
197
251
  for (let [remoteId, attachedClient] of state._attachedClients.entries()) {
198
252
  if (nodeId === attachedClient.id && remoteId === state._creatorRemoteId) {
199
253
  deleteState = true;
@@ -208,31 +262,30 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
208
262
  if (deleteState) {
209
263
  if (remoteId !== state._creatorRemoteId) {
210
264
  // send notification to other attached nodes
211
- attachedClient.transport.emit(`${_sharedStateUtils.DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
265
+ attachedClient.transport.emit(`${DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
212
266
  }
213
267
 
214
268
  this._serverStatesById.delete(state.id);
215
269
  }
216
270
  }
217
- } // if is an observer, delete it
218
-
271
+ }
219
272
 
273
+ // if is an observer, delete it
220
274
  const client = this._clientByNodeId.get(nodeId);
221
-
222
275
  this._observers.delete(client);
223
-
224
276
  this._clientByNodeId.delete(nodeId);
225
277
  }
278
+
226
279
  /**
227
280
  * Register a schema from which shared states (cf. {@link common.SharedState})
228
281
  * can be instanciated.
229
282
  *
230
283
  * @param {String} schemaName - Name of the schema.
231
- * @param {server.SharedStateManagerServer~schema} schema - Data structure
284
+ * @param {server.StateManager~schema} schema - Data structure
232
285
  * describing the states that will be created from this schema.
233
286
  *
234
- * @see {@link server.SharedStateManagerServer#create}
235
- * @see {@link client.SharedStateManagerClient#create}
287
+ * @see {@link server.StateManager#create}
288
+ * @see {@link client.StateManager#create}
236
289
  *
237
290
  * @example
238
291
  * server.stateManager.registerSchema('my-schema', {
@@ -248,20 +301,18 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
248
301
  * }
249
302
  * })
250
303
  */
251
-
252
-
253
304
  registerSchema(schemaName, schema) {
254
305
  if (this._schemas.has(schemaName)) {
255
306
  throw new Error(`[stateManager.registerSchema] cannot register schema with name: "${schemaName}", schema name already exists`);
256
307
  }
257
308
 
258
- _ParameterBag.default.validateSchema(schema);
259
-
260
- this._schemas.set(schemaName, (0, _lodash.default)(schema)); // create hooks list
261
-
309
+ ParameterBag.validateSchema(schema);
262
310
 
311
+ this._schemas.set(schemaName, clonedeep(schema));
312
+ // create hooks list
263
313
  this._hooksBySchemaName.set(schemaName, new Set());
264
314
  }
315
+
265
316
  /**
266
317
  * Delete a schema and all associated states.
267
318
  * When a schema is deleted, all states created from this schema are deleted
@@ -270,40 +321,39 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
270
321
  *
271
322
  * @param {String} schemaName - Name of the schema.
272
323
  */
273
-
274
-
275
324
  deleteSchema(schemaName) {
276
325
  // @note: deleting schema
277
326
  for (let [_id, state] of this._serverStatesById.entries()) {
278
327
  if (state.schemaName === schemaName) {
279
328
  for (let [remoteId, attached] of state._attachedClients.entries()) {
280
329
  state._detachClient(remoteId, attached);
281
-
282
- attached.transport.emit(`${_sharedStateUtils.DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
330
+ attached.transport.emit(`${DELETE_NOTIFICATION}-${state.id}-${remoteId}`);
283
331
  }
284
332
 
285
333
  this._serverStatesById.delete(this.id);
286
334
  }
287
- } // clear schema cache of all connected clients
288
-
335
+ }
289
336
 
337
+ // clear schema cache of all connected clients
290
338
  for (let client of this._clientByNodeId.values()) {
291
- client.transport.emit(`${_sharedStateUtils.DELETE_SCHEMA}`, schemaName);
339
+ client.transport.emit(`${DELETE_SCHEMA}`, schemaName);
292
340
  }
293
341
 
294
- this._schemas.delete(schemaName); // delete registered hooks
295
-
296
-
342
+ this._schemas.delete(schemaName);
343
+ // delete registered hooks
297
344
  this._hooksBySchemaName.delete(schemaName);
298
345
  }
346
+
299
347
  /**
300
- * @callback server.SharedStateManagerServer~updateHook
348
+ * @callback server.StateManager~updateHook
349
+ * @async
301
350
  *
302
351
  * @param {Object} updates - Update object as given on a set callback, or
303
352
  * result of the previous hook
304
353
  * @param {Object} currentValues - Current values of the state.
305
354
  * @param {Object} [context=null] - Optionnal context passed by the creator
306
355
  * of the update.
356
+ *
307
357
  * @return {Object} The "real" updates to be applied on the state.
308
358
  */
309
359
 
@@ -317,11 +367,20 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
317
367
  * The hook is associated to every state of its kind (i.e. schemaName) and
318
368
  * executed on every update (call of `set`). Note that the hooks are executed
319
369
  * server-side regarless the node on which `set` has been called and before
320
- * the "actual" update of the state (e.g. before the call of `subscribe`).
370
+ * the "actual" update of the state (e.g. before the call of `onUpdate`).
371
+ *
372
+ * @param {String} schemaName - Kind of states on which applying the hook.
373
+ * @param {server.StateManager~updateHook} updateHook - Function
374
+ * called between the `set` call and the actual update.
375
+ *
376
+ * @returns {Fuction} deleteHook - Handler that deletes the hook when executed.
321
377
  *
322
378
  * @example
323
- * server.stateManager.registerSchema('hooked', schema);
324
- * server.stateManager.registerUpdateHook('hooked', (updates, currentValues) => {
379
+ * server.stateManager.registerSchema('hooked', {
380
+ * name: { type: 'string', default: null, nullable: true },
381
+ * name: { numUpdates: 'integer', default: 0 },
382
+ * });
383
+ * server.stateManager.registerUpdateHook('hooked', updates => {
325
384
  * return {
326
385
  * ...updates
327
386
  * numUpdates: currentValues.numUpdates + 1,
@@ -331,15 +390,9 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
331
390
  * const state = await server.stateManager.create('hooked');
332
391
  *
333
392
  * await state.set({ name: 'test' });
334
- * state.getValues();
335
- * // > { name: 'test', numUpdates: 1 };
336
- *
337
- * @param {String} schemaName - Kind of states on which applying the hook.
338
- * @param {server.SharedStateManagerServer~updateHook} updateHook - Function
339
- * called between the `set` call and the actual update.
393
+ * const values = state.getValues();
394
+ * assert.deepEqual(result, { name: 'test', numUpdates: 1 });
340
395
  */
341
-
342
-
343
396
  registerUpdateHook(schemaName, updateHook) {
344
397
  // throw error if schemaName has not been registered
345
398
  if (!this._schemas.has(schemaName)) {
@@ -347,12 +400,11 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
347
400
  }
348
401
 
349
402
  const hooks = this._hooksBySchemaName.get(schemaName);
350
-
351
403
  hooks.add(updateHook);
404
+
352
405
  return () => hooks.delete(updateHook);
353
406
  }
354
407
 
355
408
  }
356
409
 
357
- var _default = SharedStateManagerServer;
358
- exports.default = _default;
410
+ export default StateManager;
@@ -0,0 +1,57 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+
3
+ import { getTime } from '@ircam/sc-gettime';
4
+
5
+ let stack = [];
6
+ let averageLatencyWindow = 5;
7
+ let averageLatencyPeriod = 2;
8
+
9
+ let intervalId = null;
10
+ let meanLatency = 0;
11
+
12
+ parentPort.on('message', msg => {
13
+ switch (msg.type) {
14
+ case 'config': {
15
+ averageLatencyWindow = msg.value.averageLatencyWindow;
16
+ averageLatencyPeriod = msg.value.averageLatencyPeriod;
17
+
18
+ // launch compute in its own loop so that the number of computation is
19
+ // decoupled from the number of connected clients
20
+ clearInterval(intervalId);
21
+ intervalId = setInterval(computeAverageLatency, averageLatencyPeriod * 1000);
22
+ break;
23
+ }
24
+ case 'add-measurement': {
25
+ // compute latency considering ping and pong time are equal
26
+ const entry = msg.value;
27
+ entry.latency = (entry.pong - entry.ping) / 2;
28
+ // add new measure to the stack
29
+ stack.push(entry);
30
+ }
31
+ }
32
+ });
33
+
34
+ function computeAverageLatency() {
35
+ const now = getTime();
36
+ // remove old measurements from stack
37
+ stack = stack.filter(entry => entry.pong >= now - averageLatencyWindow);
38
+
39
+ // prevent dividing by zero, just return last computed value
40
+ if (stack.length === 0) {
41
+ parentPort.postMessage({
42
+ type: 'computed-average-latency',
43
+ value: meanLatency,
44
+ });
45
+ } else {
46
+ // compute mean
47
+ let sum = 0;
48
+ stack.forEach(entry => sum += entry.latency);
49
+
50
+ meanLatency = sum / stack.length;
51
+ // send result to main thread
52
+ parentPort.postMessage({
53
+ type: 'computed-average-latency',
54
+ value: meanLatency,
55
+ });
56
+ }
57
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Internal schema used to audit the application.
3
+ */
4
+ export default {
5
+ /**
6
+ * Number of connected clients by role.
7
+ *
8
+ * @example
9
+ * {
10
+ * player: 12,
11
+ * controller: 1,
12
+ * }
13
+ */
14
+ numClients: {
15
+ type: 'any',
16
+ default: {},
17
+ },
18
+
19
+ /**
20
+ * Average latency in seconds computed from ping/pong.
21
+ */
22
+ averageNetworkLatency: {
23
+ type: 'float',
24
+ default: 0,
25
+ },
26
+
27
+ /**
28
+ * Time window in second used to compute the average latency. Defaults to 5
29
+ */
30
+ averageNetworkLatencyWindow: {
31
+ type: 'float',
32
+ default: 5,
33
+ },
34
+
35
+ /**
36
+ * Period in second at which the average latency is computed. Defaults to 2
37
+ */
38
+ averageNetworkLatencyPeriod: {
39
+ type: 'float',
40
+ default: 2,
41
+ },
42
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Configuration object for the server.
3
+ *
4
+ * @typedef ServerConfig
5
+ * @memberof server
6
+ * @type {object}
7
+ * @property {object} [app] - Application configration object.
8
+ * @property {object} app.clients - Definition of the application clients.
9
+ * @property {string} [app.name=''] - Name of the application.
10
+ * @property {string} [app.author=''] - Name of the author.
11
+ * @property {object} [env] - Environment configration object.
12
+ * @property {boolean} env.port - Port on which the server is listening.
13
+ * @property {boolean} env.useHttps - Define is the server run in http or in https.
14
+ * @property {boolean} [env.httpsInfos={}] - Path to cert files for https.
15
+ * @property {boolean} env.serverAddress - Domain name or IP of the server.
16
+ * Mandatory if node clients are defined
17
+ * @property {string} [env.websockets={}] - Configuration options for websockets.
18
+ * @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
19
+ */
20
+
21
+ /**
22
+ * Server-side part of the *soundworks* framework.
23
+ *
24
+ * ```
25
+ * import '@soundworks/helpers/polyfills.js';
26
+ * import { Server } from '@soundworks/core/server.js';
27
+ * import { loadConfig } from '../utils/load-config.js';
28
+ *
29
+ * // - General documentation: https://soundworks.dev/
30
+ * // - API documentation: https://soundworks.dev/api
31
+ * // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
32
+ * // - Wizard & Tools: `npx soundworks`
33
+ *
34
+ * const config = loadConfig(process.env.ENV, import.meta.url);
35
+ *
36
+ * const server = new Server(config);
37
+ * server.setDefaultTemplateConfig();
38
+ *
39
+ * await server.start();
40
+ * ```
41
+ *
42
+ * @namespace server
43
+ */
44
+ export { default as Server } from './Server.js';
45
+ export { default as Context } from './Context.js';
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "include": ["src/**/*.js"],
3
+
4
+ "compilerOptions": {
5
+ // Tells TypeScript to read JS files, as
6
+ // normally they are ignored as source files
7
+ "allowJs": true,
8
+ // Generate d.ts files
9
+ "declaration": true,
10
+ // This compiler run should
11
+ // only output d.ts files
12
+ "emitDeclarationOnly": true,
13
+ // Types should go into this directory.
14
+ // Removing this would place the .d.ts files
15
+ // next to the .js files
16
+ "outDir": "types",
17
+ // go to js file when using IDE functions like
18
+ // "Go to Definition" in VSCode
19
+ "declarationMap": true
20
+ }
21
+ }