@soundworks/core 4.0.0-alpha.27 → 4.0.0-alpha.29
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.
- package/.jsdoc +3 -1
- package/CHANGELOG.md +15 -5
- package/package.json +5 -5
- package/src/client/Client.js +78 -83
- package/src/client/{Context.js → ClientContext.js} +48 -38
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +22 -18
- package/src/client/{PluginManager.js → ClientPluginManager.js} +39 -35
- package/src/client/{Socket.js → ClientSocket.js} +54 -65
- package/src/client/{StateManager.js → ClientStateManager.js} +7 -8
- package/src/client/index.js +17 -41
- package/src/common/BasePlugin.js +56 -51
- package/src/common/BasePluginManager.js +175 -139
- package/src/common/BaseStateManager.js +316 -120
- package/src/common/BatchedTransport.js +24 -24
- package/src/common/ParameterBag.js +18 -6
- package/src/common/PromiseStore.js +1 -0
- package/src/common/{BaseSharedState.js → SharedState.js} +271 -199
- package/src/common/{BaseSharedStateCollection.js → SharedStateCollection.js} +92 -61
- package/src/common/logger.js +5 -0
- package/src/common/shared-state-types.js +76 -0
- package/src/common/version.js +1 -1
- package/src/server/Server.js +391 -354
- package/src/server/ServerClient.js +73 -0
- package/src/server/{Context.js → ServerContext.js} +85 -66
- package/src/server/{ContextManager.js → ServerContextManager.js} +98 -85
- package/src/server/{Plugin.js → ServerPlugin.js} +10 -24
- package/src/server/{PluginManager.js → ServerPluginManager.js} +76 -69
- package/src/server/{Socket.js → ServerSocket.js} +19 -18
- package/src/server/{Sockets.js → ServerSockets.js} +36 -27
- package/src/server/ServerStateManager.js +515 -0
- package/src/{common → server}/SharedStatePrivate.js +61 -26
- package/src/server/audit-network-latency.worker.js +9 -7
- package/src/server/audit-schema.js +1 -1
- package/src/server/crypto.js +2 -0
- package/src/server/index.js +18 -21
- package/types/client/Client.d.ts +74 -95
- package/types/client/{Context.d.ts → ClientContext.d.ts} +25 -26
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/{Plugin.d.ts → ClientPlugin.d.ts} +17 -20
- package/types/client/{PluginManager.d.ts → ClientPluginManager.d.ts} +13 -36
- package/types/client/ClientSocket.d.ts +74 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +11 -15
- package/types/client/index.d.ts +26 -2
- package/types/common/BasePlugin.d.ts +52 -46
- package/types/common/BasePluginManager.d.ts +68 -44
- package/types/common/BaseStateManager.d.ts +207 -97
- package/types/common/BatchedTransport.d.ts +1 -5
- package/types/common/ParameterBag.d.ts +16 -18
- package/types/common/PromiseStore.d.ts +1 -1
- package/types/common/SharedState.d.ts +297 -0
- package/types/common/{BaseSharedStateCollection.d.ts → SharedStateCollection.d.ts} +34 -18
- package/types/common/constants.d.ts +0 -2
- package/types/common/logger.d.ts +2 -1
- package/types/common/shared-state-types.d.ts +83 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +1 -2
- package/types/server/Server.d.ts +78 -132
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/{Context.d.ts → ServerContext.d.ts} +53 -43
- package/types/server/ServerContextManager.d.ts +80 -0
- package/types/server/{Plugin.d.ts → ServerPlugin.d.ts} +19 -33
- package/types/server/{PluginManager.d.ts → ServerPluginManager.d.ts} +39 -37
- package/types/server/ServerSocket.d.ts +85 -0
- package/types/server/ServerSockets.d.ts +50 -0
- package/types/server/ServerStateManager.d.ts +189 -0
- package/types/server/SharedStatePrivate.d.ts +21 -0
- package/types/server/audit-network-latency.worker.d.ts +1 -2
- package/types/server/audit-schema.d.ts +0 -1
- package/types/server/crypto.d.ts +2 -1
- package/types/server/index.d.ts +24 -2
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -38
- package/src/server/Client.js +0 -55
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -39
- package/src/server/StateManager.js +0 -696
- package/types/client/Client.d.ts.map +0 -1
- package/types/client/Context.d.ts.map +0 -1
- package/types/client/ContextManager.d.ts.map +0 -1
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts.map +0 -1
- package/types/client/SharedState.d.ts +0 -85
- package/types/client/SharedState.d.ts.map +0 -1
- package/types/client/SharedStateCollection.d.ts +0 -41
- package/types/client/SharedStateCollection.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -149
- package/types/client/Socket.d.ts.map +0 -1
- package/types/client/StateManager.d.ts.map +0 -1
- package/types/client/index.d.ts.map +0 -1
- package/types/common/BasePlugin.d.ts.map +0 -1
- package/types/common/BasePluginManager.d.ts.map +0 -1
- package/types/common/BaseSharedState.d.ts +0 -244
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseSharedStateCollection.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/BatchedTransport.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/PromiseStore.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -21
- package/types/common/SharedStatePrivate.d.ts.map +0 -1
- package/types/common/constants.d.ts.map +0 -1
- package/types/common/logger.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/version.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -45
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts.map +0 -1
- package/types/server/ContextManager.d.ts +0 -69
- package/types/server/ContextManager.d.ts.map +0 -1
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts.map +0 -1
- package/types/server/Server.d.ts.map +0 -1
- package/types/server/SharedState.d.ts +0 -86
- package/types/server/SharedState.d.ts.map +0 -1
- package/types/server/SharedStateCollection.d.ts +0 -41
- package/types/server/SharedStateCollection.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -164
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -80
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -405
- package/types/server/StateManager.d.ts.map +0 -1
- package/types/server/audit-network-latency.worker.d.ts.map +0 -1
- package/types/server/audit-schema.d.ts.map +0 -1
- package/types/server/crypto.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
package/src/common/BasePlugin.js
CHANGED
|
@@ -1,45 +1,43 @@
|
|
|
1
1
|
import merge from 'lodash/merge.js';
|
|
2
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
|
+
*/
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
|
-
* @
|
|
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
|
|
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
|
|
28
|
-
* @see {@link
|
|
29
|
-
* @see {@link
|
|
30
|
-
* @see {@link
|
|
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,11 +45,11 @@ class BasePlugin {
|
|
|
47
45
|
*
|
|
48
46
|
* @type {string}
|
|
49
47
|
* @readonly
|
|
50
|
-
* @see {@link
|
|
51
|
-
* @see {@link
|
|
48
|
+
* @see {@link ClientPluginManager#register}
|
|
49
|
+
* @see {@link ServerPluginManager#register}
|
|
52
50
|
*/
|
|
53
51
|
get id() {
|
|
54
|
-
return this
|
|
52
|
+
return this.#id;
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
/**
|
|
@@ -65,18 +63,28 @@ class BasePlugin {
|
|
|
65
63
|
* @readonly
|
|
66
64
|
*/
|
|
67
65
|
get type() {
|
|
68
|
-
return this.
|
|
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.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
79
|
+
* Start the plugin.
|
|
80
|
+
*
|
|
81
|
+
* This method is automatically called during the client or server `init()` lifecyle
|
|
82
|
+
* step. After `start()` is fulfilled the plugin should be ready to use.
|
|
75
83
|
*
|
|
76
84
|
* @example
|
|
77
85
|
* // server-side couterpart of a plugin that creates a dedicated global shared
|
|
78
86
|
* // state on which the server-side part can attach.
|
|
79
|
-
* class MyPlugin extends
|
|
87
|
+
* class MyPlugin extends ServerPlugin {
|
|
80
88
|
* constructor(server, id) {
|
|
81
89
|
* super(server, id);
|
|
82
90
|
*
|
|
@@ -102,13 +110,14 @@ class BasePlugin {
|
|
|
102
110
|
async start() {}
|
|
103
111
|
|
|
104
112
|
/**
|
|
105
|
-
* Stop the plugin.
|
|
106
|
-
*
|
|
113
|
+
* Stop the plugin.
|
|
114
|
+
*
|
|
115
|
+
* This method is automatically called during the client or server `stop()` lifecyle step.
|
|
107
116
|
*
|
|
108
117
|
* @example
|
|
109
118
|
* // server-side couterpart of a plugin that creates a dedicated global shared
|
|
110
|
-
* // state on which the
|
|
111
|
-
* class MyPlugin extends
|
|
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
|
*
|
|
@@ -137,20 +146,17 @@ class BasePlugin {
|
|
|
137
146
|
/**
|
|
138
147
|
* Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
|
|
139
148
|
*
|
|
140
|
-
* @param {
|
|
141
|
-
*
|
|
142
|
-
*
|
|
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.
|
|
153
|
-
return () => this.
|
|
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
|
-
*
|
|
163
|
-
* @see {@link
|
|
164
|
-
* @see {@link
|
|
165
|
-
* @see {@link server.PluginManager#onStateChange}
|
|
168
|
+
*
|
|
169
|
+
* @see {@link BasePlugin#onStateChange}
|
|
170
|
+
* @see {@link BasePluginManager#onStateChange}
|
|
166
171
|
*/
|
|
167
172
|
propagateStateChange(updates) {
|
|
168
173
|
merge(this.state, updates);
|
|
169
|
-
this.
|
|
174
|
+
this.#onStateChangeCallbacks.forEach(callback => callback(this.state));
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|
|
@@ -1,157 +1,135 @@
|
|
|
1
1
|
import { isPlainObject, isString } from '@ircam/sc-utils';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
kBasePluginStatus,
|
|
5
|
+
} from './BasePlugin.js';
|
|
6
|
+
|
|
3
7
|
import logger from './logger.js';
|
|
4
8
|
|
|
9
|
+
export const kPluginManagerStart = Symbol('soundworks:plugin-manager-start');
|
|
10
|
+
export const kPluginManagerStop = Symbol('soundworks:plugin-manager-stop');
|
|
11
|
+
export const kPluginManagerInstances = Symbol('soundworks:plugin-manager-instances');
|
|
12
|
+
|
|
5
13
|
/**
|
|
6
|
-
*
|
|
14
|
+
* Callback executed when a plugin internal state is updated.
|
|
7
15
|
*
|
|
8
|
-
* @
|
|
16
|
+
* @callback pluginManagerOnStateChangeCallback
|
|
17
|
+
* @param {object<string, ClientPlugin|ServerPlugin>} fullState - List of all plugins.
|
|
18
|
+
* @param {ClientPlugin|ServerPlugin|null} initiator - Plugin that initiated the
|
|
19
|
+
* update. The value is `null` if the change was initiated by the state manager
|
|
20
|
+
* (e.g. when the initialization of the plugins starts).
|
|
9
21
|
*/
|
|
10
|
-
class BasePluginManager {
|
|
11
|
-
constructor(node) {
|
|
12
|
-
// node may be either a soundworks server or a soundworks client
|
|
13
|
-
/** @private */
|
|
14
|
-
this._node = node;
|
|
15
|
-
/** @private */
|
|
16
|
-
this._dependencies = new Map();
|
|
17
|
-
/** @private */
|
|
18
|
-
this._instances = new Map();
|
|
19
|
-
/** @private */
|
|
20
|
-
this._instanceStartPromises = new Map();
|
|
21
|
-
/** @private */
|
|
22
|
-
this._onStateChangeCallbacks = new Set();
|
|
23
|
-
|
|
24
|
-
this.status = 'idle';
|
|
25
|
-
}
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* Refer to the plugin documentation to check its options and proper way of
|
|
33
|
-
* registering it.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} id - Unique id of the plugin. Enables the registration of the
|
|
36
|
-
* same plugin factory under different ids.
|
|
37
|
-
* @param {Function} factory - Factory function that returns the Plugin class.
|
|
38
|
-
* @param {object} [options={}] - Options to configure the plugin.
|
|
39
|
-
* @param {array} [deps=[]] - List of plugins' names the plugin depends on, i.e.
|
|
40
|
-
* the plugin initialization will start only after the plugins it depends on are
|
|
41
|
-
* fully started themselves.
|
|
42
|
-
* @see {@link client.PluginManager#register}
|
|
43
|
-
* @see {@link server.PluginManager#register}
|
|
44
|
-
* @example
|
|
45
|
-
* // client-side
|
|
46
|
-
* client.pluginManager.register('user-defined-id', pluginFactory);
|
|
47
|
-
* // server-side
|
|
48
|
-
* server.pluginManager.register('user-defined-id', pluginFactory);
|
|
49
|
-
*/
|
|
50
|
-
register(id, ctor, options = {}, deps = []) {
|
|
51
|
-
// For now we don't allow to register a plugin after `client|server.init()`.
|
|
52
|
-
// This is subject to change in the future as we may want to dynamically
|
|
53
|
-
// register new plugins during application lifetime.
|
|
54
|
-
if (this._node.status === 'inited') {
|
|
55
|
-
throw new Error(`[soundworks.PluginManager] Cannot register plugin (${id}) after "client.init()"`);
|
|
56
|
-
}
|
|
23
|
+
/**
|
|
24
|
+
* Delete the registered {@link pluginManagerOnStateChangeCallback}.
|
|
25
|
+
*
|
|
26
|
+
* @callback pluginManagerDeleteOnStateChangeCallback
|
|
27
|
+
*/
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
29
|
+
/** @private */
|
|
30
|
+
class BasePluginManager {
|
|
31
|
+
// node may be either the server or a client
|
|
32
|
+
#node = null;
|
|
33
|
+
#status = 'idle';
|
|
34
|
+
#dependencies = new Map();
|
|
35
|
+
#instanceStartPromises = new Map();
|
|
36
|
+
#onStateChangeCallbacks = new Set();
|
|
61
37
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
constructor(node) {
|
|
39
|
+
this.#node = node;
|
|
40
|
+
/** #private */
|
|
41
|
+
this[kPluginManagerInstances] = new Map();
|
|
42
|
+
}
|
|
65
43
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
#propagateStateChange(instance = null, status = null) {
|
|
45
|
+
if (instance !== null) {
|
|
46
|
+
// status is null if we forward some inner state change from the instance
|
|
47
|
+
if (status !== null) {
|
|
48
|
+
instance[kBasePluginStatus] = status;
|
|
49
|
+
}
|
|
69
50
|
|
|
70
|
-
|
|
71
|
-
|
|
51
|
+
const fullState = Object.fromEntries(this[kPluginManagerInstances]);
|
|
52
|
+
this.#onStateChangeCallbacks.forEach(callback => callback(fullState, instance));
|
|
53
|
+
} else {
|
|
54
|
+
const fullState = Object.fromEntries(this[kPluginManagerInstances]);
|
|
55
|
+
this.#onStateChangeCallbacks.forEach(callback => callback(fullState, null));
|
|
72
56
|
}
|
|
73
|
-
|
|
74
|
-
// we instanciate the plugin here, so that a plugin can register another one
|
|
75
|
-
// in its own constructor.
|
|
76
|
-
//
|
|
77
|
-
// the dependencies must be created first, so that the instance can call
|
|
78
|
-
// addDependency in its constructor
|
|
79
|
-
this._dependencies.set(id, deps);
|
|
80
|
-
|
|
81
|
-
const instance = new ctor(this._node, id, options);
|
|
82
|
-
this._instances.set(id, instance);
|
|
83
57
|
}
|
|
84
58
|
|
|
85
59
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
60
|
+
* Initialize all registered plugins.
|
|
61
|
+
*
|
|
62
|
+
* Executed during the `Client.init()` or `Server.init()` initialization step.
|
|
88
63
|
*
|
|
89
|
-
*/
|
|
90
|
-
addDependency(pluginId, dependencyId) {
|
|
91
|
-
const deps = this._dependencies.get(pluginId);
|
|
92
|
-
deps.push(dependencyId);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Returns the list of the registered plugins ids
|
|
97
|
-
* @returns {string[]}
|
|
98
|
-
*/
|
|
99
|
-
getRegisteredPlugins() {
|
|
100
|
-
return Array.from(this._instances.keys());
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Initialize all the registered plugin. Executed during the `Client.init()` or
|
|
105
|
-
* `Server.init()` initialization step.
|
|
106
64
|
* @private
|
|
107
65
|
*/
|
|
108
|
-
async
|
|
66
|
+
async [kPluginManagerStart]() {
|
|
109
67
|
logger.title('starting registered plugins');
|
|
110
68
|
|
|
111
|
-
if (this
|
|
69
|
+
if (this.#status !== 'idle') {
|
|
112
70
|
throw new Error(`[soundworks:PluginManager] Cannot call "pluginManager.init()" twice`);
|
|
113
71
|
}
|
|
114
72
|
|
|
115
|
-
this
|
|
73
|
+
this.#status = 'inited';
|
|
116
74
|
// instanciate all plugins
|
|
117
|
-
for (let
|
|
118
|
-
instance.onStateChange(
|
|
75
|
+
for (let instance of this[kPluginManagerInstances].values()) {
|
|
76
|
+
instance.onStateChange(() => this.#propagateStateChange(instance, null));
|
|
119
77
|
}
|
|
120
78
|
|
|
121
|
-
// propagate all 'idle'
|
|
122
|
-
this
|
|
79
|
+
// propagate all 'idle' status before start
|
|
80
|
+
this.#propagateStateChange(null, null);
|
|
123
81
|
|
|
124
|
-
const promises = Array.from(this.
|
|
82
|
+
const promises = Array.from(this[kPluginManagerInstances].keys()).map(id => this.getUnsafe(id));
|
|
125
83
|
|
|
126
84
|
try {
|
|
127
85
|
await Promise.all(promises);
|
|
128
|
-
this
|
|
86
|
+
this.#status = 'started';
|
|
129
87
|
} catch (err) {
|
|
130
|
-
this
|
|
88
|
+
this.#status = 'errored';
|
|
131
89
|
throw err; // throw initial error
|
|
132
90
|
}
|
|
133
91
|
}
|
|
134
92
|
|
|
135
93
|
/** @private */
|
|
136
|
-
async
|
|
137
|
-
for (let instance of this.
|
|
94
|
+
async [kPluginManagerStop]() {
|
|
95
|
+
for (let instance of this[kPluginManagerInstances].values()) {
|
|
138
96
|
await instance.stop();
|
|
139
97
|
}
|
|
140
98
|
}
|
|
141
99
|
|
|
142
100
|
/**
|
|
143
|
-
*
|
|
144
|
-
* that the pluginManager has started. This is required for starting the plugin
|
|
145
|
-
* manager itself and to require a plugin from within another plugin
|
|
101
|
+
* Status of the plugin manager
|
|
146
102
|
*
|
|
103
|
+
* @type {'idle'|'inited'|'started'|'errored'}
|
|
104
|
+
*/
|
|
105
|
+
get status() {
|
|
106
|
+
return this.#status;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Alias for existing plugins (i.e. plugin-scriptin), remove once updated
|
|
147
111
|
* @private
|
|
148
112
|
*/
|
|
149
113
|
async unsafeGet(id) {
|
|
114
|
+
return this.getUnsafe(id);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Retrieve an fully started instance of a registered plugin without checking
|
|
119
|
+
* that the pluginManager is started.
|
|
120
|
+
*
|
|
121
|
+
* This method is required for starting the plugin manager itself and to require
|
|
122
|
+
* a plugin from within another plugin.
|
|
123
|
+
*
|
|
124
|
+
* _Warning: Unless you are developing your own plugins, you should not have to use
|
|
125
|
+
* this method_
|
|
126
|
+
*/
|
|
127
|
+
async getUnsafe(id) {
|
|
150
128
|
if (!isString(id)) {
|
|
151
129
|
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.get(name)" argument should be a string`);
|
|
152
130
|
}
|
|
153
131
|
|
|
154
|
-
if (!this.
|
|
132
|
+
if (!this[kPluginManagerInstances].has(id)) {
|
|
155
133
|
throw new Error(`[soundworks:PluginManager] Cannot get plugin "${id}", plugin is not registered`);
|
|
156
134
|
}
|
|
157
135
|
|
|
@@ -159,48 +137,124 @@ class BasePluginManager {
|
|
|
159
137
|
// to be able to properly propagate the states. The code bellow should allow
|
|
160
138
|
// to dynamically register and launch plugins at runtime.
|
|
161
139
|
//
|
|
162
|
-
// if (!this.
|
|
163
|
-
// const { ctor, options } = this.
|
|
164
|
-
// const instance = new ctor(this
|
|
165
|
-
// this.
|
|
140
|
+
// if (!this[kPluginManagerInstances].has(id)) {
|
|
141
|
+
// const { ctor, options } = this.#dependencies.get(id);
|
|
142
|
+
// const instance = new ctor(this.#node, id, options);
|
|
143
|
+
// this[kPluginManagerInstances].set(id, instance);
|
|
166
144
|
// }
|
|
167
145
|
|
|
168
|
-
const instance = this.
|
|
146
|
+
const instance = this[kPluginManagerInstances].get(id);
|
|
169
147
|
|
|
170
148
|
// recursively get the dependency chain
|
|
171
|
-
const deps = this.
|
|
172
|
-
const promises = deps.map(id => this.
|
|
149
|
+
const deps = this.#dependencies.get(id);
|
|
150
|
+
const promises = deps.map(id => this.getUnsafe(id));
|
|
173
151
|
|
|
174
152
|
await Promise.all(promises);
|
|
175
153
|
|
|
176
154
|
// 'plugin.start' has already been called, just await the start promise
|
|
177
|
-
if (this.
|
|
178
|
-
await this.
|
|
155
|
+
if (this.#instanceStartPromises.has(id)) {
|
|
156
|
+
await this.#instanceStartPromises.get(id);
|
|
179
157
|
} else {
|
|
180
|
-
this
|
|
158
|
+
this.#propagateStateChange(instance, 'inited');
|
|
181
159
|
let errored = false;
|
|
182
160
|
|
|
183
161
|
try {
|
|
184
162
|
const startPromise = instance.start();
|
|
185
|
-
this.
|
|
163
|
+
this.#instanceStartPromises.set(id, startPromise);
|
|
186
164
|
|
|
187
165
|
await startPromise;
|
|
188
166
|
} catch (err) {
|
|
189
167
|
errored = true;
|
|
190
|
-
this
|
|
168
|
+
this.#propagateStateChange(instance, 'errored');
|
|
191
169
|
throw err;
|
|
192
170
|
}
|
|
193
171
|
|
|
194
172
|
// this looks silly but it prevents the try / catch to catch errors that could
|
|
195
173
|
// be triggered by the propagate status callback, putting the plugin in errored state
|
|
196
174
|
if (!errored) {
|
|
197
|
-
this
|
|
175
|
+
this.#propagateStateChange(instance, 'started');
|
|
198
176
|
}
|
|
199
177
|
}
|
|
200
178
|
|
|
201
179
|
return instance;
|
|
202
180
|
}
|
|
203
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Register a plugin into the manager.
|
|
184
|
+
*
|
|
185
|
+
* _A plugin must always be registered both on client-side and on server-side_
|
|
186
|
+
*
|
|
187
|
+
* Refer to the plugin documentation to check its options and proper way of
|
|
188
|
+
* registering it.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} id - Unique id of the plugin. Enables the registration of the
|
|
191
|
+
* same plugin factory under different ids.
|
|
192
|
+
* @param {Function} ctor - The class returned by the plugin factory method.
|
|
193
|
+
* @param {object} [options={}] - Options to configure the plugin.
|
|
194
|
+
* @param {array} [deps=[]] - List of plugins' names the plugin depends on, i.e.
|
|
195
|
+
* the plugin initialization will begin only after the plugins it depends on are
|
|
196
|
+
* fully started themselves.
|
|
197
|
+
* @see {@link ClientPluginManager#register}
|
|
198
|
+
* @see {@link ServerPluginManager#register}
|
|
199
|
+
* @example
|
|
200
|
+
* // client-side
|
|
201
|
+
* client.pluginManager.register('user-defined-id', pluginFactory);
|
|
202
|
+
* // server-side
|
|
203
|
+
* server.pluginManager.register('user-defined-id', pluginFactory);
|
|
204
|
+
*/
|
|
205
|
+
register(id, ctor, options = {}, deps = []) {
|
|
206
|
+
// For now we don't allow to register a plugin after `client|server.init()`.
|
|
207
|
+
// This is subject to change in the future as we may want to dynamically
|
|
208
|
+
// register new plugins during application lifetime.
|
|
209
|
+
if (this.#node.status === 'inited') {
|
|
210
|
+
throw new Error(`[soundworks.PluginManager] Cannot register plugin (${id}) after "client.init()"`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!isString(id)) {
|
|
214
|
+
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" first argument should be a string`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!isPlainObject(options)) {
|
|
218
|
+
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" third optionnal argument should be an object`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!Array.isArray(deps)) {
|
|
222
|
+
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" fourth optionnal argument should be an array`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this[kPluginManagerInstances].has(id)) {
|
|
226
|
+
throw new Error(`[soundworks:PluginManager] Plugin "${id}" already registered`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// We instanciate the plugin here, so that a plugin can register another one
|
|
230
|
+
// in its own constructor.
|
|
231
|
+
//
|
|
232
|
+
// The dependencies must be created first, so that the instance can call
|
|
233
|
+
// addDependency in its constructor
|
|
234
|
+
this.#dependencies.set(id, deps);
|
|
235
|
+
|
|
236
|
+
const instance = new ctor(this.#node, id, options);
|
|
237
|
+
this[kPluginManagerInstances].set(id, instance);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Manually add a dependency to a given plugin.
|
|
242
|
+
*
|
|
243
|
+
* Usefull to require a plugin within a plugin
|
|
244
|
+
*/
|
|
245
|
+
addDependency(pluginId, dependencyId) {
|
|
246
|
+
const deps = this.#dependencies.get(pluginId);
|
|
247
|
+
deps.push(dependencyId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Returns the list of the registered plugins ids
|
|
252
|
+
* @returns {string[]}
|
|
253
|
+
*/
|
|
254
|
+
getRegisteredPlugins() {
|
|
255
|
+
return Array.from(this[kPluginManagerInstances].keys());
|
|
256
|
+
}
|
|
257
|
+
|
|
204
258
|
/**
|
|
205
259
|
* Propagate a notification each time a plugin is updated (status or inner state).
|
|
206
260
|
* The callback will receive the list of all plugins as first parameter, and the
|
|
@@ -208,10 +262,8 @@ class BasePluginManager {
|
|
|
208
262
|
*
|
|
209
263
|
* _In most cases, you should not have to rely on this method._
|
|
210
264
|
*
|
|
211
|
-
* @param {
|
|
212
|
-
*
|
|
213
|
-
* @param {client.PluginManager~deleteOnStateChangeCallback|client.PluginManager~deleteOnStateChangeCallback}
|
|
214
|
-
* Function to execute to listening for changes.
|
|
265
|
+
* @param {pluginManagerOnStateChangeCallback} callback - Callback to execute on state change
|
|
266
|
+
* @returns {pluginManagerDeleteOnStateChangeCallback} - Clear the subscription when executed
|
|
215
267
|
* @example
|
|
216
268
|
* const unsubscribe = client.pluginManager.onStateChange(pluginList, initiator => {
|
|
217
269
|
* // log the current status of all plugins
|
|
@@ -227,24 +279,8 @@ class BasePluginManager {
|
|
|
227
279
|
* unsubscribe();
|
|
228
280
|
*/
|
|
229
281
|
onStateChange(callback) {
|
|
230
|
-
this.
|
|
231
|
-
return () => this.
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** @private */
|
|
235
|
-
_propagateStateChange(instance = null, status = null) {
|
|
236
|
-
if (instance !== null) {
|
|
237
|
-
// status is null if wew forward some inner state change from the instance
|
|
238
|
-
if (status !== null) {
|
|
239
|
-
instance.status = status;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const fullState = Object.fromEntries(this._instances);
|
|
243
|
-
this._onStateChangeCallbacks.forEach(callback => callback(fullState, instance));
|
|
244
|
-
} else {
|
|
245
|
-
const fullState = Object.fromEntries(this._instances);
|
|
246
|
-
this._onStateChangeCallbacks.forEach(callback => callback(fullState, null));
|
|
247
|
-
}
|
|
282
|
+
this.#onStateChangeCallbacks.add(callback);
|
|
283
|
+
return () => this.#onStateChangeCallbacks.delete(callback);
|
|
248
284
|
}
|
|
249
285
|
}
|
|
250
286
|
|