mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
The role of the master server as the party controlling every part of the cluster is not well reflected in its name and has caused confusion among users on several occasions. Rename master to controller to better reflects its role.
850 lines
27 KiB
JavaScript
850 lines
27 KiB
JavaScript
/**
|
||
* Plugin interfaces and utilities.
|
||
* @module lib/plugin
|
||
*/
|
||
"use strict";
|
||
|
||
const helpers = require("./helpers");
|
||
|
||
/**
|
||
* Conceptual base for controller and instance plugins.
|
||
* @typedef {
|
||
* module:lib/plugin.BaseControllerPlugin
|
||
* |module:lib/plugin.BaseInstancePlugin
|
||
* |module:lib/plugin.BaseControlPlugin
|
||
* } module:lib/plugin.BasePlugin
|
||
*/
|
||
|
||
/**
|
||
* Base class for instance plugins
|
||
*
|
||
* Instance plugins are subclasses of this class which get instantiated by
|
||
* the slave when it brings up an instance with the plugin enabled in the
|
||
* config. To be discovered the class must be exported under the name
|
||
* `InstancePlugin` in the module specified by the `instanceEntrypoint` in
|
||
* the plugin's info.js file.
|
||
*
|
||
* Instances may be started and stopped many times, and many instances may
|
||
* be running at the same time, each of which will have their own instance
|
||
* of the InstancePlugin class.
|
||
*
|
||
* @static
|
||
*/
|
||
class BaseInstancePlugin {
|
||
constructor(info, instance, slave) {
|
||
/**
|
||
* The plugin's own info module
|
||
*/
|
||
this.info = info;
|
||
|
||
/**
|
||
* Instance the plugin started for
|
||
* @type {module:slave/src/Instance}
|
||
*/
|
||
this.instance = instance;
|
||
|
||
/**
|
||
* Slave running the instance
|
||
*
|
||
* With the exepction of accessing the slave's config you should
|
||
* avoid ineracting with the slave object directly.
|
||
*
|
||
* @type {module:slave/src/Slave}
|
||
*/
|
||
this.slave = slave;
|
||
|
||
/**
|
||
* Logger for this plugin
|
||
*
|
||
* Instance of winston Logger for sending log messages from this
|
||
* plugin. Supported methods and their corresponding log levels are
|
||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||
*/
|
||
this.logger = instance.logger.child({ plugin: this.info.name });
|
||
|
||
this._pendingRconMessages = [];
|
||
this._sendingRconMessages = false;
|
||
}
|
||
|
||
/**
|
||
* Called immediately after the class is instantiated
|
||
*/
|
||
async init() { }
|
||
|
||
/**
|
||
* Called when the value of a config field changed.
|
||
*
|
||
* Invoked after the value of the config field given by `field` has
|
||
* changed.
|
||
*
|
||
* @param {module:lib/config.ConfigGroup} group -
|
||
* The group who's field got changed.
|
||
* @param {string} field - Name of the field that changed.
|
||
* @param {*} prev - The previous value of the field.
|
||
*/
|
||
async onInstanceConfigFieldChanged(group, field, prev) { }
|
||
|
||
/**
|
||
* Called before collecting Prometheus metrics
|
||
*
|
||
* Invoked before the default metrics of prometheus is collected. Note
|
||
* that since this is done while replying to /metrics this should not
|
||
* take a long time, otherwise the requst will time out.
|
||
*
|
||
* Although you can return the results of collecting your prometheus
|
||
* collectors directly, all gauges in the default registry is already
|
||
* automatically collected. It's therefore recommended to only update
|
||
* the Gauges/Counters/etc in onMetric and let Clusterio deal will
|
||
* collecting the values.
|
||
*
|
||
* @returns {*} an async iterator of prometheus metric results or undefined.
|
||
*/
|
||
async onMetrics() { }
|
||
|
||
/**
|
||
* Called after the Factorio server is started
|
||
*/
|
||
async onStart() { }
|
||
|
||
/**
|
||
* Called before the Factorio server is stopped
|
||
*
|
||
* This will not be called if for example the Factorio server crashes or
|
||
* is killed.
|
||
*/
|
||
async onStop() { }
|
||
|
||
/**
|
||
* Called when the instance exits
|
||
*
|
||
* Invoked when the instance is shut down. This may occur after init
|
||
* has been called if an error occurs during startup. Note that if
|
||
* the plugin's init() throws this method will still be invoked.
|
||
*/
|
||
onExit() { }
|
||
|
||
/**
|
||
* Called when the Factorio outputs a line
|
||
*
|
||
* Called when the Factorio server outputs a line on either stdout or
|
||
* stderr. The output is a parsed form of the line and contains the
|
||
* following properties:
|
||
*
|
||
* @param {Object} parsed - parsed server output.
|
||
* @param {string} parsed.source -
|
||
* Where the message came from, one of "stdout" and "stderr".
|
||
* @param {string} parsed.format -
|
||
* Timestamp format, one of "date", "seconds" and "none".
|
||
* @param {string=} parsed.time -
|
||
* Timestamp of the message. Not present if format is "none".
|
||
* @param {string} parsed.type -
|
||
* Type of message, one of "log", "action" and "generic".
|
||
* @param {string=} parsed.level -
|
||
* Log level for "log" type. i.e "Info" normally.
|
||
* @param {string=} parsed.file - File reported for "log" type.
|
||
* @param {string=} parsed.action -
|
||
* Kind of action for "action" type. i.e "CHAT" for chat.
|
||
* @param {string} parsed.message - Main content of the line.
|
||
* @param {string} line - raw line of server output.
|
||
*/
|
||
async onOutput(parsed, line) { }
|
||
|
||
/**
|
||
* Called when an event on the controller connection happens
|
||
*
|
||
* The event param may be one of connect, drop, resume and close and has
|
||
* the following meaning:
|
||
*
|
||
* ##### connect
|
||
*
|
||
* Invoked when a new connection to the controller has been established.
|
||
*
|
||
* ##### drop
|
||
*
|
||
* Invoked when a connection loss is detected between the slave and the
|
||
* controller. Plugins should respond to this event by throtteling messages
|
||
* it is sending to the controller to an absolute minimum.
|
||
*
|
||
* Messages sent over a dropped controller connection will get queued up in
|
||
* memory on the slave and sent all in one go when the connection is
|
||
* re-established again.
|
||
*
|
||
* ##### resume
|
||
*
|
||
* Invoked when the connection that had previously dropped is
|
||
* re-established.
|
||
*
|
||
* ##### close
|
||
*
|
||
* Invoked when the connection to the controller has been closed. This
|
||
* typically means the controller has shut down. Plugins should not
|
||
* send any messages that goes to or via the controller after the
|
||
* connection has been closed and before a new one is established.
|
||
*
|
||
* @param {string} event - one of connect, drop, resume and close
|
||
*/
|
||
onControllerConnectionEvent(event) { }
|
||
|
||
/**
|
||
* Called when the controller is preparing to disconnect from the slave
|
||
*
|
||
* Invoked when the controller has requested the disconnection from
|
||
* the slave. This typically happens when it is in the process of
|
||
* shutting down.
|
||
*
|
||
* Plugins must stop sending messages to the controller, or are forwarded
|
||
* via the controller after the prepare disconnect has been handled.
|
||
*
|
||
* @param {module:controller/src/SlaveConnection} connection -
|
||
* The connection to the slave preparing to disconnect.
|
||
*/
|
||
async onPrepareControllerDisconnect(connection) { }
|
||
|
||
/**
|
||
* Called when a player joins or leaves the game
|
||
*
|
||
* Invoked when a player either joins or leaves the instance.
|
||
*
|
||
* @param {Object} event - Information about the event.
|
||
* @param {string} event.type - Either "join", "leave" or "import".
|
||
* @param {string} event.name - Name of the player that joined/left.
|
||
* @param {string=} event.reason -
|
||
* Only present for type "leave". Reason for player leaving the
|
||
* game, one of the possible reasons in defines.disconnect_reason
|
||
* or "server_quit" if the server exits while the player is online.
|
||
*/
|
||
async onPlayerEvent(event) { }
|
||
|
||
/**
|
||
* Send RCON message to instance
|
||
*
|
||
* Send a message or command to the server which can potentially be
|
||
* executed out of order in relation to other RCON commands.
|
||
*
|
||
* This should not be called before onStart or after onStop. A simple
|
||
* way to achieve this is to check that the instance status is running
|
||
* before sending commands. Note that the instance status is set to
|
||
* running some time after `onStart` and some time before `onStop` is
|
||
* invoked.
|
||
*
|
||
* @param {string} message - message to send to server over RCON.
|
||
* @param {boolean=} [expectEmpty=false] -
|
||
* if true throw if the response is not empty. Useful for detecting
|
||
* errors that might have been sent in response.
|
||
* @returns {Promise<string>} response from server.
|
||
*/
|
||
async sendRcon(message, expectEmpty=false) {
|
||
return await this.instance.sendRcon(message, expectEmpty, this.info.name);
|
||
}
|
||
|
||
/**
|
||
* Send serially ordered RCON message to instance
|
||
*
|
||
* Send a message or command to the server which will not be executed
|
||
* out of order in relation to other RCON commands sent with this
|
||
* method. The ordering applies per plugin, and two plugins sending
|
||
* commands with this method may execute out of order in relation to
|
||
* each other.
|
||
*
|
||
* This should not be called before onStart or after onStop. A simple
|
||
* way to achieve this is to check that the instance status is running
|
||
* before sending commands. Note that the instance status is set to
|
||
* running some time after `onStart` and some time before `onStop` is
|
||
* invoked.
|
||
*
|
||
* @param {string} message - message to send to server over RCON.
|
||
* @param {boolean=} [expectEmpty=false] -
|
||
* if true throw if the response is not empty. Useful for detecting
|
||
* errors that might have been sent in response.
|
||
* @returns {Promise<string>} response from server.
|
||
*/
|
||
async sendOrderedRcon(message, expectEmpty=false) {
|
||
let promise = new Promise((resolve, reject) => {
|
||
this._pendingRconMessages.push({resolve, reject, message, expectEmpty});
|
||
});
|
||
if (!this._sendingRconMessages) {
|
||
this._sendPendingRconMessages();
|
||
}
|
||
return await promise;
|
||
}
|
||
|
||
async _sendPendingRconMessages() {
|
||
this._sendingRconMessages = true;
|
||
while (this._pendingRconMessages.length) {
|
||
let task = this._pendingRconMessages.shift();
|
||
try {
|
||
let result = await this.sendRcon(task.message, task.expectEmpty);
|
||
task.resolve(result);
|
||
} catch (err) {
|
||
task.reject(err);
|
||
}
|
||
}
|
||
this._sendingRconMessages = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Base class for controller plugins
|
||
*
|
||
* Controller plugins are subclasses of this class which get instantiated by
|
||
* the controller on startup when the plugin is enabled in the config.
|
||
* To be discovered the class must be exported under the name `ControllerPlugin`
|
||
* in the module specified by the `controllerEntrypoint` in the plugin's info.js
|
||
* file.
|
||
*
|
||
* @static
|
||
*/
|
||
class BaseControllerPlugin {
|
||
constructor(info, controller, metrics, logger) {
|
||
this.info = info;
|
||
|
||
/**
|
||
* Controller
|
||
* @type {module:controller/src/Controller}
|
||
*/
|
||
this.controller = controller;
|
||
this.metrics = metrics;
|
||
|
||
/**
|
||
* Logger for this plugin
|
||
*
|
||
* Instance of winston Logger for sending log messages from this
|
||
* plugin. Supported methods and their corresponding log levels are
|
||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||
*/
|
||
this.logger = logger.child({ plugin: this.info.name });
|
||
}
|
||
|
||
/**
|
||
* Called immediately after the class is instantiated
|
||
*/
|
||
async init() { }
|
||
|
||
/**
|
||
* Called when the status of an instance changes
|
||
*
|
||
* Invoked when the controller has changed the status of an instance
|
||
* or received notice from a slave that the status of an instance has
|
||
* changed. The possible statuses that can be notified about are:
|
||
* - `unassigned:`: Instance is no longer asssigned to a slave.
|
||
* - `unknown`: Slave assigned to instance is offline.
|
||
* - `stopped`: Instance is no longer running or was just assigned to a
|
||
* slave.
|
||
* - `starting`: Instance is in the process of starting up.
|
||
* - `running`: Instance startup completed and is now running.
|
||
* - `stopping`: Instance is in the processing stopping.
|
||
* - `creating_save`: Instance is in the process of creating a save.
|
||
* - `exporting_data`: Instance is in the process of exporting item
|
||
* icons and locale data.
|
||
* - `deleted`: Instance was deleted.
|
||
*
|
||
* On controller startup all known instances gets the status `unknown` if
|
||
* they are assigned to a slave, when the slave then connects to the
|
||
* controller and informs of the current status of its instances this hook
|
||
* is invoked for all those instances. You can detect this situation by
|
||
* checking if prev equals `unknown`. If the slave disconnects the
|
||
* instances will again get the `unknown` status.
|
||
*
|
||
* When instances are created on the controller they will notify of a status
|
||
* change with prev set to null. While the status of new instances is
|
||
* in most cases `unassigned` it's possible for the created instance to
|
||
* start with any state in some slave connection corner cases.
|
||
*
|
||
* Note that it's possible for status change notification to get lost in
|
||
* the case of network outages if reconnect fails to re-establish the
|
||
* session between the controller and the slave.
|
||
*
|
||
* @param {Object} instance - the instance that changed.
|
||
* @param {?string} prev - the previous status of the instance.
|
||
*/
|
||
async onInstanceStatusChanged(instance, prev) { }
|
||
|
||
/**
|
||
* Called when the value of a controller config field changed.
|
||
*
|
||
* Invoked after the value of the config field given by `field` has
|
||
* changed on the controller.
|
||
*
|
||
* @param {module:lib/config.ConfigGroup} group -
|
||
* The group who's field got changed.
|
||
* @param {string} field - Name of the field that changed.
|
||
* @param {*} prev - The previous value of the field.
|
||
*/
|
||
async onControllerConfigFieldChanged(group, field, prev) { }
|
||
|
||
/**
|
||
* Called when the value of an instance config field changed.
|
||
*
|
||
* Invoked after the value of the config field given by `field` has
|
||
* changed on an instance.
|
||
*
|
||
* @param {Object} instance - The instance the config changed on.
|
||
* @param {module:lib/config.ConfigGroup} group -
|
||
* The group who's field got changed.
|
||
* @param {string} field - Name of the field that changed.
|
||
* @param {*} prev - The previous value of the field.
|
||
*/
|
||
async onInstanceConfigFieldChanged(instance, group, field, prev) { }
|
||
|
||
/**
|
||
* Called before collecting Prometheus metrics
|
||
*
|
||
* Invoked before the default metrics of prometheus is collected. Note
|
||
* that since this is done while replying to /metrics this should not
|
||
* take a long time, otherwise the requst will time out.
|
||
*
|
||
* Although you can return the results of collecting your prometheus
|
||
* collectors directly, all gauges in the default registry is already
|
||
* automatically collected. It's therefore recommended to only update
|
||
* the Gauges/Counters/etc in onMetric and let Clusterio deal will
|
||
* collecting the values.
|
||
*
|
||
* @returns {*} an async iterator of prometheus metric results or undefined.
|
||
*/
|
||
async onMetrics() { }
|
||
|
||
/**
|
||
* Called when the controller is shutting down
|
||
*/
|
||
async onShutdown() { }
|
||
|
||
/**
|
||
* Called when an event on a slave connection happens
|
||
*
|
||
* The event param may be one of connect, drop, resume and close and has
|
||
* the following meaning:
|
||
*
|
||
* ##### connect
|
||
*
|
||
* Invoked when a new connection to the slave has been established.
|
||
*
|
||
* ##### drop
|
||
*
|
||
* Invoked when a connection loss is detected between the controller and a
|
||
* slave. Plugins should respond to this event by throtteling messages
|
||
* it is sending to the given slave connection to an absolute minimum.
|
||
*
|
||
* Messages sent over a dropped slave connection will get queued up in
|
||
* memory on the controller and sent all in one go when the connection is
|
||
* re-established again.
|
||
*
|
||
* ##### resume
|
||
*
|
||
* Invoked when a connection that had previously been dropped is
|
||
* re-established
|
||
*
|
||
* ##### close
|
||
*
|
||
* Invoked when the connection to the slave has been closed.
|
||
*
|
||
* @param {module:controller/src/SlaveConnection} connection -
|
||
* The connection the event occured on.
|
||
* @param {string} event - one of connect, drop, resume and close
|
||
*/
|
||
onSlaveConnectionEvent(connection, event) { }
|
||
|
||
/**
|
||
* Called when an avent on a control connection happens
|
||
*
|
||
* The event param may be one of connect, drop, resume and close and has
|
||
* the following meaning:
|
||
*
|
||
* ##### connect
|
||
*
|
||
* Invoked when a new connection to the control has been established.
|
||
*
|
||
* ##### drop
|
||
*
|
||
* Invoked when a connection loss is detected between the controller and a
|
||
* control. Plugins should respond to this event by throtteling
|
||
* messages it is sending to the given control connection to an absolute
|
||
* minimum.
|
||
*
|
||
* Messages sent over a dropped control connection will get queued up in
|
||
* memory on the controller and sent all in one go when the connection is
|
||
* re-established again.
|
||
*
|
||
* ##### resume
|
||
*
|
||
* Invoked when a connection that had previously been dropped is
|
||
* re-established.
|
||
*
|
||
* ##### close
|
||
*
|
||
* Invoked when the connection to the control has been closed.
|
||
*
|
||
* @param {module:controller/src/ControlConnection} connection -
|
||
* The connection the event occured on.
|
||
* @param {string} event - one of connect, drop, resume, and close.
|
||
*/
|
||
onControlConnectionEvent(connection, event) { }
|
||
|
||
/**
|
||
* Called when a slave is preparing to disconnect from the controller
|
||
*
|
||
* Invoked when a slave has requested the disconnection from the controller.
|
||
* This typically happens when it is in the process of shutting down.
|
||
*
|
||
* Plugins must stop sending messages to the slave in question after the
|
||
* prepare disconnect has been handled.
|
||
*
|
||
* @param {module:controller/src/SlaveConnection} connection -
|
||
* The connection to the slave preparing to disconnect.
|
||
*/
|
||
async onPrepareSlaveDisconnect(connection) { }
|
||
|
||
/**
|
||
* Called when a mod pack is updated
|
||
*
|
||
* Invoked when a mod pack has been added, updated or deleted in the
|
||
* cluster.
|
||
*
|
||
* If the mod pack has been deleted its `.isDeleted` property will be
|
||
* true.
|
||
*
|
||
* @param {module:lib/data.ModPack} modPack - Mod pack that updated.
|
||
*/
|
||
async onModPackUpdated(modPack) { }
|
||
|
||
/**
|
||
* Called when a mod stored on the controller is updated
|
||
*
|
||
* Invoked when a mod has been added, updated or deleted from the pool
|
||
* of shared mods stored on the cluster.
|
||
*
|
||
* If a mod has been deleted its `.isDeleted` property will be true.
|
||
*
|
||
* @param {module:lib/data.ModInfo} mod - Mod that updated.
|
||
*/
|
||
async onModUpdated(mod) { }
|
||
|
||
/**
|
||
* Called when a player joins or leaves an instance
|
||
*
|
||
* Invoked when a player either joins or leaves an instance in the
|
||
* cluster.
|
||
*
|
||
* @param {Object} instance - the instance it occured on.
|
||
* @param {Object} event - Information about the event.
|
||
* @param {string} event.type - Either "join", "leave" or "import".
|
||
* @param {string} event.name - Name of the player that joined/left.
|
||
* @param {string=} event.reason -
|
||
* Only present for type "leave". Reason for player leaving the
|
||
* game, one of the possible reasons in defines.disconnect_reason
|
||
* or "server_quit" if the server exits while the player is online.
|
||
*/
|
||
async onPlayerEvent(instance, event) { }
|
||
|
||
/**
|
||
* Broadcast event to all connected slaves
|
||
*
|
||
* Sends the given event to all slaves connected to the controller.
|
||
* This does not include slaves that are in the process of closing the
|
||
* connection, which typically happens when they are shutting down.
|
||
*
|
||
* @param {module:lib/link.Event} event - Event to send
|
||
* @param {Object} data - Data ta pass with the event.
|
||
*/
|
||
broadcastEventToSlaves(event, data={}) {
|
||
for (let slaveConnection of this.controller.wsServer.slaveConnections.values()) {
|
||
if (
|
||
!slaveConnection.connector.closing
|
||
&& (!event.plugin || slaveConnection.plugins.has(event.plugin))
|
||
) {
|
||
event.send(slaveConnection, data);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Base class for clusterioctl plugins
|
||
*
|
||
* Control plugins are subclasses of this class which get instantiated by
|
||
* clusterioctl in order to extend its functionallity. To be discovered the
|
||
* class must be exported under the name `ControlPlugin` in the module
|
||
* specified by the `controlEntrypoint` in the plugin's info.js file.
|
||
*
|
||
* @static
|
||
*/
|
||
class BaseControlPlugin {
|
||
constructor(info, logger) {
|
||
/**
|
||
* The plugin's own info module
|
||
*/
|
||
this.info = info;
|
||
|
||
/**
|
||
* Logger for this plugin
|
||
*
|
||
* Instance of winston Logger for sending log messages from this
|
||
* plugin. Supported methods and their corresponding log levels are
|
||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||
*/
|
||
this.logger = logger.child({ plugin: this.info.name });
|
||
}
|
||
|
||
/**
|
||
* Called immediately after the class is instantiated
|
||
*/
|
||
async init() { }
|
||
|
||
/**
|
||
* Called to add commands to the command line interface.
|
||
*
|
||
* Invoked by clusterioctl to let plugins add commands. `rootCommand` is
|
||
* the top level command node which the plugin should add its own {@link
|
||
* module:lib/command.CommandTree} to.
|
||
*
|
||
* @param {module:lib/command.CommandTree} rootCommand -
|
||
* Root of the clusterioctl command tree.
|
||
*/
|
||
async addCommands(rootCommand) { }
|
||
}
|
||
|
||
|
||
/**
|
||
* Plugin supplied login form
|
||
* @typedef {Object} module:lib/plugin~LoginForm
|
||
* @property {string} name -
|
||
* Internal name of the login form, this should start with the
|
||
* plugin name followed by a dot.
|
||
* @property {string} title -
|
||
* Name displayed above this form in the login window.
|
||
* @property {React.Component} Component -
|
||
* React component that's rendered for this login form. This is
|
||
* supplied the setToken function via its props which should be
|
||
* called when an authentication token is aquired via this
|
||
* form.
|
||
*/
|
||
|
||
/**
|
||
* Plugin supplied pages
|
||
* @typedef {Object} module:lib/plugin~Page
|
||
* @property {string} path - URL path to this page.
|
||
* @property {string=} sidebarPath -
|
||
* If present and this path matches one of the pages in the sidebar it
|
||
* will cause that sidebar entry to be highlighted as active.
|
||
* @property {string=} sidebarGroup -
|
||
* If present group this entry under a group of the given name in the
|
||
* sidebar.
|
||
* @property {string=} sidebarName -
|
||
* If present creates an entry in the sidebar for this page with the
|
||
* given text.
|
||
* @property {ReactNode} content -
|
||
* A react node which is rendered when this page is navigated to.
|
||
* Should render a PageLayout.
|
||
*/
|
||
|
||
/**
|
||
* Base class for web interface plugins
|
||
*
|
||
* @static
|
||
*/
|
||
class BaseWebPlugin {
|
||
constructor(container, packageData, info, logger) {
|
||
/**
|
||
* Webpack container for this plugin
|
||
*/
|
||
this.container = container;
|
||
|
||
/**
|
||
* Contents of the plugin's package.json file
|
||
* @type {Object}
|
||
*/
|
||
this.package = packageData;
|
||
|
||
/**
|
||
* The plugin's own info module
|
||
*/
|
||
this.info = info;
|
||
|
||
/**
|
||
* Control link to the controller, not available until the
|
||
* connect event in onControllerConnectionEvent is signaled.
|
||
* @type {?module:lib/link.Link}
|
||
*/
|
||
this.control = null;
|
||
|
||
/**
|
||
* Logger for this plugin
|
||
*
|
||
* Instance of winston Logger for sending log messages from this
|
||
* plugin. Supported methods and their corresponding log levels are
|
||
* `error`, `warn`, `audit`, `info` and `verbose`.
|
||
*/
|
||
this.logger = logger.child({ plugin: this.info.name });
|
||
|
||
/**
|
||
* List of login forms provided by this plugin
|
||
*
|
||
* @type {Array<module:lib/plugin~LoginForm>}
|
||
*/
|
||
this.loginForms = [];
|
||
|
||
/**
|
||
* List of pages provided by this plugin
|
||
*
|
||
* @type {Array<module:lib/plugin~Page>}
|
||
*/
|
||
this.pages = [];
|
||
|
||
/**
|
||
* Extra react component to add to core components
|
||
*
|
||
* Interface to augment core components of the web UI. Setting a
|
||
* component as one of the supported properties of this object will
|
||
* cause the web UI to render it when displaying that component,
|
||
* usually at the end. Each component will receive a `plugin` param
|
||
* which is the instance of the web plugin that contained the
|
||
* component extra.
|
||
*
|
||
* @type {object}
|
||
* @property {React.ComponentType} ControllerPage -
|
||
* Placed at the end of the controller page.
|
||
* @property {React.ComponentType} SlavesPage -
|
||
* Placed at the end of the slaves list page.
|
||
* @property {React.ComponentType} SlaveViewPage -
|
||
* Placed at the end of each slave page. Takes a `slave` param
|
||
* which is the slave the page is displayed for.
|
||
* @property {React.ComponentType} InstancesPage -
|
||
* Placed at the end of the instance list page.
|
||
* @property {React.ComponentType} InstanceViewPage -
|
||
* Placed at the end of each instance page. Takes an `instance`
|
||
* param which is the instance the page is displayed for.
|
||
* @property {React.ComponentType} UsersPage -
|
||
* Placed at the end of the users list page.
|
||
* @property {React.ComponentType} UserViewPage -
|
||
* Placed at the end of each user page. Takes a `user` param
|
||
* which is the user object the page is displayed for.
|
||
* @property {React.ComponentType} RolesPage -
|
||
* Placed at the end of the roles list page.
|
||
* @property {React.ComponentType} RoleViewPage -
|
||
* Placed at the end of each role page. Takes a `role` param
|
||
* which is the role object the page is displayed for.
|
||
*/
|
||
this.componentExtra = {};
|
||
}
|
||
|
||
/**
|
||
* Called immediately after the class is instantiated.
|
||
*/
|
||
async init() { }
|
||
|
||
/**
|
||
* Called when an event on the controller connection happens
|
||
*
|
||
* The event param may be one of connect, drop, resume and close and has
|
||
* the following meaning:
|
||
*
|
||
* ##### connect
|
||
*
|
||
* Invoked when a new connection to the controller has been established.
|
||
*
|
||
* ##### drop
|
||
*
|
||
* Invoked when a connection loss is detected between the control link
|
||
* and the controller. Plugins should respond to this event by throtteling
|
||
* messages it is sending to the controller to an absolute minimum.
|
||
*
|
||
* Messages sent over a dropped controller connection will get queued up in
|
||
* memory in the browser and sent all in one go when the connection is
|
||
* re-established again.
|
||
*
|
||
* ##### resume
|
||
*
|
||
* Invoked when the connection that had previously dropped is
|
||
* re-established.
|
||
*
|
||
* ##### close
|
||
*
|
||
* Invoked when the connection to the controller has been closed. This
|
||
* typically means the controller has shut down. Plugins should not
|
||
* send any messages that goes to or via the controller after the
|
||
* connection has been closed and before a new one is established.
|
||
*
|
||
* @param {string} event - one of connect, drop, resume and close
|
||
*/
|
||
onControllerConnectionEvent(event) { }
|
||
}
|
||
|
||
/**
|
||
* Attach plugin messages
|
||
*
|
||
* Attaches all messages defined in the `.info.message` property of the
|
||
* `plugin` passed with handlers taken from the `pluin`.
|
||
*
|
||
* @param {module:lib/link.Link} link - Link to attach handlers to.
|
||
* @param {module:lib/plugin.BasePlugin} plugin -
|
||
* The instance of the plugin attach messages and handlers from.
|
||
*/
|
||
function attachPluginMessages(link, plugin) {
|
||
let messageDefinitions = plugin.info.messages || [];
|
||
for (let [name, messageFormat] of Object.entries(messageDefinitions)) {
|
||
if (messageFormat.plugin !== plugin.info.name) {
|
||
throw new Error(`Type of ${name} message must start with "${plugin.info.name}:"`);
|
||
}
|
||
|
||
let handler = name + messageFormat.handlerSuffix;
|
||
try {
|
||
if (plugin === null || !plugin[handler]) {
|
||
messageFormat.attach(link);
|
||
|
||
} else {
|
||
messageFormat.attach(link, async (message, format) => await plugin[handler](message, format, link));
|
||
}
|
||
|
||
} catch (err) {
|
||
if (err.code === "MISSING_LINK_HANDLER") {
|
||
err.plugin = plugin.info.name;
|
||
err.handler = handler;
|
||
}
|
||
|
||
throw err;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Invokes the given hook on all plugins
|
||
*
|
||
* @param {Map<string, Object>} plugins -
|
||
* Mapping of plugin names to plugins to invoke the hook on.
|
||
* @param {string} hook - Name of hook to invoke.
|
||
* @param {...*} args - Arguments to pass on to the hook.
|
||
* @returns {Promise<Array>} Non-empty return values from the hooks.
|
||
*/
|
||
async function invokeHook(plugins, hook, ...args) {
|
||
let results = [];
|
||
for (let [name, plugin] of plugins) {
|
||
try {
|
||
// Use an object to detect if the hook failed on timeout or tried to return a value looking like an error
|
||
const timeout = {};
|
||
let result = await helpers.timeout(
|
||
plugin[hook](...args),
|
||
15000,
|
||
timeout
|
||
);
|
||
if (result === timeout) {
|
||
throw new Error(`Invoking hook ${hook} timed out for plugin ${name}`);
|
||
} else if (result !== undefined) {
|
||
results.push(result);
|
||
}
|
||
} catch (err) {
|
||
plugin.logger.error(`Ignoring error from plugin ${name} in ${hook}:\n${err.stack}`);
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
|
||
module.exports = {
|
||
BaseInstancePlugin,
|
||
BaseControllerPlugin,
|
||
BaseControlPlugin,
|
||
BaseWebPlugin,
|
||
|
||
attachPluginMessages,
|
||
invokeHook,
|
||
};
|