mirror of
https://github.com/thepeacockproject/Peacock
synced 2024-11-29 09:15:11 +01:00
252 lines
6.8 KiB
TypeScript
252 lines
6.8 KiB
TypeScript
/*
|
|
* The Peacock Project - a HITMAN server replacement.
|
|
* Copyright (C) 2021-2023 The Peacock Project Team
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* A HookMap is a helper class for a Map with Hooks.
|
|
*
|
|
* @example
|
|
* const myHookMap = new HookMap(key => new SyncHook())
|
|
*
|
|
* @example
|
|
* hookMap.for("some-key").tap("MyPlugin", (arg) => { })
|
|
*
|
|
* @example
|
|
* const hook = hookMap.for("some-key")
|
|
* hook.call("some value", 123456)
|
|
*/
|
|
export class HookMap<Hook> {
|
|
private readonly _map: Map<string, Hook>
|
|
|
|
public constructor(private readonly _createFunc: (key: string) => Hook) {
|
|
this._map = new Map()
|
|
}
|
|
|
|
/**
|
|
* Get a hook for the given key.
|
|
*
|
|
* @param key The hook to get.
|
|
* @returns The hook.
|
|
*/
|
|
public for(key: string): Hook {
|
|
if (this._map.has(key)) {
|
|
return this._map.get(key)!
|
|
}
|
|
|
|
const hook = this._createFunc(key)
|
|
this._map.set(key, hook)
|
|
return hook
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The options for a hook. Will either be just the name (as a string), or an object containing the additional options.
|
|
*/
|
|
export type TapOptions = string | { name: string; context: boolean }
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
type AsArray<T> = T extends any[] ? T : [T]
|
|
|
|
/**
|
|
* An internal interface containing the properties held by a single taps' container object.
|
|
*/
|
|
interface Tap<T, R> {
|
|
name: string
|
|
func: (...args: AsArray<T>) => R
|
|
enableContext: boolean
|
|
}
|
|
|
|
/**
|
|
* The structure of an intercept.
|
|
*
|
|
* @see name
|
|
* @see call
|
|
* @see tap
|
|
*/
|
|
export interface Intercept<Params, Return> {
|
|
/**
|
|
* The name of the intercept.
|
|
*/
|
|
name: string
|
|
|
|
/**
|
|
* A function called just after the hook is called, and before all taps run.
|
|
*
|
|
* @param context The context object. Can be modified.
|
|
* @param params The parameters that the taps will get. Can be modified.
|
|
*/
|
|
call(context, ...params: AsArray<Params>): void
|
|
|
|
/**
|
|
* A function called when the hook is tapped. Note that it will not be called when an interceptor is registered, since that doesn't count as a tap.
|
|
*
|
|
* @param name The name of the tap.
|
|
* @param func The tap's function.
|
|
*/
|
|
tap(name: string, func: (...args: AsArray<Params>) => Return): void
|
|
}
|
|
|
|
/**
|
|
* The base for a hook, including {@link tap} and {@link intercept} functionality.
|
|
*
|
|
* @see SyncHook
|
|
* @see SyncBailHook
|
|
* @see AsyncSeriesHook
|
|
*/
|
|
export abstract class BaseImpl<Params, Return = void> {
|
|
protected _intercepts: Intercept<Params, Return>[]
|
|
protected _taps: Tap<Params, Return>[]
|
|
|
|
/**
|
|
* Register an interceptor.
|
|
* Interceptors can listen for certain events, and control things like context for them.
|
|
*
|
|
* @param intercept An object containing the intercept.
|
|
* @see Intercept
|
|
*/
|
|
public intercept(intercept: Intercept<Params, Return>): void {
|
|
this._intercepts.push(intercept)
|
|
}
|
|
|
|
/**
|
|
* Tap the hook.
|
|
*
|
|
* @param nameOrOptions A string containing the tap's name, or an object containing the tap's details.
|
|
* @param consumer The function that will be called when the hook is.
|
|
* @see TapOptions
|
|
*/
|
|
public tap(
|
|
nameOrOptions: TapOptions,
|
|
consumer: (...args: AsArray<Params>) => Return,
|
|
): void {
|
|
const name =
|
|
typeof nameOrOptions === "string"
|
|
? nameOrOptions
|
|
: nameOrOptions.name
|
|
const enableContext =
|
|
typeof nameOrOptions === "string" ? false : nameOrOptions.context
|
|
|
|
for (const intercept of this._intercepts) {
|
|
if (intercept.tap) {
|
|
intercept.tap(name, consumer)
|
|
}
|
|
}
|
|
|
|
this._taps.push({
|
|
name,
|
|
func: consumer,
|
|
enableContext,
|
|
})
|
|
}
|
|
|
|
public get allTapNames(): string[] {
|
|
return this._taps.map((t) => t.name)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A hook that runs each tap one-by-one.
|
|
*/
|
|
export class SyncHook<Params> extends BaseImpl<Params> {
|
|
public constructor() {
|
|
super()
|
|
this._taps = []
|
|
this._intercepts = []
|
|
}
|
|
|
|
public call(...params: AsArray<Params>): void {
|
|
const context = {}
|
|
|
|
for (const intercept of this._intercepts) {
|
|
if (intercept.call) {
|
|
intercept.call(context, ...params)
|
|
}
|
|
}
|
|
|
|
for (const tap of this._taps) {
|
|
const args = tap.enableContext ? [context, ...params] : [...params]
|
|
|
|
// @ts-expect-error TypeScript things.
|
|
tap.func(...args)
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A hook that runs each tap one-by-one until one returns a result.
|
|
*/
|
|
export class SyncBailHook<Params, Return> extends BaseImpl<Params, Return> {
|
|
public constructor() {
|
|
super()
|
|
this._taps = []
|
|
this._intercepts = []
|
|
}
|
|
|
|
public call(...params: AsArray<Params>): Return | null {
|
|
const context = {}
|
|
|
|
for (const intercept of this._intercepts) {
|
|
if (intercept.call) {
|
|
intercept.call(context, ...params)
|
|
}
|
|
}
|
|
|
|
for (const tap of this._taps) {
|
|
const args = tap.enableContext ? [context, ...params] : [...params]
|
|
|
|
// @ts-expect-error TypeScript things.
|
|
const result = tap.func(...args)
|
|
|
|
if (result) {
|
|
return result
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A hook that runs each tap, one-by-one, in an async context (each tap may be an async function).
|
|
*/
|
|
export class AsyncSeriesHook<Params> extends BaseImpl<Params, Promise<void>> {
|
|
public constructor() {
|
|
super()
|
|
this._taps = []
|
|
this._intercepts = []
|
|
}
|
|
|
|
public async callAsync(...params: AsArray<Params>): Promise<void> {
|
|
const context = {}
|
|
|
|
for (const intercept of this._intercepts) {
|
|
if (intercept.call) {
|
|
await intercept.call(context, ...params)
|
|
}
|
|
}
|
|
|
|
for (const tap of this._taps) {
|
|
const args = tap.enableContext ? [context, ...params] : [...params]
|
|
|
|
// @ts-expect-error TypeScript things.
|
|
await tap.func(...args)
|
|
}
|
|
}
|
|
}
|