Files
youtube-music/src/utils/index.ts
2023-12-02 05:01:29 +09:00

175 lines
4.3 KiB
TypeScript

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<Config, 'enabled'> & {
enabled: boolean;
};
},
) => def;
export const createBackend = <
BackendProperties,
Config extends PluginConfig = PluginConfig,
>(
back: {
[Key in keyof BackendProperties]: BackendProperties[Key];
} & PluginLifecycle<Config, BackendContext<Config>, BackendProperties>,
) => back;
export const createPreload = <
PreloadProperties,
Config extends PluginConfig = PluginConfig,
>(
preload: {
[Key in keyof PreloadProperties]: PreloadProperties[Key];
} & PluginLifecycle<Config, PreloadContext<Config>, PreloadProperties>,
) => preload;
export const createRenderer = <
RendererProperties,
Config extends PluginConfig = PluginConfig,
>(
renderer: {
[Key in keyof RendererProperties]: RendererProperties[Key];
} & RendererPluginLifecycle<
Config,
RendererContext<Config>,
RendererProperties
>,
) => renderer;
type Options<Config extends PluginConfig> =
| { ctx: 'backend'; context: BackendContext<Config> }
| { ctx: 'preload'; context: PreloadContext<Config> }
| { ctx: 'renderer'; context: RendererContext<Config> };
export const startPlugin = async <Config extends PluginConfig>(
id: string,
def: PluginDef<unknown, unknown, unknown, Config>,
options: Options<Config>,
) => {
const lifecycle =
typeof def[options.ctx] === 'function'
? (def[options.ctx] as PluginLifecycleSimple<Config, unknown>)
: (
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 <Config extends PluginConfig>(
id: string,
def: PluginDef<unknown, unknown, unknown, Config>,
options: Options<Config>,
) => {
if (!def || !def[options.ctx]) return false;
if (typeof def[options.ctx] === 'function') return false;
const defCtx = def[options.ctx] as
| { stop: PluginLifecycleSimple<Config, unknown> }
| 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;
}
};