import merge from 'lodash/merge.js';
export const kBasePluginStatus = Symbol('soundworks:base-plugin-status');
/**
* Callback executed when the plugin state is updated.
*
* @callback pluginOnStateChangeCallback
* @param {BasePlugin#state} state - Current state of the plugin.
*/
/**
* Delete the registered {@link pluginOnStateChangeCallback}.
*
* @callback pluginDeleteOnStateChangeCallback
*/
/** @private */
class BasePlugin {
#id = null;
#onStateChangeCallbacks = new Set();
constructor(id) {
this.#id = id;
/** @private */
this[kBasePluginStatus] = 'idle';
/**
* Placeholder that stores internal (local) state of the plugin. The state
* should be modified through the `propagateStateChange` method to ensure
* the change to be properly propagated to manager `onStateChange` callbacks.
*
* @type {object}
* @protected
* @see {@link ClientPlugin#onStateChange}
* @see {@link ServerPlugin#onStateChange}
* @see {@link ClientPlugin#propagateStateChange}
* @see {@link ServerPlugin#propagateStateChange}
*/
this.state = {};
}
/**
* User defined ID of the plugin.
*
* @type {string}
* @readonly
* @see {@link ClientPluginManager#register}
* @see {@link ServerPluginManager#register}
*/
get id() {
return this.#id;
}
/**
* Type of the plugin, i.e. the ClassName.
*
* Usefull to do perform some logic based on certain types of plugins without
* knowing under which `id` they have been registered. (e.g. creating some generic
* views, etc.)
*
* @type {string}
* @readonly
*/
get type() {
return this.constructor.name;
}
/**
* Current status of the plugin.
*
* @type {'idle'|'inited'|'started'|'errored'}
*/
get status() {
return this[kBasePluginStatus];
}
/**
* Start the plugin.
*
* This method is automatically called during the client or server `init()` lifecyle
* step. After `start()` is fulfilled the plugin should be ready to use.
*
* @example
* // server-side couterpart of a plugin that creates a dedicated global shared
* // state on which the server-side part can attach.
* class MyPlugin extends ServerPlugin {
* constructor(server, id) {
* super(server, id);
*
* this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
* someParam: {
* type: 'boolean',
* default: false,
* },
* // ...
* });
* }
*
* async start() {
* await super.start()
* this.sharedState = await this.server.stateManager.create(`my-plugin:${this.id}`);
* }
*
* async stop() {
* await this.sharedState.delete();
* }
* }
*/
async start() {}
/**
* Stop the plugin.
*
* This method is automatically called during the client or server `stop()` lifecyle step.
*
* @example
* // server-side couterpart of a plugin that creates a dedicated global shared
* // state on which the client-side part can attach.
* class MyPlugin extends ServerPlugin {
* constructor(server, id) {
* super(server, id);
*
* this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
* someParam: {
* type: 'boolean',
* default: false,
* },
* // ...
* });
* }
*
* async start() {
* await super.start()
* this.sharedState = await this.server.stateManager.create(`my-plugin:${this.id}`);
* this.sharedState.onUpdate(updates => this.doSomething(updates));
* }
*
* async stop() {
* await this.sharedState.delete();
* }
* }
*/
async stop() {}
/**
* Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
*
* @param {pluginOnStateChangeCallback} callback - Callback to execute when a state change is propagated.
* @returns {pluginDeleteOnStateChangeCallback}
*
* @example
* const unsubscribe = plugin.onStateChange(pluginState => console.log(pluginState));
* // stop listening state changes
* unsubscribe();
*/
onStateChange(callback) {
this.#onStateChangeCallbacks.add(callback);
return () => this.#onStateChangeCallbacks.delete(callback);
}
/**
* Apply updates to the plugin state and propagate the updated state to the
* `onStateChange` listeners. The state changes will also be propagated
* through the `PluginManager#onStateChange` listeners.
*
* @param {object} updates - Updates to be merged in the plugin state.
*
* @see {@link BasePlugin#onStateChange}
* @see {@link BasePluginManager#onStateChange}
*/
propagateStateChange(updates) {
merge(this.state, updates);
this.#onStateChangeCallbacks.forEach(callback => callback(this.state));
}
}
export default BasePlugin;