feat(plugins/settings): new api, remove ErrorBoundary trigger, add eval JS

This commit is contained in:
PalmDevs 2025-05-04 20:31:20 +07:00
parent 9959b5be6c
commit 0cc3e215eb
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
16 changed files with 157 additions and 55 deletions

View file

@ -15,7 +15,9 @@ export const MobileSetting = {
HERMES_VERSION: 'HERMES_VERSION',
CALL_GARBAGE_COLLECTOR: 'CALL_GARBAGE_COLLECTOR',
TRIGGER_ERROR_BOUNDARY: 'TRIGGER_ERROR_BOUNDARY',
EVALUATE_JAVASCRIPT: 'EVALUATE_JAVASCRIPT',
EVALUATE_JAVASCRIPT_INSPECT_DEPTH: 'EVALUATE_JAVASCRIPT_INSPECT_DEPTH',
EVALUATE_JAVASCRIPT_AWAIT_RESULT: 'EVALUATE_JAVASCRIPT_AWAIT_RESULT',
REVENGE_NOT_IMPLEMENTED: 'REVENGE_NOT_IMPLEMENTED',
} as const
@ -26,7 +28,6 @@ export const UserSettingsSections = {
[MobileSetting.REVENGE_THEMES]: 'Revenge Themes',
[MobileSetting.REVENGE_FONTS]: 'Revenge Fonts',
[MobileSetting.REVENGE_DEVELOPER]: 'Revenge Developer',
[MobileSetting.TRIGGER_ERROR_BOUNDARY]: 'Trigger Error Boundary',
} as const
export const AlwaysHidden = () => false

View file

