diff --git a/lib/modules/src/metro/_internal.ts b/lib/modules/src/metro/_internal.ts index 2a478d7..42d7eb7 100644 --- a/lib/modules/src/metro/_internal.ts +++ b/lib/modules/src/metro/_internal.ts @@ -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() @@ -9,7 +9,7 @@ export const _bl = new Set() export let _mInitingId = -1 /** Uninitialized IDs */ export const _mUninited = new Set() -/** Initialized IDs */ +/** Known initialized IDs (exports aren't bad) */ export const _mInited = new Set() export const _mPaths = new Map() @@ -21,35 +21,37 @@ export function patchMetroDefine(metroDefine: Metro.DefineFn) { const metadata = [deps!] as typeof _mMd extends Map ? 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. diff --git a/lib/modules/src/metro/index.ts b/lib/modules/src/metro/index.ts index 234ab74..446c366 100644 --- a/lib/modules/src/metro/index.ts +++ b/lib/modules/src/metro/index.ts @@ -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 diff --git a/lib/modules/src/metro/utils.ts b/lib/modules/src/metro/utils.ts new file mode 100644 index 0000000..1338319 --- /dev/null +++ b/lib/modules/src/metro/utils.ts @@ -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) + ) +}