Files
factorio-clusterio/packages/web_ui/src/util/websocket.jsx
T
Hornwitser a96fae5ffd Add save listing and subscriptions
Add link messages for listing saves and listening for changes to the
list of saves for instances along with a table in the instance view page
showing the save list for the instance and a list-saves command to ctl.
2021-06-01 11:22:22 +02:00

280 lines
6.6 KiB
React

import libLink from "@clusterio/lib/link";
import libPlugin from "@clusterio/lib/plugin";
import libErrors from "@clusterio/lib/errors";
import { logger } from "@clusterio/lib/logging";
import packageJson from "../../package.json";
/**
* Connector for control connection to master server
* @private
*/
export class ControlConnector extends libLink.WebSocketClientConnector {
constructor(url, reconnectDelay) {
super(url, reconnectDelay);
this.token = null;
}
register() {
if (!this.token) {
throw new Error("Token not set");
}
logger.verbose("SOCKET | registering control");
this.sendHandshake("register_control", {
token: this.token,
agent: "Clusterio Web UI",
version: packageJson.version,
});
}
}
/**
* Handles running the control
*
* Connects to the master server over WebSocket and sends commands to it.
* @static
*/
export class Control extends libLink.Link {
constructor(connector, plugins) {
super("control", "master", connector);
libLink.attachAllMessages(this);
/**
* Mapping of plugin names to their instance for loaded plugins.
* @type {Map<string, module:lib/plugin.BaseWebPlugin>}
*/
this.plugins = plugins;
for (let plugin of plugins.values()) {
plugin.control = this;
libPlugin.attachPluginMessages(this, plugin);
}
/**
* Flag indicating the connection is in the process of logging out.
* @type {boolean}
*/
this.loggingOut = false;
/**
* Name of the account this control link is connected as.
* @type {?string}
*/
this.accountName = null;
this.instanceUpdateHandlers = new Map();
this.saveListUpdateHandlers = new Map();
this.instanceLogHandlers = new Map();
this.connector.on("connect", data => {
this.accountName = data.account.name;
this.updateInstanceSubscriptions().catch(err => logger.error(
`Unexpected error updating instance subscriptions:\n${err.stack}`
));
this.updateSaveListSubscriptions().catch(err => logger.error(
`Unexpected error updating save list subscriptions:\n${err.stack}`
));
this.updateLogSubscriptions().catch(err => logger.error(
`Unexpected error updating log subscriptions:\n${err.stack}`
));
});
this.connector.on("close", () => {
this.accountName = null;
});
for (let event of ["connect", "drop", "resume", "close"]) {
this.connector.on(event, () => {
for (let plugin of this.plugins.values()) {
plugin.onMasterConnectionEvent(event);
}
});
}
}
async instanceUpdateEventHandler(message) {
let handlers = this.instanceUpdateHandlers.get(message.data.id);
for (let handler of handlers || []) {
handler(message.data);
}
};
async onInstanceUpdate(id, handler) {
if (!Number.isInteger(id)) {
throw new Error("Invalid instance id");
}
let handlers = this.instanceUpdateHandlers.get(id);
if (!handlers) {
handlers = [];
this.instanceUpdateHandlers.set(id, handlers);
}
handlers.push(handler);
if (handlers.length === 1) {
await this.updateInstanceSubscriptions();
}
}
async offInstanceUpdate(id, handler) {
let handlers = this.instanceUpdateHandlers.get(id);
if (!handlers || !handlers.length) {
throw new Error(`No handlers for instance ${id} exist`);
}
let index = handlers.lastIndexOf(handler);
if (index === -1) {
throw new Error(`Given handler is not registered for instance ${id}`);
}
handlers.splice(index, 1);
if (!handlers.length) {
this.instanceUpdateHandlers.delete(id);
await this.updateInstanceSubscriptions();
}
}
async updateInstanceSubscriptions() {
if (!this.connector.connected) {
return;
}
await libLink.messages.setInstanceSubscriptions.send(this, {
all: false,
instance_ids: [...this.instanceUpdateHandlers.keys()],
});
}
async saveListUpdateEventHandler(message) {
let handlers = this.saveListUpdateHandlers.get(message.data.instance_id);
for (let handler of handlers || []) {
handler(message.data);
}
};
async onSaveListUpdate(id, handler) {
if (!Number.isInteger(id)) {
throw new Error("Invalid instance id");
}
let handlers = this.saveListUpdateHandlers.get(id);
if (!handlers) {
handlers = [];
this.saveListUpdateHandlers.set(id, handlers);
}
handlers.push(handler);
if (handlers.length === 1) {
await this.updateSaveListSubscriptions();
}
}
async offSaveListUpdate(id, handler) {
let handlers = this.saveListUpdateHandlers.get(id);
if (!handlers) {
throw new Error(`No handlers for instance ${id} exists`);
}
let index = handlers.lastIndexOf(handler);
if (index === -1) {
throw new Error(`Given handler is not registered for instance ${id}`);
handlers.splice(index, 1);
if (!handlers.length) {
this.saveListUpdateHandlers.delete(id);
await this.updateSaveListSubscriptions();
}
}
}
async updateSaveListSubscriptions() {
if (!this.connector.connected) {
return;
}
await libLink.messages.setSaveListSubscriptions.send(this, {
all: false,
instance_ids: [...this.saveListUpdateHandlers.keys()],
});
}
async logMessageEventHandler(message) {
let info = message.data.info;
if (info.instance_id !== undefined) {
let instanceHandlers = this.instanceLogHandlers.get(info.instance_id);
for (let handler of instanceHandlers || []) {
handler(info);
}
}
}
async onInstanceLog(id, handler) {
if (!Number.isInteger(id)) {
throw new Error("Invalid instance id");
}
let handlers = this.instanceLogHandlers.get(id);
if (!handlers) {
handlers = [];
this.instanceLogHandlers.set(id, handlers);
}
handlers.push(handler);
if (handlers.length === 1) {
await this.updateLogSubscriptions();
}
}
async offInstanceLog(id, handler) {
let handlers = this.instanceLogHandlers.get(id);
if (!handlers || !handlers.length) {
throw new Error(`No handlers for instance ${id} exist`);
}
let index = handlers.lastIndexOf(handler);
if (index === -1) {
throw new Error(`Given handler is not registered for instance ${id}`);
}
handlers.splice(index, 1);
if (!handlers.length) {
await this.updateLogSubscriptions();
}
}
async updateLogSubscriptions() {
if (!this.connector.connected) {
return;
}
await libLink.messages.setLogSubscriptions.send(this, {
all: false,
master: false,
slave_ids: [],
instance_ids: [...this.instanceLogHandlers.keys()],
max_level: null,
});
}
async debugWsMessageEventHandler(message) {
// eslint-disable-next-line no-console
console.log("WS", message.data.direction, message.data.content);
}
async shutdown() {
try {
await libLink.messages.prepareDisconnect.send(this);
} catch (err) {
if (!(err instanceof libErrors.SessionLost)) {
throw err;
}
}
await this.connector.close(1000, "Control Quit");
}
}