mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 07:04:52 +09:00
838 lines
25 KiB
TypeScript
838 lines
25 KiB
TypeScript
/**
|
||
* Plugin interfaces and utilities.
|
||
* @module lib/plugin
|
||
*/
|
||
import * as libHelpers from "./helpers";
|
||
import type { Logger } from "./logging";
|
||
import type { ConfigGroup } from "./config";
|
||
import type { CollectorResult } from "./prometheus";
|
||
import type { ParsedFactorioOutput } from "./logging_utils";
|
||
import type { ModPack, ModInfo } from "./data";
|
||
import type { Link } from "./link";
|
||
import type { CommandTree } from "./command";
|
||
|
||
/**
|
||
* Conceptual base for controller and instance plugins.
|
||
*/
|
||
export type BasePlugin =
|
||
| BaseControllerPlugin
|
||
| BaseInstancePlugin
|
||
| BaseControlPlugin
|
||
| BaseWebPlugin
|
||
;
|
||
|
||
// TODO Add proper typing for plugins
|
||
export type PluginInfo = any;
|
||
type Controller = any;
|
||
type Instance = any;
|
||
type Host = any;
|
||
type InstanceInfo = any;
|
||
type HostConnection = any;
|
||
type ControlConnection = any;
|
||
type Event = any;
|
||
type React = any;
|
||
|
||
/**
|
||
* Information about the event.
|
||
*/
|
||
interface PlayerEvent {
|
||
type: "join" | "leave" | "import";
|
||
/** Name of the player that joined/left */
|
||
name: string,
|
||
/**
|
||
* 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.
|
||
*/
|
||
reason?: string;
|
||
}
|
||
type InstanceStatus =
|
||
| "unassigned"
|
||
| "unknown"
|
||
| "stopped"
|
||
| "starting"
|
||
| "running"
|
||
| "stopping"
|
||
| "creating_save"
|
||
| "exporting_data"
|
||
| "deleted"
|
||
;
|
||
|
||
/**
|
||
* Base class for instance plugins
|
||
*
|
||
* Instance plugins are subclasses of this class which get instantiated by
|
||
* the host 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.
|
||
*/
|
||
export class BaseInstancePlugin {
|
||
/**
|
||
* 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`.
|
||
*/
|
||
logger: Logger;
|
||
|
||
private _pendingRconMessages: {
|
||
resolve: (result: string) => void,
|
||
reject: (err: Error) => void,
|
||
message: string,
|
||
expectEmpty: boolean,
|
||
}[] = [];
|
||
private _sendingRconMessages = false;
|
||
|
||
constructor(
|
||
/**
|
||
* The plugin's own info module
|
||
*/
|
||
public info: PluginInfo,
|
||
/**
|
||
* Instance the plugin started for
|
||
*/
|
||
public instance: Instance,
|
||
/**
|
||
* Host running the instance
|
||
*
|
||
* With the exepction of accessing the host's config you should
|
||
* avoid ineracting with the host object directly.
|
||
*/
|
||
public host: Host,
|
||
) {
|
||
this.logger = instance.logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||
|
||
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 group -
|
||
* The group who's field got changed.
|
||
* @param field - Name of the field that changed.
|
||
* @param prev - The previous value of the field.
|
||
*/
|
||
async onInstanceConfigFieldChanged(group: ConfigGroup, field: string, prev: unknown) { }
|
||
|
||
/**
|
||
* 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(): Promise<void | AsyncIterator<CollectorResult>> { }
|
||
|
||
/**
|
||
* 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 parsed - parsed server output.
|
||
* @param line - raw line of server output.
|
||
*/
|
||
async onOutput(parsed: ParsedFactorioOutput, line: string) { }
|
||
|
||
/**
|
||
* 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 host 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 host 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 event - one of connect, drop, resume and close
|
||
*/
|
||
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") { }
|
||
|
||
/**
|
||
* Called when the controller is preparing to disconnect from the host
|
||
*
|
||
* Invoked when the controller has requested the disconnection from
|
||
* the host. 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 connection -
|
||
* The connection to the host preparing to disconnect.
|
||
*/
|
||
async onPrepareControllerDisconnect(connection: HostConnection) { }
|
||
|
||
/**
|
||
* Called when a player joins or leaves the game
|
||
*
|
||
* Invoked when a player either joins or leaves the instance.
|
||
*
|
||
* @param event - Information about the event.
|
||
*/
|
||
async onPlayerEvent(event: PlayerEvent) { }
|
||
|
||
/**
|
||
* 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 message - message to send to server over RCON.
|
||
* @param expectEmpty -
|
||
* if true throw if the response is not empty. Useful for detecting
|
||
* errors that might have been sent in response.
|
||
* @returns response from server.
|
||
*/
|
||
async sendRcon(message: string, expectEmpty=false): Promise<string> {
|
||
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 message - message to send to server over RCON.
|
||
* @param [expectEmpty=false] -
|
||
* if true throw if the response is not empty. Useful for detecting
|
||
* errors that might have been sent in response.
|
||
* @returns response from server.
|
||
*/
|
||
async sendOrderedRcon(message: string, expectEmpty=false) {
|
||
let promise = new Promise<string>((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.
|
||
*/
|
||
export class BaseControllerPlugin {
|
||
/**
|
||
* 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`.
|
||
*/
|
||
logger: Logger;
|
||
|
||
constructor(
|
||
public info: PluginInfo,
|
||
public controller: Controller,
|
||
public metrics: any[],
|
||
logger: Logger
|
||
) {
|
||
this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||
}
|
||
|
||
/**
|
||
* 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 host 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 host.
|
||
* - `unknown`: Host assigned to instance is offline.
|
||
* - `stopped`: Instance is no longer running or was just assigned to a
|
||
* host.
|
||
* - `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 host, when the host 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 host 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 host 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 host.
|
||
*
|
||
* @param instance -
|
||
* The instance that changed.
|
||
* @param prev - the previous status of the instance.
|
||
*/
|
||
async onInstanceStatusChanged(instance: InstanceInfo, prev?: InstanceStatus) { }
|
||
|
||
/**
|
||
* 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 group -
|
||
* The group who's field got changed.
|
||
* @param field - Name of the field that changed.
|
||
* @param prev - The previous value of the field.
|
||
*/
|
||
async onControllerConfigFieldChanged(group: ConfigGroup, field: string, prev: unknown) { }
|
||
|
||
/**
|
||
* 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 instance -
|
||
* The instance the config changed on.
|
||
* @param group -
|
||
* The group who's field got changed.
|
||
* @param field - Name of the field that changed.
|
||
* @param prev - The previous value of the field.
|
||
*/
|
||
async onInstanceConfigFieldChanged(instance: InstanceInfo, group: ConfigGroup, field: string, prev: unknown) { }
|
||
|
||
/**
|
||
* 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(): Promise<void | AsyncIterator<CollectorResult>> { }
|
||
|
||
/**
|
||
* Called when the controller is shutting down
|
||
*/
|
||
async onShutdown() { }
|
||
|
||
/**
|
||
* Called when an event on a host 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 host has been established.
|
||
*
|
||
* ##### drop
|
||
*
|
||
* Invoked when a connection loss is detected between the controller and a
|
||
* host. Plugins should respond to this event by throtteling messages
|
||
* it is sending to the given host connection to an absolute minimum.
|
||
*
|
||
* Messages sent over a dropped host 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 host has been closed.
|
||
*
|
||
* @param connection -
|
||
* The connection the event occured on.
|
||
* @param event - one of connect, drop, resume and close
|
||
*/
|
||
onHostConnectionEvent(connection: HostConnection, event: "connect" | "drop" | "resume" | "close") { }
|
||
|
||
/**
|
||
* 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 connection -
|
||
* The connection the event occured on.
|
||
* @param event - one of connect, drop, resume, and close.
|
||
*/
|
||
onControlConnectionEvent(connection: ControlConnection, event: "connect" | "drop" | "resume" | "close") { }
|
||
|
||
/**
|
||
* Called when a host is preparing to disconnect from the controller
|
||
*
|
||
* Invoked when a host 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 host in question after the
|
||
* prepare disconnect has been handled.
|
||
*
|
||
* @param connection -
|
||
* The connection to the host preparing to disconnect.
|
||
*/
|
||
async onPrepareHostDisconnect(connection: HostConnection) { }
|
||
|
||
/**
|
||
* 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 modPack - Mod pack that updated.
|
||
*/
|
||
async onModPackUpdated(modPack: 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 mod - Mod that updated.
|
||
*/
|
||
async onModUpdated(mod: ModInfo) { }
|
||
|
||
/**
|
||
* Called when a player joins or leaves an instance
|
||
*
|
||
* Invoked when a player either joins or leaves an instance in the
|
||
* cluster.
|
||
*
|
||
* @param instance -
|
||
* The instance it occured on.
|
||
* @param event - Information about the event.
|
||
*/
|
||
async onPlayerEvent(instance: InstanceInfo, event: PlayerEvent) { }
|
||
|
||
/**
|
||
* Broadcast event to all connected hosts
|
||
*
|
||
* Sends the given event to all hosts connected to the controller.
|
||
* This does not include hosts that are in the process of closing the
|
||
* connection, which typically happens when they are shutting down.
|
||
*
|
||
* @param event - Event to send
|
||
*/
|
||
broadcastEventToHosts(event: Event) {
|
||
for (let hostConnection of this.controller.wsServer.hostConnections.values()) {
|
||
if (
|
||
!hostConnection.connector.closing
|
||
&& (!event.constructor.plugin || hostConnection.plugins.has(event.constructor.plugin))
|
||
) {
|
||
hostConnection.send(event);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
export class BaseControlPlugin {
|
||
/**
|
||
* 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`.
|
||
*/
|
||
logger: Logger;
|
||
|
||
constructor(
|
||
/**
|
||
* The plugin's own info module
|
||
*/
|
||
public info: PluginInfo,
|
||
logger: Logger,
|
||
) {
|
||
this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
* CommandTree} to.
|
||
*
|
||
* @param rootCommand -
|
||
* Root of the clusterioctl command tree.
|
||
*/
|
||
async addCommands(rootCommand: CommandTree) { }
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Plugin supplied login form
|
||
*/
|
||
export interface PluginLoginForm {
|
||
/**
|
||
* Internal name of the login form, this should start with the
|
||
* plugin name followed by a dot.
|
||
*/
|
||
name: string;
|
||
|
||
/** Name displayed above this form in the login window. */
|
||
title: string;
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
Component: React["Component"];
|
||
};
|
||
|
||
|
||
/**
|
||
* Plugin supplied pages
|
||
*/
|
||
export interface PluginPage {
|
||
/** URL path to this page. */
|
||
path: string;
|
||
/**
|
||
* If present and this path matches one of the pages in the sidebar it
|
||
* will cause that sidebar entry to be highlighted as active.
|
||
*/
|
||
sidebarPath?: string;
|
||
/**
|
||
* If present group this entry under a group of the given name in the
|
||
* sidebar.
|
||
*/
|
||
sidebarGroup?: string;
|
||
/**
|
||
* If present creates an entry in the sidebar for this page with the
|
||
* given text.
|
||
*/
|
||
sidebarName?: string;
|
||
/**
|
||
* A react node which is rendered when this page is navigated to.
|
||
* Should render a PageLayout.
|
||
*/
|
||
content?: React["ReactNode"];
|
||
};
|
||
|
||
/**
|
||
* Base class for web interface plugins
|
||
*/
|
||
export class BaseWebPlugin {
|
||
/**
|
||
* Contents of the plugin's package.json file
|
||
*/
|
||
package: any;
|
||
/**
|
||
* Control link to the controller, not available until the
|
||
* connect event in onControllerConnectionEvent is signaled.
|
||
*/
|
||
control?: Link;
|
||
/**
|
||
* 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`.
|
||
*/
|
||
logger: Logger;
|
||
/**
|
||
* List of login forms provided by this plugin
|
||
*/
|
||
loginForms: PluginLoginForm[] = [];
|
||
/**
|
||
* List of pages provided by this plugin
|
||
*/
|
||
pages: PluginPage[] = [];
|
||
/**
|
||
* 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.
|
||
*/
|
||
componentExtra: {
|
||
/** Placed at the end of the controller page. */
|
||
ControllerPage?: React["ComponentType"],
|
||
/** Placed at the end of the hosts list page. */
|
||
HostsPage?: React["ComponentType"],
|
||
/**
|
||
* Placed at the end of each host page. Takes a `host` param which
|
||
* is the host the page is displayed for.
|
||
*/
|
||
HostViewPage?: React["ComponentType"],
|
||
/** Placed at the end of the instance list page. */
|
||
InstancesPage?: React["ComponentType"],
|
||
/**
|
||
* Placed at the end of each instance page. Takes an `instance`
|
||
* param which is the instance the page is displayed for.
|
||
*/
|
||
InstanceViewPage?: React["ComponentType"],
|
||
/** Placed at the end of the users list page. */
|
||
UsersPage?: React["ComponentType"],
|
||
/**
|
||
* Placed at the end of each user page. Takes a `user` param which
|
||
* is the user object the page is displayed for.
|
||
*/
|
||
UserViewPage?: React["ComponentType"],
|
||
/** Placed at the end of the roles list page. */
|
||
RolesPage?: React["ComponentType"],
|
||
/**
|
||
* Placed at the end of each role page. Takes a `role` param which
|
||
* is the role object the page is displayed for.
|
||
*/
|
||
RoleViewPage?: React["ComponentType"],
|
||
} = {};
|
||
|
||
|
||
constructor(
|
||
/**
|
||
* Webpack container for this plugin
|
||
*/
|
||
public container: any,
|
||
packageData: any,
|
||
/**
|
||
* The plugin's own info module
|
||
*/
|
||
public info: PluginInfo,
|
||
logger: Logger,
|
||
) {
|
||
this.package = packageData;
|
||
this.logger = logger.child({ plugin: this.info.name }) as unknown as Logger;
|
||
}
|
||
|
||
/**
|
||
* 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 event - one of connect, drop, resume and close
|
||
*/
|
||
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") { }
|
||
}
|
||
|
||
/**
|
||
* Invokes the given hook on all plugins
|
||
*
|
||
* @param plugins -
|
||
* Mapping of plugin names to plugins to invoke the hook on.
|
||
* @param hook - Name of hook to invoke.
|
||
* @param args - Arguments to pass on to the hook.
|
||
* @returns Non-undefined return values from the hooks.
|
||
*/
|
||
export async function invokeHook(plugins: Map<string, BasePlugin>, hook: string, ...args: any[]) {
|
||
let results: unknown[] = [];
|
||
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 libHelpers.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;
|
||
}
|