diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 49d924b80..ae339cf9f 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -46,7 +46,7 @@ interface Application extends pluginapi.Application { /* * Clone of the above without functions. */ - plugin: Omit + plugin: Omit } /** @@ -254,6 +254,21 @@ export class PluginAPI { return p } + + public async dispose(): Promise { + await Promise.all( + Array.from(this.plugins.values()).map(async (p) => { + if (!p.deinit) { + return + } + try { + await p.deinit() + } catch (error) { + this.logger.error("plugin failed to deinit", field("name", p.name), field("error", error.message)) + } + }), + ) + } } interface PackageJSON { diff --git a/src/node/routes/index.ts b/src/node/routes/index.ts index 73116bfb2..0070c6957 100644 --- a/src/node/routes/index.ts +++ b/src/node/routes/index.ts @@ -15,6 +15,7 @@ import { Heart } from "../heart" import { redirect, replaceTemplates } from "../http" import { PluginAPI } from "../plugin" import { getMediaMime, paths } from "../util" +import { wrapper } from "../wrapper" import * as apps from "./apps" import * as domainProxy from "./domainProxy" import * as health from "./health" @@ -148,6 +149,7 @@ export const register = async ( await papi.loadPlugins() papi.mount(app, wsApp) app.use("/api/applications", apps.router(papi)) + wrapper.onDispose(() => papi.dispose()) app.use(() => { throw new HttpError("Not Found", HttpCode.NotFound) diff --git a/typings/pluginapi.d.ts b/typings/pluginapi.d.ts index 5c56303e3..e2ba32395 100644 --- a/typings/pluginapi.d.ts +++ b/typings/pluginapi.d.ts @@ -167,6 +167,11 @@ export interface Plugin { */ init(config: PluginConfig): void + /** + * Called when the plugin should dispose/shutdown everything. + */ + deinit?(): Promise + /** * Returns the plugin's router. *