mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
/**
|
|
* Everything a plugin registered, so deactivate can undo all of it.
|
|
*
|
|
* The rule this enforces: a plugin is only as disableable as its cleanup. A
|
|
* listening port, a live SSH session or an interval that outlives deactivate
|
|
* turns "disabled" into a lie, and re-enabling then fails on a port that is
|
|
* still held.
|
|
*
|
|
* Disposal runs in reverse registration order, so a thing torn down last was
|
|
* set up first. Every disposer is isolated: one that throws is logged and the
|
|
* rest still run, because a half-disposed plugin is worse than a noisy log.
|
|
*/
|
|
|
|
import { pluginLogger } from "../utils/logger.js";
|
|
|
|
export type Disposer = () => void | Promise<void>;
|
|
|
|
interface Entry {
|
|
dispose: Disposer;
|
|
/** What this is, for the log line when it throws. */
|
|
label: string;
|
|
}
|
|
|
|
export class DisposableBag {
|
|
private entries: Entry[] = [];
|
|
private disposed = false;
|
|
|
|
constructor(private readonly pluginId: string) {}
|
|
|
|
add(dispose: Disposer, label = "disposable"): void {
|
|
if (this.disposed) {
|
|
// Registering after teardown means the resource would never be cleaned
|
|
// up. Dispose it immediately rather than silently leaking it.
|
|
void this.run({ dispose, label });
|
|
return;
|
|
}
|
|
this.entries.push({ dispose, label });
|
|
}
|
|
|
|
get size(): number {
|
|
return this.entries.length;
|
|
}
|
|
|
|
/**
|
|
* Runs every disposer, newest first. Safe to call more than once, and never
|
|
* throws: the caller is already tearing the plugin down.
|
|
*/
|
|
async disposeAll(): Promise<void> {
|
|
this.disposed = true;
|
|
const entries = this.entries.reverse();
|
|
this.entries = [];
|
|
|
|
for (const entry of entries) {
|
|
await this.run(entry);
|
|
}
|
|
}
|
|
|
|
private async run(entry: Entry): Promise<void> {
|
|
try {
|
|
await entry.dispose();
|
|
} catch (error) {
|
|
pluginLogger.error(
|
|
`Plugin ${this.pluginId} failed to dispose ${entry.label}`,
|
|
error instanceof Error ? error : new Error(String(error)),
|
|
{ operation: "plugin_dispose" },
|
|
);
|
|
}
|
|
}
|
|
}
|