@soundworks/core 4.0.0-alpha.7 → 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.
Files changed (144) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +16 -50
  3. package/package.json +26 -44
  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 +63 -58
  13. package/src/common/BasePluginManager.js +191 -142
  14. package/src/common/BaseStateManager.js +636 -143
  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 +559 -821
  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 -517
  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,43 @@
1
- import merge from 'lodash.merge';
1
+ import merge from 'lodash/merge.js';
2
+
3
+ export const kBasePluginStatus = Symbol('soundworks:base-plugin-status');
4
+
5
+ /**
6
+ * Callback executed when the plugin state is updated.
7
+ *
8
+ * @callback pluginOnStateChangeCallback
9
+ * @param {BasePlugin#state} state - Current state of the plugin.
10
+ */
2
11
 
3
12
  /**
4
- * @private
13
+ * Delete the registered {@link pluginOnStateChangeCallback}.
14
+ *
15
+ * @callback pluginDeleteOnStateChangeCallback
5
16
  */
17
+
18
+ /** @private */
6
19
  class BasePlugin {
20
+ #id = null;
21
+ #onStateChangeCallbacks = new Set();
22
+
7
23
  constructor(id) {
24
+ this.#id = id;
8
25
  /** @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 = {};
26
+ this[kBasePluginStatus] = 'idle';
19
27
 
20
28
  /**
21
29
  * Placeholder that stores internal (local) state of the plugin. The state
22
30
  * should be modified through the `propagateStateChange` method to ensure
23
- * the change to be properly propagated to `onStateChange` callbacks.
31
+ * the change to be properly propagated to manager `onStateChange` callbacks.
24
32
  *
25
33
  * @type {object}
26
34
  * @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}
35
+ * @see {@link ClientPlugin#onStateChange}
36
+ * @see {@link ServerPlugin#onStateChange}
37
+ * @see {@link ClientPlugin#propagateStateChange}
38
+ * @see {@link ServerPlugin#propagateStateChange}
31
39
  */
32
40
  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
41
  }
44
42
 
45
43
  /**
@@ -47,17 +45,17 @@ class BasePlugin {
47
45
  *
48
46
  * @type {string}
49
47
  * @readonly
50
- * @see {@link client.PluginManager#register}
51
- * @see {@link server.PluginManager#register}
48
+ * @see {@link ClientPluginManager#register}
49
+ * @see {@link ServerPluginManager#register}
52
50
  */
53
51
  get id() {
54
- return this._id;
52
+ return this.#id;
55
53
  }
56
54
 
57
55
  /**
58
56
  * Type of the plugin, i.e. the ClassName.
59
57
  *
60
- * Usefull to do perform some logic based on certain types of plugins without
58
+ * Useful to do perform some logic based on certain types of plugins without
61
59
  * knowing under which `id` they have been registered. (e.g. creating some generic
62
60
  * views, etc.)
63
61
  *
@@ -65,22 +63,32 @@ class BasePlugin {
65
63
  * @readonly
66
64
  */
67
65
  get type() {
68
- return this._type;
66
+ return this.constructor.name;
67
+ }
68
+
69
+ /**
70
+ * Current status of the plugin.
71
+ *
72
+ * @type {'idle'|'inited'|'started'|'errored'}
73
+ */
74
+ get status() {
75
+ return this[kBasePluginStatus];
69
76
  }
70
77
 
71
78
  /**
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.
79
+ * Start the plugin.
80
+ *
81
+ * This method is automatically called during the client or server `init()` lifecycle
82
+ * step. After `start()` is fulfilled the plugin should be ready to use.
75
83
  *
76
84
  * @example
77
- * // server-side couterpart of a plugin that creates a dedicated global shared
85
+ * // server-side counterpart of a plugin that creates a dedicated global shared
78
86
  * // state on which the server-side part can attach.
79
- * class MyPlugin extends Plugin {
87
+ * class MyPlugin extends ServerPlugin {
80
88
  * constructor(server, id) {
81
89
  * super(server, id);
82
90
  *
83
- * this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
91
+ * this.server.stateManager.defineClass(`my-plugin:${this.id}`, {
84
92
  * someParam: {
85
93
  * type: 'boolean',
86
94
  * default: false,
@@ -102,17 +110,18 @@ class BasePlugin {
102
110
  async start() {}
103
111
 
104
112
  /**
105
- * Stop the plugin. This method is automatically called during the client or server
106
- * `stop()` lifecyle step.
113
+ * Stop the plugin.
114
+ *
115
+ * This method is automatically called during the client or server `stop()` lifecycle step.
107
116
  *
108
117
  * @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 {
118
+ * // server-side counterpart of a plugin that creates a dedicated global shared
119
+ * // state on which the client-side part can attach.
120
+ * class MyPlugin extends ServerPlugin {
112
121
  * constructor(server, id) {
113
122
  * super(server, id);
114
123
  *
115
- * this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
124
+ * this.server.stateManager.defineClass(`my-plugin:${this.id}`, {
116
125
  * someParam: {
117
126
  * type: 'boolean',
118
127
  * default: false,
@@ -137,20 +146,17 @@ class BasePlugin {
137
146
  /**
138
147
  * Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
139
148
  *
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}
149
+ * @param {pluginOnStateChangeCallback} callback - Callback to execute when a state change is propagated.
150
+ * @returns {pluginDeleteOnStateChangeCallback}
151
+ *
146
152
  * @example
147
153
  * const unsubscribe = plugin.onStateChange(pluginState => console.log(pluginState));
148
154
  * // stop listening state changes
149
155
  * unsubscribe();
150
156
  */
151
157
  onStateChange(callback) {
152
- this._onStateChangeCallbacks.add(callback);
153
- return () => this._onStateChangeCallbacks.delete(callback);
158
+ this.#onStateChangeCallbacks.add(callback);
159
+ return () => this.#onStateChangeCallbacks.delete(callback);
154
160
  }
155
161
 
156
162
  /**
@@ -159,14 +165,13 @@ class BasePlugin {
159
165
  * through the `PluginManager#onStateChange` listeners.
160
166
  *
161
167
  * @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}
168
+ *
169
+ * @see {@link BasePlugin#onStateChange}
170
+ * @see {@link BasePluginManager#onStateChange}
166
171
  */
167
172
  propagateStateChange(updates) {
168
- merge(this.state, updates);
169
- this._onStateChangeCallbacks.forEach(callback => callback(this.state));
173
+ this.state = merge(this.state, updates);
174
+ this.#onStateChangeCallbacks.forEach(callback => callback(this.state));
170
175
  }
171
176
  }
172
177