@ -2,9 +2,9 @@ import { React } from '@revenge-mod/react'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const CallGarbageCollectorSetting: SettingsRowConfig = {
const CallGarbageCollectorSetting: SettingsItem = {
parent: null,
IconComponent: () => <AssetIcon name="FireIcon" />,
title: () => 'Call Garbage Collector',

View file

@ -0,0 +1,121 @@
import { React } from '@revenge-mod/react'
import { AlertActionCreators } from '@revenge-mod/discord/actions'
import { nodeUtils } from '@revenge-mod/discord/polyfills'
import { Components } from '@revenge-mod/discord/ui'
import { getErrorStack } from '@revenge-mod/utils/errors'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const ALERT_KEY = 'evaluate-javascript'
const EvaluateJavaScriptSetting: SettingsItem = {
parent: null,
IconComponent: () => <AssetIcon name="FileIcon" />,
title: () => 'Evaluate JavaScript',
useDescription: () => 'Runs a JavaScript code snippet.',
onPress: () => AlertActionCreators.openAlert(ALERT_KEY, <EvaluateJavaScriptAlert />),
type: 'pressable',
}
const {
AlertActionButton,
AlertModal,
Button,
Slider,
Stack,
TableRow,
TableRowGroup,
TableSwitchRow,
Text,
TextArea,
} = Components
function EvaluateJavaScriptAlert() {
const code = React.useRef('')
const [awaitResult, setAwaitResult] = React.useState(true)
const [showHidden, setShowHidden] = React.useState(true)
const [inspectDepth, setInspectDepth] = React.useState(3)
const [loading, setLoading] = React.useState(false)
return (
<AlertModal
title="Evaluate JavaScript"
extraContent={
<Stack>
<TextArea
autoFocus
label="Code"
size="md"
placeholder="ReactNative.NativeModules.BundleUpdaterManager.reload()"
onChange={v => (code.current = v)}
/>
<TableRowGroup>
<TableSwitchRow
label="Await result"
subLabel="Wait for the result of the code before displaying it."
value={awaitResult}
onValueChange={setAwaitResult}
/>
<TableSwitchRow
label="Show hidden"
subLabel="Show hidden properties of the object."
value={showHidden}
onValueChange={setShowHidden}
/>
<TableRow
label="Inspect depth"
subLabel="The depth of the object to inspect."
trailing={<Text>{inspectDepth}</Text>}
/>
<Slider
startIcon={<Text>1</Text>}
endIcon={<Text>10</Text>}
step={1}
minimumValue={1}
maximumValue={10}
value={inspectDepth}
onValueChange={setInspectDepth}
/>
</TableRowGroup>
</Stack>
}
actions={
<Stack>
<Button
text="Evaluate"
variant="primary"
loading={loading}
// Async arrow functions are not supported
onPress={async function onPress() {
setLoading(true)
try {
// Do a no-local-scope-access eval
// biome-ignore lint/security/noGlobalEval: This is intentional
const res = globalThis.eval?.(code.current)
alert(
nodeUtils.inspect(awaitResult && res instanceof Promise ? await res : res, {
depth: inspectDepth,
showHidden,
}),
)
} catch (e) {
alert(getErrorStack(e))
}
setLoading(false)
AlertActionCreators.dismissAlert(ALERT_KEY)
}}
/>
<AlertActionButton text="Cancel" variant="secondary" />
</Stack>
}
/>
)
}
export default EvaluateJavaScriptSetting

View file

@ -3,12 +3,12 @@ import { React } from '@revenge-mod/react'
import AssetIcon from '../components/icons/AssetIcon'
import { MobileSetting } from '../SettingsConstants'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
// @ts-expect-error
const props = HermesInternal.getRuntimeProperties()
const HermesVersionSetting: SettingsRowConfig = {
const HermesVersionSetting: SettingsItem = {
parent: MobileSetting.REVENGE,
IconComponent: () => <AssetIcon name="TranscriptOutlineIcon" />,
title: () => 'Hermes',

View file

@ -3,12 +3,12 @@ import { React, ReactNative } from '@revenge-mod/react'
import AssetIcon from '../components/icons/AssetIcon'
import { MobileSetting } from '../SettingsConstants'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const { major, minor, patch, prerelease } = ReactNative.Platform.constants.reactNativeVersion
const version = `${major}.${minor}.${patch}${prerelease ? `-${prerelease}` : ''}`
const ReactNativeVersionSetting: SettingsRowConfig = {
const ReactNativeVersionSetting: SettingsItem = {
parent: MobileSetting.REVENGE,
IconComponent: () => <AssetIcon name="ScienceIcon" />,
title: () => 'React Native',

View file

@ -4,9 +4,9 @@ import { React } from '@revenge-mod/react'
import { MobileSetting } from '../SettingsConstants'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const ReactVersionSetting: SettingsRowConfig = {
const ReactVersionSetting: SettingsItem = {
parent: MobileSetting.REVENGE,
IconComponent: () => <Components.TableRow.Icon source={getAssetByName('ScienceIcon')!.id} />,
title: () => 'React',

View file

@ -1,9 +1,9 @@
import { MobileSetting, UserSettingsSections } from '../SettingsConstants'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengeDeveloperSetting: SettingsRowConfig = {
const RevengeDeveloperSetting: SettingsItem = {
parent: null,
type: 'route',
IconComponent: () => <AssetIcon name="WrenchIcon" />,

View file

@ -1,9 +1,9 @@
import { AlwaysHidden, MobileSetting, UserSettingsSections } from '../SettingsConstants'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengeFontsSetting: SettingsRowConfig = {
const RevengeFontsSetting: SettingsItem = {
parent: null,
type: 'route',
IconComponent: () => <AssetIcon name="TextIcon" />,

View file

@ -2,9 +2,9 @@ import { React } from '@revenge-mod/react'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengeNotImplementedSetting: SettingsRowConfig = {
const RevengeNotImplementedSetting: SettingsItem = {
parent: null,
IconComponent: () => <AssetIcon name="CircleXIcon-primary" variant="danger" />,
title: () => 'Not Implemented',

View file

@ -3,9 +3,9 @@ import { getEnabledPluginsCount } from '@revenge-mod/plugins/_'
import { AlwaysHidden, MobileSetting, UserSettingsSections } from '../SettingsConstants'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengePluginsSetting: SettingsRowConfig = {
const RevengePluginsSetting: SettingsItem = {
parent: null,
type: 'route',
IconComponent: () => <AssetIcon name="PuzzlePieceIcon" />,

View file

@ -1,9 +1,9 @@
import RevengeIcon from '../components/icons/RevengeIcon'
import { MobileSetting, UserSettingsSections } from '../SettingsConstants'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengeSetting: SettingsRowConfig = {
const RevengeSetting: SettingsItem = {
parent: null,
type: 'route',
IconComponent: RevengeIcon,

View file

@ -1,9 +1,9 @@
import { AlwaysHidden, MobileSetting, UserSettingsSections } from '../SettingsConstants'
import AssetIcon from '../components/icons/AssetIcon'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengeThemesSetting: SettingsRowConfig = {
const RevengeThemesSetting: SettingsItem = {
parent: null,
type: 'route',
IconComponent: () => <AssetIcon name="PaintPaletteIcon" />,

View file

@ -1,9 +1,9 @@
import RevengeIcon from '../components/icons/RevengeIcon'
import { MobileSetting } from '../SettingsConstants'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import type { SettingsItem } from '@revenge-mod/discord/ui/settings'
const RevengeVersionSetting: SettingsRowConfig = {
const RevengeVersionSetting: SettingsItem = {
parent: MobileSetting.REVENGE,
IconComponent: RevengeIcon,
title: () => 'Revenge',

View file

@ -1,21 +0,0 @@
import { React } from '@revenge-mod/react'
import AssetIcon from '../components/icons/AssetIcon'
import { MobileSetting, UserSettingsSections } from '../SettingsConstants'
import type { SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
const TriggerErrorBoundarySetting: SettingsRowConfig = {
parent: MobileSetting.REVENGE_DEVELOPER,
IconComponent: () => <AssetIcon name="ScreenXIcon" variant="danger" />,
title: () => 'Trigger Error Boundary',
variant: 'danger',
useDescription: () => 'This will crash the app.',
type: 'route',
screen: {
route: UserSettingsSections[MobileSetting.TRIGGER_ERROR_BOUNDARY],
getComponent: () => require('../screens/TriggerErrorBoundarySettingScreen').default,
},
}
export default TriggerErrorBoundarySetting

View file

@ -4,7 +4,7 @@ import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
import { waitForModules } from '@revenge-mod/modules/finders'
import { byName, byProps } from '@revenge-mod/modules/finders/filters'
import { registerSetting, registerSettingsSection, type SettingsRowConfig } from '@revenge-mod/discord/ui/settings'
import { registerSettingsItem, registerSettingsSection, type SettingsItem } from '@revenge-mod/discord/ui/settings'
import { _suiData } from '@revenge-mod/discord/_/ui/settings'
import { after } from '@revenge-mod/patcher'
@ -21,8 +21,8 @@ import ReactNativeVersionSetting from './definitions/ReactNativeVersionSetting'
import RevengeVersionSetting from './definitions/RevengeVersionSetting'
import HermesVersionSetting from './definitions/HermesVersionSetting'
import CallGarbageCollectorSetting from './definitions/CallGarbageCollectorSetting'
import TriggerErrorBoundarySetting from './definitions/TriggerErrorBoundarySetting'
import RevengeNotImplementedSetting from './definitions/RevengeNotImplementedSetting'
import EvaluateJavaScriptSetting from './definitions/EvaluateJavaScriptSetting'
import type { FC } from 'react'
@ -47,10 +47,10 @@ registerPlugin(
[MobileSetting.REACT_NATIVE_VERSION, ReactNativeVersionSetting],
[MobileSetting.HERMES_VERSION, HermesVersionSetting],
[MobileSetting.CALL_GARBAGE_COLLECTOR, CallGarbageCollectorSetting],
[MobileSetting.TRIGGER_ERROR_BOUNDARY, TriggerErrorBoundarySetting],
[MobileSetting.EVALUATE_JAVASCRIPT, EvaluateJavaScriptSetting],
[MobileSetting.REVENGE_NOT_IMPLEMENTED, RevengeNotImplementedSetting],
] as Array<[string, SettingsRowConfig]>)
registerSetting(key, setting)
] as Array<[string, SettingsItem]>)
registerSettingsItem(key, setting)
registerSettingsSection('REVENGE', {
label: 'Revenge',
@ -67,7 +67,7 @@ registerPlugin(
unsubForRendererConfig()
const SettingRendererConfig = exports as {
SETTING_RENDERER_CONFIG: Record<string, SettingsRowConfig>
SETTING_RENDERER_CONFIG: Record<string, SettingsItem>
}
let ORIGINAL_RENDERER_CONFIG = SettingRendererConfig.SETTING_RENDERER_CONFIG
@ -77,7 +77,7 @@ registerPlugin(
({
...ORIGINAL_RENDERER_CONFIG,
..._suiData.config,
}) as Record<string, SettingsRowConfig>,
}) as Record<string, SettingsItem>,
set: v => (ORIGINAL_RENDERER_CONFIG = v),
})
})

View file

@ -7,11 +7,12 @@ export default function RevengeDeveloperSettingScreen() {
<SettingListRenderer.SettingsList
sections={[
{
settings: [MobileSetting.CALL_GARBAGE_COLLECTOR],
label: 'React DevTools',
settings: [MobileSetting.REVENGE_NOT_IMPLEMENTED],
},
{
label: 'Tests',
settings: [MobileSetting.TRIGGER_ERROR_BOUNDARY],
label: 'Tools',
settings: [MobileSetting.EVALUATE_JAVASCRIPT, MobileSetting.CALL_GARBAGE_COLLECTOR],
},
]}
/>