@soundworks/core 4.0.0-alpha.9 → 5.0.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 (144) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +14 -29
  3. package/package.json +24 -42
  4. package/src/client/Client.js +306 -256
  5. package/src/client/{Context.js → ClientContext.js} +63 -54
  6. package/src/client/ClientContextManager.js +115 -0
  7. package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
  8. package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
  9. package/src/client/ClientSocket.js +270 -0
  10. package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
  11. package/src/client/index.js +36 -39
  12. package/src/common/BasePlugin.js +64 -58
  13. package/src/common/BasePluginManager.js +191 -142
  14. package/src/common/BaseStateManager.js +635 -145
  15. package/src/common/BatchedTransport.js +73 -0
  16. package/src/common/ParameterBag.js +90 -93
  17. package/src/common/PromiseStore.js +54 -0
  18. package/src/common/SharedState.js +821 -0
  19. package/src/common/SharedStateCollection.js +492 -0
  20. package/src/common/constants.js +14 -2
  21. package/src/common/logger.js +64 -68
  22. package/src/common/shared-state-types.js +82 -0
  23. package/src/common/sockets-utils.js +0 -64
  24. package/src/common/version.js +2 -0
  25. package/src/server/Server.js +552 -824
  26. package/src/server/ServerClient.js +74 -0
  27. package/src/server/{Context.js → ServerContext.js} +96 -77
  28. package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
  29. package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
  30. package/src/server/ServerPluginManager.js +177 -0
  31. package/src/server/ServerSocket.js +256 -0
  32. package/src/server/ServerSockets.js +150 -0
  33. package/src/server/ServerStateManager.js +706 -0
  34. package/src/server/SharedStatePrivate.js +277 -0
  35. package/src/server/audit-network-latency.worker.js +19 -4
  36. package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
  37. package/src/server/create-http-server.js +127 -0
  38. package/src/server/crypto.js +8 -7
  39. package/src/server/index.js +60 -17
  40. package/types/client/Client.d.ts +95 -70
  41. package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
  42. package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
  43. package/types/client/ClientPlugin.d.ts +51 -0
  44. package/types/client/ClientPluginManager.d.ts +112 -0
  45. package/types/client/ClientSocket.d.ts +76 -0
  46. package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
  47. package/types/client/index.d.ts +33 -6
  48. package/types/common/BasePlugin.d.ts +64 -53
  49. package/types/common/BasePluginManager.d.ts +84 -39
  50. package/types/common/BaseStateManager.d.ts +227 -49
  51. package/types/common/BatchedTransport.d.ts +17 -0
  52. package/types/common/ParameterBag.d.ts +73 -76
  53. package/types/common/PromiseStore.d.ts +11 -0
  54. package/types/common/SharedState.d.ts +291 -0
  55. package/types/common/SharedStateCollection.d.ts +311 -0
  56. package/types/common/constants.d.ts +10 -3
  57. package/types/common/logger.d.ts +8 -6
  58. package/types/common/shared-state-types.d.ts +95 -0
  59. package/types/common/sockets-utils.d.ts +0 -5
  60. package/types/common/version.d.ts +2 -0
  61. package/types/server/Server.d.ts +215 -136
  62. package/types/server/ServerClient.d.ts +45 -0
  63. package/types/server/ServerContext.d.ts +209 -0
  64. package/types/server/ServerContextManager.d.ts +75 -0
  65. package/types/server/ServerPlugin.d.ts +57 -0
  66. package/types/server/ServerPluginManager.d.ts +115 -0
  67. package/types/server/ServerSocket.d.ts +69 -0
  68. package/types/server/ServerSockets.d.ts +42 -0
  69. package/types/server/ServerStateManager.d.ts +284 -0
  70. package/types/server/SharedStatePrivate.d.ts +23 -0
  71. package/types/server/audit-network-latency.worker.d.ts +2 -2
  72. package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
  73. package/types/server/create-http-server.d.ts +5 -0
  74. package/types/server/crypto.d.ts +4 -0
  75. package/types/server/index.d.ts +51 -3
  76. package/.eslintrc +0 -15
  77. package/.github/workflows/gh-pages.yml +0 -31
  78. package/.github/workflows/unit-tests.yml +0 -18
  79. package/src/client/ContextManager.js +0 -111
  80. package/src/client/SharedState.js +0 -84
  81. package/src/client/SharedStateCollection.js +0 -35
  82. package/src/client/Socket.js +0 -402
  83. package/src/common/BaseSharedState.js +0 -583
  84. package/src/common/BaseSharedStateCollection.js +0 -179
  85. package/src/common/SharedStatePrivate.js +0 -192
  86. package/src/common/promise-store.js +0 -25
  87. package/src/server/Client.js +0 -55
  88. package/src/server/PluginManager.js +0 -170
  89. package/src/server/SharedState.js +0 -85
  90. package/src/server/SharedStateCollection.js +0 -36
  91. package/src/server/Socket.js +0 -396
  92. package/src/server/Sockets.js +0 -224
  93. package/src/server/StateManager.js +0 -620
  94. package/tsconfig.json +0 -21
  95. package/types/client/Client.d.ts.map +0 -1
  96. package/types/client/Context.d.ts.map +0 -1
  97. package/types/client/ContextManager.d.ts.map +0 -1
  98. package/types/client/Plugin.d.ts +0 -53
  99. package/types/client/Plugin.d.ts.map +0 -1
  100. package/types/client/PluginManager.d.ts +0 -96
  101. package/types/client/PluginManager.d.ts.map +0 -1
  102. package/types/client/SharedState.d.ts +0 -85
  103. package/types/client/SharedState.d.ts.map +0 -1
  104. package/types/client/Socket.d.ts +0 -138
  105. package/types/client/Socket.d.ts.map +0 -1
  106. package/types/client/StateManager.d.ts.map +0 -1
  107. package/types/client/index.d.ts.map +0 -1
  108. package/types/common/BasePlugin.d.ts.map +0 -1
  109. package/types/common/BasePluginManager.d.ts.map +0 -1
  110. package/types/common/BaseSharedState.d.ts +0 -173
  111. package/types/common/BaseSharedState.d.ts.map +0 -1
  112. package/types/common/BaseStateManager.d.ts.map +0 -1
  113. package/types/common/ParameterBag.d.ts.map +0 -1
  114. package/types/common/SharedStatePrivate.d.ts +0 -20
  115. package/types/common/SharedStatePrivate.d.ts.map +0 -1
  116. package/types/common/constants.d.ts.map +0 -1
  117. package/types/common/logger.d.ts.map +0 -1
  118. package/types/common/promise-store.d.ts +0 -4
  119. package/types/common/promise-store.d.ts.map +0 -1
  120. package/types/common/sockets-utils.d.ts.map +0 -1
  121. package/types/common/utils.d.ts +0 -5
  122. package/types/common/utils.d.ts.map +0 -1
  123. package/types/server/Client.d.ts +0 -40
  124. package/types/server/Client.d.ts.map +0 -1
  125. package/types/server/Context.d.ts +0 -59
  126. package/types/server/Context.d.ts.map +0 -1
  127. package/types/server/ContextManager.d.ts +0 -69
  128. package/types/server/ContextManager.d.ts.map +0 -1
  129. package/types/server/Plugin.d.ts +0 -70
  130. package/types/server/Plugin.d.ts.map +0 -1
  131. package/types/server/PluginManager.d.ts +0 -91
  132. package/types/server/PluginManager.d.ts.map +0 -1
  133. package/types/server/Server.d.ts.map +0 -1
  134. package/types/server/SharedState.d.ts +0 -86
  135. package/types/server/SharedState.d.ts.map +0 -1
  136. package/types/server/Socket.d.ts +0 -128
  137. package/types/server/Socket.d.ts.map +0 -1
  138. package/types/server/Sockets.d.ts +0 -73
  139. package/types/server/Sockets.d.ts.map +0 -1
  140. package/types/server/StateManager.d.ts +0 -190
  141. package/types/server/StateManager.d.ts.map +0 -1
  142. package/types/server/audit-network-latency.worker.d.ts.map +0 -1
  143. package/types/server/audit-schema.d.ts.map +0 -1
  144. package/types/server/index.d.ts.map +0 -1
@@ -0,0 +1,270 @@
1
+ import { isBrowser } from '@ircam/sc-utils';
2
+ import WebSocket from 'isomorphic-ws';
3
+
4
+ import {
5
+ PING_MESSAGE,
6
+ PONG_MESSAGE,
7
+ } from '../common/constants.js';
8
+ import logger from '../common/logger.js';
9
+ import {
10
+ packStringMessage,
11
+ unpackStringMessage,
12
+ } from '../common/sockets-utils.js';
13
+
14
+ export const kSocketTerminate = Symbol('soundworks:socket-terminate');
15
+
16
+ /**
17
+ * The ClientSocket class is a simple publish / subscribe wrapper built on top of the
18
+ * [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws) library.
19
+ * An instance of `ClientSocket` is automatically created by the `soundworks.Client`
20
+ * (see {@link Client#socket}).
21
+ *
22
+ * _Important: In most cases, you should consider using a {@link SharedState}
23
+ * rather than directly using the sockets._
24
+ *
25
+ * The ClientSocket class concurrently opens two different WebSockets:
26
+ * - a socket configured with `binaryType = 'blob'` for JSON compatible data
27
+ * types (i.e. string, number, boolean, object, array and null).
28
+ * - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming
29
+ * of binary data.
30
+ *
31
+ * Both sockets re-emits all "native" ws events ('open', 'upgrade', 'close', 'error'
32
+ * and 'message'.
33
+ *
34
+ * @hideconstructor
35
+ */
36
+ class ClientSocket {
37
+ #role = null;
38
+ #config = null;
39
+ #socketOptions = null;
40
+ #socket = null;
41
+ #listeners = new Map();
42
+
43
+ constructor(role, config, socketOptions) {
44
+ this.#role = role;
45
+ this.#config = config;
46
+ this.#socketOptions = socketOptions;
47
+ }
48
+
49
+ /**
50
+ * Initialize a websocket connection with the server. Automatically called
51
+ * during {@link Client#init}
52
+ *
53
+ * @param {string} role - Role of the client (see {@link Client#role})
54
+ * @param {object} config - Configuration of the sockets
55
+ * @private
56
+ */
57
+ async init() {
58
+ let { path } = this.#socketOptions;
59
+ // cf. https://github.com/collective-soundworks/soundworks/issues/35
60
+ if (this.#config.env.baseUrl) {
61
+ path = `${this.#config.env.baseUrl}/${path}`;
62
+ }
63
+
64
+ const protocol = this.#config.env.useHttps ? 'wss:' : 'ws:';
65
+ const port = this.#config.env.port;
66
+ let serverAddress;
67
+ let webSocketOptions;
68
+
69
+ if (isBrowser()) {
70
+ const hostname = window.location.hostname;
71
+
72
+ if (this.#config.env.serverAddress === '') {
73
+ serverAddress = hostname;
74
+ } else {
75
+ serverAddress = this.#config.env.serverAddress;
76
+ }
77
+
78
+ // when in https with self-signed certificates, we don't want to use
79
+ // the serverAddress defined in config as the socket would be blocked, so we
80
+ // just override serverAddress with hostname in this case
81
+ const localhosts = ['127.0.0.1', 'localhost'];
82
+
83
+ if (this.#config.env.useHttps && localhosts.includes(hostname)) {
84
+ serverAddress = window.location.hostname;
85
+ }
86
+
87
+ if (this.#config.env.useHttps && window.location.hostname !== serverAddress) {
88
+ console.warn(`ClientSocket will try to connect at ${serverAddress} while the page is accessed from ${hostname}. This can lead to socket errors, e.g. If you run the application with self-signed certificates as the certificate may not have been accepted for ${serverAddress}. In such case you should rather access the page from ${serverAddress}.`);
89
+ }
90
+
91
+ webSocketOptions = [];
92
+ } else {
93
+ if (this.#config.env.serverAddress === '') {
94
+ serverAddress = '127.0.0.1';
95
+ } else {
96
+ serverAddress = this.#config.env.serverAddress;
97
+ }
98
+
99
+ webSocketOptions = {
100
+ // handshakeTimeout: 2000,
101
+ // do not reject self-signed certificates
102
+ rejectUnauthorized: false,
103
+ };
104
+ }
105
+
106
+ let queryParams = `role=${this.#role}`;
107
+
108
+ if (this.#config.token) {
109
+ queryParams += `&token=${this.#config.token}`;
110
+ }
111
+
112
+ const url = `${protocol}//${serverAddress}:${port}/${path}?${queryParams}`;
113
+
114
+ // ----------------------------------------------------------
115
+ // Init socket
116
+ // ----------------------------------------------------------
117
+ return new Promise(resolve => {
118
+ const trySocket = async () => {
119
+ const ws = new WebSocket(url, webSocketOptions);
120
+
121
+ // WebSocket "native" events:
122
+ // - `close`: Fired when a connection with a websocket is closed.
123
+ // - `error`: Fired when a connection with a websocket has been closed
124
+ // because of an error, such as when some data couldn't be sent.
125
+ // - `message`: Fired when data is received through a websocket.
126
+ // - `open`: Fired when a connection with a websocket is opened.
127
+
128
+ ws.addEventListener('open', openEvent => {
129
+ this.#socket = ws;
130
+
131
+ this.#socket.addEventListener('message', e => {
132
+ if (e.data === PING_MESSAGE) {
133
+ this.#socket.send(PONG_MESSAGE);
134
+ return; // do not propagate ping pong messages
135
+ }
136
+
137
+ // Parse incoming message, dispatch in pubsub system and propagate raw event.
138
+ const [channel, args] = unpackStringMessage(e.data);
139
+ this.#dispatchEvent(channel, ...args);
140
+ this.#dispatchEvent('message', e);
141
+ });
142
+
143
+ // Propagate other native events
144
+ // @note - isn't it too late for the 'upgrade' message?
145
+ ['close', 'error', 'upgrade'].forEach(eventName => {
146
+ this.#socket.addEventListener(eventName, e => this.#dispatchEvent(eventName, e));
147
+ });
148
+
149
+ // Forward open event and continue initialization
150
+ this.#dispatchEvent('open', openEvent);
151
+ resolve();
152
+ });
153
+
154
+ // cf. https://github.com/collective-soundworks/soundworks/issues/17
155
+ // cf. https://github.com/collective-soundworks/soundworks/issues/97
156
+ ws.addEventListener('error', e => {
157
+ ws.terminate ? ws.terminate() : ws.close();
158
+
159
+ if (e.error) {
160
+ const msg = `ClientSocket error - code: ${e.error.code}, message: ${e.error.message}`;
161
+ logger.log(msg);
162
+ }
163
+
164
+ // Try to reconnect in all cases. Note that if the socket has been connected,
165
+ // the close event will be propagated and the launcher will restart the process.
166
+ setTimeout(trySocket, 1000);
167
+ });
168
+ };
169
+
170
+ trySocket();
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Removes all listeners and immediately close the two sockets. Is automatically
176
+ * called on `client.stop()`
177
+ *
178
+ * Is also called when a disconnection is detected by the heartbeat (note that
179
+ * in this case, the launcher will call `client.stop()` but the listeners are
180
+ * already cleared so the event will be triggered only once.
181
+ *
182
+ * @private
183
+ */
184
+ async [kSocketTerminate]() {
185
+ const closeListeners = this.#listeners.get('close');
186
+ this.removeAllListeners();
187
+
188
+ this.#socket.close();
189
+
190
+ if (closeListeners) {
191
+ closeListeners.forEach(listener => listener());
192
+ }
193
+
194
+ return Promise.resolve();
195
+ }
196
+
197
+ /**
198
+ * @param {string} channel - Channel name.
199
+ * @param {...*} args - Content of the message.
200
+ */
201
+ #dispatchEvent(channel, ...args) {
202
+ if (this.#listeners.has(channel)) {
203
+ const callbacks = this.#listeners.get(channel);
204
+ callbacks.forEach(callback => callback(...args));
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Send messages with JSON compatible data types on a given channel.
210
+ *
211
+ * @param {string} channel - Channel name.
212
+ * @param {...*} args - Payload of the message. As many arguments as needed, of
213
+ * JSON compatible data types (i.e. string, number, boolean, object, array and null).
214
+ */
215
+ send(channel, ...args) {
216
+ if (this.#socket.readyState === 1) {
217
+ const msg = packStringMessage(channel, ...args);
218
+ this.#socket.send(msg);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Listen messages with JSON compatible data types on a given channel.
224
+ *
225
+ * @param {string} channel - Channel name.
226
+ * @param {Function} callback - Callback to execute when a message is received,
227
+ * arguments of the callback function will match the arguments sent using the
228
+ * {@link ServerSocket#send} method.
229
+ */
230
+ addListener(channel, callback) {
231
+ if (!this.#listeners.has(channel)) {
232
+ this.#listeners.set(channel, new Set());
233
+ }
234
+
235
+ const callbacks = this.#listeners.get(channel);
236
+ callbacks.add(callback);
237
+ }
238
+
239
+ /**
240
+ * Remove a listener from JSON compatible messages on a given channel.
241
+ *
242
+ * @param {string} channel - Channel name.
243
+ * @param {Function} callback - Callback to remove.
244
+ */
245
+ removeListener(channel, callback) {
246
+ if (this.#listeners.has(channel)) {
247
+ const callbacks = this.#listeners.get(channel);
248
+ callbacks.delete(callback);
249
+
250
+ if (callbacks.size === 0) {
251
+ this.#listeners.delete(channel);
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Remove all listeners of messages with JSON compatible data types.
258
+ *
259
+ * @param {string} channel - Channel name.
260
+ */
261
+ removeAllListeners(channel = null) {
262
+ if (channel === null) {
263
+ this.#listeners.clear();
264
+ } else if (this.#listeners.has(channel)) {
265
+ this.#listeners.delete(channel);
266
+ }
267
+ }
268
+ }
269
+
270
+ export default ClientSocket;
@@ -1,19 +1,20 @@
1
1
  import BaseStateManager from '../common/BaseStateManager.js';
2
2
 
3
3
  /**
4
- * @callback client.StateManager~ObserveCallback
4
+ * @callback ClientStateManager~ObserveCallback
5
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
6
+ * @param {String} className - Name of the shared state class.
7
+ * @param {Number} stateId - Id of the state.
8
+ * @param {Number} nodeId - Id of the node that created the state.
9
9
  */
10
10
 
11
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.
12
+ * The `ClientStateManager` allows to create new {@link SharedState}s, or attach
13
+ * to {@link SharedState}s created by other nodes (clients or server) on the network.
15
14
  *
16
- * An instance of `StateManager` is automatically created by the `soundworks.Client`
15
+ * It can also observe all the {@link SharedState}s created on the network.
16
+ *
17
+ * An instance of `ClientStateManager` is automatically created by the `soundworks.Client`
17
18
  * at initialization (cf. {@link client.Client#stateManager}).
18
19
  *
19
20
  * See {@link client.Client#stateManager}
@@ -25,8 +26,8 @@ import BaseStateManager from '../common/BaseStateManager.js';
25
26
  * import { Server } from '@soundworks/server/index.js';
26
27
  *
27
28
  * const server = new Server(config);
28
- * // declare and register the schema of a shared state.
29
- * server.stateManager.registerSchema('some-global-state', {
29
+ * // define a class of shared state.
30
+ * server.stateManager.defineClass('some-global-state', {
30
31
  * myRandom: {
31
32
  * type: 'float',
32
33
  * default: 0,
@@ -57,11 +58,10 @@ import BaseStateManager from '../common/BaseStateManager.js';
57
58
  * }, 1000);
58
59
  * ```
59
60
  *
60
- * @memberof client
61
61
  * @extends BaseStateManager
62
62
  * @inheritdoc
63
63
  * @hideconstructor
64
64
  */
65
- class StateManager extends BaseStateManager {}
65
+ class ClientStateManager extends BaseStateManager {}
66
66
 
67
- export default StateManager;
67
+ export default ClientStateManager;
@@ -1,44 +1,41 @@
1
1
  /**
2
- * Client-side part of the `soundworks` framework.
3
- *
4
- * A `soundworks` client can run seamlessly in a browser or in a Node.js runtime.
5
- *
6
- * ```
7
- * import '@soundworks/helpers/polyfills.js';
8
- * import { Client } from '@soundworks/core/client.js';
9
- * import launcher from '@soundworks/helpers/launcher.js';
10
- *
11
- * // - General documentation: https://soundworks.dev/
12
- * // - API documentation: https://soundworks.dev/api
13
- * // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
14
- * // - Wizard & Tools: `npx soundworks`
15
- *
16
- * const config = window.SOUNDWORKS_CONFIG;
17
- *
18
- * async function main($container) {
19
- * try {
20
- * const client = new Client(config);
21
- *
22
- * // client.pluginManager.register(pluginName, pluginFactory, {options}, [dependencies])
23
- *
24
- * launcher.register(client, {
25
- * initScreensContainer: $container,
26
- * reloadOnVisibilityChange: false,
27
- * });
28
- *
29
- * await client.start();
30
- *
31
- * $container.innerText = `client ${client.id} started`);
32
- *
33
- * } catch(err) {
34
- * console.error(err);
35
- * }
36
- * }
2
+ * @license
3
+ * Copyright (c) 2014-present IRCAM – Centre Pompidou (France, Paris)
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ */
6
+
7
+ // these types must be defined here, so that they can be imported into the application
8
+
9
+ /**
10
+ * Application configuration object.
37
11
  *
38
- * launcher.execute(main);
39
- * ```
12
+ * @typedef ClientAppConfig
13
+ * @type {object}
14
+ * @property {string} [name=''] - Name of the application.
15
+ * @property {string} [author=''] - Name of the author.
16
+ */
17
+ /**
18
+ * Environment configuration object.
19
+ *
20
+ * @typedef ClientEnvConfig
21
+ * @type {objecy}
22
+ * @property {number} env.port - Port on which the server is listening.
23
+ * @property {string} env.serverAddress - Domain name or IP of the server.
24
+ * Mandatory when node clients are declared
25
+ * @property {boolean} env.useHttps - Define is the server run in http or in https.
26
+ * @property {string} [env.baseUrl=''] - If running behind a proxy, base URL of the application.
27
+ */
28
+ /**
29
+ * Configuration object for a client running in a browser runtime.
40
30
  *
41
- * @namespace client
31
+ * @typedef ClientConfig
32
+ * @type {object}
33
+ * @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
34
+ * @property {ClientAppConfig} app - Application configuration object.
35
+ * @property {ClientEnvConfig} env - Environment configuration object.
42
36
  */
43
- export { default as Context } from './Context.js';
37
+
44
38
  export { default as Client } from './Client.js';
39
+ export { default as ClientPlugin } from './ClientPlugin.js';
40
+ export { default as ClientContext } from './ClientContext.js';
41
+ export { default as version } from '../common/version.js';
@@ -1,45 +1,44 @@
1
- import merge from 'lodash.merge';
1
+ import merge from 'lodash/merge.js';
2
+
3
+ // Notes: we use Symbol.for because we need to override this in helpers test
4
+ export const kBasePluginStatus = Symbol.for('soundworks:base-plugin-status');
5
+
6
+ /**
7
+ * Callback executed when the plugin state is updated.
8
+ *
9
+ * @callback pluginOnStateChangeCallback
10
+ * @param {BasePlugin#state} state - Current state of the plugin.
11
+ */
2
12
 
3
13
  /**
4
- * @private
14
+ * Delete the registered {@link pluginOnStateChangeCallback}.
15
+ *
16
+ * @callback pluginDeleteOnStateChangeCallback
5
17
  */
18
+
19
+ /** @private */
6
20
  class BasePlugin {
21
+ #id = null;
22
+ #onStateChangeCallbacks = new Set();
23
+
7
24
  constructor(id) {
25
+ this.#id = id;
8
26
  /** @private */
9
- this._id = id;
10
- /** @private */
11
- this._type = this.constructor.name;
12
-
13
- /**
14
- * Options of the plugin.
15
- *
16
- * @type {object}
17
- */
18
- this.options = {};
27
+ this[kBasePluginStatus] = 'idle';
19
28
 
20
29
  /**
21
30
  * Placeholder that stores internal (local) state of the plugin. The state
22
31
  * should be modified through the `propagateStateChange` method to ensure
23
- * the change to be properly propagated to `onStateChange` callbacks.
32
+ * the change to be properly propagated to manager `onStateChange` callbacks.
24
33
  *
25
34
  * @type {object}
26
35
  * @protected
27
- * @see {@link client.Plugin#onStateChange}
28
- * @see {@link server.Plugin#onStateChange}
29
- * @see {@link client.Plugin#propagateStateChange}
30
- * @see {@link server.Plugin#propagateStateChange}
36
+ * @see {@link ClientPlugin#onStateChange}
37
+ * @see {@link ServerPlugin#onStateChange}
38
+ * @see {@link ClientPlugin#propagateStateChange}
39
+ * @see {@link ServerPlugin#propagateStateChange}
31
40
  */
32
41
  this.state = {};
33
-
34
- /**
35
- * Current status of the plugin, i.e. 'idle', 'inited', 'started', 'errored'
36
- *
37
- * @type {string}
38
- */
39
- this.status = 'idle';
40
-
41
- /** @private */
42
- this._onStateChangeCallbacks = new Set();
43
42
  }
44
43
 
45
44
  /**
@@ -47,17 +46,17 @@ class BasePlugin {
47
46
  *
48
47
  * @type {string}
49
48
  * @readonly
50
- * @see {@link client.PluginManager#register}
51
- * @see {@link server.PluginManager#register}
49
+ * @see {@link ClientPluginManager#register}
50
+ * @see {@link ServerPluginManager#register}
52
51
  */
53
52
  get id() {
54
- return this._id;
53
+ return this.#id;
55
54
  }
56
55
 
57
56
  /**
58
57
  * Type of the plugin, i.e. the ClassName.
59
58
  *
60
- * Usefull to do perform some logic based on certain types of plugins without
59
+ * Useful to do perform some logic based on certain types of plugins without
61
60
  * knowing under which `id` they have been registered. (e.g. creating some generic
62
61
  * views, etc.)
63
62
  *
@@ -65,22 +64,32 @@ class BasePlugin {
65
64
  * @readonly
66
65
  */
67
66
  get type() {
68
- return this._type;
67
+ return this.constructor.name;
68
+ }
69
+
70
+ /**
71
+ * Current status of the plugin.
72
+ *
73
+ * @type {'idle'|'inited'|'started'|'errored'}
74
+ */
75
+ get status() {
76
+ return this[kBasePluginStatus];
69
77
  }
70
78
 
71
79
  /**
72
- * Start the plugin. This method is automatically called during the client or
73
- * server `init()` lifecyle step. After `start()` is fulfilled the plugin should
74
- * be ready to use.
80
+ * Start the plugin.
81
+ *
82
+ * This method is automatically called during the client or server `init()` lifecycle
83
+ * step. After `start()` is fulfilled the plugin should be ready to use.
75
84
  *
76
85
  * @example
77
- * // server-side couterpart of a plugin that creates a dedicated global shared
86
+ * // server-side counterpart of a plugin that creates a dedicated global shared
78
87
  * // state on which the server-side part can attach.
79
- * class MyPlugin extends Plugin {
88
+ * class MyPlugin extends ServerPlugin {
80
89
  * constructor(server, id) {
81
90
  * super(server, id);
82
91
  *
83
- * this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
92
+ * this.server.stateManager.defineClass(`my-plugin:${this.id}`, {
84
93
  * someParam: {
85
94
  * type: 'boolean',
86
95
  * default: false,
@@ -102,17 +111,18 @@ class BasePlugin {
102
111
  async start() {}
103
112
 
104
113
  /**
105
- * Stop the plugin. This method is automatically called during the client or server
106
- * `stop()` lifecyle step.
114
+ * Stop the plugin.
115
+ *
116
+ * This method is automatically called during the client or server `stop()` lifecycle step.
107
117
  *
108
118
  * @example
109
- * // server-side couterpart of a plugin that creates a dedicated global shared
110
- * // state on which the server-side part can attach.
111
- * class MyPlugin extends Plugin {
119
+ * // server-side counterpart of a plugin that creates a dedicated global shared
120
+ * // state on which the client-side part can attach.
121
+ * class MyPlugin extends ServerPlugin {
112
122
  * constructor(server, id) {
113
123
  * super(server, id);
114
124
  *
115
- * this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
125
+ * this.server.stateManager.defineClass(`my-plugin:${this.id}`, {
116
126
  * someParam: {
117
127
  * type: 'boolean',
118
128
  * default: false,
@@ -137,20 +147,17 @@ class BasePlugin {
137
147
  /**
138
148
  * Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
139
149
  *
140
- * @param {client.Plugin~onStateChangeCallback|server.Plugin~onStateChangeCallback} callback -
141
- * Callback to execute when a state change is propagated.
142
- * @returns {client.Plugin~deleteOnStateChangeCallback|server.Plugin~deleteOnStateChangeCallback}
143
- * Execute the function to delete the listener from the callback list.
144
- * @see {@link client.Plugin#propagateStateChange}
145
- * @see {@link server.Plugin#propagateStateChange}
150
+ * @param {pluginOnStateChangeCallback} callback - Callback to execute when a state change is propagated.
151
+ * @returns {pluginDeleteOnStateChangeCallback}
152
+ *
146
153
  * @example
147
154
  * const unsubscribe = plugin.onStateChange(pluginState => console.log(pluginState));
148
155
  * // stop listening state changes
149
156
  * unsubscribe();
150
157
  */
151
158
  onStateChange(callback) {
152
- this._onStateChangeCallbacks.add(callback);
153
- return () => this._onStateChangeCallbacks.delete(callback);
159
+ this.#onStateChangeCallbacks.add(callback);
160
+ return () => this.#onStateChangeCallbacks.delete(callback);
154
161
  }
155
162
 
156
163
  /**
@@ -159,14 +166,13 @@ class BasePlugin {
159
166
  * through the `PluginManager#onStateChange` listeners.
160
167
  *
161
168
  * @param {object} updates - Updates to be merged in the plugin state.
162
- * @see {@link client.Plugin#onStateChange}
163
- * @see {@link server.Plugin#onStateChange}
164
- * @see {@link client.PluginManager#onStateChange}
165
- * @see {@link server.PluginManager#onStateChange}
169
+ *
170
+ * @see {@link BasePlugin#onStateChange}
171
+ * @see {@link BasePluginManager#onStateChange}
166
172
  */
167
173
  propagateStateChange(updates) {
168
- merge(this.state, updates);
169
- this._onStateChangeCallbacks.forEach(callback => callback(this.state));
174
+ this.state = merge(this.state, updates);
175
+ this.#onStateChangeCallbacks.forEach(callback => callback(this.state));
170
176
  }
171
177
  }
172
178