feat(lib/modules)!: refactor finders and metro APIs
This commit is contained in:
parent
206cd6ee78
commit
f15c3d38db
12 changed files with 387 additions and 330 deletions
|
|
@ -1,71 +1,91 @@
|
|||
import { _isExportsBad } from '../metro/_internal'
|
||||
import { _isExportBad } from '~/metro/_internal'
|
||||
|
||||
import type { If } from '@revenge-mod/utils/types'
|
||||
import type { Metro } from '#/metro'
|
||||
import type { Filter } from './filters'
|
||||
import type { LookupModulesOptions } from './lookup'
|
||||
import type { Metro } from '../../types'
|
||||
|
||||
const FindResultFlags = {
|
||||
// ? Our design does not allow for this to be used, but it is here for future reference.
|
||||
// /**
|
||||
// * The find was unsuccessful.
|
||||
// */
|
||||
// NotFound: 0,
|
||||
export type RunFilterOptions = {
|
||||
/**
|
||||
* A module was found and the find was successful.
|
||||
* However, the module type could not be determined. It is most likely a CommonJS module.
|
||||
* For ES modules, whether to skip checking the default export.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
esmSkipDefault?: boolean
|
||||
}
|
||||
|
||||
export type RunFilterReturnExportsOptions<ReturnNamespace extends boolean = boolean> = RunFilterOptions &
|
||||
If<
|
||||
ReturnNamespace,
|
||||
{
|
||||
/**
|
||||
* For ES modules, whether to return the whole module with all exports instead of just the default export **if the default export matches**.
|
||||
*
|
||||
* CommonJS modules will always return all exports.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
esmReturnNamespace: true
|
||||
},
|
||||
{
|
||||
esmReturnNamespace?: false
|
||||
}
|
||||
>
|
||||
|
||||
const FilterResultFlags = {
|
||||
/**
|
||||
* A module was found.
|
||||
*/
|
||||
Found: 1,
|
||||
/**
|
||||
* The module was found and the find was successful.
|
||||
* The module is an ES module, and the filter matched the module namespace.
|
||||
* A module was found, and it is an ES module, and the filter matched the module namespace.
|
||||
*/
|
||||
ESModuleNamespace: 2,
|
||||
/**
|
||||
* The module was found and the find was successful.
|
||||
* The module is an ES module, and the filter matched the default export.
|
||||
* A module was found, and it is an ES module, and the filter matched the default export.
|
||||
*/
|
||||
ESModuleDefault: 3,
|
||||
}
|
||||
|
||||
export type FindResultFlag = (typeof FindResultFlags)[keyof typeof FindResultFlags]
|
||||
export type FilterResultFlag = (typeof FilterResultFlags)[keyof typeof FilterResultFlags]
|
||||
|
||||
export function runFilterReturnExports(
|
||||
filter: Filter<any>,
|
||||
exports: Metro.ModuleExports,
|
||||
// The reason this returns a flag is because flags are never falsy, while exports may be falsy when using ID-only filters (eg. `byDependencies`).
|
||||
export function runFilter<F extends Filter>(
|
||||
filter: F,
|
||||
id: Metro.ModuleID,
|
||||
options?: LookupModulesOptions,
|
||||
) {
|
||||
const flag = runFilter(filter, exports, id, options)
|
||||
|
||||
if (flag) {
|
||||
if (flag === FindResultFlags.ESModuleDefault && !options?.esmReturnNamespace) return exports.default
|
||||
|
||||
return exports
|
||||
}
|
||||
}
|
||||
|
||||
export function runFilter(
|
||||
filter: Filter<any>,
|
||||
exports: Metro.ModuleExports,
|
||||
id: Metro.ModuleID,
|
||||
options?: LookupModulesOptions,
|
||||
): FindResultFlag | false {
|
||||
if (exports.__esModule) {
|
||||
exports: F extends Filter<any, true> ? Metro.ModuleExports : undefined,
|
||||
options?: RunFilterOptions,
|
||||
): FilterResultFlag | false {
|
||||
if (exports?.__esModule) {
|
||||
const { default: defaultExport } = exports
|
||||
|
||||
if (!options?.esmSkipDefault && !_isExportsBad(defaultExport) && filter(defaultExport, id)) {
|
||||
// TODO(modules/caches/flags): could cache here that default export is bad
|
||||
if (!options?.esmSkipDefault && !_isExportBad(defaultExport) && filter(id, defaultExport)) {
|
||||
// TODO(modules/finders/caches::esm::default)
|
||||
return FindResultFlags.ESModuleDefault
|
||||
return FilterResultFlags.ESModuleDefault
|
||||
}
|
||||
|
||||
if (filter(exports, id)) {
|
||||
if (filter(id, exports)) {
|
||||
// TODO(modules/finders/caches::esm::namespace)
|
||||
return FindResultFlags.ESModuleNamespace
|
||||
return FilterResultFlags.ESModuleNamespace
|
||||
}
|
||||
} else if (filter(exports, id)) {
|
||||
} else if (filter(id, exports)) {
|
||||
// TODO(modules/finders/caches)
|
||||
return FindResultFlags.Found
|
||||
return FilterResultFlags.Found
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function runFilterReturnExports(
|
||||
filter: Filter,
|
||||
id: Metro.ModuleID,
|
||||
exports: Metro.ModuleExports,
|
||||
options?: RunFilterReturnExportsOptions,
|
||||
) {
|
||||
const flag = runFilter(filter, id, exports, options)
|
||||
|
||||
if (flag) {
|
||||
if (flag === FilterResultFlags.ESModuleDefault && !options?.esmReturnNamespace) return exports.default
|
||||
return exports
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,59 @@
|
|||
import { _mMetadatas } from '../metro/_internal'
|
||||
import type { Metro } from '../../types/metro'
|
||||
import { _mMetadatas } from '~/metro/_internal'
|
||||
|
||||
export type FilterResult<F> = F extends Filter<infer R> ? R : never
|
||||
import type { If, LogicalOr } from '@revenge-mod/utils/types'
|
||||
import type { Metro } from '#/metro'
|
||||
|
||||
export interface Filter<_Inferable> {
|
||||
(exports: Metro.ModuleExports, id: Metro.ModuleID): boolean
|
||||
export type FilterResult<F> = F extends Filter<infer R, boolean> ? R : never
|
||||
|
||||
export type IsFilterWithExports<F> = F extends Filter<any, infer W> ? W : never
|
||||
|
||||
export type IsFilterGeneratorWithExports<F> = F extends (...args: any[]) => infer F ? IsFilterWithExports<F> : never
|
||||
|
||||
export interface Filter<_Inferable = any, WithExports extends boolean = boolean> {
|
||||
(id: Metro.ModuleID, exports: If<WithExports, Metro.ModuleExports, never>): boolean
|
||||
key: string
|
||||
}
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (args: A, exps: Metro.ModuleExports, id: Metro.ModuleID) => boolean,
|
||||
/**
|
||||
* Create a filter generator.
|
||||
*
|
||||
* @param filter The function that filters the modules.
|
||||
* @param keyFor The function that generates the key for the filter.
|
||||
* @returns A function that generates a filter with the specified arguments.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // function createFilterGenerator<Arguments, WithExports>(filter, keyFor): ...
|
||||
* // * Arguments: The arguments for the filter function.
|
||||
* // * WithExports: Whether the filter function also filters exports.
|
||||
*
|
||||
* const custom = createFilterGenerator<[arg1: number, arg2: string], false>(
|
||||
* ([arg1, arg2], id, exports) => {
|
||||
* // filter logic
|
||||
* return true
|
||||
* },
|
||||
* ([arg1, arg2]) => `revenge.custom(${arg1}, ${arg2})`
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @see {@link byProps} for an example on custom-typed filters.
|
||||
*/
|
||||
export function createFilterGenerator<A extends any[], WithExports extends boolean>(
|
||||
filter: (args: A, id: Metro.ModuleID, exps: If<WithExports, Metro.ModuleExports, never>) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
) {
|
||||
return (...args: A) => {
|
||||
const f = (exps: Metro.ModuleExports, id: Metro.ModuleID) => filter(args, exps, id)
|
||||
const f = (id: Metro.ModuleID, exps: If<WithExports, Metro.ModuleExports, never>) => filter(args, id, exps)
|
||||
f.key = keyFor(args)
|
||||
// biome-ignore lint/complexity/noBannedTypes: So it doesn't turn into `any` when you combine it with other typed filters
|
||||
return f as Filter<{}>
|
||||
return f as Filter<{}, WithExports>
|
||||
}
|
||||
}
|
||||
|
||||
export type ByProps = <T extends Record<string, any> = Record<string, any>>(
|
||||
prop: keyof T,
|
||||
...props: Array<keyof T>
|
||||
) => Filter<T>
|
||||
) => Filter<T, true>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having all of the specified properties.
|
||||
|
|
@ -33,18 +63,18 @@ export type ByProps = <T extends Record<string, any> = Record<string, any>>(
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const React = await find(byProps<typeof import('react')>('createElement'))
|
||||
* const React = await findModule(byProps<typeof import('react')>('createElement'))
|
||||
* // const React: typeof import('react')
|
||||
* ```
|
||||
*/
|
||||
export const byProps = createFilterGenerator<Parameters<ByProps>>(
|
||||
(props, exports) => props.every(prop => exports[prop]),
|
||||
export const byProps = createFilterGenerator<Parameters<ByProps>, IsFilterGeneratorWithExports<ByProps>>(
|
||||
(props, _, exports) => props.every(prop => exports[prop]),
|
||||
props => `revenge.props(${props.join(',')})`,
|
||||
) as ByProps
|
||||
|
||||
export type ByName = <T extends string | object>(
|
||||
name: T extends string ? T : T extends { name: string } ? T['name'] : string,
|
||||
) => Filter<T extends string ? { name: T } : T & { name: typeof name }>
|
||||
) => Filter<T extends string ? { name: T } : T & { name: typeof name }, true>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having the specified name.
|
||||
|
|
@ -55,7 +85,7 @@ export type ByName = <T extends string | object>(
|
|||
*
|
||||
* @example Auto-typing as object
|
||||
* ```ts
|
||||
* const SomeComponent = await find(byName('SomeComponent'))
|
||||
* const SomeComponent = await findModule(byName('SomeComponent'))
|
||||
* // const SomeComponent: { name: 'SomeComponent' }
|
||||
* ```
|
||||
*
|
||||
|
|
@ -63,7 +93,7 @@ export type ByName = <T extends string | object>(
|
|||
* ```ts
|
||||
* type MyComponent = React.FC<{ foo: string }>
|
||||
*
|
||||
* const MyComponent = await find(byName<MyComponent>('MyComponent'))
|
||||
* const MyComponent = await findModule(byName<MyComponent>('MyComponent'))
|
||||
* // const MyComponent: MyComponent & { name: 'MyComponent' }
|
||||
* ```
|
||||
*
|
||||
|
|
@ -73,11 +103,11 @@ export type ByName = <T extends string | object>(
|
|||
* someMethod(): void
|
||||
* }
|
||||
*
|
||||
* const SomeClass = await find(byName<{ new(param: string): SomeClass }>('SomeClass'))
|
||||
* const SomeClass = await findModule(byName<{ new(param: string): SomeClass }>('SomeClass'))
|
||||
* // const SomeClass: { new(): SomeClass, name: 'SomeClass' }
|
||||
*/
|
||||
export const byName = createFilterGenerator<Parameters<ByName>>(
|
||||
([name], exports) => exports.name === name,
|
||||
export const byName = createFilterGenerator<Parameters<ByName>, IsFilterGeneratorWithExports<ByName>>(
|
||||
([name], _, exports) => exports.name === name,
|
||||
([name]) => `revenge.name(${name})`,
|
||||
) as ByName
|
||||
|
||||
|
|
@ -88,19 +118,20 @@ export const byName = createFilterGenerator<Parameters<ByName>>(
|
|||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const Logger = await find(byDependencies([4, undefined, 2]))
|
||||
* const Logger = await findModule(byDependencies([4, undefined, 2]))
|
||||
* // or
|
||||
* const Logger = await find(byDependencies([4, , 2]))
|
||||
* const Logger = await findModule(byDependencies([4, , 2]))
|
||||
* ```
|
||||
*/
|
||||
export const byDependencies = createFilterGenerator<[Array<Metro.ModuleID | undefined>]>(
|
||||
([deps], _, id) => {
|
||||
export const byDependencies = createFilterGenerator<[Array<Metro.ModuleID | undefined>], false>(
|
||||
([deps], id) => {
|
||||
const actDeps = _mMetadatas.get(id)![0]
|
||||
if (actDeps.length === deps.length)
|
||||
for (let i = 0; i < deps.length; i++) {
|
||||
const cmp = deps[i]
|
||||
if (Number.isInteger(cmp) && actDeps[i] !== cmp) return false
|
||||
}
|
||||
else return false
|
||||
|
||||
return true
|
||||
},
|
||||
|
|
@ -108,57 +139,79 @@ export const byDependencies = createFilterGenerator<[Array<Metro.ModuleID | unde
|
|||
)
|
||||
|
||||
/**
|
||||
* Combines multiple filters into one.
|
||||
* Combines multiple filters into one, returning true if **every** filter matches.
|
||||
*
|
||||
* @param filters The filters to combine.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const SomeModule = await find(combine(
|
||||
* const SomeModule = await findModule(every(
|
||||
* byProps('x', 'name'),
|
||||
* byName('SomeName'),
|
||||
* byDependencies([1, 485, , 2]),
|
||||
* ))
|
||||
* ```
|
||||
*/
|
||||
export function combine<F1 extends Filter<any>, F2 extends Filter<any>>(
|
||||
export function every<F1 extends Filter, F2 extends Filter>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
): Filter<FilterResult<F1> & FilterResult<F2>>
|
||||
): Filter<FilterResult<F1> & FilterResult<F2>, LogicalOr<IsFilterWithExports<F1>, IsFilterWithExports<F2>>>
|
||||
|
||||
export function combine<F1 extends Filter<any>, F2 extends Filter<any>, F3 extends Filter<any>>(
|
||||
export function every<F1 extends Filter, F2 extends Filter, F3 extends Filter>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
): Filter<FilterResult<F1> & FilterResult<F2> & FilterResult<F3>>
|
||||
): Filter<
|
||||
FilterResult<F1> & FilterResult<F2> & FilterResult<F3>,
|
||||
LogicalOr<LogicalOr<IsFilterWithExports<F1>, IsFilterWithExports<F2>>, IsFilterWithExports<F3>>
|
||||
>
|
||||
|
||||
export function combine<F1 extends Filter<any>, F2 extends Filter<any>, F3 extends Filter<any>, F4 extends Filter<any>>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
f4: F4,
|
||||
): Filter<FilterResult<F1> & FilterResult<F2> & FilterResult<F3> & FilterResult<F4>>
|
||||
|
||||
export function combine<
|
||||
F1 extends Filter<any>,
|
||||
F2 extends Filter<any>,
|
||||
F3 extends Filter<any>,
|
||||
F4 extends Filter<any>,
|
||||
F5 extends Filter<any>,
|
||||
>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
f4: F4,
|
||||
f5: F5,
|
||||
): Filter<FilterResult<F1> & FilterResult<F2> & FilterResult<F3> & FilterResult<F4> & FilterResult<F5>>
|
||||
|
||||
export function combine(...filters: Filter<any>[]): Filter<any> {
|
||||
const cf: Filter<any> = (exports, id) => {
|
||||
for (const filter of filters) if (!filter(exports, id)) return false
|
||||
export function every(...filters: Filter[]): Filter {
|
||||
const cf: Filter = (id, exports) => {
|
||||
for (const filter of filters) if (!filter(id, exports)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
cf.key = `revenge.combine(${filters.map(filter => filter.key).join(',')})`
|
||||
cf.key = `revenge.every(${filters.map(filter => filter.key).join(',')})`
|
||||
|
||||
return cf
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple filters into one, returning true if **some** filters match.
|
||||
*
|
||||
* @param filters The filters to combine.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const SomeModule = await findModule(some(
|
||||
* byProps('x', 'name'),
|
||||
* byName('SomeName'),
|
||||
* byDependencies([1, 485, , 2]),
|
||||
* ))
|
||||
* ```
|
||||
*/
|
||||
export function some<F1 extends Filter, F2 extends Filter>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
): Filter<FilterResult<F1> | FilterResult<F2>, IsFilterWithExports<F1> | IsFilterWithExports<F2>>
|
||||
|
||||
export function some<F1 extends Filter, F2 extends Filter, F3 extends Filter>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
f3: F3,
|
||||
): Filter<
|
||||
FilterResult<F1> | FilterResult<F2> | FilterResult<F3>,
|
||||
IsFilterWithExports<F1> | IsFilterWithExports<F2> | IsFilterWithExports<F3>
|
||||
>
|
||||
|
||||
export function some(...filters: Filter[]): Filter {
|
||||
const cf: Filter = (id, exports) => {
|
||||
for (const filter of filters) if (filter(id, exports)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
cf.key = `revenge.some(${filters.map(filter => filter.key).join(',')})`
|
||||
|
||||
return cf
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import { lookupModuleByImportedPath, lookupModuleIds, lookupModules, type LookupModulesOptions } from './lookup'
|
||||
import { waitForModuleByImportedPath, waitForModules, type WaitForModulesOptions } from './wait'
|
||||
import { type LookupModulesOptions, lookupModuleByImportedPath, lookupModuleIds, lookupModules } from './lookup'
|
||||
import { type WaitForModulesOptions, waitForModuleByImportedPath, waitForModules } from './wait'
|
||||
|
||||
import type { MaybeDefaultExportMatched } from '.'
|
||||
import type { LookupModuleIdsOptions, MaybeDefaultExportMatched } from '.'
|
||||
import type { Metro } from '#/metro'
|
||||
import type { Filter, FilterResult } from './filters'
|
||||
import type { Metro } from '../../types/metro'
|
||||
import { proxify, type ProxifyOptions } from '@revenge-mod/utils/proxy'
|
||||
|
||||
export type FindModuleOptions = WaitForModulesOptions & {
|
||||
// TODO(modules/finders::options.force): This can cause issues by initializing unexpected modules. Likely won't actually be implemented.
|
||||
// Maybe we can add an option to force initialize when cached instead.
|
||||
// /**
|
||||
// * Force initialization of modules that haven't been initialized yet. **Not recommended for general use.**
|
||||
// *
|
||||
// * Forcing initialization of modules can lead to unexpected behavior and side effects and should be avoided if possible.
|
||||
// *
|
||||
// * @default false
|
||||
// */
|
||||
// force?: boolean
|
||||
export type BaseFindModuleOptions = {
|
||||
/**
|
||||
* Abort signal for the find.
|
||||
*/
|
||||
abortSignal?: AbortSignal
|
||||
// TODO(modules/finders/find::options): Add an option to force initialize when module ID is known (find is cached).
|
||||
}
|
||||
|
||||
export type FindModuleOptions = WaitForModulesOptions & BaseFindModuleOptions
|
||||
|
||||
export type FindModuleIdOptions = LookupModuleIdsOptions & FindModuleOptions
|
||||
|
||||
/**
|
||||
* Find a module by its exports.
|
||||
*
|
||||
|
|
@ -33,44 +33,24 @@ export type FindModuleOptions = WaitForModulesOptions & {
|
|||
* const ReactNative = await findModule(byProps<typeof import('react-native')>('AppRegistry'))
|
||||
* ```
|
||||
*/
|
||||
export function findModule<F extends Filter<any>>(filter: F): Promise<FilterResult<F>>
|
||||
export function findModule<F extends Filter<any>, O extends FindModuleOptions>(
|
||||
export function findModule<F extends Filter>(filter: F): Promise<FilterResult<F>>
|
||||
export function findModule<F extends Filter, O extends FindModuleOptions>(
|
||||
filter: F,
|
||||
options: O,
|
||||
): Promise<O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>>
|
||||
export function findModule<F extends Filter<any>, O extends FindModuleOptions>(filter: F, options?: O) {
|
||||
export function findModule<F extends Filter, O extends FindModuleOptions>(filter: F, options?: O) {
|
||||
type Result = FilterResult<F>
|
||||
|
||||
return new Promise<O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<Result> : Result>((ok, err) => {
|
||||
for (const exports of lookupModules(filter, options!)) return ok(exports)
|
||||
|
||||
// TODO(modules/finders::options.force): Initial implementation for `options.force`.
|
||||
// if (options?.force) {
|
||||
// if (signal) console.warn(
|
||||
// "You shouldn't use `options.force` and `options.abortSignal` together! `options.abortSignal` will be ignored.",
|
||||
// )
|
||||
const unsub = waitForModules(filter, (_, exports) => ok(exports), options)
|
||||
|
||||
// for (const id of _uninitdIds) {
|
||||
// // TODO(modules/finders): check blacklist cache
|
||||
const onAbort = () => {
|
||||
unsub()
|
||||
err(new Error(`${findModule.name} aborted before resolved: ${filter.key}`))
|
||||
}
|
||||
|
||||
// try {
|
||||
// __r!(id)
|
||||
// } catch {}
|
||||
|
||||
// const { exports } = _modules.get(id)![1]!
|
||||
// if (_initdIds.has(id)) {
|
||||
// const result = runFilter(filter, exports, options)
|
||||
// if (result) return rs(result)
|
||||
// }
|
||||
// }
|
||||
|
||||
// // TODO(modules/finders/caches::notfound)
|
||||
// return
|
||||
// }
|
||||
|
||||
waitForModules(filter, ok, options)
|
||||
|
||||
const onAbort = () => err(new Error(`findModule aborted before resolved: ${filter.key}`))
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) return onAbort()
|
||||
|
|
@ -84,7 +64,7 @@ export function findModule<F extends Filter<any>, O extends FindModuleOptions>(f
|
|||
*
|
||||
* @param filter The filter to use to find the module.
|
||||
* @param options The options to use for the find.
|
||||
* @returns A promise that resolves to the module ID or rejects if the module is not found.
|
||||
* @returns A promise that resolves to the module ID or rejects if the find is aborted before a module ID is found.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
|
|
@ -94,18 +74,22 @@ export function findModule<F extends Filter<any>, O extends FindModuleOptions>(f
|
|||
* const React = __r(ReactId)
|
||||
* const ReactNative = __r(ReactNativeId)
|
||||
*/
|
||||
export function findModuleId<F extends Filter<any>>(filter: F): Promise<Metro.ModuleID>
|
||||
export function findModuleId<F extends Filter<any>, O extends FindModuleOptions>(
|
||||
export function findModuleId<F extends Filter>(filter: F): Promise<Metro.ModuleID>
|
||||
export function findModuleId<F extends Filter, O extends FindModuleIdOptions>(
|
||||
filter: F,
|
||||
options: O,
|
||||
): Promise<O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>>
|
||||
export function findModuleId(filter: Filter<any>, options?: FindModuleOptions) {
|
||||
export function findModuleId(filter: Filter, options?: FindModuleIdOptions) {
|
||||
return new Promise<Metro.ModuleID>((ok, err) => {
|
||||
for (const id of lookupModuleIds(filter, options)) return ok(id)
|
||||
|
||||
waitForModules(filter, (_, id) => ok(id), options)
|
||||
const unsub = waitForModules(filter, (_, id) => ok(id), options)
|
||||
|
||||
const onAbort = () => {
|
||||
unsub()
|
||||
err(new Error(`${findModuleId.name} aborted before resolved: ${filter.key}`))
|
||||
}
|
||||
|
||||
const onAbort = () => err(new Error(`findModuleId aborted before resolved: ${filter.key}`))
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) return onAbort()
|
||||
|
|
@ -114,32 +98,34 @@ export function findModuleId(filter: Filter<any>, options?: FindModuleOptions) {
|
|||
})
|
||||
}
|
||||
|
||||
export function findModuleByImportedPath(path: string, options?: FindModuleOptions) {
|
||||
return new Promise<any>((ok, err) => {
|
||||
const m1 = lookupModuleByImportedPath(path)
|
||||
if (m1) return ok(m1)
|
||||
|
||||
waitForModuleByImportedPath(path, ok, options)
|
||||
|
||||
const onAbort = () => err(new Error(`findModuleByImportedPath aborted before resolved: ${path}`))
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) return onAbort()
|
||||
signal.addEventListener('abort', onAbort, { once: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export type FindModuleSyncOptions = WaitForModulesOptions & {
|
||||
/**
|
||||
* The hint for the proxified object.
|
||||
*
|
||||
* @default 'function'
|
||||
* Find a module by its imported path.
|
||||
* @param path The path to find the module by.
|
||||
* @param options The options to use for the find.
|
||||
* @returns A promise that resolves to the module's exports or rejects if the find is aborted before the module is found.
|
||||
*/
|
||||
hint?: 'object' | 'function'
|
||||
export function findModuleByImportedPath(path: string, options?: BaseFindModuleOptions) {
|
||||
return new Promise<any>((ok, err) => {
|
||||
const m = lookupModuleByImportedPath(path)
|
||||
if (m) return ok(m)
|
||||
|
||||
const unsub = waitForModuleByImportedPath(path, (_, exports) => ok(exports))
|
||||
|
||||
const onAbort = () => {
|
||||
unsub()
|
||||
err(new Error(`${findModuleByImportedPath.name} aborted before resolved: ${path}`))
|
||||
}
|
||||
|
||||
// TODO(modules/finders::sync): make a lazyValue function to create a proxified object instead of dupe
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) return onAbort()
|
||||
signal.addEventListener('abort', onAbort, { once: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export type BaseFindModuleSyncOptions = BaseFindModuleOptions & ProxifyOptions
|
||||
export type FindModuleSyncOptions = FindModuleOptions & BaseFindModuleSyncOptions
|
||||
|
||||
/**
|
||||
* Find a module by its exports synchronously.
|
||||
|
|
@ -150,7 +136,7 @@ export type FindModuleSyncOptions = WaitForModulesOptions & {
|
|||
*
|
||||
* @example A little bit after index module is required:
|
||||
* ```ts
|
||||
* const React = findModuleSync(byProps<typeof import('react')>('createElement'))
|
||||
* const React = findModuleSync(byProps<typeof import('react')>('createElement'), { hint: 'object' })
|
||||
* const App = findModuleSync(byName<React.FC>('App'))
|
||||
*
|
||||
* // React is defined here because React is loaded before index module is required
|
||||
|
|
@ -167,45 +153,31 @@ export type FindModuleSyncOptions = WaitForModulesOptions & {
|
|||
* })
|
||||
* ```
|
||||
*/
|
||||
export function findModuleSync<F extends Filter<any>>(filter: F): FilterResult<F>
|
||||
export function findModuleSync<F extends Filter<any>, O extends FindModuleSyncOptions>(
|
||||
export function findModuleSync<F extends Filter>(filter: F): FilterResult<F>
|
||||
export function findModuleSync<F extends Filter, O extends FindModuleSyncOptions>(
|
||||
filter: F,
|
||||
options: O,
|
||||
): O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>
|
||||
export function findModuleSync<F extends Filter<any>, O extends FindModuleSyncOptions>(filter: F, options?: O) {
|
||||
export function findModuleSync<F extends Filter, O extends FindModuleSyncOptions>(filter: F, options?: O) {
|
||||
for (const exports of lookupModules(filter, options!)) return exports
|
||||
|
||||
type Result = FilterResult<F>
|
||||
let value: (O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<Result> : Result) | undefined
|
||||
let value: unknown | undefined
|
||||
const unsub = waitForModules(
|
||||
filter,
|
||||
exports => {
|
||||
(_, exports) => {
|
||||
unsub()
|
||||
value = exports
|
||||
},
|
||||
options,
|
||||
)
|
||||
|
||||
const handler: ProxyHandler<any> = {
|
||||
// biome-ignore lint/complexity/noBannedTypes: Function is the right type here
|
||||
apply: (_, thisArg, argArray) => Reflect.apply(value as Function, thisArg, argArray),
|
||||
// biome-ignore lint/complexity/noBannedTypes: Function is the right type here
|
||||
construct: (_, argArray, newTarget) => Reflect.construct(value as Function, argArray, newTarget),
|
||||
defineProperty: (_, property, attributes) => Reflect.defineProperty(value!, property, attributes),
|
||||
deleteProperty: (_, p) => Reflect.deleteProperty(value!, p),
|
||||
get: (_, p, receiver) => Reflect.get(value!, p, receiver),
|
||||
getOwnPropertyDescriptor: (_, p) => Reflect.getOwnPropertyDescriptor(value!, p),
|
||||
getPrototypeOf: _ => Reflect.getPrototypeOf(value!),
|
||||
has: (_, p) => Reflect.has(value!, p),
|
||||
isExtensible: _ => Reflect.isExtensible(value!),
|
||||
ownKeys: _ => Reflect.ownKeys(value!),
|
||||
preventExtensions: _ => Reflect.preventExtensions(value!),
|
||||
set: (_, p, newValue, receiver) => Reflect.set(value!, p, newValue, receiver),
|
||||
setPrototypeOf: (_, v) => Reflect.setPrototypeOf(value!, v),
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) unsub()
|
||||
else signal.addEventListener('abort', unsub, { once: true })
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/useArrowFunction: We need a function with a constructor
|
||||
return new Proxy((options?.hint === 'object' ? {} : function () {}) as any, handler) as FilterResult<F>
|
||||
return proxify(() => value, options)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -215,38 +187,24 @@ export function findModuleSync<F extends Filter<any>, O extends FindModuleSyncOp
|
|||
* @param options The options to use for the find.
|
||||
* @returns The module exports object if the module is already initialized, or proxified object of the module exports once the module is initialized, or undefined if otherwise.
|
||||
*/
|
||||
export function findModuleByImportedPathSync<T = any>(path: string, options?: FindModuleSyncOptions): T | undefined {
|
||||
const m1 = lookupModuleByImportedPath<T>(path)
|
||||
if (m1) return m1
|
||||
export function findModuleByImportedPathSync<T = any>(
|
||||
path: string,
|
||||
options?: BaseFindModuleSyncOptions,
|
||||
): T | undefined {
|
||||
const m = lookupModuleByImportedPath<T>(path)
|
||||
if (m) return m
|
||||
|
||||
let value: T | undefined
|
||||
const unsub = waitForModuleByImportedPath(
|
||||
path,
|
||||
(exports: T) => {
|
||||
let value: unknown | undefined
|
||||
const unsub = waitForModuleByImportedPath(path, (_, exports) => {
|
||||
unsub()
|
||||
value = exports
|
||||
},
|
||||
options,
|
||||
)
|
||||
})
|
||||
|
||||
const handler: ProxyHandler<any> = {
|
||||
// biome-ignore lint/complexity/noBannedTypes: Function is the right type here
|
||||
apply: (_, thisArg, argArray) => Reflect.apply(value as Function, thisArg, argArray),
|
||||
// biome-ignore lint/complexity/noBannedTypes: Function is the right type here
|
||||
construct: (_, argArray, newTarget) => Reflect.construct(value as Function, argArray, newTarget),
|
||||
defineProperty: (_, property, attributes) => Reflect.defineProperty(value!, property, attributes),
|
||||
deleteProperty: (_, p) => Reflect.deleteProperty(value!, p),
|
||||
get: (_, p, receiver) => Reflect.get(value!, p, receiver),
|
||||
getOwnPropertyDescriptor: (_, p) => Reflect.getOwnPropertyDescriptor(value!, p),
|
||||
getPrototypeOf: _ => Reflect.getPrototypeOf(value!),
|
||||
has: (_, p) => Reflect.has(value!, p),
|
||||
isExtensible: _ => Reflect.isExtensible(value!),
|
||||
ownKeys: _ => Reflect.ownKeys(value!),
|
||||
preventExtensions: _ => Reflect.preventExtensions(value!),
|
||||
set: (_, p, newValue, receiver) => Reflect.set(value!, p, newValue, receiver),
|
||||
setPrototypeOf: (_, v) => Reflect.setPrototypeOf(value!, v),
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) unsub()
|
||||
else signal.addEventListener('abort', unsub, { once: true })
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/useArrowFunction: We need a function with a constructor
|
||||
return new Proxy((options?.hint === 'object' ? {} : function () {}) as any, handler) as T
|
||||
return proxify(() => value, options)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { _bl, _blacklist, _mInited, _isExportsBad, _mMetadatas, _mUninited, _mPaths } from '../metro/_internal'
|
||||
|
||||
export * from './lookup'
|
||||
export * from './wait'
|
||||
export * from './find'
|
||||
|
|
|
|||
|
|
@ -1,63 +1,96 @@
|
|||
import { runFilter, runFilterReturnExports } from './_internal'
|
||||
import { _mInited, _mMetadatas, _mPaths } from '../metro/_internal'
|
||||
import { _mInited, _mMetadatas, _mPaths, _mUninited } from '~/metro/_internal'
|
||||
import {
|
||||
runFilter,
|
||||
runFilterReturnExports,
|
||||
type RunFilterOptions,
|
||||
type RunFilterReturnExportsOptions,
|
||||
} from './_internal'
|
||||
|
||||
import type { MaybeDefaultExportMatched } from '.'
|
||||
import type { Metro } from '#/metro'
|
||||
import type { Filter, FilterResult } from './filters'
|
||||
|
||||
export type LookupModulesOptions<ReturnNamespace extends boolean = boolean> = {
|
||||
export type BaseLookupModulesOptions = RunFilterOptions
|
||||
|
||||
export type LookupModulesOptions<ReturnNamespace extends boolean = boolean> =
|
||||
RunFilterReturnExportsOptions<ReturnNamespace>
|
||||
|
||||
export type LookupModulesResult<O extends LookupModulesOptions, F extends Filter> = O extends LookupModulesOptions<true>
|
||||
? MaybeDefaultExportMatched<FilterResult<F>>
|
||||
: FilterResult<F>
|
||||
|
||||
export type LookupModuleIdsOptions<IncludeUninitialized extends boolean = boolean> = BaseLookupModulesOptions & {
|
||||
/**
|
||||
* For ES modules, whether to skip checking the default export.
|
||||
* Whether to include initialized modules in the lookup.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
includeInitialized?: boolean
|
||||
/**
|
||||
* Whether to include uninitialized modules in the lookup.
|
||||
*
|
||||
* Options that require modules to be initialized (eg. `esmReturnNamespace`, `esmSkipDefault`) will be ignored during uninitialized module ID lookup.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
esmSkipDefault?: boolean
|
||||
} & (ReturnNamespace extends true
|
||||
? {
|
||||
/**
|
||||
* For ES modules, whether to return the whole module with all exports instead of just the default export if the default export matches.
|
||||
*
|
||||
* CommonJS modules will always return all exports.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
esmReturnNamespace: true
|
||||
includeUninitialized?: IncludeUninitialized
|
||||
}
|
||||
: {
|
||||
esmReturnNamespace?: false
|
||||
})
|
||||
|
||||
/**
|
||||
* Lookup module IDs by its exports.
|
||||
* This is the way to get **initialized** module IDs that match a filter.
|
||||
* Lookup module IDs by its exports. You may lookup both uninitialized module IDs when filtering via ID-only filters (eg. `byDependencies`).
|
||||
*
|
||||
* @param filter The filter to use.
|
||||
* @param options The options to use for the lookup.
|
||||
* @returns A generator that yields the module exports that match the filter.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const lookup = lookupModuleIds(byProps('createElement'))
|
||||
* // Log all module IDs that export React
|
||||
* for (const id of lookup) console.log(id)
|
||||
* ```
|
||||
*
|
||||
* @example Lookup uninitialized modules
|
||||
* ```ts
|
||||
* const lookup = lookupModuleIds(byDependencies([...]), { includeUninitialized: true })
|
||||
* // Log all module IDs that has those dependencies
|
||||
* for (const id of lookup) console.log(id)
|
||||
* ```
|
||||
*/
|
||||
export function* lookupModuleIds(filter: Filter<any>, options?: LookupModulesOptions) {
|
||||
export function* lookupModuleIds<O extends LookupModuleIdsOptions>(
|
||||
filter: O extends LookupModuleIdsOptions<true> ? Filter<any, false> : Filter,
|
||||
options?: O,
|
||||
): Generator<Metro.ModuleID, undefined> {
|
||||
if (options?.includeInitialized ?? true)
|
||||
for (const id of _mInited) {
|
||||
const { exports } = _mMetadatas.get(id)![1]!
|
||||
if (runFilter(filter, exports, id, options)) yield id
|
||||
if (runFilter(filter, id, exports, options)) yield id
|
||||
}
|
||||
|
||||
if (options?.includeUninitialized)
|
||||
for (const id of _mUninited) if (runFilter<Filter<any, false>>(filter, id, undefined, options)) yield id
|
||||
}
|
||||
|
||||
export function lookupModules<F extends Filter>(filter: F): Generator<FilterResult<F>, undefined>
|
||||
|
||||
export function lookupModules<F extends Filter, O extends LookupModulesOptions>(
|
||||
filter: F,
|
||||
options: O,
|
||||
): Generator<LookupModulesResult<O, F>, undefined>
|
||||
|
||||
/**
|
||||
* Lookup modules by its exports.
|
||||
* This is the way to get **initialized** module exports that match a filter.
|
||||
*
|
||||
* `lookupModules` only filters initialized modules. You will need to use `waitForModules` to filter initializing modules.
|
||||
*
|
||||
* @param filter The filter to use.
|
||||
* @param options The options to use for the lookup.
|
||||
* @returns A generator that yields the module exports that match the filter.
|
||||
*/
|
||||
export function lookupModules<F extends Filter<any>>(filter: F): Generator<FilterResult<F>>
|
||||
export function lookupModules<F extends Filter<any>, O extends LookupModulesOptions>(
|
||||
filter: F,
|
||||
options: O,
|
||||
): Generator<O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>>
|
||||
export function* lookupModules<F extends Filter<any>, O extends LookupModulesOptions>(filter: F, options?: O) {
|
||||
export function* lookupModules<F extends Filter, O extends LookupModulesOptions>(filter: F, options?: O) {
|
||||
for (const id of _mInited) {
|
||||
const { exports } = _mMetadatas.get(id)![1]!
|
||||
const result = runFilterReturnExports(filter, exports, id, options)
|
||||
const result = runFilterReturnExports(filter, id, exports, options)
|
||||
if (result) yield result
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +98,8 @@ export function* lookupModules<F extends Filter<any>, O extends LookupModulesOpt
|
|||
/**
|
||||
* Lookup module exports by its imported path.
|
||||
*
|
||||
* `lookupModuleByImportedPath` only finds initialized modules. You will need to use `waitForModuleByImportedPath` to filter initializing modules.
|
||||
*
|
||||
* @param path The path to lookup the module by.
|
||||
* @returns The module exports if the module is initialized, or undefined if the module is not found or not initialized.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
import { onAnyModuleInitialized, onModuleFinishedImporting } from '~/metro'
|
||||
import { _bl, _mMetadatas } from '~/metro/_internal'
|
||||
import { runFilterReturnExports } from './_internal'
|
||||
import { onAnyModuleInitialized, onModuleFinishedImporting } from '../metro'
|
||||
import { _bl, _mMetadatas } from '../metro/_internal'
|
||||
|
||||
import type { MaybeDefaultExportMatched } from '.'
|
||||
import type { Metro } from '#/metro'
|
||||
import type { Filter, FilterResult } from './filters'
|
||||
import type { LookupModulesOptions } from './lookup'
|
||||
import type { Metro } from '../../types'
|
||||
|
||||
export type WaitForModulesOptions = LookupModulesOptions & {
|
||||
/**
|
||||
* Abort signal for the wait.
|
||||
*
|
||||
* **The find will only be aborted when it is pending/requires waiting for the module to be initialized.**
|
||||
*/
|
||||
abortSignal?: AbortSignal
|
||||
}
|
||||
export type WaitForModulesOptions = LookupModulesOptions
|
||||
|
||||
/**
|
||||
* Wait for modules to initialize by its exports.
|
||||
|
|
@ -32,46 +25,35 @@ export type WaitForModulesOptions = LookupModulesOptions & {
|
|||
* waitForModule(
|
||||
* byName<typeof import('@shopify/flash-list')>('FlashList'),
|
||||
* // (exports: typeof import('@shopify/flash-list'), id: Metro.ModuleID) => any
|
||||
* (exports, id) => {...}
|
||||
* (id, exports) => {...}
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
export function waitForModules<F extends Filter<any>>(
|
||||
export function waitForModules<F extends Filter>(
|
||||
filter: F,
|
||||
callback: (exports: FilterResult<F>, id: Metro.ModuleID) => any,
|
||||
callback: (id: Metro.ModuleID, exports: FilterResult<F>) => any,
|
||||
): () => void
|
||||
export function waitForModules<F extends Filter<any>, O extends WaitForModulesOptions>(
|
||||
|
||||
export function waitForModules<F extends Filter, O extends WaitForModulesOptions | undefined>(
|
||||
filter: F,
|
||||
callback: (
|
||||
exports: O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>,
|
||||
id: Metro.ModuleID,
|
||||
exports: O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>,
|
||||
) => any,
|
||||
options?: O,
|
||||
options: O,
|
||||
): () => void
|
||||
export function waitForModules<F extends Filter<any>, O extends WaitForModulesOptions>(
|
||||
filter: F,
|
||||
callback: (
|
||||
exports: O extends LookupModulesOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>,
|
||||
id: Metro.ModuleID,
|
||||
) => any,
|
||||
options?: O,
|
||||
|
||||
export function waitForModules(
|
||||
filter: Filter,
|
||||
callback: (id: Metro.ModuleID, exports: Metro.ModuleExports) => any,
|
||||
options?: WaitForModulesOptions,
|
||||
) {
|
||||
const unsub = onAnyModuleInitialized(({ exports }, id) => {
|
||||
const unsub = onAnyModuleInitialized((id, exports) => {
|
||||
if (_bl.has(id)) return
|
||||
const result = runFilterReturnExports(filter, exports, id, options)
|
||||
if (result) callback(result, id)
|
||||
const result = runFilterReturnExports(filter, id, exports, options)
|
||||
if (result) callback(id, exports)
|
||||
})
|
||||
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
unsub()
|
||||
return unsub
|
||||
}
|
||||
|
||||
signal.addEventListener('abort', unsub, { once: true })
|
||||
}
|
||||
|
||||
return unsub
|
||||
}
|
||||
|
||||
|
|
@ -81,27 +63,14 @@ export function waitForModules<F extends Filter<any>, O extends WaitForModulesOp
|
|||
* @param callback The callback to call once the module is initialized.
|
||||
* @returns A function to unsubscribe.
|
||||
*/
|
||||
export function waitForModuleByImportedPath<T = any>(
|
||||
path: string,
|
||||
callback: (exports: T, id: Metro.ModuleID) => any,
|
||||
options?: WaitForModulesOptions,
|
||||
) {
|
||||
export function waitForModuleByImportedPath<T = any>(path: string, callback: (id: Metro.ModuleID, exports: T) => any) {
|
||||
const unsub = onModuleFinishedImporting((id, cmpPath) => {
|
||||
if (path === cmpPath) {
|
||||
callback(_mMetadatas.get(id)![1]!.exports, id)
|
||||
const { exports } = _mMetadatas.get(id)![1]!
|
||||
callback(id, exports)
|
||||
unsub()
|
||||
}
|
||||
})
|
||||
|
||||
const signal = options?.abortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
unsub()
|
||||
return unsub
|
||||
}
|
||||
|
||||
signal.addEventListener('abort', unsub, { once: true })
|
||||
}
|
||||
|
||||
return unsub
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { isProxy } from '@revenge-mod/utils/proxy'
|
||||
import { _executeSubscription } from './subscriptions/_internal'
|
||||
|
||||
import type { Metro } from '../../types/metro'
|
||||
import type { Metro } from '#/metro'
|
||||
|
||||
export const _bl = new Set<Metro.ModuleID>()
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ export function patchMetroDefine(metroDefine: Metro.DefineFn) {
|
|||
} finally {
|
||||
_mInitingId = prevIId
|
||||
_mUninited.delete(id)
|
||||
_executeSubscription(id, m)
|
||||
_executeSubscription(id, m.exports)
|
||||
}
|
||||
}) satisfies Metro.FactoryFn,
|
||||
id,
|
||||
|
|
@ -73,6 +73,22 @@ export function _blacklist(id: Metro.ModuleID) {
|
|||
// TODO(modules): caching
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a particular export of a module is bad.
|
||||
* @param export The exports of the module
|
||||
* @returns Whether the module has bad exports
|
||||
* @internal
|
||||
*/
|
||||
export function _isExportBad(exp: Metro.ModuleExports) {
|
||||
return (
|
||||
// Nullish?
|
||||
exp === undefined ||
|
||||
exp === null ||
|
||||
// Is it a proxy?
|
||||
isProxy(exp)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -81,11 +97,9 @@ export function _blacklist(id: Metro.ModuleID) {
|
|||
*/
|
||||
export function _isExportsBad(exports: Metro.ModuleExports) {
|
||||
return (
|
||||
exports === undefined ||
|
||||
exports === null ||
|
||||
// Empty object (module 0 for example, exports an empty object)
|
||||
(exports.__proto__ === Object.prototype && !Reflect.ownKeys(exports).length) ||
|
||||
isProxy(exports.default) ||
|
||||
// Exports will always be an object, we are checking if the object is empty
|
||||
!Reflect.ownKeys(exports).length ||
|
||||
// Circular reference
|
||||
exports === globalThis
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { _mMetadatas, _mInitingId, _mPaths, _mUninited, _bl } from './_internal'
|
||||
import '@revenge-mod/assets/patches'
|
||||
|
||||
import { _bl, _mInitingId, _mMetadatas, _mPaths, _mUninited } from './_internal'
|
||||
import { onAnyModuleInitialized } from './subscriptions'
|
||||
|
||||
import '@revenge-mod/assets/patches'
|
||||
import { _executeImportedPathSubscription } from './subscriptions/_internal'
|
||||
|
||||
const unsubForImportTracker = onAnyModuleInitialized(({ exports }, id) => {
|
||||
const unsubForImportTracker = onAnyModuleInitialized((id, exports) => {
|
||||
if (_bl.has(id)) return
|
||||
|
||||
const orig = exports.fileFinishedImporting
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import type { ModuleInitializedCallback, ModuleWithImportedPathInitializedCallback } from '.'
|
||||
import type { Metro } from '../../../types/metro'
|
||||
import type { Metro } from '#/metro'
|
||||
|
||||
export const _all = new Set<ModuleInitializedCallback>()
|
||||
export const _specific = new Map<Metro.ModuleID, Set<ModuleInitializedCallback>>()
|
||||
export const _importedPath = new Set<ModuleWithImportedPathInitializedCallback>()
|
||||
|
||||
export function _executeSubscription(id: Metro.ModuleID, module: Metro.ModuleExports) {
|
||||
for (const cb of _all) cb(module, id)
|
||||
if (_specific.has(id)) for (const cb of _specific.get(id)!) cb(module, id)
|
||||
export function _executeSubscription(id: Metro.ModuleID, exports: Metro.ModuleExports) {
|
||||
for (const cb of _all) cb(id, exports)
|
||||
if (_specific.has(id)) for (const cb of _specific.get(id)!) cb(id, exports)
|
||||
}
|
||||
|
||||
export function _executeImportedPathSubscription(id: Metro.ModuleID, path: string) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { Metro } from '#/metro'
|
||||
import { _all, _importedPath, _specific } from './_internal'
|
||||
import type { Metro } from '../../../types/metro'
|
||||
|
||||
export type ModuleInitializedCallback = (module: Metro.Module, id: Metro.ModuleID) => void
|
||||
export type ModuleWithImportedPathInitializedCallback = (id: Metro.ModuleID, importedPath: string) => void
|
||||
export type ModuleInitializedCallback = (id: Metro.ModuleID, exports: Metro.ModuleExports) => void
|
||||
export type ModuleWithImportedPathInitializedCallback = (id: Metro.ModuleID, path: string) => void
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when any module is initialized. **Some module exports may be bad.**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DiscordNativeModules } from '../types/discord.native'
|
||||
import type { DiscordNativeModules } from '#/discord.native'
|
||||
|
||||
/**
|
||||
* Naming conventions:
|
||||
|
|
|
|||
9
lib/modules/tsconfig.json
Normal file
9
lib/modules/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "@revenge-mod/types/internal/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"~/*": ["./src/*"],
|
||||
"#/*": ["./types/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue