fix(lib/modules/metro): move functions to a separate file to avoid patching during preinit

This commit is contained in:
PalmDevs 2025-04-11 18:38:38 +07:00
parent 97aa69038c
commit 8b13204c7b
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
3 changed files with 104 additions and 97 deletions

View file

@ -1,7 +1,7 @@
import { _executeSubscription } from './subscriptions/_internal'
import { isBlacklisted, isModuleExportsBad } from './utils'
import type { Metro } from '../../types/metro'
import { isBlacklisted, isModuleExportBad } from '.'
export const _bl = new Set<Metro.ModuleID>()
@ -9,7 +9,7 @@ export const _bl = new Set<Metro.ModuleID>()
export let _mInitingId = -1
/** Uninitialized IDs */
export const _mUninited = new Set<Metro.ModuleID>()
/** Initialized IDs */
/** Known initialized IDs (exports aren't bad) */
export const _mInited = new Set<Metro.ModuleID>()
export const _mPaths = new Map<string, Metro.ModuleID>()
@ -21,35 +21,37 @@ export function patchMetroDefine(metroDefine: Metro.DefineFn) {
const metadata = [deps!] as typeof _mMd extends Map<any, infer V> ? V : never
_mMd.set(id, metadata)
if (isBlacklisted(id)) {
metroDefine(origFactory, id, deps)
return
if (isBlacklisted(id)) metroDefine(origFactory, id, deps)
else {
_mUninited.add(id)
metroDefine(
((global, req, importDefault, importAll, module, exports, deps) => {
metadata[1] = module
const prevIId = _mInitingId
_mInitingId = id
try {
origFactory(global, req, importDefault, importAll, module, exports, deps)
// Don't use exports here, as modules can set module.exports to a different object
if (isModuleExportsBad(module.exports)) throw undefined
// Add the module to the initialized set only if the factory doesn't error or the exports aren't bad
_mInited.add(id)
} catch {
_blacklist(id)
} finally {
_mInitingId = prevIId
_mUninited.delete(id)
// Don't use exports here, as modules can set module.exports to a different object
_executeSubscription(id, module.exports)
}
}) satisfies Metro.FactoryFn,
id,
deps,
)
}
_mUninited.add(id)
metroDefine(
((g, r, ipD, ipA, m, e, d) => {
metadata[1] = m
const prevIId = _mInitingId
_mInitingId = id
try {
origFactory(g, r, ipD, ipA, m, e, d)
if (isModuleExportBad(m.exports)) _blacklist(id)
// If exports isn't bad, we can put it in the list of initialized modules
else _mInited.add(id)
} finally {
_mInitingId = prevIId
_mUninited.delete(id)
_executeSubscription(id, m.exports)
}
}) satisfies Metro.FactoryFn,
id,
deps,
)
}) satisfies Metro.DefineFn
}
@ -65,6 +67,8 @@ export function patchMetroDefine(metroDefine: Metro.DefineFn) {
*/
export function _blacklist(id: Metro.ModuleID) {
_bl.add(id)
// In case blacklisting happens before the module is initialized
_mUninited.delete(id)
// Usually we'd also remove the module from _mInited too,
// but we already have an else block in the patchMetroDefine function that does that for us.

View file

@ -1,72 +1,6 @@
import { isProxy } from '@revenge-mod/utils/proxy'
import { _bl, _mMd, _mInited } from './_internal'
import type { Metro } from '../../types/metro'
import './patches'
export * from './subscriptions'
/**
* Returns whether a module is blacklisted.
* @param id The module ID to check.
*/
export function isBlacklisted(id: Metro.ModuleID) {
return _bl.has(id)
}
/**
* Returns whether a module is initialized.
* @param id The module ID to check.
*/
export function isModuleInitialized(id: Metro.ModuleID) {
return _mInited.has(id)
}
/**
* Returns the dependencies of a module.
* @param id The module ID.
*/
export function getModuleDependencies(id: Metro.ModuleID) {
return _mMd.get(id)?.[0]
}
/**
* Returns the exports of an initialized module.
* @param id The module ID.
*/
export function getInitializedModuleExports(id: Metro.ModuleID) {
return _mMd.get(id)?.[1]?.exports
}
/**
* Returns whether a particular module export is bad. This is used for filter functions to check whether an export is filterable.
* @param exp The export to check.
*/
export function isModuleExportBad(exp: Metro.ModuleExports[PropertyKey]) {
return (
// Nullish?
exp == null ||
// Is it a proxy? (discord-intl has proxy exports)
isProxy(exp) ||
// Can't run isProxy() on this because this isn't your typical proxy:
// https://github.com/discord/react-native/blob/master/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp
exp === nativeModuleProxy
)
}
/**
* Returns whether the module has bad exports. If it does, it should be blacklisted and never hooked into.
* @param exports The exports of the module.
*/
export function isModuleExportsBad(exports: Metro.ModuleExports) {
return (
// Nullish?
exports == null ||
// Checking if the object is empty
(exports.__proto__ === Object.prototype && !Reflect.ownKeys(exports).length)
)
}
export * from './utils'
// TODO(modules): caching

View file

@ -0,0 +1,69 @@
import { isProxy } from '@revenge-mod/utils/proxy'
import { _bl, _mMd, _mInited } from './_internal'
import type { Metro } from '../../types/metro'
/**
* Returns whether a module is blacklisted.
* @param id The module ID to check.
*/
export function isBlacklisted(id: Metro.ModuleID) {
return _bl.has(id)
}
/**
* Returns whether a module is initialized.
* @param id The module ID to check.
*/
export function isModuleInitialized(id: Metro.ModuleID) {
return _mInited.has(id)
}
/**
* Returns the dependencies of a module.
* @param id The module ID.
*/
export function getModuleDependencies(id: Metro.ModuleID) {
return _mMd.get(id)?.[0]
}
/**
* Returns the exports of an initialized module.
* @param id The module ID.
*/
export function getInitializedModuleExports(id: Metro.ModuleID) {
return _mMd.get(id)?.[1]?.exports
}
/**
* Returns whether a particular module export is bad. This is used for filter functions to check whether an export is filterable.
* @param exp The export to check.
*/
export function isModuleExportBad(exp: Metro.ModuleExports[PropertyKey]) {
return (
// Nullish?
exp == null ||
// Is it a proxy? (discord-intl has proxy exports)
isProxy(exp) ||
// Can't run isProxy() on this because this isn't your typical proxy:
// https://github.com/discord/react-native/blob/master/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp
exp === nativeModuleProxy
)
}
/**
* Returns whether the module has bad exports. If it does, it should be blacklisted and never hooked into.
* @param exports The exports of the module.
*/
export function isModuleExportsBad(exports: Metro.ModuleExports) {
return (
// Nullish?
exports == null ||
// Booleans are not useful
typeof exports === 'boolean' ||
// Number exports are not particularly useful either
// Usually can be found in asset modules, but we already have patches for those
typeof exports === 'number' ||
// Checking if the object is empty
(exports.__proto__ === Object.prototype && !Reflect.ownKeys(exports).length)
)
}