@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
@@ -1,4 +1,8 @@
1
1
  import Client from './Client.js';
2
+ import {
3
+ kClientContextManagerRegister,
4
+ } from './ClientContextManager.js';
5
+ import PromiseStore from '../common/PromiseStore.js';
2
6
  import {
3
7
  CONTEXT_ENTER_REQUEST,
4
8
  CONTEXT_ENTER_RESPONSE,
@@ -7,19 +11,19 @@ import {
7
11
  CONTEXT_EXIT_RESPONSE,
8
12
  CONTEXT_EXIT_ERROR,
9
13
  } from '../common/constants.js';
10
- import {
11
- storeRequestPromise,
12
- resolveRequest,
13
- rejectRequest,
14
- } from '../common/promise-store.js';
14
+
15
+ export const kClientContextStatus = Symbol('soundworks:client-context-status');
16
+
17
+ // share between all context, as channels are common to all contexts
18
+ const promiseStore = new PromiseStore('Context');
15
19
 
16
20
  /**
17
21
  * Base class to extend in order to implement a new Context.
18
22
  *
19
23
  * In the `soundworks` paradigm, a client has a "role" (e.g. _player_, _controller_)
20
- * see {@link client.Client#role}) and can be in different "contexts" (e.g. different
24
+ * see {@link Client#role}) and can be in different "contexts" (e.g. different
21
25
  * part of the experience such as sections of a music piece, etc.). This class
22
- * provides a simple and unified way to model these reccuring aspects of an application.
26
+ * provides a simple and unified way to model these recurring aspects of an application.
23
27
  *
24
28
  * You can also think of a `Context` as a state of a state machine from which a
25
29
  * client can `enter()` or `exit()` (be aware that `soundworks` does not provide
@@ -30,12 +34,12 @@ import {
30
34
  * the context. In such case, `soundworks` guarantees that the server-side
31
35
  * logic is executed before the `enter()` and `exit()` promises are fulfilled.
32
36
  *
33
- * ```
34
- * import { Client, Context } from '@soundworks/core/index.js'
37
+ * ```js
38
+ * import { Client, ClientContext } from '@soundworks/core/index.js'
35
39
  *
36
40
  * const client = new Client(config);
37
41
  *
38
- * class MyContext extends Context {
42
+ * class MyContext extends ClientContext {
39
43
  * async enter() {
40
44
  * await super.enter();
41
45
  * console.log(`client ${this.client.id} entered my context`);
@@ -54,73 +58,78 @@ import {
54
58
  * await new Promise(resolve => setTimeout(resolve, 2000));
55
59
  * await myContext.exit();
56
60
  * ```
57
- *
58
- * @memberof client
59
61
  */
60
- class Context {
62
+ class ClientContext {
63
+ #client = null;
64
+
61
65
  /**
62
- * @param {client.Client} client - The soundworks client instance.
66
+ * @param {Client} client - The soundworks client instance.
63
67
  * @throws Will throw if the first argument is not a soundworks client instance.
64
68
  */
65
69
  constructor(client) {
66
70
  if (!(client instanceof Client)) {
67
- throw new Error(`[soundworks:Context] Invalid argument, context "${this.name}" should receive a "soundworks.Client" instance as first argument`);
71
+ throw new TypeError(`Cannot create 'ClientContext' (${this.name}): Argument 1 must be an instance of Client`);
68
72
  }
69
73
 
70
- /**
71
- * The soundworks client instance.
72
- * @type {client.Client}
73
- * @readonly
74
- */
75
- this.client = client;
76
-
77
- /**
78
- * Status of the context, i.e. 'idle', 'inited', 'started', 'errored'
79
- * @type {string}
80
- * @readonly
81
- */
82
- this.status = 'idle';
83
-
84
- this.client.socket.addListener(CONTEXT_ENTER_RESPONSE, (reqId, contextName) => {
74
+ this.#client = client;
75
+ this[kClientContextStatus] = 'idle';
76
+
77
+ this.#client.socket.addListener(CONTEXT_ENTER_RESPONSE, (reqId, contextName) => {
85
78
  if (contextName !== this.name) {
86
79
  return;
87
80
  }
88
81
 
89
- resolveRequest(reqId);
82
+ promiseStore.resolve(reqId);
90
83
  });
91
84
 
92
- this.client.socket.addListener(CONTEXT_ENTER_ERROR, (reqId, contextName, msg) => {
85
+ this.#client.socket.addListener(CONTEXT_ENTER_ERROR, (reqId, contextName, msg) => {
93
86
  if (contextName !== this.name) {
94
87
  return;
95
88
  }
96
89
 
97
- rejectRequest(reqId, msg);
90
+ promiseStore.reject(reqId, `Cannot execute 'enter' on Context: ${msg}`);
98
91
  });
99
92
 
100
- this.client.socket.addListener(CONTEXT_EXIT_RESPONSE, (reqId, contextName) => {
93
+ this.#client.socket.addListener(CONTEXT_EXIT_RESPONSE, (reqId, contextName) => {
101
94
  if (contextName !== this.name) {
102
95
  return;
103
96
  }
104
97
 
105
- resolveRequest(reqId);
98
+ promiseStore.resolve(reqId);
106
99
  });
107
100
 
108
- this.client.socket.addListener(CONTEXT_EXIT_ERROR, (reqId, contextName, msg) => {
101
+ this.#client.socket.addListener(CONTEXT_EXIT_ERROR, (reqId, contextName, msg) => {
109
102
  if (contextName !== this.name) {
110
103
  return;
111
104
  }
112
105
 
113
- rejectRequest(reqId, msg);
106
+ promiseStore.reject(reqId, `Cannot execute 'exit' on Context: ${msg}`);
114
107
  });
115
108
 
116
- this.client.contextManager.register(this);
109
+ this.#client.contextManager[kClientContextManagerRegister](this);
110
+ }
111
+
112
+ /**
113
+ * The soundworks client instance.
114
+ * @type {Client}
115
+ */
116
+ get client() {
117
+ return this.#client;
118
+ }
119
+
120
+ /**
121
+ * Status of the context.
122
+ * @type {'idle'|'inited'|'started'|'errored'}
123
+ */
124
+ get status() {
125
+ return this[kClientContextStatus];
117
126
  }
118
127
 
119
128
  /**
120
- * Optionnal user-defined name of the context (defaults to the class name).
129
+ * Optional user-defined name of the context (defaults to the class name).
121
130
  *
122
131
  * The context manager will match the client-side and server-side contexts based
123
- * on this name. If the {@link server.ContextManager} don't find a corresponding
132
+ * on this name. If the {@link ServerContextManager} don't find a corresponding
124
133
  * user-defined context with the same name, it will use a default (noop) context.
125
134
  *
126
135
  * @readonly
@@ -138,10 +147,10 @@ class Context {
138
147
  }
139
148
 
140
149
  /**
141
- * Start the context. This method is lazilly called when the client enters the
142
- * context for the first time (cf. ${client.Context#enter}). If you know some
150
+ * Start the context. This method is lazily called when the client enters the
151
+ * context for the first time (cf. ${ClientContext#enter}). If you know some
143
152
  * some heavy and/or potentially long job has to be done when starting the context
144
- * (e.g. call to a web service) it may be a good practice to call it explicitely.
153
+ * (e.g. call to a web service) it may be a good practice to call it explicitly.
145
154
  *
146
155
  * This method should be implemented to perform operations that are valid for the
147
156
  * whole lifetime of the context, regardless the client enters or exits the context.
@@ -178,12 +187,12 @@ class Context {
178
187
  * done (e.g. creating a shared state, etc.) when the context is entered.
179
188
  *
180
189
  * If a server-side part of the context is defined (i.e. a context with the same
181
- * {@link client.Context#name}), the corresponding server-side `enter()` method
190
+ * {@link ClientContext#name}), the corresponding server-side `enter()` method
182
191
  * will be executed before the returned Promise is fulfilled.
183
192
  *
184
- * If the context has not been started yet, the `start` method is implicitely executed.
193
+ * If the context has not been started yet, the `start` method is implicitly executed.
185
194
  *
186
- * @returns {Promise} - Promise that resolves when the context is entered.
195
+ * @returns {Promise<void>} - Promise that resolves when the context is entered.
187
196
  * @example
188
197
  * class MyContext extends Context {
189
198
  * async enter() {
@@ -200,14 +209,14 @@ class Context {
200
209
  async enter() {
201
210
  // lazily start the context if registered after `client.start()`
202
211
  if (this.status !== 'started' && this.status !== 'errored') {
203
- await this.client.contextManager.get(this.name);
212
+ await this.#client.contextManager.get(this.name);
204
213
  }
205
214
 
206
215
  // we need the try/catch block to change the promise rejection into proper error
207
216
  try {
208
217
  await new Promise((resolve, reject) => {
209
- const reqId = storeRequestPromise(resolve, reject);
210
- this.client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);
218
+ const reqId = promiseStore.add(resolve, reject, 'ClientContext#enter');
219
+ this.#client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);
211
220
  });
212
221
  } catch (err) {
213
222
  throw new Error(err);
@@ -219,10 +228,10 @@ class Context {
219
228
  * done (e.g. delete a shared state, etc.) when the context is exited.
220
229
  *
221
230
  * If a server-side part of the context is defined (i.e. a context with the same
222
- * {@link client.Context#name}), the corresponding server-side `exit()` method
231
+ * {@link ClientContext#name}), the corresponding server-side `exit()` method
223
232
  * will be executed before the returned Promise is fulfilled.
224
233
  *
225
- * @returns {Promise} - Promise that resolves when the context is exited.
234
+ * @returns {Promise<void>} - Promise that resolves when the context is exited.
226
235
  * @example
227
236
  * class MyContext extends Context {
228
237
  * async enter() {
@@ -240,8 +249,8 @@ class Context {
240
249
  // we need the try/catch block to change the promise rejection into proper error
241
250
  try {
242
251
  await new Promise((resolve, reject) => {
243
- const reqId = storeRequestPromise(resolve, reject);
244
- this.client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);
252
+ const reqId = promiseStore.add(resolve, reject, 'ClientContext#exit');
253
+ this.#client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);
245
254
  });
246
255
  } catch (err) {
247
256
  throw new Error(err);
@@ -249,4 +258,4 @@ class Context {
249
258
  }
250
259
  }
251
260
 
252
- export default Context;
261
+ export default ClientContext;
@@ -0,0 +1,115 @@
1
+ import {
2
+ kClientContextStatus,
3
+ } from './ClientContext.js';
4
+
5
+ export const kClientContextManagerStart = Symbol('soundworks:client-context-manager-start');
6
+ export const kClientContextManagerStop = Symbol('soundworks:client-context-manager-stop');
7
+ export const kClientContextManagerRegister = Symbol('soundworks:client-context-manager-register');
8
+
9
+ /** @private */
10
+ class ContextCollection {
11
+ #inner = [];
12
+
13
+ constructor() {
14
+ this.#inner = [];
15
+ }
16
+
17
+ add(context) {
18
+ this.#inner.push(context);
19
+ }
20
+
21
+ has(name) {
22
+ return this.#inner.find(c => c.name === name) !== undefined;
23
+ }
24
+
25
+ get(name) {
26
+ return this.#inner.find(c => c.name === name);
27
+ }
28
+
29
+ map(func) {
30
+ return this.#inner.map(func);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Manage the different contexts and their lifecycle.
36
+ *
37
+ * See {@link Client#contextManager}
38
+ *
39
+ * _WARNING: In most cases, you should not have to manipulate the context manager directly._
40
+ *
41
+ * @hideconstructor
42
+ */
43
+ class ClientContextManager {
44
+ #contexts = new ContextCollection();
45
+ #contextStartPromises = new Map();
46
+
47
+ /**
48
+ * Register the context into the manager. Is called in context constructor.
49
+ *
50
+ * @param {ClientContext} context - The context to be registered.
51
+ * @private
52
+ */
53
+ [kClientContextManagerRegister](context) {
54
+ if (this.#contexts.has(context.name)) {
55
+ throw new DOMException(`Cannot register '${context.name}': a Context with same name has already been registered`, 'NotSupportedError');
56
+ }
57
+
58
+ this.#contexts.add(context);
59
+ }
60
+
61
+ /**
62
+ * Start all registered contexts. Called during `client.start()`
63
+ * @private
64
+ */
65
+ async [kClientContextManagerStart]() {
66
+ const promises = this.#contexts.map(context => this.get(context.name));
67
+ await Promise.all(promises);
68
+ }
69
+
70
+ /**
71
+ * Stop all registered contexts. Called during `client.stop()`
72
+ * @private
73
+ */
74
+ async [kClientContextManagerStop]() {
75
+ const promises = this.#contexts.map(context => context.stop());
76
+ await Promise.all(promises);
77
+ }
78
+
79
+ /**
80
+ * Retrieve a started context from its name.
81
+ *
82
+ * _WARNING: Most of the time, you should not have to call this method manually._
83
+ *
84
+ * @param {string} contextName - Name of the context.
85
+ * @returns {Promise<ClientContext>}
86
+ */
87
+ async get(contextName) {
88
+ if (!this.#contexts.has(contextName)) {
89
+ throw new ReferenceError(`Cannot execute 'get' on ClientContextManager: Context '${contextName}' is not registered`);
90
+ }
91
+
92
+ const context = this.#contexts.get(contextName);
93
+
94
+ if (this.#contextStartPromises.has(contextName)) {
95
+ const startPromise = this.#contextStartPromises.get(contextName);
96
+ await startPromise;
97
+ } else {
98
+ context[kClientContextStatus] = 'inited';
99
+
100
+ try {
101
+ const startPromise = context.start();
102
+ this.#contextStartPromises.set(contextName, startPromise);
103
+ await startPromise;
104
+ context[kClientContextStatus] = 'started';
105
+ } catch (err) {
106
+ context[kClientContextStatus] = 'errored';
107
+ throw new Error(`Cannot execute 'get' on ClientContextManager: ${err.message}`);
108
+ }
109
+ }
110
+
111
+ return context;
112
+ }
113
+ }
114
+
115
+ export default ClientContextManager;
@@ -3,14 +3,14 @@ import BasePlugin from '../common/BasePlugin.js';
3
3
  /**
4
4
  * Callback executed when the plugin state is updated.
5
5
  *
6
- * @callback client.Plugin~onStateChangeCallback
7
- * @param {client.Plugin#state} state - Current state of the plugin.
6
+ * @callback ClientPlugin~onStateChangeCallback
7
+ * @param {ClientPlugin#state} state - Current state of the plugin.
8
8
  */
9
9
 
10
10
  /**
11
- * Delete the registered {@link client.Plugin~onStateChangeCallback}.
11
+ * Delete the registered {@link ClientPlugin~onStateChangeCallback}.
12
12
  *
13
- * @callback client.Plugin~deleteOnStateChangeCallback
13
+ * @callback ClientPlugin~deleteOnStateChangeCallback
14
14
  */
15
15
 
16
16
  /**
@@ -24,44 +24,48 @@ import BasePlugin from '../common/BasePlugin.js';
24
24
  * have both a client-side and a server-side part.
25
25
  *
26
26
  * See [https://soundworks.dev/guide/ecosystem](https://soundworks.dev/guide/ecosystem)
27
- * for more informations on the available plugins.
27
+ * for more information on the available plugins.
28
28
  *
29
29
  * _Creating new plugins should be considered an advanced usage._
30
30
  *
31
- * @memberof client
32
31
  * @extends BasePlugin
33
32
  * @inheritdoc
34
33
  */
35
- class Plugin extends BasePlugin {
34
+ class ClientPlugin extends BasePlugin {
35
+ #client = null;
36
36
  /**
37
- * @param {client.Client} client - The soundworks client instance.
37
+ * @param {Client} client - A soundworks client instance.
38
38
  * @param {string} id - User defined id of the plugin as defined in
39
- * {@link client.PluginManager#register}.
39
+ * {@link ClientPluginManager#register}.
40
40
  */
41
41
  constructor(client, id) {
42
42
  super(id);
43
43
 
44
- /**
45
- * Instance of soundworks client.
46
- *
47
- * @type {client.Client}
48
- * @see {@link client.Client}
49
- */
50
- this.client = client;
44
+ this.#client = client;
45
+ }
46
+
47
+ /**
48
+ * Instance of soundworks client.
49
+ *
50
+ * @type {Client}
51
+ * @see {@link Client}
52
+ */
53
+ get client() {
54
+ return this.#client;
51
55
  }
52
56
 
53
57
  // @todo
54
58
  // /**
55
59
  // * Interface method to implement if specific logic should be done when a
56
- // * {@link client.Client} enters the plugin.
60
+ // * {@link Client} enters the plugin.
57
61
  // */
58
62
  // async activate() {}
59
63
  //
60
64
  // /**
61
65
  // * Interface method to implement if specific logic should be done when a
62
- // * {@link client.Client} exists the plugin.
66
+ // * {@link Client} exists the plugin.
63
67
  // */
64
68
  // async deactivate() {}
65
69
  }
66
70
 
67
- export default Plugin;
71
+ export default ClientPlugin;
@@ -1,38 +1,22 @@
1
1
  import BasePluginManager from '../common/BasePluginManager.js';
2
- import Plugin from './Plugin.js';
2
+ import ClientPlugin from './ClientPlugin.js';
3
3
  import Client from './Client.js';
4
4
 
5
- /**
6
- * Callback executed when a plugin internal state is updated.
7
- *
8
- * @callback client.PluginManager~onStateChangeCallback
9
- * @param {object<client.Plugin#id, client.Plugin>} fullState - List of all plugins.
10
- * @param {client.Plugin|null} initiator - Plugin that initiated the update or `null`
11
- * if the change was initiated by the state manager (i.e. when the initialization
12
- * of the plugins starts).
13
- */
14
-
15
- /**
16
- * Delete the registered {@link client.PluginManager~onStateChangeCallback}.
17
- *
18
- * @callback client.PluginManager~deleteOnStateChangeCallback
19
- */
20
-
21
5
  /**
22
6
  * The `PluginManager` allows to register and retrieve `soundworks` plugins.
23
7
  *
24
8
  * Plugins should always be registered both client-side and server-side,
25
- * and before {@link client.Client#start} or {@link server.Server#start}
9
+ * and before {@link Client#start} or {@link Server#start}
26
10
  * to be properly initialized.
27
11
  *
28
- * In some sitautions, you might want to register the same plugin factory several times
12
+ * In some situations, you might want to register the same plugin factory several times
29
13
  * using different ids (e.g. for watching several parts of the file system, etc.).
30
14
  *
31
15
  * Refer to the plugins' documentation for more precise examples, and the specific API
32
16
  * they expose. See [https://soundworks.dev/guide/ecosystem](https://soundworks.dev/guide/ecosystem)
33
- * for more informations on the available plugins.
17
+ * for more information on the available plugins.
34
18
  *
35
- * See {@link client.Client#pluginManager}
19
+ * See {@link Client#pluginManager}
36
20
  *
37
21
  * ```
38
22
  * // client-side
@@ -72,28 +56,50 @@ import Client from './Client.js';
72
56
  * }, 1000);
73
57
  * ```
74
58
  *
75
- * @memberof client
76
59
  * @extends BasePluginManager
77
60
  * @inheritdoc
78
- * @hideconstructor
61
+ * @template T
79
62
  */
80
- class PluginManager extends BasePluginManager {
63
+ class ClientPluginManager extends BasePluginManager {
81
64
  /**
82
- * @param {client.Client} client - The soundworks client instance.
65
+ * @param {Client} client - The soundworks client instance.
66
+ * @hideconstructor
83
67
  */
84
68
  constructor(client) {
85
69
  if (!(client instanceof Client)) {
86
- throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(client)" should receive an instance of "soundworks.Client" as argument`);
70
+ throw new TypeError(`Cannot construct ClientPluginManager: Argument 1 must be an instance of Client`);
87
71
  }
88
72
 
89
73
  super(client);
90
74
  }
91
75
 
92
- register(id, factory, options = {}, deps = []) {
93
- const ctor = factory(Plugin);
94
-
95
- if (!(ctor.prototype instanceof Plugin)) {
96
- throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" second argument should be a factory function returning a class extending the "Plugin" base class`);
76
+ /**
77
+ * Register a plugin into the manager.
78
+ *
79
+ * _A plugin must always be registered both on client-side and on server-side_
80
+ *
81
+ * Refer to the plugin documentation to check its options and proper way of
82
+ * registering it.
83
+ *
84
+ * @param {string} id - Unique id of the plugin. Enables the registration of the
85
+ * same plugin factory under different ids.
86
+ * @param {T<ClientPlugin>} ctor - The client-side Class of the plugin.
87
+ * @param {object} [options={}] - Options to configure the plugin.
88
+ * @param {array} [deps=[]] - List of plugins' names the plugin depends on, i.e.
89
+ * the plugin initialization will start only after the plugins it depends on are
90
+ * fully started themselves.
91
+ * @see {@link ClientPluginManager#register}
92
+ * @see {@link ServerPluginManager#register}
93
+ * @example
94
+ * // client-side
95
+ * client.pluginManager.register('user-defined-id', pluginFactory);
96
+ * // server-side
97
+ * server.pluginManager.register('user-defined-id', pluginFactory);
98
+ */
99
+ register(id, ctor, options = {}, deps = []) {
100
+ // Note that other arguments are checked on the BasePluginManager
101
+ if (!ctor || !(ctor.prototype instanceof ClientPlugin)) {
102
+ throw new TypeError(`Cannot execute 'register' on ClientPluginManager: argument 2 must be a class that extends 'ClientPlugin'`);
97
103
  }
98
104
 
99
105
  super.register(id, ctor, options, deps);
@@ -112,21 +118,18 @@ class PluginManager extends BasePluginManager {
112
118
  * on the `onStateChange` method.
113
119
  *
114
120
  * _Note: the async API is designed to enable the dynamic creation of plugins
115
- * (hopefully without brealing changes) in a future release._
116
- *
117
- * @param {client.Plugin#id} id - Id of the plugin as defined when registered.
118
- * @returns {client.Plugin}
119
- * @see {@link client.PluginManager#onStateChange}
121
+ * in a future release._
120
122
  *
121
- * @private
123
+ * @param {string} id - Id of the plugin as defined when registered.
124
+ * @returns {Promise<T<ClientPlugin>>}
122
125
  */
123
126
  async get(id) {
124
127
  if (this.status !== 'started') {
125
- throw new Error(`[soundworks.PluginManager] Cannot get plugin before "client.init()"`);
128
+ throw new DOMException(`Cannot execute 'get' on ClientPluginManager: 'Client#init' has not been called yet`, 'NotSupportedError');
126
129
  }
127
130
 
128
- return super.unsafeGet(id);
131
+ return super.getUnsafe(id);
129
132
  }
130
133
  }
131
134
 
132
- export default PluginManager;
135
+ export default ClientPluginManager;