@soundworks/core 3.6.0 → 4.0.0-alpha.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 (125) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +57 -26
  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/src/common/BaseSharedState.js +473 -0
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +82 -122
  19. package/{common → src/common}/SharedStatePrivate.js +56 -64
  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 +119 -94
  33. package/{server → src/server}/Sockets.js +73 -61
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +183 -131
  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 -190
  108. package/client/PluginManager.js +0 -267
  109. package/client/Socket.js +0 -338
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedState.js +0 -480
  113. package/common/SharedStateManagerClient.js +0 -194
  114. package/common/Signal.js +0 -40
  115. package/common/SignalAll.js +0 -44
  116. package/common/shared-state-utils.js +0 -90
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -211
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -177
  122. package/server/Server.js +0 -746
  123. package/server/index.js +0 -49
  124. package/server/utils/logger.js +0 -134
  125. package/server.js +0 -28
@@ -0,0 +1,225 @@
1
+ import Client from './Client.js';
2
+ import {
3
+ CONTEXT_ENTER_REQUEST,
4
+ CONTEXT_ENTER_RESPONSE,
5
+ CONTEXT_ENTER_ERROR,
6
+ CONTEXT_EXIT_REQUEST,
7
+ CONTEXT_EXIT_RESPONSE,
8
+ CONTEXT_EXIT_ERROR,
9
+ } from '../common/constants.js';
10
+ import {
11
+ storeRequestPromise,
12
+ resolveRequest,
13
+ rejectRequest,
14
+ } from '../common/promise-store.js';
15
+
16
+ /**
17
+ * Base class to extend in order to implement a new Context.
18
+ *
19
+ * 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
21
+ * 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.
23
+ *
24
+ * You can also think of a `Context` as a state of a state machine from which a
25
+ * client can `enter()` or `exit()` (be aware that `soundworks` does not provide
26
+ * an implementation for the state machine).
27
+ *
28
+ * Optionally, a `Context` can also have a server-side counterpart to perform
29
+ * some logic (e.g. updating some global shared state) when a client enters or exits
30
+ * the context. In such case, `soundworks` guarantees that the server-side
31
+ * logic is executed before the `enter()` and `exit()` promises are fulfilled.
32
+ *
33
+ * ```
34
+ * import { Client, Context } from '@soundworks/core/index.js'
35
+ *
36
+ * const client = new Client(config);
37
+ *
38
+ * class MyContext extends Context {
39
+ * async enter() {
40
+ * await super.enter();
41
+ * console.log(`client ${this.client.id} entered my context`);
42
+ * }
43
+ *
44
+ * async exit() {
45
+ * await super.exit();
46
+ * console.log(`client ${this.client.id} exited my context`);
47
+ * }
48
+ * }
49
+ * const myContext = new MyContext(client);
50
+ *
51
+ * await client.start();
52
+ * await myContext.enter();
53
+ *
54
+ * await new Promise(resolve => setTimeout(resolve, 2000));
55
+ * await myContext.exit();
56
+ * ```
57
+ *
58
+ * @memberof client
59
+ */
60
+ class Context {
61
+ /**
62
+ * @param {client.Client} client - The soundworks client instance.
63
+ * @throws Will throw if the first argument is not a soundworks client instance.
64
+ */
65
+ constructor(client) {
66
+ if (!(client instanceof Client)) {
67
+ throw new Error(`[soundworks:Context] Invalid argument, context "${this.name}" should receive a "soundworks.Client" instance as first argument`);
68
+ }
69
+
70
+ /**
71
+ * The soundworks client instance.
72
+ */
73
+ this.client = client;
74
+
75
+ /**
76
+ * Status of the context, i.e. 'idle', 'inited', 'started', 'errored'
77
+ */
78
+ this.status = 'idle';
79
+
80
+ this.client.socket.addListener(CONTEXT_ENTER_RESPONSE, (reqId, contextName) => {
81
+ if (contextName !== this.name) {
82
+ return;
83
+ }
84
+
85
+ resolveRequest(reqId);
86
+ });
87
+
88
+ this.client.socket.addListener(CONTEXT_ENTER_ERROR, (reqId, contextName, msg) => {
89
+ if (contextName !== this.name) {
90
+ return;
91
+ }
92
+
93
+ rejectRequest(reqId, msg);
94
+ });
95
+
96
+ this.client.socket.addListener(CONTEXT_EXIT_RESPONSE, (reqId, contextName) => {
97
+ if (contextName !== this.name) {
98
+ return;
99
+ }
100
+
101
+ resolveRequest(reqId);
102
+ });
103
+
104
+ this.client.socket.addListener(CONTEXT_EXIT_ERROR, (reqId, contextName, msg) => {
105
+ if (contextName !== this.name) {
106
+ return;
107
+ }
108
+
109
+ rejectRequest(reqId, msg);
110
+ });
111
+
112
+ this.client.contextManager.register(this);
113
+ }
114
+
115
+ /**
116
+ * Name of the context. Defaults to the class name, override to use a user-defined name.
117
+ *
118
+ * @returns {string} - Name of the context.
119
+ * @example
120
+ * class MyContext extends Context {
121
+ * get name() {
122
+ * return 'my-user-defined-context-name';
123
+ * }
124
+ * }
125
+ */
126
+ get name() {
127
+ return this.constructor.name;
128
+ }
129
+
130
+ /**
131
+ * Start the context. This method is automatically called when `await client.start()`
132
+ * is called, or lazilly called when entering the context (i.e. `context.enter()`)
133
+ * if the context has been created after `client.start()`.
134
+ *
135
+ * In some situation, you might want to implement this method to handle application
136
+ * wise behavior regardeless the client enters or exists the context.
137
+ */
138
+ async start() {}
139
+
140
+ /**
141
+ * Stop the context. This method is automatically called when `await client.stop()`
142
+ * is called.
143
+ *
144
+ * In some situation, you might want to implement this method to handle application
145
+ * wise behavior regardeless the client enters or exists the context.
146
+ */
147
+ async stop() {}
148
+
149
+ /**
150
+ * Enter the context. Implement this method to define the logic that should be
151
+ * done (e.g. creating a shared state, etc.) when the context is entered.
152
+ *
153
+ * If a server-side part of the context is defined (i.e. a context with the same
154
+ * {@link client.Context#name}), the corresponding server-side `enter()` method
155
+ * will be executed before the returned Promise is fulfilled.
156
+ *
157
+ * If the context has not been started yet, the `start` method is implicitely executed.
158
+ *
159
+ * @returns {Promise} - Promise that resolves when the context is entered.
160
+ * @example
161
+ * class MyContext extends Context {
162
+ * async enter() {
163
+ * await super.enter();
164
+ * this.state = await this.client.stateManager.create('my-context-state');
165
+ * }
166
+ *
167
+ * async exit() {
168
+ * await super.exit();
169
+ * await this.state.delete();
170
+ * }
171
+ * }
172
+ */
173
+ async enter() {
174
+ // lazily start the context if registered after `client.start()`
175
+ if (this.status !== 'started' && this.status !== 'errored') {
176
+ await this.client.contextManager.get(this.name);
177
+ }
178
+
179
+ // we need the try/catch block to change the promise rejection into proper error
180
+ try {
181
+ await new Promise((resolve, reject) => {
182
+ const reqId = storeRequestPromise(resolve, reject);
183
+ this.client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);
184
+ });
185
+ } catch(err) {
186
+ throw new Error(err);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Exit the context. Implement this method to define the logic that should be
192
+ * done (e.g. delete a shared state, etc.) when the context is exited.
193
+ *
194
+ * If a server-side part of the context is defined (i.e. a context with the same
195
+ * {@link client.Context#name}), the corresponding server-side `exit()` method
196
+ * will be executed before the returned Promise is fulfilled.
197
+ *
198
+ * @returns {Promise} - Promise that resolves when the context is exited.
199
+ * @example
200
+ * class MyContext extends Context {
201
+ * async enter() {
202
+ * await super.enter();
203
+ * this.state = await this.client.stateManager.create('my-context-state');
204
+ * }
205
+ *
206
+ * async exit() {
207
+ * await super.exit();
208
+ * await this.state.delete();
209
+ * }
210
+ * }
211
+ */
212
+ async exit() {
213
+ // we need the try/catch block to change the promise rejection into proper error
214
+ try {
215
+ await new Promise((resolve, reject) => {
216
+ const reqId = storeRequestPromise(resolve, reject);
217
+ this.client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);
218
+ });
219
+ } catch(err) {
220
+ throw new Error(err);
221
+ }
222
+ }
223
+ }
224
+
225
+ export default Context;
@@ -0,0 +1,111 @@
1
+ /** @private */
2
+ class ContextCollection {
3
+ constructor() {
4
+ this.inner = [];
5
+ }
6
+
7
+ add(context) {
8
+ this.inner.push(context);
9
+ }
10
+
11
+ has(name) {
12
+ return this.inner.find(c => c.name === name) !== undefined;
13
+ }
14
+
15
+ get(name) {
16
+ return this.inner.find(c => c.name === name);
17
+ }
18
+
19
+ map(func) {
20
+ return this.inner.map(func);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Manage the different contexts and their lifecycle.
26
+ *
27
+ * See {@link client.Client#contextManager}
28
+ *
29
+ * _WARNING: In most cases, you should not have to manipulate the context manager directly._
30
+ *
31
+ * @memberof client
32
+ * @hideconstructor
33
+ */
34
+ class ContextManager {
35
+ constructor() {
36
+ /** @private */
37
+ this._contexts = new ContextCollection();
38
+ /** @private */
39
+ this._contextStartPromises = new Map();
40
+ }
41
+
42
+ /**
43
+ * Register the context into the manager. Is called in context constructor.
44
+ *
45
+ * @param {client.Context} context - The context to be registered.
46
+ * @private
47
+ */
48
+ register(context) {
49
+ if (this._contexts.has(context.name)) {
50
+ throw new Error(`[soundworks:context-manager] Context "${context.name}" already registered`);
51
+ }
52
+
53
+ this._contexts.add(context);
54
+ }
55
+
56
+ /**
57
+ * Retrieve a started context from its name.
58
+ *
59
+ * _WARNING: Most of the time, you should not have to call this method manually._
60
+ *
61
+ * @param {client.Context#name} contextName - Name of the context.
62
+ */
63
+ async get(contextName) {
64
+ if (!this._contexts.has(contextName)) {
65
+ throw new Error(`[soundworks:ContextManager] Can't get context "${contextName}", not registered`);
66
+ }
67
+
68
+ const context = this._contexts.get(contextName);
69
+
70
+ if (this._contextStartPromises.has(contextName)) {
71
+ const startPromise = this._contextStartPromises.get(contextName);
72
+ await startPromise;
73
+ } else {
74
+ context.status = 'inited';
75
+
76
+ try {
77
+ const startPromise = context.start();
78
+ this._contextStartPromises.set(contextName, startPromise);
79
+ await startPromise;
80
+ context.status = 'started';
81
+ } catch(err) {
82
+ context.status = 'errored';
83
+ throw new Error(err);
84
+ }
85
+ }
86
+
87
+ return context;
88
+ }
89
+
90
+ /**
91
+ * Start all registered contexts. Called during `client.start()`
92
+ *
93
+ * @private
94
+ */
95
+ async start() {
96
+ const promises = this._contexts.map(context => this.get(context.name));
97
+ await Promise.all(promises);
98
+ }
99
+
100
+ /**
101
+ * Stop all registered contexts. Called during `client.stop()`
102
+ *
103
+ * @private
104
+ */
105
+ async stop() {
106
+ const promises = this._contexts.map(context => context.stop());
107
+ await Promise.all(promises);
108
+ }
109
+ }
110
+
111
+ export default ContextManager;
@@ -0,0 +1,66 @@
1
+ import BasePlugin from '../common/BasePlugin.js';
2
+
3
+ /**
4
+ * Callback executed when the plugin state is updated.
5
+ *
6
+ * @callback client.Plugin~onStateChangeCallback
7
+ * @param {client.Plugin#state} state - Current state of the plugin.
8
+ */
9
+
10
+ /**
11
+ * Delete the registered {@link client.Plugin~onStateChangeCallback}.
12
+ *
13
+ * @callback client.Plugin~deleteOnStateChangeCallback
14
+ */
15
+
16
+ /**
17
+ * Base class to extend in order to create new `soundworks` plugins.
18
+ *
19
+ * In the `soundworks` paradigm, a plugin is a component that allows to extend
20
+ * the framework capabilities by encapsulating common and reusable logic in
21
+ * an application wise perspective. For example, plugins are available to handle
22
+ * clock synchronization, to deal with the file system, etc. Plugin should always
23
+ * have both a client-side and a server-side part.
24
+ *
25
+ * See [https://soundworks.dev/guide/ecosystem](https://soundworks.dev/guide/ecosystem)
26
+ * for more informations on the available plugins.
27
+ *
28
+ * _Creating new plugins should be considered an advanced usage._
29
+ *
30
+ * @memberof client
31
+ * @extends BasePlugin
32
+ * @inheritdoc
33
+ */
34
+ class Plugin extends BasePlugin {
35
+ /**
36
+ * @param {client.Client} client - The soundworks client instance.
37
+ * @param {string} id - User defined id of the plugin as defined in
38
+ * {@link client.PluginManager#register}.
39
+ */
40
+ constructor(client, id) {
41
+ super(id);
42
+
43
+ /**
44
+ * Instance of soundworks client.
45
+ *
46
+ * @type {client.Client}
47
+ * @see {@link client.Client}
48
+ */
49
+ this.client = client;
50
+ }
51
+
52
+ // @todo
53
+ // /**
54
+ // * Interface method to implement if specific logic should be done when a
55
+ // * {@link client.Client} enters the plugin.
56
+ // */
57
+ // async activate() {}
58
+ //
59
+ // /**
60
+ // * Interface method to implement if specific logic should be done when a
61
+ // * {@link client.Client} exists the plugin.
62
+ // */
63
+ // async deactivate() {}
64
+ }
65
+
66
+ export default Plugin;
@@ -0,0 +1,123 @@
1
+ import BasePluginManager from '../common/BasePluginManager.js';
2
+ import Plugin from './Plugin.js';
3
+ import Client from './Client.js';
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
+ /**
22
+ * The `PluginManager` allows to register and retrieve `soundworks` plugins.
23
+ *
24
+ * Plugins should always be registered both client-side and server-side,
25
+ * and before {@link client.Client#start} or {@link server.Server#start}
26
+ * are called for proper initialization.
27
+ *
28
+ * In some sitautions, you might want to register the same plugin factory several times
29
+ * using different ids (e.g. for watching several parts of the file system, etc.).
30
+ *
31
+ * Refer to the plugins' documentation for more precise examples, and the specific API
32
+ * they expose. See [https://soundworks.dev/guide/ecosystem](https://soundworks.dev/guide/ecosystem)
33
+ * for more informations on the available plugins.
34
+ *
35
+ * See {@link client.Client#pluginManager}
36
+ *
37
+ * ```
38
+ * import { Client } from '@soundworks/core/client.js';
39
+ * import platformPlugin from '@soundworks/plugin-sync/client.js';
40
+ *
41
+ * const client = new Client(config);
42
+ * // register the plugin before `client.start()`
43
+ * client.pluginManager.register('sync', pluginSync);
44
+ *
45
+ * await client.start();
46
+ *
47
+ * const sync = await client.pluginManager.get('sync');
48
+ *
49
+ * setInterval(() => {
50
+ * // log the estimated global synced clock alongside the local clock.
51
+ * console.log(sync.getSyncTime(), sync.getLocalTime());
52
+ * }, 1000);
53
+ * ```
54
+ *
55
+ * @memberof client
56
+ * @extends BasePluginManager
57
+ * @inheritdoc
58
+ * @hideconstructor
59
+ */
60
+ class PluginManager extends BasePluginManager {
61
+ /**
62
+ * @param {client.Client} client - The soundworks client instance.
63
+ */
64
+ constructor(client) {
65
+ if (!(client instanceof Client)) {
66
+ throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(client)" should receive an instance of "soundworks.Client as argument"`);
67
+ }
68
+
69
+ super(client);
70
+ }
71
+
72
+ register(id, factory, options = {}, deps = []) {
73
+ const ctor = factory(Plugin);
74
+
75
+ if (!(ctor.prototype instanceof Plugin)) {
76
+ throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" second argument should be a factory function returning a class extending the "Plugin" base class`);
77
+ }
78
+
79
+ super.register(id, ctor, options, deps);
80
+ }
81
+
82
+ /**
83
+ * Retrieve an fully started instance of a registered plugin.
84
+ *
85
+ * Be aware that the `get` method resolves only when the plugin is fully 'started',
86
+ * which is what we want 99.99% of the time. As such, and to prevent the application
87
+ * to be stuck in some kind of Promise dead lock, this method will throw if
88
+ * used before `client.init()` (or `client.start()`).
89
+ *
90
+ * To handle the remaining 0.01% cases and access plugin instances during their
91
+ * initialization (e.g. to display initialization screen, etc.), you should rely
92
+ * on the `onStateChange` method.
93
+ *
94
+ * _Note: the async API is designed to enable the dynamic creation of plugins (hopefully
95
+ * without brealing changes) in a future release._
96
+ *
97
+ * @param {client.Plugin#id} id - Id of the plugin as defined when registered.
98
+ * @returns {client.Plugin}
99
+ * @see {@link client.PluginManager#onStateChange}
100
+ *
101
+ * @private
102
+ */
103
+ async get(id) {
104
+ if (this.status !== 'started') {
105
+ throw new Error(`[soundworks.PluginManager] Cannot get plugin before "client.init()"`);
106
+ }
107
+
108
+ return super.getUnsafe(id);
109
+ }
110
+
111
+ // client only methods
112
+ // ------------------------------------------------------------
113
+
114
+ /**
115
+ * @protected
116
+ * @ignore
117
+ */
118
+ getRegisteredPlugins() {
119
+ return Array.from(this._registeredPlugins.keys());
120
+ }
121
+ }
122
+
123
+ export default PluginManager;
@@ -0,0 +1,84 @@
1
+ import BaseSharedState from '../common/BaseSharedState.js';
2
+
3
+ // this file exists only for documentation purposes
4
+
5
+ /**
6
+ * @callback client.SharedState~onUpdateCallback
7
+ * @param {Object} newValues - Key / value pairs of the updates that have been
8
+ * applied to the state.
9
+ * @param {Object} oldValues - Key / value pairs of the updated params before
10
+ * the updates has been applied to the state.
11
+ * @param {Mixed} [context=null] - Optionnal context object that has been passed
12
+ * with the values updates in the `set` call.
13
+ */
14
+
15
+ /**
16
+ * Delete the registered {@link client.SharedState~onUpdateCallback}.
17
+ *
18
+ * @callback client.SharedState~deleteOnUpdateCallback
19
+ */
20
+
21
+
22
+ /**
23
+ * The `SharedState` is one of the most important and versatile abstraction provided
24
+ * by `soundworks`. It represents a set of parameters that are synchronized accross
25
+ * every nodes of the application (clients and server) that declared some interest
26
+ * to the shared state.
27
+ *
28
+ * A `SharedState` is created according to a "schema" (in the sense of a database
29
+ * schema) that must be declared and registered server-side. Any number of `SharedState`s
30
+ * can be created from a single schema.
31
+ *
32
+ * A shared state can be created both by the clients or by the server (in which case
33
+ * it is generally considered as a global state of the application). Similarly any
34
+ * node of the application (clients or server) can declare interest and "attach" to
35
+ * a state created by another node. All node attached to a state can modify its values
36
+ * and/or react to the modifications applied by other nodes.
37
+ *
38
+ * Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
39
+ *
40
+ * ```
41
+ * // server-side
42
+ * import { Server } from '@soundworks/server/index.js';
43
+ *
44
+ * const server = new Server(config);
45
+ * // declare and register the schema of a shared state.
46
+ * server.stateManager.registerSchema('some-global-state', {
47
+ * myRandom: {
48
+ * type: 'float',
49
+ * default: 0,
50
+ * }
51
+ * });
52
+ *
53
+ * await server.start();
54
+ *
55
+ * // create a global state server-side
56
+ * const globalState = await server.stateManager.create('some-global-state');
57
+ * // listen and react to the changes made by the clients
58
+ * globalState.onUpdate(updates => console.log(updates));
59
+ * ```
60
+ *
61
+ * ```
62
+ * // client-side
63
+ * import { Client } from '@soundworks/client.index.js';
64
+ *
65
+ * const client = new Client(config);
66
+ * await client.start();
67
+ *
68
+ * // attach to the global state created by the server
69
+ * const globalState = await client.stateManager.attach('some-global-state');
70
+ *
71
+ * // update the value of a `myRandom` parameter every seconds
72
+ * setInterval(() => {
73
+ * globalState.set({ myRandom: Math.random() });
74
+ * }, 1000);
75
+ * ```
76
+ *
77
+ * @memberof client
78
+ * @extends BaseSharedState
79
+ * @inheritdoc
80
+ * @hideconstructor
81
+ */
82
+ class SharedState extends BaseSharedState {}
83
+
84
+ export default SharedState;