feat(lib/modules/finders/waits): cached-only waits

This commit is contained in:
PalmDevs 2025-07-31 19:15:08 +07:00
parent 00e9794baf
commit 578b78dd7e
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
9 changed files with 112 additions and 38 deletions

View file

@ -35,6 +35,8 @@ const byAssetSourceResolver = byDependencies([
invariantId,
])
const cachedOnly = { cached: true }
// Tracking/caching assets
const unsubAR = waitForModules(
byProps<ReactNative.AssetsRegistry>('registerAsset'),
@ -82,6 +84,7 @@ const unsubAR = waitForModules(
return (asset.id = result)
}
},
cachedOnly,
)
/**
@ -138,4 +141,5 @@ const unsubRAS = waitForModules(
// @ts-expect-error
rAS.addCustomSourceTransformer(({ asset }) => aOverrides.get(asset))
},
cachedOnly,
)

View file

@ -76,7 +76,7 @@ export function getStore<T>(
return noop
}
return waitForModules(byStoreName<T>(name), callback)
return waitForModules(byStoreName<T>(name), callback, { cached: true })
}
/// FILTERING

View file

@ -37,4 +37,7 @@ const unsubSRC = waitForModules(
},
)
},
{
cached: true,
},
)

View file

@ -1,3 +1,5 @@
import { noop } from '@revenge-mod/utils/callback'
import { getCachedFilterRegistry } from '../caches'
import { mInitialized } from '../metro/patches'
import {
onAnyModuleInitialized,
@ -34,7 +36,18 @@ export type WaitForModulesOptions<
ReturnNamespace extends boolean = boolean,
All extends boolean = boolean,
> = RunFilterReturnExportsOptions<ReturnNamespace> &
BaseWaitForModulesOptions<All>
BaseWaitForModulesOptions<All> & {
/**
* Use cached results **only** (if possible).
* If there is no cache result, this works as if you did not pass this option at all.
*
* By default, waits cache results but does not use them, because new modules may still be found.
* Use this option as an optimization if you are sure that you don't need to find new modules once results are cached.
*
* @default false
*/
cached?: boolean
}
export type WaitForModulesResult<
F extends Filter,
@ -84,6 +97,46 @@ export function waitForModules(
callback: WaitForModulesCallback<any>,
options?: WaitForModulesOptions,
): WaitForModulesUnsubscribeFunction {
if (options?.cached) {
const reg = getCachedFilterRegistry(filter.key)
if (reg === null) return noop
if (reg) {
if (__BUILD_FLAG_DEBUG_MODULE_WAITS__)
nativeLoggingHook(
`\u001b[32mUsing cached results for wait \u001b[33m${filter.key}\u001b[0m`,
1,
)
const runCachedCallback = (id: Metro.ModuleID) => {
const flag = reg[id]
DEBUG_logWaitMatched(filter.key, id, flag, true)
callback(
exportsFromFilterResultFlag(
flag,
getInitializedModuleExports(id),
options,
),
id,
)
}
const cleanups: Array<() => void> = []
for (const sId of Object.keys(reg)) {
const id = Number(sId)
if (mInitialized.has(id)) continue
cleanups.push(onModuleInitialized(id, runCachedCallback))
}
return () => {
for (const cleanup of cleanups) cleanup()
}
}
}
if (__BUILD_FLAG_DEBUG_MODULE_WAITS__)
nativeLoggingHook(
`\u001b[94mWaiting for module matching: \u001b[93m${filter.key}\u001b[0m`,
@ -173,9 +226,10 @@ function DEBUG_logWaitMatched(
key: string,
id: Metro.ModuleID,
flag: FilterResultFlag,
cached?: boolean,
) {
nativeLoggingHook(
`\u001b[32mWait matched: \u001b[33m${key}\u001b[0m (matched ${id}, ${FilterResultFlagToHumanReadable[flag]})`,
`\u001b[32mWait matched: \u001b[33m${key}\u001b[0m (matched ${id}, ${FilterResultFlagToHumanReadable[flag]}${cached ? ', \u001b[92mcached\u001b[0m' : ''})`,
1,
)
}

View file

@ -29,6 +29,11 @@ const unsubRN = waitForModules(
ReactNativeModuleId = id
ReactNative = exports
require('./native/patches')
},
{
cached: true,
},
)

View file

@ -1,5 +1,3 @@
import { byProps } from '@revenge-mod/modules/finders/filters'
import { waitForModules } from '@revenge-mod/modules/finders/wait'
import { after, before } from '@revenge-mod/patcher'
import { ReactNative } from '..'
import type { RunApplicationCallback } from '../types'
@ -7,22 +5,18 @@ import type { RunApplicationCallback } from '../types'
export const sBeforeRunApplication = new Set<RunApplicationCallback>()
export const sAfterRunApplication = new Set<RunApplicationCallback>()
const unsub = waitForModules(byProps('AppRegistry'), () => {
unsub()
before(ReactNative.AppRegistry, 'runApplication', args => {
for (const cb of sBeforeRunApplication)
try {
cb()
} catch {}
return args
})
after(ReactNative.AppRegistry, 'runApplication', res => {
for (const cb of sAfterRunApplication)
try {
cb()
} catch {}
return res
})
before(ReactNative.AppRegistry, 'runApplication', args => {
for (const cb of sBeforeRunApplication)
try {
cb()
} catch {}
return args
})
after(ReactNative.AppRegistry, 'runApplication', res => {
for (const cb of sAfterRunApplication)
try {
cb()
} catch {}
return res
})

View file

@ -7,6 +7,10 @@ import { PluginFlags } from '@revenge-mod/plugins/constants'
import { noop } from '@revenge-mod/utils/callback'
import { getCurrentStack } from '@revenge-mod/utils/error'
const cachedOnly = {
cached: true,
}
// TODO(plugins/no-track): Block Sentry native-side
registerPlugin(
{
@ -42,6 +46,7 @@ registerPlugin(
instead(SentryUtils, 'setTags', noop)
instead(SentryUtils, 'setUser', noop)
},
cachedOnly,
)
// modules/errors/native/SentryInitUtils.tsx
@ -52,6 +57,7 @@ registerPlugin(
instead(SentryInitUtils, 'initSentry', noop)
},
cachedOnly,
)
// Discord uses ReactNavigationInstrumentation to track navigation
@ -71,6 +77,7 @@ registerPlugin(
},
)
},
cachedOnly,
)
if (__DEV__)
@ -86,17 +93,7 @@ registerPlugin(
cleanup(unsubSU, unsubSIU, unsubSentryInst)
},
start({
cleanup,
logger,
unscoped: {
discord: {
common: {
flux: { DispatcherModuleId },
},
},
},
}) {
init({ cleanup }) {
// utils/AnalyticsUtils.tsx
const unsubAU = waitForModules(
byProps<{
@ -109,7 +106,6 @@ registerPlugin(
AnalyticsUtils => {
unsubAU()
logger.info('Patching AnalyticsUtils...')
instead(AnalyticsUtils.default, 'track', noop)
instead(AnalyticsUtils, 'trackNetworkAction', noop)
@ -119,8 +115,22 @@ registerPlugin(
for (const key of Object.keys(handlers))
instead(handlers, key, noop)
},
cachedOnly,
)
cleanup(unsubAU)
},
start({
cleanup,
logger,
unscoped: {
discord: {
common: {
flux: { DispatcherModuleId },
},
},
},
}) {
// modules/app_analytics/useTrackImpression.tsx
const unsubTI = waitForModules(
byProps<{
@ -130,11 +140,10 @@ registerPlugin(
useTrackImpression => {
unsubTI()
logger.info('Patching useTrackImpression...')
instead(useTrackImpression, 'trackImpression', noop)
instead(useTrackImpression, 'default', noop)
},
cachedOnly,
)
// actions/AnalyticsTrackActionCreators.tsx
@ -148,9 +157,10 @@ registerPlugin(
instead(AnalyticsTrackActionCreators, 'track', noop)
}
},
cachedOnly,
)
cleanup(unsubAU, unsubTI, unsubATAC)
cleanup(unsubTI, unsubATAC)
},
stop({ plugin }) {
plugin.flags |= PluginFlags.ReloadRequired

View file

@ -43,6 +43,9 @@ registerPlugin(
},
)
},
{
cached: true,
},
)
cleanup(unsubEB)

View file

@ -44,6 +44,7 @@ const pluginSettings = registerPlugin(
patchSettingsOverviewScreen(exports)
},
{
cached: true,
returnNamespace: true,
},
)