fix(lib/modules/metro): move functions to a separate file to avoid patching during preinit
This commit is contained in:
parent
97aa69038c
commit
8b13204c7b
3 changed files with 104 additions and 97 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
69
lib/modules/src/metro/utils.ts
Normal file
69
lib/modules/src/metro/utils.ts
Normal 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)
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue