@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
@@ -0,0 +1,389 @@
1
+ import WebSocket from 'isomorphic-ws';
2
+
3
+ import {
4
+ packBinaryMessage,
5
+ unpackBinaryMessage,
6
+ packStringMessage,
7
+ unpackStringMessage,
8
+ } from '../common/sockets-utils.js';
9
+ import logger from '../common/logger.js';
10
+ import { isBrowser } from '../common/utils.js';
11
+
12
+ // WebSocket events:
13
+ //
14
+ // `close`:
15
+ // Fired when a connection with a websocket is closed.
16
+ // Also available via the onclose property
17
+ // `error`:
18
+ // Fired when a connection with a websocket has been closed because of an error,
19
+ // such as whensome data couldn't be sent.
20
+ // Also available via the onerror property.
21
+ // `message`:
22
+ // Fired when data is received through a websocket.
23
+ // Also available via the onmessage property.
24
+ // `open`:
25
+ // Fired when a connection with a websocket is opened.
26
+ // Also available via the onopen property.
27
+
28
+ /**
29
+ * The Socket class is a simple publish / subscribe wrapper built on top of the
30
+ * [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws) library.
31
+ * An instance of `Socket` is automatically created by the `soundworks.Client`.
32
+ *
33
+ * _Important: In most cases, you should consider using a {@link client.SharedState} rather than
34
+ * sending messages directly through the sockets._
35
+ *
36
+ * The Socket class concurrently opens two different WebSockets:
37
+ * - a socket configured with `binaryType = 'string'` for JSON compatible string
38
+ * messages.
39
+ * - a socket configured with `binaryType=arraybuffer` for efficient streaming
40
+ * of binary data.
41
+ *
42
+ * Both sockets re-emits all "native" ws events ('open', 'upgrade', 'close', 'error'
43
+ * and 'message'.
44
+ *
45
+ * See {@link client.Client#socket}
46
+ *
47
+ * @memberof client
48
+ * @hideconstructor
49
+ */
50
+ class Socket {
51
+ constructor() {
52
+ /**
53
+ * WebSocket instance w/ string protocol, i.e. `binaryType = 'string'`.
54
+ *
55
+ * @private
56
+ */
57
+ this.ws = null;
58
+ /**
59
+ * WebSocket instance w/ binary protocol, i.e. `binaryType = 'arraybuffer'`.
60
+ *
61
+ * @private
62
+ */
63
+ this.binaryWs = null;
64
+ /** @private */
65
+ this._stringListeners = new Map();
66
+ /** @private */
67
+ this._binaryListeners = new Map();
68
+ }
69
+
70
+ /**
71
+ * Initialize a websocket connection with the server. Automatically called
72
+ * during `client.init()`
73
+ *
74
+ * @param {string} role - Role of the client (see {@link client.Client#role})
75
+ * @param {object} config - Configuration of the sockets
76
+ * @protected
77
+ * @ignore
78
+ */
79
+ async init(role, config) {
80
+ // unique key that allows to associate the two sockets to the same client.
81
+ // note: the key is only used to pair to two sockets, so its usage is very
82
+ // limited in time therefore a random number should hopefully be sufficient.
83
+ const key = (Math.random() + '').replace(/^0\./, '');
84
+
85
+ // open web sockets
86
+ let { path } = config.env.websockets;
87
+
88
+ // @note: keep this check
89
+ // backward compatibility w/ assetsDomain and websocket old config way
90
+ // cf. https://github.com/collective-soundworks/soundworks/issues/35
91
+ if (config.env.subpath) {
92
+ path = `${config.env.subpath}/${path}`;
93
+ }
94
+
95
+ let url;
96
+ let webSocketOptions;
97
+
98
+ if (isBrowser()) {
99
+ const protocol = window.location.protocol.replace(/^http?/, 'ws');
100
+ const { hostname, port } = window.location;
101
+
102
+ url = `${protocol}//${hostname}:${port}/${path}`;
103
+ webSocketOptions = [];
104
+ } else {
105
+ const protocol = config.env.useHttps ? 'wss:' : 'ws:';
106
+ const { serverAddress, port } = config.env;
107
+
108
+ url = `${protocol}//${serverAddress}:${port}/${path}`;
109
+ webSocketOptions = {
110
+ rejectUnauthorized: false,
111
+ };
112
+ }
113
+
114
+ const queryParams = `role=${role}&key=${key}`;
115
+
116
+ // ----------------------------------------------------------
117
+ // init string socket
118
+ // ----------------------------------------------------------
119
+ const stringSocketUrl = `${url}?binary=0&${queryParams}`;
120
+
121
+ await new Promise(resolve => {
122
+ let connectionRefusedLogged = false;
123
+
124
+ const trySocket = async () => {
125
+ const ws = new WebSocket(stringSocketUrl, webSocketOptions);
126
+
127
+ ws.addEventListener('open', connectEvent => {
128
+ // parse incoming messages for pubsub
129
+ this.ws = ws;
130
+ this.ws.addEventListener('message', e => {
131
+ const [channel, args] = unpackStringMessage(e.data);
132
+ this._emit(false, channel, ...args);
133
+ });
134
+
135
+ // broadcast all `WebSocket` native events
136
+ ['close', 'error', 'upgrade', 'message'].forEach(eventName => {
137
+ this.ws.addEventListener(eventName, (e) => {
138
+ this._emit(false, eventName, e);
139
+ });
140
+ });
141
+
142
+ // forward open event
143
+ this._emit(false, 'open', connectEvent);
144
+ // continue with raw socket
145
+ resolve();
146
+ });
147
+
148
+ // cf. https://github.com/collective-soundworks/soundworks/issues/17
149
+ ws.addEventListener('error', e => {
150
+ if (e.type === 'error' && e.error.code === 'ECONNREFUSED') {
151
+ if (!connectionRefusedLogged) {
152
+ logger.log('[soundworks.Socket] Connection refused, waiting for the server to start');
153
+ connectionRefusedLogged = true;
154
+ }
155
+
156
+ if (ws.terminate) {
157
+ ws.terminate();
158
+ } else {
159
+ ws.close();
160
+ }
161
+
162
+ setTimeout(trySocket, 1000);
163
+ }
164
+ });
165
+ };
166
+
167
+ trySocket();
168
+ });
169
+
170
+ // @todo - review/fix
171
+ // - the `ws.on` method only exists on node implementation, and the 'ping'
172
+ // message is not received on addEventListener
173
+ // - there seems to be no way to access the ping event in browsers...
174
+ //
175
+ // let pingTimeoutId = null;
176
+ // const pingInterval = config.env.websockets.pingInterval;
177
+ // // detect broken connection
178
+ // // cf. https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
179
+ // const heartbeat = () => {
180
+ // try {
181
+ // console.log('ping received');
182
+ // clearTimeout(pingTimeoutId);
183
+
184
+ // // pingTimeoutId = setTimeout(() => {
185
+ // // console.log('terminate');
186
+ // // this.terminate();
187
+ // // }, pingInterval + 2000);
188
+ // } catch (err) {
189
+ // console.error(err);
190
+ // }
191
+ // };
192
+ //
193
+ // this.ws.on('ping', heartbeat);
194
+ // this.ws.addEventListener('close', () => {
195
+ // clearTimeout(pingTimeoutId);
196
+ // });
197
+
198
+ // heartbeat();
199
+
200
+ // ----------------------------------------------------------
201
+ // init binary socket
202
+ // ----------------------------------------------------------
203
+ const binarySocketUrl = `${url}?binary=1&${queryParams}`;
204
+
205
+ await new Promise(resolve => {
206
+ const ws = new WebSocket(binarySocketUrl, webSocketOptions);
207
+ ws.binaryType = 'arraybuffer';
208
+
209
+ ws.addEventListener('open', connectEvent => {
210
+ // parse incoming messages for pubsub
211
+ this.binaryWs = ws;
212
+ this.binaryWs.addEventListener('message', e => {
213
+ const [channel, data] = unpackBinaryMessage(e.data);
214
+ this._emit(true, channel, data);
215
+ });
216
+
217
+ // broadcast all `WebSocket` native events
218
+ ['close', 'error', 'upgrade', 'message'].forEach(eventName => {
219
+ this.binaryWs.addEventListener(eventName, (e) => {
220
+ this._emit(true, eventName, e);
221
+ });
222
+ });
223
+
224
+ // forward open event
225
+ this._emit(true, 'open', connectEvent);
226
+ resolve();
227
+ });
228
+ });
229
+
230
+ // wait for both sockets connected
231
+ return Promise.resolve();
232
+ }
233
+
234
+ /**
235
+ * @param {boolean} binary - Emit to either the string or binary socket.
236
+ * @param {string} channel - Channel name.
237
+ * @param {...*} args - Content of the message.
238
+ * @private
239
+ */
240
+ _emit(binary, channel, ...args) {
241
+ const listeners = binary ? this._binaryListeners : this._stringListeners;
242
+
243
+ if (listeners.has(channel)) {
244
+ const callbacks = listeners.get(channel);
245
+ callbacks.forEach(callback => callback(...args));
246
+ }
247
+ }
248
+
249
+ /**
250
+ * @param {Function[]} listeners - List of listeners, either for the string or binary socket.
251
+ * @param {string} channel - Channel name.
252
+ * @param {Function} callback - The function to be added to the listeners.
253
+ * @private
254
+ */
255
+ _addListener(listeners, channel, callback) {
256
+ if (!listeners.has(channel)) {
257
+ listeners.set(channel, new Set());
258
+ }
259
+
260
+ const callbacks = listeners.get(channel);
261
+ callbacks.add(callback);
262
+ }
263
+
264
+ /**
265
+ * @param {Function[]} listeners - List of listeners, either for the string or binary socket.
266
+ * @param {string} channel - Channel name.
267
+ * @param {Function} callback - The function to be removed from the listeners.
268
+ * @private
269
+ */
270
+ _removeListener(listeners, channel, callback) {
271
+ if (listeners.has(channel)) {
272
+ const callbacks = listeners.get(channel);
273
+ callbacks.delete(callback);
274
+
275
+ if (callbacks.size === 0) {
276
+ listeners.delete(channel);
277
+ }
278
+ }
279
+ }
280
+
281
+ /**
282
+ * @param {Function[]} listeners - List of listeners, either for the string or binary socket.
283
+ * @param {string} [channel=null] - Channel name of the listeners to remove. If null
284
+ * all the listeners are cleared.
285
+ * @private
286
+ */
287
+ _removeAllListeners(listeners, channel = null) {
288
+ if (channel === null) {
289
+ listeners.clear();
290
+ } else if (listeners.has(channel)) {
291
+ listeners.delete(channel);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Send JSON compatible messages on a given channel.
297
+ *
298
+ * @param {string} channel - Channel name of the message.
299
+ * @param {...*} args - Message list, as many as needed, of any serializable type).
300
+ */
301
+ send(channel, ...args) {
302
+ const msg = packStringMessage(channel, ...args);
303
+ this.ws.send(msg);
304
+ }
305
+
306
+ /**
307
+ * Listen to JSON compatible messages on a given channel.
308
+ *
309
+ * @param {string} channel - Channel name of the message.
310
+ * @param {Function} callback - Callback to execute when a message is received.
311
+ */
312
+ addListener(channel, callback) {
313
+ this._addListener(this._stringListeners, channel, callback);
314
+ }
315
+
316
+ /**
317
+ * Remove a listener from JSON compatible messages on a given channel.
318
+ *
319
+ * @param {string} channel - Channel name of the message.
320
+ * @param {Function} callback - Callback to remove.
321
+ */
322
+ removeListener(channel, callback) {
323
+ this._removeListener(this._stringListeners, channel, callback);
324
+ }
325
+
326
+ /**
327
+ * Remove all listeners from JSON compatible messages on a given channel.
328
+ *
329
+ * @param {string} channel - Channel name of the message.
330
+ */
331
+ removeAllListeners(channel = null) {
332
+ this._removeAllListeners(this._stringListeners, channel);
333
+ }
334
+
335
+ /**
336
+ * Send binary messages on a given channel.
337
+ *
338
+ * @param {string} channel - Channel name of the message.
339
+ * @param {TypedArray} typedArray - Binary data to be sent.
340
+ */
341
+ sendBinary(channel, typedArray) {
342
+ const msg = packBinaryMessage(channel, typedArray);
343
+ this.binaryWs.send(msg);
344
+ }
345
+
346
+ /**
347
+ * Listen binary messages on a given channel.
348
+ *
349
+ * @param {string} channel - Channel name of the message.
350
+ * @param {Function} callback - Callback to execute when a message is received.
351
+ */
352
+ addBinaryListener(channel, callback) {
353
+ this._addListener(this._binaryListeners, channel, callback);
354
+ }
355
+
356
+ /**
357
+ * Remove a listener from binary compatible messages on a given channel
358
+ *
359
+ * @param {string} channel - Channel of the message.
360
+ * @param {Function} callback - Callback to cancel.
361
+ */
362
+ removeBinaryListener(channel, callback) {
363
+ this._removeListener(this._binaryListeners, channel, callback);
364
+ }
365
+
366
+ /**
367
+ * Remove all listeners from binary compatible messages on a given channel
368
+ *
369
+ * @param {string} channel - Channel of the message.
370
+ */
371
+ removeAllBinaryListeners(channel = null) {
372
+ this._removeAllListeners(this._binaryListeners, channel);
373
+ }
374
+
375
+ /**
376
+ * Removes all listeners and immediately close the two sockets.
377
+ */
378
+ async terminate() {
379
+ this.removeAllListeners();
380
+ this.removeAllBinaryListeners();
381
+
382
+ this.ws.close();
383
+ this.binaryWs.close();
384
+
385
+ return Promise.resolve();
386
+ }
387
+ }
388
+
389
+ export default Socket;
@@ -0,0 +1,67 @@
1
+ import BaseStateManager from '../common/BaseStateManager.js';
2
+
3
+ /**
4
+ * @callback client.StateManager~ObserveCallback
5
+ * @async
6
+ * @param {String} schemaName - name of the schema
7
+ * @param {Number} stateId - id of the state
8
+ * @param {Number} nodeId - id of the node that created the state
9
+ */
10
+
11
+ /**
12
+ * The `StateManager` allows to create new {@link client.SharedState}s, or attach
13
+ * to {@link client.SharedState}s created by other nodes (clients or server). It
14
+ * can also track all the {@link client.SharedState}s created by other nodes.
15
+ *
16
+ * An instance of `StateManager` is automatically created by the `soundworks.Client`
17
+ * at initialization (cf. {@link client.Client#stateManager}).
18
+ *
19
+ * See {@link client.Client#stateManager}
20
+ *
21
+ * Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
22
+ *
23
+ * ```
24
+ * // server-side
25
+ * import { Server } from '@soundworks/server/index.js';
26
+ *
27
+ * const server = new Server(config);
28
+ * // declare and register the schema of a shared state.
29
+ * server.stateManager.registerSchema('some-global-state', {
30
+ * myRandom: {
31
+ * type: 'float',
32
+ * default: 0,
33
+ * }
34
+ * });
35
+ *
36
+ * await server.start();
37
+ *
38
+ * // create a global state server-side
39
+ * const globalState = await server.stateManager.create('some-global-state');
40
+ * // listen and react to the changes made by the clients
41
+ * globalState.onUpdate(updates => console.log(updates));
42
+ * ```
43
+ *
44
+ * ```
45
+ * // client-side
46
+ * import { Client } from '@soundworks/client.index.js';
47
+ *
48
+ * const client = new Client(config);
49
+ * await client.start();
50
+ *
51
+ * // attach to the global state created by the server
52
+ * const globalState = await client.stateManager.attach('some-global-state');
53
+ *
54
+ * // update the value of a `myRandom` parameter every seconds
55
+ * setInterval(() => {
56
+ * globalState.set({ myRandom: Math.random() });
57
+ * }, 1000);
58
+ * ```
59
+ *
60
+ * @memberof client
61
+ * @extends BaseStateManager
62
+ * @inheritdoc
63
+ * @hideconstructor
64
+ */
65
+ class StateManager extends BaseStateManager {}
66
+
67
+ export default StateManager;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Configuration object for a client running in a browser runtime.
3
+ *
4
+ * @typedef BrowserClientConfig
5
+ * @memberof client
6
+ * @type {object}
7
+ * @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
8
+ * @property {object} [app] - Application configration object.
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 {string} [env.websockets={}] - Configuration options for websockets.
13
+ * @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
14
+ */
15
+
16
+ /**
17
+ * Configuration object for a client running in a node runtime.
18
+ *
19
+ * @typedef NodeClientConfig
20
+ * @memberof client
21
+ * @type {object}
22
+ * @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
23
+ * @property {object} [app] - Application configration object.
24
+ * @property {string} [app.name=''] - Name of the application.
25
+ * @property {string} [app.author=''] - Name of the author.
26
+ * @property {object} env - Environment configration object.
27
+ * @property {boolean} env.serverAddress - Domain name or IP of the server.
28
+ * @property {boolean} env.useHttps - Define is the server run in http or in https.
29
+ * @property {boolean} env.port - Port on which the server is listening.
30
+ * @property {string} [env.websockets={}] - Configuration options for websockets.
31
+ * @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
32
+ */
33
+
34
+ /**
35
+ * Client-side part of the `soundworks` framework.
36
+ *
37
+ * A `soundworks` client can run seamlessly in a browser or in a Node.js runtime.
38
+ *
39
+ * ```
40
+ * import '@soundworks/helpers/polyfills.js';
41
+ * import { Client } from '@soundworks/core/client.js';
42
+ * import launcher from '@soundworks/helpers/launcher.js';
43
+ *
44
+ * // - General documentation: https://soundworks.dev/
45
+ * // - API documentation: https://soundworks.dev/api
46
+ * // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
47
+ * // - Wizard & Tools: `npx soundworks`
48
+ *
49
+ * const config = window.SOUNDWORKS_CONFIG;
50
+ *
51
+ * async function main($container) {
52
+ * try {
53
+ * const client = new Client(config);
54
+ *
55
+ * // client.pluginManager.register(pluginName, pluginFactory, {options}, [dependencies])
56
+ *
57
+ * launcher.register(client, {
58
+ * initScreensContainer: $container,
59
+ * reloadOnVisibilityChange: false,
60
+ * });
61
+ *
62
+ * await client.start();
63
+ *
64
+ * $container.innerText = `client ${client.id} started`);
65
+ *
66
+ * } catch(err) {
67
+ * console.error(err);
68
+ * }
69
+ * }
70
+ *
71
+ * launcher.execute(main);
72
+ * ```
73
+ *
74
+ * @namespace client
75
+ */
76
+ export { default as Context } from './Context.js';
77
+ export { default as Client } from './Client.js';
@@ -0,0 +1,163 @@
1
+ import merge from 'lodash.merge';
2
+
3
+ /**
4
+ * @private
5
+ */
6
+ class BasePlugin {
7
+ constructor(id) {
8
+ /**
9
+ * User defined ID of the plugin.
10
+ *
11
+ * @type {string}
12
+ * @see {@link client.PluginManager#register}
13
+ * @see {@link server.PluginManager#register}
14
+ */
15
+ this.id = id;
16
+
17
+ /**
18
+ * Type of the plugin, i.e. the ClassName.
19
+ *
20
+ * Usefull to do perform some logic based on certain types of plugins without
21
+ * knowing under which `id` they have been registered. (e.g. creating some generic
22
+ * views, etc.)
23
+ *
24
+ * @type {string}
25
+ * @readonly
26
+ */
27
+ this.type = this.constructor.name;
28
+
29
+ /**
30
+ * Options of the plugin.
31
+ *
32
+ * @type {object}
33
+ */
34
+ this.options = {};
35
+
36
+ /**
37
+ * Placeholder that stores internal (local) state of the plugin. The state
38
+ * should be modified through the `propagateStateChange` method to ensure
39
+ * the change to be properly propagated to `onStateChange` callbacks.
40
+ *
41
+ * @type {object}
42
+ * @protected
43
+ * @see {@link client.Plugin#onStateChange}
44
+ * @see {@link server.Plugin#onStateChange}
45
+ * @see {@link client.Plugin#propagateStateChange}
46
+ * @see {@link server.Plugin#propagateStateChange}
47
+ */
48
+ this.state = {};
49
+
50
+ /**
51
+ * Current status of the plugin, i.e. 'idle', 'inited', 'started', 'errored'
52
+ *
53
+ * @type {string}
54
+ */
55
+ this.status = 'idle';
56
+
57
+ /** @private */
58
+ this._onStateChangeCallbacks = new Set();
59
+ }
60
+
61
+ /**
62
+ * Start the plugin. This method is automatically called during the client or
63
+ * server `init()` lifecyle step. After `start()` is fulfilled the plugin should
64
+ * be ready to use.
65
+ *
66
+ * @example
67
+ * // server-side couterpart of a plugin that creates a dedicated global shared
68
+ * // state on which the server-side part can attach.
69
+ * class MyPlugin extends Plugin {
70
+ * constructor(server, id) {
71
+ * super(server, id);
72
+ *
73
+ * this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
74
+ * someParam: {
75
+ * type: 'boolean',
76
+ * default: false,
77
+ * },
78
+ * // ...
79
+ * });
80
+ * }
81
+ *
82
+ * async start() {
83
+ * await super.start()
84
+ * this.sharedState = await this.server.stateManager.create(`my-plugin:${this.id}`);
85
+ * }
86
+ *
87
+ * async stop() {
88
+ * await this.sharedState.delete();
89
+ * }
90
+ * }
91
+ */
92
+ async start() {}
93
+
94
+ /**
95
+ * Stop the plugin. This method is automatically called during the client or server
96
+ * `stop()` lifecyle step.
97
+ *
98
+ * @example
99
+ * // server-side couterpart of a plugin that creates a dedicated global shared
100
+ * // state on which the server-side part can attach.
101
+ * class MyPlugin extends Plugin {
102
+ * constructor(server, id) {
103
+ * super(server, id);
104
+ *
105
+ * this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
106
+ * someParam: {
107
+ * type: 'boolean',
108
+ * default: false,
109
+ * },
110
+ * // ...
111
+ * });
112
+ * }
113
+ *
114
+ * async start() {
115
+ * await super.start()
116
+ * this.sharedState = await this.server.stateManager.create(`my-plugin:${this.id}`);
117
+ * this.sharedState.onUpdate(updates => this.doSomething(updates));
118
+ * }
119
+ *
120
+ * async stop() {
121
+ * await this.sharedState.delete();
122
+ * }
123
+ * }
124
+ */
125
+ async stop() {}
126
+
127
+ /**
128
+ * Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
129
+ *
130
+ * @param {client.Plugin~onStateChangeCallback|server.Plugin~onStateChangeCallback} callback -
131
+ * Callback to execute when a state change is propagated.
132
+ * @returns {client.Plugin~deleteOnStateChangeCallback|server.Plugin~deleteOnStateChangeCallback}
133
+ * Execute the function to delete the listener from the callback list.
134
+ * @see {@link client.Plugin#propagateStateChange}
135
+ * @see {@link server.Plugin#propagateStateChange}
136
+ * @example
137
+ * const unsubscribe = plugin.onStateChange(pluginState => console.log(pluginState));
138
+ * // stop listening state changes
139
+ * unsubscribe();
140
+ */
141
+ onStateChange(callback) {
142
+ this._onStateChangeCallbacks.add(callback);
143
+ return () => this._onStateChangeCallbacks.delete(callback);
144
+ }
145
+
146
+ /**
147
+ * Apply updates to the plugin state and propagate the updated state to the
148
+ * `onStateChange` listeners. The state changes will also be propagated
149
+ * through the `PluginManager#onStateChange` listeners.
150
+ *
151
+ * @param {object} updates - Updates to be merged in the plugin state.
152
+ * @see {@link client.Plugin#onStateChange}
153
+ * @see {@link server.Plugin#onStateChange}
154
+ * @see {@link client.PluginManager#onStateChange}
155
+ * @see {@link server.PluginManager#onStateChange}
156
+ */
157
+ propagateStateChange(updates) {
158
+ merge(this.state, updates);
159
+ this._onStateChangeCallbacks.forEach(callback => callback(this.state));
160
+ }
161
+ }
162
+
163
+ export default BasePlugin;