diff --git a/lib/discord/package.json b/lib/discord/package.json index 182fc88..b08ee68 100644 --- a/lib/discord/package.json +++ b/lib/discord/package.json @@ -21,6 +21,9 @@ "./native": { "default": "./src/native.ts" }, + "./init": { + "default": "./src/init.ts" + }, "./preinit": { "default": "./src/preinit.ts" }, diff --git a/lib/discord/src/init.ts b/lib/discord/src/init.ts new file mode 100644 index 0000000..3a784a2 --- /dev/null +++ b/lib/discord/src/init.ts @@ -0,0 +1,26 @@ +import { _initExts, _uapi } from '@revenge-mod/plugins/_' +import { defineLazyProperty } from '@revenge-mod/utils/objects' + +import { Logger } from './common' + +import type { UnscopedInitPluginApi } from '@revenge-mod/plugins/types' +import type { PluginApiDiscord } from './types/revenge' + +const uapi = _uapi as UnscopedInitPluginApi +const discord = (uapi.discord = {} as PluginApiDiscord) + +// TODO: Rolldown issue: Bug requires us to do { ...require('...') } instead of just require('...') +defineLazyProperty(discord, 'actions', () => ({ ...require('@revenge-mod/discord/actions') })) +defineLazyProperty(discord, 'common', () => ({ ...require('@revenge-mod/discord/common') })) +defineLazyProperty(discord, 'design', () => ({ ...require('@revenge-mod/discord/design') })) +defineLazyProperty(discord, 'native', () => ({ ...require('@revenge-mod/discord/native') })) +defineLazyProperty(discord, 'modules', () => ({ + settings: { + ...require('@revenge-mod/discord/modules/settings'), + renderer: require('@revenge-mod/discord/modules/settings/renderer'), + }, +})) + +_initExts.push((api, { manifest: { id } }) => + defineLazyProperty(api, 'logger', () => new Logger(`Revenge > Plugins (${id})`)), +) diff --git a/lib/discord/src/types/revenge.ts b/lib/discord/src/types/revenge.ts new file mode 100644 index 0000000..b325a35 --- /dev/null +++ b/lib/discord/src/types/revenge.ts @@ -0,0 +1,32 @@ +import type { DiscordModules } from '.' + +export interface PluginApiDiscord { + actions: PluginApiDiscord.Actions + common: PluginApiDiscord.Common + design: PluginApiDiscord.Design + modules: PluginApiDiscord.Modules + native: PluginApiDiscord.Native +} + +export namespace PluginApiDiscord { + export type Actions = typeof import('@revenge-mod/discord/actions') + export type Common = typeof import('@revenge-mod/discord/common') + export type Design = typeof import('@revenge-mod/discord/design') + export type Native = typeof import('@revenge-mod/discord/native') + + export interface Modules { + settings: typeof import('@revenge-mod/discord/modules/settings') & { + renderer: typeof import('@revenge-mod/discord/modules/settings/renderer') + } + } +} + +declare module '@revenge-mod/plugins/types' { + export interface UnscopedInitPluginApi { + discord: PluginApiDiscord + } + + export interface InitPluginApi { + logger: InstanceType + } +} diff --git a/lib/plugins/src/_internal.ts b/lib/plugins/src/_internal.ts index 88070c5..9b03780 100644 --- a/lib/plugins/src/_internal.ts +++ b/lib/plugins/src/_internal.ts @@ -1,7 +1,7 @@ import { type StorageOptions, getStorage } from '@revenge-mod/storage' import { allSettled, sleepReject } from '@revenge-mod/utils/promises' -import { _uapi } from './apis' +import { _uapi as uapi } from './apis' import { PluginFlags as Flag, PluginsStorageDirectory, PluginStatus as Status } from './constants' @@ -16,6 +16,12 @@ import type { PreInitPluginApi, } from './types' +export const _uapi = uapi + +export const _preInitExts: Array<(api: PreInitPluginApi, plugin: InternalPlugin) => void> = [] +export const _initExts: Array<(api: InitPluginApi, plugin: InternalPlugin) => void> = [] +export const _startExts: Array<(api: PluginApi, plugin: InternalPlugin) => void> = [] + export const _plugins = new Map() export const _metas = new Map< PluginManifest['id'], @@ -68,62 +74,56 @@ function handlePluginError(e: unknown, plugin: InternalPlugin) { if (!(iflags & InternalPluginFlags.Essential)) return plugin.disable() } -function preparePluginPreInit(id: PluginManifest['id']) { - const plugin = _plugins.get(id)! - const meta = _metas.get(id)! +function preparePluginPreInit(plugin: InternalPlugin) { + const meta = _metas.get(plugin.manifest.id)! // Clear errors from previous runs plugin.errors = [] plugin.status &= ~Flag.Errored - meta[0] = { + const api = (meta[0] = { cleanup: (...items) => { plugin._c.push(...items) }, plugin, unscoped: _uapi, - } + }) + + for (const ext of _preInitExts) ext(api, plugin) meta[3] = PluginApiLevel.PreInit } -function preparePluginInit(id: PluginManifest['id'], storageOptions?: StorageOptions) { - const meta = _metas.get(id)! +function preparePluginInit(plugin: InternalPlugin, storageOptions?: StorageOptions) { + const meta = _metas.get(plugin.manifest.id)! const api = meta[0] as InitPluginApi - Object.defineProperties(api, { - logger: { - configurable: true, - get() { - // @ts-expect-error - // biome-ignore lint/performance/noDelete: - delete api.logger - return (api.logger = new api.unscoped.discord.common.Logger(`Revenge > Plugins (${id})`)) - }, - }, - storage: { - configurable: true, - get() { - // @ts-expect-error - // biome-ignore lint/performance/noDelete: - delete api.storage - return (api.storage = getStorage(`${PluginsStorageDirectory}/${id}.json`, { - ...storageOptions, - directory: 'documents', - })) - }, + Object.defineProperty(api, 'storage', { + configurable: true, + get() { + // @ts-expect-error + // biome-ignore lint/performance/noDelete: + delete api.storage + return (api.storage = getStorage(`${PluginsStorageDirectory}/${plugin.manifest.id}.json`, { + ...storageOptions, + directory: 'documents', + })) }, }) + for (const ext of _initExts) ext(api, plugin) + meta[3] = PluginApiLevel.Init } -function preparePluginStart(id: PluginManifest['id']) { - const meta = _metas.get(id)! +function preparePluginStart(plugin: InternalPlugin) { + const meta = _metas.get(plugin.manifest.id)! - // const api = meta[0] as PluginApi + const api = meta[0] as PluginApi // api.settings = ... + for (const ext of _startExts) ext(api, plugin) + meta[3] = PluginApiLevel.Start } @@ -146,17 +146,20 @@ export function enablePlugin(plugin: InternalPlugin, late: boolean) { } export async function preInitPlugin(plugin: InternalPlugin) { - const { manifest, lifecycles } = plugin + const { + manifest: { id }, + lifecycles, + } = plugin if (!lifecycles.preInit) return - const meta = _metas.get(manifest.id)! + const meta = _metas.get(id)! const [, promises] = meta - preparePluginPreInit(manifest.id) + preparePluginPreInit(plugin) - if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`) + if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`) if (plugin.status & (Status.PreIniting | Status.PreInited)) - throw new Error(`Plugin preInit lifecycle for "${manifest.id}" is already running`) + throw new Error(`Plugin preInit lifecycle for "${id}" is already running`) plugin.status |= Status.PreIniting @@ -174,18 +177,21 @@ export async function preInitPlugin(plugin: InternalPlugin) { } export async function initPlugin(plugin: InternalPlugin) { - const { manifest, lifecycles } = plugin + const { + manifest: { id }, + lifecycles, + } = plugin if (!lifecycles.init) return - const meta = _metas.get(manifest.id)! + const meta = _metas.get(id)! const [, promises, , apiLevel] = meta - if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`) + if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`) if (plugin.status & (Status.Initing | Status.Inited)) - throw new Error(`Plugin init lifecycle for "${manifest.id}" is already running`) + throw new Error(`Plugin init lifecycle for "${id}" is already running`) - if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id) - if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id) + if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin) + if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin) try { const prom = lifecycles.init(meta[0] as InitPluginApi) @@ -201,21 +207,24 @@ export async function initPlugin(plugin: InternalPlugin) { } export async function startPlugin(plugin: InternalPlugin) { - const { manifest, lifecycles } = plugin + const { + manifest: { id }, + lifecycles, + } = plugin if (!lifecycles.start) return - const meta = _metas.get(manifest.id)! + const meta = _metas.get(id)! const [, promises, , apiLevel] = meta - if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`) + if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`) if (plugin.status & (Status.Starting | Status.Started)) - throw new Error(`Plugin start lifecycle for "${manifest.id}" is already running`) + throw new Error(`Plugin start lifecycle for "${id}" is already running`) plugin.status |= Status.Starting - if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id) - if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id) - if (apiLevel < PluginApiLevel.Start) preparePluginStart(manifest.id) + if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin) + if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin) + if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin) try { const prom = lifecycles.start(meta[0] as PluginApi) @@ -233,15 +242,17 @@ export async function startPlugin(plugin: InternalPlugin) { const MaxWaitTime = 5000 export async function stopPlugin(plugin: InternalPlugin) { - const { manifest, lifecycles } = plugin - const meta = _metas.get(manifest.id)! + const { + manifest: { id }, + lifecycles, + } = plugin + const meta = _metas.get(id)! const [, promises, iflags, apiLevel] = meta - if (iflags & InternalPluginFlags.Essential) - throw new Error(`Plugin "${manifest.id}" is essential and cannot be stopped`) + if (iflags & InternalPluginFlags.Essential) throw new Error(`Plugin "${id}" is essential and cannot be stopped`) - if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`) - if (plugin.status & Status.Stopping) throw new Error(`Plugin "${manifest.id}" is stopping`) + if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`) + if (plugin.status & Status.Stopping) throw new Error(`Plugin "${id}" is stopping`) // If the plugin is running its lifecycles, we need to wait for it to finish, then we'll stop it // We want to wait at max 5 seconds for the lifecycles to finish @@ -256,11 +267,11 @@ export async function stopPlugin(plugin: InternalPlugin) { }) .catch(e => handlePluginError(e, plugin)) else if (!(plugin.status & (Status.PreInited | Status.Inited | Status.Started))) - throw new Error(`Plugin "${manifest.id}" is not running`) + throw new Error(`Plugin "${id}" is not running`) - if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id) - if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id, plugin._s) - if (apiLevel < PluginApiLevel.Start) preparePluginStart(manifest.id) + if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin) + if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin, plugin._s) + if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin) plugin.status |= Status.Stopping diff --git a/lib/plugins/src/apis/discord.ts b/lib/plugins/src/apis/discord.ts deleted file mode 100644 index 0decf12..0000000 --- a/lib/plugins/src/apis/discord.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface PluginApiDiscord { - actions: PluginApiDiscordActions - common: PluginApiDiscordCommon - design: PluginApiDiscordDesign - modules: PluginApiDiscordModules - native: PluginApiDiscordNative -} - -export type PluginApiDiscordActions = typeof import('@revenge-mod/discord/actions') -export type PluginApiDiscordCommon = typeof import('@revenge-mod/discord/common') -export type PluginApiDiscordDesign = typeof import('@revenge-mod/discord/design') -export type PluginApiDiscordNative = typeof import('@revenge-mod/discord/native') - -export interface PluginApiDiscordModules { - settings: typeof import('@revenge-mod/discord/modules/settings') & { - renderer: typeof import('@revenge-mod/discord/modules/settings/renderer') - } -} - -export const discord: PluginApiDiscord = { - get actions() { - return (this.actions = require('@revenge-mod/discord/actions')) - }, - get common() { - return (this.common = require('@revenge-mod/discord/common')) - }, - get design() { - return (this.design = require('@revenge-mod/discord/design')) - }, - native: require('@revenge-mod/discord/native'), - modules: { - get settings() { - const api = { - ...require('@revenge-mod/discord/modules/settings'), - renderer: require('@revenge-mod/discord/modules/settings/renderer'), - } - - return (this.settings = api) - }, - }, -} diff --git a/lib/plugins/src/apis/externals.ts b/lib/plugins/src/apis/externals.ts index 41a8f6e..7aedec5 100644 --- a/lib/plugins/src/apis/externals.ts +++ b/lib/plugins/src/apis/externals.ts @@ -1,3 +1,5 @@ +import { defineLazyProperty } from '@revenge-mod/utils/objects' + export interface PluginApiExternals { Browserify: typeof import('@revenge-mod/externals/browserify') ReactNativeClipboard: typeof import('@revenge-mod/externals/react-native-clipboard') @@ -5,17 +7,12 @@ export interface PluginApiExternals { Shopify: typeof import('@revenge-mod/externals/shopify') } -export const externals: PluginApiExternals = { - get Browserify() { - return (this.Browserify = require('@revenge-mod/externals/browserify')) - }, - get ReactNativeClipboard() { - return (this.ReactNativeClipboard = require('@revenge-mod/externals/react-native-clipboard')) - }, - get ReactNavigation() { - return (this.ReactNavigation = require('@revenge-mod/externals/react-navigation')) - }, - get Shopify() { - return (this.Shopify = require('@revenge-mod/externals/shopify')) - }, -} +export const externals = {} as PluginApiExternals + +// TODO: Rolldown issue: Bug requires us to do { ...require('...') } instead of just require('...') +defineLazyProperty(externals, 'Browserify', () => ({ ...require('@revenge-mod/externals/browserify') })) +defineLazyProperty(externals, 'ReactNativeClipboard', () => ({ + ...require('@revenge-mod/externals/react-native-clipboard'), +})) +defineLazyProperty(externals, 'ReactNavigation', () => ({ ...require('@revenge-mod/externals/react-navigation') })) +defineLazyProperty(externals, 'Shopify', () => ({ ...require('@revenge-mod/externals/shopify') })) diff --git a/lib/plugins/src/init.ts b/lib/plugins/src/init.ts index 1bf30dd..c7a38d6 100644 --- a/lib/plugins/src/init.ts +++ b/lib/plugins/src/init.ts @@ -3,7 +3,6 @@ import { onRunApplication } from '@revenge-mod/react/native' import { _uapi } from './apis' import * as AssetsLibrary from '@revenge-mod/assets' -import { discord } from './apis/discord' import { externals } from './apis/externals' import { react } from './apis/react' @@ -15,7 +14,6 @@ import type { UnscopedInitPluginApi } from './types' // Setup init plugin APIs const uapi = _uapi as UnscopedInitPluginApi uapi.assets = AssetsLibrary -uapi.discord = discord uapi.externals = externals uapi.react = react diff --git a/lib/plugins/src/types.ts b/lib/plugins/src/types.ts index 67e9332..1626dad 100644 --- a/lib/plugins/src/types.ts +++ b/lib/plugins/src/types.ts @@ -1,11 +1,8 @@ import type { PluginFlags, PluginStatus } from './constants' -import type { DiscordModules } from '@revenge-mod/discord/types' - import type { Storage, StorageOptions } from '@revenge-mod/storage' import type { AnyObject } from '@revenge-mod/utils/types' -import type { PluginApiDiscord } from './apis/discord' import type { PluginApiExternals } from './apis/externals' import type { PluginApiModules } from './apis/modules' import type { PluginApiPlugins } from './apis/plugins' @@ -28,7 +25,6 @@ export interface UnscopedPreInitPluginApi { */ export interface UnscopedInitPluginApi extends UnscopedPreInitPluginApi { assets: typeof import('@revenge-mod/assets') - discord: PluginApiDiscord externals: PluginApiExternals react: PluginApiReact } @@ -60,7 +56,6 @@ export interface PreInitPluginApi { */ export interface InitPluginApi extends PreInitPluginApi { unscoped: UnscopedInitPluginApi - logger: InstanceType /** * The plugin storage. * diff --git a/src/index.ts b/src/index.ts index 21c3cec..cb4b3e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,7 @@ onModuleFirstRequired(IndexModuleId, function onIndexRequired() { onModuleInitialized(IndexModuleId, function onIndexInitialized() { runCatching(() => { // Initialize init libraries + require('@revenge-mod/discord/init') // Run all init plugins require('~/plugins/init')