Files
factorio-expcluster/exp_reports/instance.ts
T
BastiaanandClaude Opus 5.5 ea9bf1fb78 Construct plugins with a static fromContext (#487)
Each plugin class now has a private constructor and a static async
fromContext(context) that loads its datastores, constructs the instance
and attaches its handlers and hooks. This replaces the public constructor
followed by init(), and AutoRestart's start().

Datastores are loaded before construction and passed to the constructor,
so the definite assignment assertions on them are gone. Tests build the
plugins through fromContext and pass a wrapped logger in the context
instead of overwriting plugin.logger before init().

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:19:53 +01:00

111 lines
3.7 KiB
TypeScript

import type { Instance, InstancePluginContext } from "@clusterio/host";
import type * as lib from "@clusterio/lib";
import * as messages from "./messages.js";
/** Sent by the lua side when a player reports another. */
export type IpcReportCreate = {
player_name: string,
by_player_name: string,
reason: string,
};
/** Sent by the lua side to list the reports for a player, or for everyone. */
export type IpcReportList = {
caller: string,
player_name: string | undefined,
};
/** Sent by the lua side to delete the reports against a player, optionally only those from one player. */
export type IpcReportDelete = {
caller: string,
player_name: string,
by_player_name: string | undefined,
};
/**
* Bridges the lua side and the controller.
*
* Nothing is kept in the lua state, every request goes to the controller and
* the answer is handed back to lua once it arrives. The instance may have
* stopped by then, in which case the answer is dropped.
*/
export class InstancePlugin {
private constructor(
public instance: Instance,
public logger: lib.Logger,
public name: string,
) {}
static async fromContext(context: InstancePluginContext) {
const instance = context.instance;
const plugin = new InstancePlugin(instance, context.logger, context.plugin.name);
instance.server.handle("exp_reports:create", plugin.handleCreateIPC.bind(plugin));
instance.server.handle("exp_reports:list", plugin.handleListIPC.bind(plugin));
instance.server.handle("exp_reports:delete", plugin.handleDeleteIPC.bind(plugin));
return plugin;
}
async handleCreateIPC(event: IpcReportCreate) {
try {
const report = await this.instance.sendTo("controller", new messages.ReportCreateRequest(
event.player_name, event.reason, event.by_player_name,
));
const reports = await this.instance.sendTo("controller", new messages.ReportListRequest(event.player_name));
await this.luaSend("receive_created", {
report: report.toJSON(),
reports: reports.map(other => other.toJSON()),
});
} catch (err: any) {
await this.luaSend("receive_error", { caller: event.by_player_name, message: err.message });
}
}
async handleListIPC(event: IpcReportList) {
try {
const reports = await this.instance.sendTo("controller", new messages.ReportListRequest(event.player_name));
await this.luaSend("receive_list", {
caller: event.caller,
player_name: event.player_name,
reports: reports.map(report => report.toJSON()),
});
} catch (err: any) {
await this.luaSend("receive_error", { caller: event.caller, message: err.message });
}
}
async handleDeleteIPC(event: IpcReportDelete) {
try {
let reports = await this.instance.sendTo("controller", new messages.ReportListRequest(event.player_name));
if (event.by_player_name !== undefined) {
reports = reports.filter(report => report.byPlayerName === event.by_player_name);
}
for (const report of reports) {
await this.instance.sendTo("controller", new messages.ReportDeleteRequest(report.id));
}
await this.luaSend("receive_deleted", {
caller: event.caller,
player_name: event.player_name,
by_player_name: event.by_player_name,
count: reports.length,
});
} catch (err: any) {
await this.luaSend("receive_error", { caller: event.caller, message: err.message });
}
}
/** Hand an answer to lua, unless the instance stopped while it was being fetched. */
async luaSend(receiver: string, json: any) {
if (this.instance.status !== "running") {
return;
}
await this.instance.sendRcon(
`/sc exp_reports.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true
);
}
}
export default async function (context: InstancePluginContext) {
await InstancePlugin.fromContext(context);
}