@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.
- package/CHANGELOG.md +102 -0
- package/README.md +14 -29
- package/package.json +24 -42
- package/src/client/Client.js +306 -256
- package/src/client/{Context.js → ClientContext.js} +63 -54
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
- package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
- package/src/client/ClientSocket.js +270 -0
- package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
- package/src/client/index.js +36 -39
- package/src/common/BasePlugin.js +64 -58
- package/src/common/BasePluginManager.js +191 -142
- package/src/common/BaseStateManager.js +635 -145
- package/src/common/BatchedTransport.js +73 -0
- package/src/common/ParameterBag.js +90 -93
- package/src/common/PromiseStore.js +54 -0
- package/src/common/SharedState.js +821 -0
- package/src/common/SharedStateCollection.js +492 -0
- package/src/common/constants.js +14 -2
- package/src/common/logger.js +64 -68
- package/src/common/shared-state-types.js +82 -0
- package/src/common/sockets-utils.js +0 -64
- package/src/common/version.js +2 -0
- package/src/server/Server.js +552 -824
- package/src/server/ServerClient.js +74 -0
- package/src/server/{Context.js → ServerContext.js} +96 -77
- package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
- package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
- package/src/server/ServerPluginManager.js +177 -0
- package/src/server/ServerSocket.js +256 -0
- package/src/server/ServerSockets.js +150 -0
- package/src/server/ServerStateManager.js +706 -0
- package/src/server/SharedStatePrivate.js +277 -0
- package/src/server/audit-network-latency.worker.js +19 -4
- package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
- package/src/server/create-http-server.js +127 -0
- package/src/server/crypto.js +8 -7
- package/src/server/index.js +60 -17
- package/types/client/Client.d.ts +95 -70
- package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/ClientPlugin.d.ts +51 -0
- package/types/client/ClientPluginManager.d.ts +112 -0
- package/types/client/ClientSocket.d.ts +76 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
- package/types/client/index.d.ts +33 -6
- package/types/common/BasePlugin.d.ts +64 -53
- package/types/common/BasePluginManager.d.ts +84 -39
- package/types/common/BaseStateManager.d.ts +227 -49
- package/types/common/BatchedTransport.d.ts +17 -0
- package/types/common/ParameterBag.d.ts +73 -76
- package/types/common/PromiseStore.d.ts +11 -0
- package/types/common/SharedState.d.ts +291 -0
- package/types/common/SharedStateCollection.d.ts +311 -0
- package/types/common/constants.d.ts +10 -3
- package/types/common/logger.d.ts +8 -6
- package/types/common/shared-state-types.d.ts +95 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +2 -0
- package/types/server/Server.d.ts +215 -136
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/ServerContext.d.ts +209 -0
- package/types/server/ServerContextManager.d.ts +75 -0
- package/types/server/ServerPlugin.d.ts +57 -0
- package/types/server/ServerPluginManager.d.ts +115 -0
- package/types/server/ServerSocket.d.ts +69 -0
- package/types/server/ServerSockets.d.ts +42 -0
- package/types/server/ServerStateManager.d.ts +284 -0
- package/types/server/SharedStatePrivate.d.ts +23 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -2
- package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
- package/types/server/create-http-server.d.ts +5 -0
- package/types/server/crypto.d.ts +4 -0
- package/types/server/index.d.ts +51 -3
- package/.eslintrc +0 -15
- package/.github/workflows/gh-pages.yml +0 -31
- package/.github/workflows/unit-tests.yml +0 -18
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -35
- package/src/client/Socket.js +0 -402
- package/src/common/BaseSharedState.js +0 -583
- package/src/common/BaseSharedStateCollection.js +0 -179
- package/src/common/SharedStatePrivate.js +0 -192
- package/src/common/promise-store.js +0 -25
- package/src/server/Client.js +0 -55
- package/src/server/PluginManager.js +0 -170
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -36
- package/src/server/Socket.js +0 -396
- package/src/server/Sockets.js +0 -224
- package/src/server/StateManager.js +0 -620
- package/tsconfig.json +0 -21
- 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 +0 -53
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts +0 -96
- 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/Socket.d.ts +0 -138
- 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 -173
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -20
- 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/promise-store.d.ts +0 -4
- package/types/common/promise-store.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/utils.d.ts +0 -5
- package/types/common/utils.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -40
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts +0 -59
- 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 +0 -70
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts +0 -91
- 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/Socket.d.ts +0 -128
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -73
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -190
- 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/index.d.ts.map +0 -1
|
@@ -1,206 +1,273 @@
|
|
|
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
|
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Delete the registered {@link pluginManagerOnStateChangeCallback}.
|
|
25
|
+
*
|
|
26
|
+
* @callback pluginManagerDeleteOnStateChangeCallback
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** @private */
|
|
10
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();
|
|
37
|
+
|
|
11
38
|
constructor(node) {
|
|
12
|
-
|
|
13
|
-
/** @private */
|
|
14
|
-
this._node = node;
|
|
39
|
+
this.#node = node;
|
|
15
40
|
/** @private */
|
|
16
|
-
this
|
|
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';
|
|
41
|
+
this[kPluginManagerInstances] = new Map();
|
|
25
42
|
}
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!isString(id)) {
|
|
59
|
-
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" first argument should be a string`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (!isPlainObject(options)) {
|
|
63
|
-
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" third optionnal argument should be an object`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!Array.isArray(deps)) {
|
|
67
|
-
throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.register" fourth optionnal argument should be an array`);
|
|
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 {@link Client#init} or {@link Client#stop} 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
|
|
112
|
-
throw new
|
|
69
|
+
if (this.#status !== 'idle') {
|
|
70
|
+
throw new DOMException(`Cannot execute 'kPluginManagerStart' on BasePluginManager: Lifecycle methods must be called in following order: kPluginManagerStart, kPluginManagerStop`, 'InvalidAccessError');
|
|
113
71
|
}
|
|
114
72
|
|
|
115
|
-
this
|
|
116
|
-
//
|
|
117
|
-
for (let
|
|
118
|
-
instance.onStateChange(
|
|
73
|
+
this.#status = 'inited';
|
|
74
|
+
// instantiate all plugins
|
|
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
|
-
|
|
94
|
+
async [kPluginManagerStop]() {
|
|
95
|
+
if (this.#status !== 'started') {
|
|
96
|
+
throw new DOMException(`Cannot execute 'kPluginManagerStop' on BasePluginManager: Lifecycle methods must be called in following order: kPluginManagerStart, kPluginManagerStop`, 'InvalidAccessError');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (let instance of this[kPluginManagerInstances].values()) {
|
|
138
100
|
await instance.stop();
|
|
139
101
|
}
|
|
140
102
|
}
|
|
141
103
|
|
|
142
104
|
/**
|
|
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
|
|
105
|
+
* Status of the plugin manager
|
|
146
106
|
*
|
|
107
|
+
* @type {'idle'|'inited'|'started'|'errored'}
|
|
108
|
+
*/
|
|
109
|
+
get status() {
|
|
110
|
+
return this.#status;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Alias for existing plugins (i.e. plugin-scripting), remove once updated
|
|
147
115
|
* @private
|
|
116
|
+
* @deprecated
|
|
148
117
|
*/
|
|
149
118
|
async unsafeGet(id) {
|
|
119
|
+
return this.getUnsafe(id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Retrieve an fully started instance of a registered plugin without checking
|
|
124
|
+
* that the pluginManager is started.
|
|
125
|
+
*
|
|
126
|
+
* This method is required for starting the plugin manager itself and to require
|
|
127
|
+
* a plugin from within another plugin.
|
|
128
|
+
*
|
|
129
|
+
* _Warning: Unless you are developing your own plugins, you should not have to use
|
|
130
|
+
* this method_
|
|
131
|
+
*/
|
|
132
|
+
async getUnsafe(id) {
|
|
150
133
|
if (!isString(id)) {
|
|
151
|
-
throw new
|
|
134
|
+
throw new TypeError(`Cannot execute 'get' on BasePluginManager: Argument 1 must be of type string`);
|
|
152
135
|
}
|
|
153
136
|
|
|
154
|
-
if (!this.
|
|
155
|
-
throw new
|
|
137
|
+
if (!this[kPluginManagerInstances].has(id)) {
|
|
138
|
+
throw new ReferenceError(`Cannot execute 'get' on BasePluginManager: Plugin '${id}' is not registered`);
|
|
156
139
|
}
|
|
157
140
|
|
|
158
141
|
// @note - For now, all instances are created at the beginning of `start()`
|
|
159
142
|
// to be able to properly propagate the states. The code bellow should allow
|
|
160
143
|
// to dynamically register and launch plugins at runtime.
|
|
161
144
|
//
|
|
162
|
-
// if (!this.
|
|
163
|
-
// const { ctor, options } = this.
|
|
164
|
-
// const instance = new ctor(this
|
|
165
|
-
// this.
|
|
145
|
+
// if (!this[kPluginManagerInstances].has(id)) {
|
|
146
|
+
// const { ctor, options } = this.#dependencies.get(id);
|
|
147
|
+
// const instance = new ctor(this.#node, id, options);
|
|
148
|
+
// this[kPluginManagerInstances].set(id, instance);
|
|
166
149
|
// }
|
|
167
150
|
|
|
168
|
-
const instance = this.
|
|
151
|
+
const instance = this[kPluginManagerInstances].get(id);
|
|
169
152
|
|
|
170
153
|
// recursively get the dependency chain
|
|
171
|
-
const deps = this.
|
|
172
|
-
const promises = deps.map(id => this.
|
|
154
|
+
const deps = this.#dependencies.get(id);
|
|
155
|
+
const promises = deps.map(id => this.getUnsafe(id));
|
|
173
156
|
|
|
174
157
|
await Promise.all(promises);
|
|
175
158
|
|
|
176
159
|
// 'plugin.start' has already been called, just await the start promise
|
|
177
|
-
if (this.
|
|
178
|
-
await this.
|
|
160
|
+
if (this.#instanceStartPromises.has(id)) {
|
|
161
|
+
await this.#instanceStartPromises.get(id);
|
|
179
162
|
} else {
|
|
180
|
-
this
|
|
163
|
+
this.#propagateStateChange(instance, 'inited');
|
|
181
164
|
let errored = false;
|
|
182
165
|
|
|
183
166
|
try {
|
|
184
167
|
const startPromise = instance.start();
|
|
185
|
-
this.
|
|
168
|
+
this.#instanceStartPromises.set(id, startPromise);
|
|
186
169
|
|
|
187
170
|
await startPromise;
|
|
188
171
|
} catch (err) {
|
|
189
172
|
errored = true;
|
|
190
|
-
this
|
|
173
|
+
this.#propagateStateChange(instance, 'errored');
|
|
191
174
|
throw err;
|
|
192
175
|
}
|
|
193
176
|
|
|
194
177
|
// this looks silly but it prevents the try / catch to catch errors that could
|
|
195
178
|
// be triggered by the propagate status callback, putting the plugin in errored state
|
|
196
179
|
if (!errored) {
|
|
197
|
-
this
|
|
180
|
+
this.#propagateStateChange(instance, 'started');
|
|
198
181
|
}
|
|
199
182
|
}
|
|
200
183
|
|
|
201
184
|
return instance;
|
|
202
185
|
}
|
|
203
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Register a plugin into the manager.
|
|
189
|
+
*
|
|
190
|
+
* _A plugin must always be registered both on client-side and on server-side_
|
|
191
|
+
*
|
|
192
|
+
* Plugins must be registered between the instantiation of {@link Client} and {@link Server},
|
|
193
|
+
* and their respective initialization, i.e.:
|
|
194
|
+
*
|
|
195
|
+
* ```js
|
|
196
|
+
* const client = new Client(config);
|
|
197
|
+
* client.pluginManager.register('my-plugin', plugin);
|
|
198
|
+
* await client.start();
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* Refer to the plugins documentation to check their configuration options and API.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} id - Unique id of the plugin. Enables the registration of the
|
|
204
|
+
* same plugin factory under different ids.
|
|
205
|
+
* @param {Function} ctor - The class returned by the plugin factory method.
|
|
206
|
+
* @param {object} [options={}] - Options to configure the plugin.
|
|
207
|
+
* @param {array} [deps=[]] - List of plugins' names the plugin depends on, i.e.
|
|
208
|
+
* the plugin initialization will begin only after the plugins it depends on are
|
|
209
|
+
* fully started themselves.
|
|
210
|
+
* @see {@link ClientPluginManager#register}
|
|
211
|
+
* @see {@link ServerPluginManager#register}
|
|
212
|
+
* @example
|
|
213
|
+
* // client-side
|
|
214
|
+
* client.pluginManager.register('user-defined-id', pluginFactory);
|
|
215
|
+
* // server-side
|
|
216
|
+
* server.pluginManager.register('user-defined-id', pluginFactory);
|
|
217
|
+
*/
|
|
218
|
+
register(id, ctor, options = {}, deps = []) {
|
|
219
|
+
// For now we don't allow to register a plugin after `client|server.init()`.
|
|
220
|
+
// This is subject to change in the future as we may want to dynamically
|
|
221
|
+
// register new plugins during application lifetime.
|
|
222
|
+
if (this.#node.status === 'inited') {
|
|
223
|
+
throw new DOMException(`Cannot execute 'register' on BasePluginManager: Host (client or server) has already been initialized`, 'InvalidAccessError');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!isString(id)) {
|
|
227
|
+
throw new TypeError(`Cannot execute 'register' on BasePluginManager: Argument 1 must be a string`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!isPlainObject(options)) {
|
|
231
|
+
throw new TypeError(`Cannot execute 'register' on BasePluginManager: Argument 3 must be an object`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!Array.isArray(deps)) {
|
|
235
|
+
throw new TypeError(`Cannot execute 'register' on BasePluginManager: Argument 3 must be an array`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (this[kPluginManagerInstances].has(id)) {
|
|
239
|
+
throw new DOMException(`Cannot execute 'register' on BasePluginManager: A plugin with same id (${id}) has already registered`, 'NotSupportedError');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// We instantiate the plugin here, so that a plugin can register another one
|
|
243
|
+
// in its own constructor.
|
|
244
|
+
//
|
|
245
|
+
// The dependencies must be created first, so that the instance can call
|
|
246
|
+
// addDependency in its constructor
|
|
247
|
+
this.#dependencies.set(id, deps);
|
|
248
|
+
|
|
249
|
+
const instance = new ctor(this.#node, id, options);
|
|
250
|
+
this[kPluginManagerInstances].set(id, instance);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Manually add a dependency to a given plugin.
|
|
255
|
+
*
|
|
256
|
+
* Useful to require a plugin within a plugin
|
|
257
|
+
*/
|
|
258
|
+
addDependency(pluginId, dependencyId) {
|
|
259
|
+
const deps = this.#dependencies.get(pluginId);
|
|
260
|
+
deps.push(dependencyId);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Returns the list of the registered plugins ids
|
|
265
|
+
* @returns {string[]}
|
|
266
|
+
*/
|
|
267
|
+
getRegisteredPlugins() {
|
|
268
|
+
return Array.from(this[kPluginManagerInstances].keys());
|
|
269
|
+
}
|
|
270
|
+
|
|
204
271
|
/**
|
|
205
272
|
* Propagate a notification each time a plugin is updated (status or inner state).
|
|
206
273
|
* The callback will receive the list of all plugins as first parameter, and the
|
|
@@ -208,10 +275,8 @@ class BasePluginManager {
|
|
|
208
275
|
*
|
|
209
276
|
* _In most cases, you should not have to rely on this method._
|
|
210
277
|
*
|
|
211
|
-
* @param {
|
|
212
|
-
*
|
|
213
|
-
* @param {client.PluginManager~deleteOnStateChangeCallback|client.PluginManager~deleteOnStateChangeCallback}
|
|
214
|
-
* Function to execute to listening for changes.
|
|
278
|
+
* @param {pluginManagerOnStateChangeCallback} callback - Callback to execute on state change
|
|
279
|
+
* @returns {pluginManagerDeleteOnStateChangeCallback} - Clear the subscription when executed
|
|
215
280
|
* @example
|
|
216
281
|
* const unsubscribe = client.pluginManager.onStateChange(pluginList, initiator => {
|
|
217
282
|
* // log the current status of all plugins
|
|
@@ -227,24 +292,8 @@ class BasePluginManager {
|
|
|
227
292
|
* unsubscribe();
|
|
228
293
|
*/
|
|
229
294
|
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
|
-
}
|
|
295
|
+
this.#onStateChangeCallbacks.add(callback);
|
|
296
|
+
return () => this.#onStateChangeCallbacks.delete(callback);
|
|
248
297
|
}
|
|
249
298
|
}
|
|
250
299
|
|