import { t } from '@/i18n'; import type { BackendContext, PreloadContext, RendererContext, } from '@/types/contexts'; import type { PluginDef, PluginConfig, PluginLifecycleExtra, PluginLifecycleSimple, PluginLifecycle, RendererPluginLifecycle, } from '@/types/plugins'; export const LoggerPrefix = '[YTMusic]'; export const createPlugin = < BackendProperties, PreloadProperties, RendererProperties, Config extends PluginConfig = PluginConfig, >( def: PluginDef< BackendProperties, PreloadProperties, RendererProperties, Config > & { config?: Omit & { enabled: boolean; }; }, ) => def; export const createBackend = < BackendProperties, Config extends PluginConfig = PluginConfig, >( back: { [Key in keyof BackendProperties]: BackendProperties[Key]; } & PluginLifecycle, BackendProperties>, ) => back; export const createPreload = < PreloadProperties, Config extends PluginConfig = PluginConfig, >( preload: { [Key in keyof PreloadProperties]: PreloadProperties[Key]; } & PluginLifecycle, PreloadProperties>, ) => preload; export const createRenderer = < RendererProperties, Config extends PluginConfig = PluginConfig, >( renderer: { [Key in keyof RendererProperties]: RendererProperties[Key]; } & RendererPluginLifecycle< Config, RendererContext, RendererProperties >, ) => renderer; type Options = | { ctx: 'backend'; context: BackendContext } | { ctx: 'preload'; context: PreloadContext } | { ctx: 'renderer'; context: RendererContext }; export const startPlugin = async ( id: string, def: PluginDef, options: Options, ) => { const lifecycle = typeof def[options.ctx] === 'function' ? (def[options.ctx] as PluginLifecycleSimple) : ( def[options.ctx] as PluginLifecycleExtra< Config, typeof options.context, unknown > )?.start; try { // HACK: for bind 'this' to context const defContext = def[options.ctx]; if (defContext && typeof defContext !== 'function') { Object.entries(defContext).forEach(([key, value]) => { if (typeof value === 'function') { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access defContext[key as keyof typeof defContext] = value.bind(defContext); } }); } const start = performance.now(); await lifecycle?.call( defContext, options.context as Config & typeof options.context, ); console.log( LoggerPrefix, t('common.console.plugins.executed-at-ms', { pluginName: id, contextName: options.ctx, ms: (performance.now() - start).toFixed(2), }), ); return lifecycle ? true : null; } catch (err) { console.error( LoggerPrefix, t('common.console.plugins.execute-failed', { pluginName: id, contextName: options.ctx, }), ); console.trace(err); return false; } }; export const stopPlugin = async ( id: string, def: PluginDef, options: Options, ) => { if (!def || !def[options.ctx]) return false; if (typeof def[options.ctx] === 'function') return false; const defCtx = def[options.ctx] as | { stop: PluginLifecycleSimple } | undefined; if (!defCtx?.stop) return null; try { const stop = defCtx.stop; const start = performance.now(); await stop.call( def[options.ctx], options.context as Config & typeof options.context, ); console.log( LoggerPrefix, t('common.console.plugins.executed-at-ms', { pluginName: id, contextName: options.ctx, ms: performance.now() - start, }), ); return true; } catch (err) { console.error( LoggerPrefix, t('common.console.plugins.execute-failed', { pluginName: id, contextName: options.ctx, }), ); console.trace(err); return false; } };