diff --git a/libraries/plugins/src/index.ts b/libraries/plugins/src/index.ts index 1c3a18e..7bdb695 100644 --- a/libraries/plugins/src/index.ts +++ b/libraries/plugins/src/index.ts @@ -1,10 +1,10 @@ import { afterAppRender } from '@revenge-mod/app' import { getErrorStack } from '@revenge-mod/utils/errors' -import { appRenderedCallbacks, corePluginIds, plugins, registerPlugin } from './internals' +import { appRenderedCallbacks, corePluginIds, plugins } from './internals' import { logger } from './shared' -import type { PluginDefinition, PluginStage, PluginStorage } from './types' +import type { PluginDefinition, PluginStage } from './types' export type * from './types' @@ -12,19 +12,9 @@ afterAppRender(() => { for (const cb of appRenderedCallbacks) cb() }) -export const PluginsLibrary = { - /** - * Defines a plugin - * @param definition The plugin definition - * @returns The plugin object - */ - definePlugin, -} - -export function definePlugin( - definition: PluginDefinition, -) { - return registerPlugin(definition) +export function installPlugin() { + // TODO + throw new Error('Not implemented') } /** @@ -65,8 +55,6 @@ export function startPluginsMetroModuleSubscriptions() { for (const plugin of Object.values(plugins)) plugin.startMetroModuleSubscriptions!() } -export type PluginsLibrary = typeof PluginsLibrary - export type PluginContextFor = Definition extends PluginDefinition ? Parameters]>>[0] : never diff --git a/libraries/plugins/src/internals.ts b/libraries/plugins/src/internals.ts index f273c6e..0b78b83 100644 --- a/libraries/plugins/src/internals.ts +++ b/libraries/plugins/src/internals.ts @@ -7,6 +7,7 @@ import { type PluginStates, pluginsStates } from '@revenge-mod/preferences' import { type Patcher, createPatcherInstance } from '@revenge-mod/patcher' import { awaitStorage, createStorage } from '@revenge-mod/storage' +import { PluginStoragePath } from '@revenge-mod/shared/paths' import { getErrorStack } from '@revenge-mod/utils/errors' import { objectSeal } from '@revenge-mod/utils/functions' @@ -19,6 +20,7 @@ import type React from 'react' import type { PluginContext, PluginDefinition, + PluginManifest, PluginModuleSubscriptionContext, PluginStopConfig, PluginStorage, @@ -40,6 +42,7 @@ const DefaultStopConfig: Required = { export function registerPlugin( definition: PluginDefinition & + PluginManifest & Partial< Omit, keyof PluginDefinition> >, @@ -55,7 +58,7 @@ export function registerPlugin { instance.patcher ||= createPatcherInstance(`revenge.plugins.plugin#${definition.id}`) - instance.storage ||= createStorage(`revenge/plugins/${definition.id}/storage.json`, { + instance.storage ||= createStorage(PluginStoragePath(definition.id), { initial: definition.initializeStorage?.() ?? {}, }) } @@ -245,79 +248,80 @@ export function registerPlugin = Omit< PluginDefinition, 'settings' -> & { - state: PluginStates[PluginDefinition['id']] - /** - * Runs when a Metro module loads, useful for patching modules very early on. - * @internal - */ - onMetroModuleLoad?: ( - context: PluginModuleSubscriptionContext, - id: Metro.ModuleID, - exports: Metro.ModuleExports, - unsubscribe: () => void, - ) => void - /** - * Disables the plugin - * @returns A full plugin stop config object - * @see {@link PluginStopConfig} - */ - disable(): Required - /** - * Enables the plugin - * @internal - * @returns Whether a reload should be required - */ - enable(): boolean - /** - * Starts the plugin's Metro module subscriptions (if it exists) - * @internal - */ - startMetroModuleSubscriptions: () => void - /** - * Starts the plugin normal lifecycles - * @internal - */ - start(): Promise - /** - * Stops the plugin - * @internal - * @returns A full plugin stop config object - */ - stop(): Required - /** - * Whether the plugin is stopped - * @internal - */ - stopped: boolean - /** - * Whether the plugin is enabled. This will be set to `false` in the `beforeStop` lifecycle if the user disables the plugin. - */ - enabled: boolean - /** - * The plugin's status - * @internal - */ - status: (typeof PluginStatus)[keyof typeof PluginStatus] - /** - * The plugin's settings component - * @internal - **/ - SettingsComponent?: React.FC> - /** - * Whether the plugin is a core plugin - * @internal - */ - core: boolean - /** - * Whether the plugin is manageable (can be disabled/enabled) - * @internal - */ - manageable: boolean - /** - * The plugin's errors - * @internal - **/ - // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown - errors: any[] -} +> & + PluginManifest & { + state: PluginStates[PluginManifest['id']] + /** + * Runs when a Metro module loads, useful for patching modules very early on. + * @internal + */ + onMetroModuleLoad?: ( + context: PluginModuleSubscriptionContext, + id: Metro.ModuleID, + exports: Metro.ModuleExports, + unsubscribe: () => void, + ) => void + /** + * Disables the plugin + * @returns A full plugin stop config object + * @see {@link PluginStopConfig} + */ + disable(): Required + /** + * Enables the plugin + * @internal + * @returns Whether a reload should be required + */ + enable(): boolean + /** + * Starts the plugin's Metro module subscriptions (if it exists) + * @internal + */ + startMetroModuleSubscriptions: () => void + /** + * Starts the plugin normal lifecycles + * @internal + */ + start(): Promise + /** + * Stops the plugin + * @internal + * @returns A full plugin stop config object + */ + stop(): Required + /** + * Whether the plugin is stopped + * @internal + */ + stopped: boolean + /** + * Whether the plugin is enabled. This will be set to `false` in the `beforeStop` lifecycle if the user disables the plugin. + */ + enabled: boolean + /** + * The plugin's status + * @internal + */ + status: (typeof PluginStatus)[keyof typeof PluginStatus] + /** + * The plugin's settings component + * @internal + **/ + SettingsComponent?: React.FC> + /** + * Whether the plugin is a core plugin + * @internal + */ + core: boolean + /** + * Whether the plugin is manageable (can be disabled/enabled) + * @internal + */ + manageable: boolean + /** + * The plugin's errors + * @internal + **/ + // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown + errors: any[] + } diff --git a/libraries/plugins/src/types.d.ts b/libraries/plugins/src/types.d.ts index 3872727..55e6ef7 100644 --- a/libraries/plugins/src/types.d.ts +++ b/libraries/plugins/src/types.d.ts @@ -6,8 +6,7 @@ import type React from 'react' import type { WhitelistedPluginObjectKeys } from './constants' import type { InternalPluginDefinition } from './internals' -// biome-ignore lint/suspicious/noExplicitAny: Defaulting to unknown does NOT work -export type PluginDefinition = { +export interface PluginManifest { /** * The friendly name of the plugin */ @@ -32,7 +31,10 @@ export type PluginDefinition = { /** * Runs before the app gets rendered AND even before the plugin is refetched and updated. * If your plugin receives a new update, your old version will continue to run until the user decides to reload the app. diff --git a/libraries/preferences/src/index.ts b/libraries/preferences/src/index.ts index 2f41475..9d8406c 100644 --- a/libraries/preferences/src/index.ts +++ b/libraries/preferences/src/index.ts @@ -1,3 +1,4 @@ +import { PluginsStatesFilePath, SettingsFilePath } from '@revenge-mod/shared/paths' import { createStorage } from '@revenge-mod/storage' export interface Settings { @@ -15,7 +16,7 @@ export type SerializedPluginError = { stack: string } -export const settings = createStorage('revenge/settings.json', { +export const settings = createStorage(SettingsFilePath, { initial: { safeMode: { enabled: false, @@ -24,6 +25,6 @@ export const settings = createStorage('revenge/settings.json', { }, }) -export const pluginsStates = createStorage('revenge/plugins/states.json', { +export const pluginsStates = createStorage(PluginsStatesFilePath, { initial: {}, }) diff --git a/libraries/shared/src/paths.ts b/libraries/shared/src/paths.ts new file mode 100644 index 0000000..036f500 --- /dev/null +++ b/libraries/shared/src/paths.ts @@ -0,0 +1,7 @@ +const BaseDirectory = 'revenge' + +export const SettingsFilePath = `${BaseDirectory}/settings.json` + +export const PluginsDirectoryPath = `${BaseDirectory}/plugins` +export const PluginsStatesFilePath = `${PluginsDirectoryPath}/states.json` +export const PluginStoragePath = (id: string) => `${PluginsDirectoryPath}/${id}/storage.json` diff --git a/src/plugins/developer-settings/pages/Developer.tsx b/src/plugins/developer-settings/pages/Developer.tsx index b795353..8ba3a7a 100644 --- a/src/plugins/developer-settings/pages/Developer.tsx +++ b/src/plugins/developer-settings/pages/Developer.tsx @@ -26,23 +26,24 @@ import { import { settings } from '@revenge-mod/preferences' import { ScrollView } from 'react-native' import { PluginContext } from '..' +import { useContext, useEffect, useRef, useState } from 'react' +import { PluginsDirectoryPath } from '@revenge-mod/shared/paths' export default function DeveloperSettingsPage() { + const context = useContext(PluginContext) const { storage, revenge: { assets, modules }, - } = React.useContext(PluginContext) + } = context useObservable([storage]) const navigation = NavigationNative.useNavigation() - const refEvalCode = React.useRef('') - const refDevToolsAddr = React.useRef(storage.reactDevTools.address || 'localhost:8097') + const refDevToolsAddr = useRef(storage.reactDevTools.address || 'localhost:8097') + const [connected, setConnected] = useState(DevToolsContext.connected) - const [connected, setConnected] = React.useState(DevToolsContext.connected) - - React.useEffect(() => { + useEffect(() => { const listener: DevToolsEventsListeners['*'] = evt => { if (evt === 'connect') setConnected(true) else setConnected(false) @@ -114,38 +115,9 @@ export default function DeveloperSettingsPage() { onPress={() => { alerts.openAlert( 'revenge.plugins.storage.evaluate', - (refEvalCode.current = v)} - /> - } - actions={ - - - alert( - modules.findProp< - (val: unknown, opts?: { depth?: number }) => string - >('inspect')!( - // biome-ignore lint/security/noGlobalEval: This is intentional - globalThis.eval(refEvalCode.current), - { depth: 5 }, - ), - ) - } - /> - - - } - />, + + + , ) }} /> @@ -171,7 +143,7 @@ export default function DeveloperSettingsPage() { subLabel="This will remove the all plugin-related data and reload the app." icon={} onPress={async () => { - await FileModule.clearFolder('documents', './revenge/plugins') + await FileModule.clearFolder('documents', PluginsDirectoryPath) BundleUpdaterManager.reload() }} /> @@ -225,3 +197,56 @@ export default function DeveloperSettingsPage() { ) } + +function DeveloperSettingsPageEvaluateJavaScriptAlert() { + const { + revenge: { modules }, + } = useContext(PluginContext) + + const [evalAwaitResult, setEvalAwaitResult] = useState(true) + const codeRef = useRef('') + + return ( + +