Strongly type invokeHook

Add strong typing of the invokeHook function to prevent incorrect
paramaters being passed to plugin hooks. This detected a few places
where the parameters passed were incorrect.
This commit is contained in:
Hornwitser committed 2023-11-28 14:28:22 +01:00
1 parent e3e5867d60
commit 654df76065
7 files changed
+24 -18

No files matched your search

+1 -1
View File
@@ -661,7 +661,7 @@ export default class Controller {
let instance = new InstanceInfo({ config: instanceConfig, status: "unassigned" });
this.instances!.set(instanceId, instance);
await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance, null);
await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance);
this.addInstanceHooks(instance);
return instance;
}
+1 -1
View File
@@ -233,7 +233,7 @@ export default class HostConnection extends BaseConnection {
instanceConfig.get("instance.id"), instanceConfig.serialize("host")
)
);
await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance, null);
await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance);
}
// Push lists to make sure they are in sync.
+1 -2
View File
@@ -37,8 +37,7 @@ async function getMetrics(req: Request, res: Response, next: any) {
const controller: Controller = req.app.locals.controller;
let results: lib.CollectorResult[] = [];
type ResultIterator = AsyncIterable<lib.CollectorResult> | Iterable<lib.CollectorResult>
let pluginResults = await lib.invokeHook(controller.plugins, "onMetrics") as ResultIterator[];
let pluginResults = await lib.invokeHook(controller.plugins, "onMetrics");
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator) {
results.push(metric);
+2 -3
View File
@@ -594,8 +594,7 @@ export default class Host extends lib.Link {
}
let results = [];
type ResultIterator = AsyncIterable<lib.CollectorResult> | Iterable<lib.CollectorResult>
let pluginResults = await lib.invokeHook(this.plugins, "onMetrics") as ResultIterator[];
let pluginResults = await lib.invokeHook(this.plugins, "onMetrics");
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator) {
results.push(metric);
@@ -859,7 +858,7 @@ export default class Host extends lib.Link {
}
async prepareDisconnect() {
await lib.invokeHook(this.plugins, "onPrepareControllerDisconnect");
await lib.invokeHook(this.plugins, "onPrepareControllerDisconnect", this);
for (let instanceConnection of this.instanceConnections.values()) {
await instanceConnection.send(new lib.PrepareControllerDisconnectRequest());
}
+2 -3
View File
@@ -965,14 +965,13 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
}
async handlePrepareControllerDisconnectRequest() {
await lib.invokeHook(this.plugins, "onPrepareControllerDisconnect");
await lib.invokeHook(this.plugins, "onPrepareControllerDisconnect", this);
}
async handleInstanceMetricsRequest() {
let results: ReturnType<typeof lib.serializeResult>[] = [];
if (!["stopped", "stopping"].includes(this._status)) {
type ResultIterator = AsyncIterable<lib.CollectorResult> | Iterable<lib.CollectorResult>
let pluginResults = await lib.invokeHook(this.plugins, "onMetrics") as ResultIterator[];
let pluginResults = await lib.invokeHook(this.plugins, "onMetrics");
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator) {
results.push(lib.serializeResult(metric));
+2 -2
View File
@@ -96,11 +96,11 @@ export class ControllerConnectionEvent {
static dst = "instance" as const;
constructor(
public event: string,
public event: "connect" | "drop" | "resume" | "close",
) { }
static jsonSchema = Type.Object({
"event": Type.String(),
"event": StringEnum(["connect", "drop", "resume", "close"]),
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
+15 -6
View File
@@ -3,6 +3,7 @@
* @module lib/plugin
*/
import * as libHelpers from "./helpers";
import type { Logger } from "./logging";
import type { PluginConfigGroup } from "./config";
import type PlayerStats from "./PlayerStats";
@@ -71,14 +72,22 @@ export interface PlayerEvent {
* @param args - Arguments to pass on to the hook.
* @returns Non-undefined return values from the hooks.
*/
export async function invokeHook(plugins: Map<string, any>, hook: string, ...args: any[]) {
let results: unknown[] = [];
export async function invokeHook<
Hook extends string,
R,
Args extends [...any],
Plugin extends { logger: Logger } & Record<Hook, (...hookArgs: Args) => R | Promise<R>>
>(
plugins: Map<string, Plugin>,
hook: Hook,
...args: Args
): Promise<Exclude<Awaited<ReturnType<Plugin[Hook]>>, void>[]> {
let results: any[] = [];
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 as any)[hook](...args),
const timeout = Symbol("timeout-token");
let result = await libHelpers.timeout<R | typeof timeout>(
plugin[hook](...args) as Promise<R>,
15000,
timeout
);