feat+refactor(metro): implement byFilePath
This commit is contained in:
parent
c89dc175e3
commit
259b9399d0
8 changed files with 133 additions and 111 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import { createFilterDefinition } from "@metro/filters";
|
||||
import { find } from "@metro/finders";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { createFilterDefinition, findByProps } from "@metro/utils";
|
||||
import { RedesignModuleKeys } from "@ui/types";
|
||||
import { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
|
|
|
|||
|
|
@ -1,34 +1,27 @@
|
|||
import { ClientInfoManager, MMKVManager } from "@lib/api/native/modules";
|
||||
import { throttle } from "@lib/utils/throttle";
|
||||
import SparkMD5 from "spark-md5";
|
||||
|
||||
const BUNNY_METRO_CACHE_KEY = "__bunny_metro_cache_key_v5__";
|
||||
|
||||
type ModulesIndexer = {
|
||||
_?: 1;
|
||||
[id: number]: 1 | void;
|
||||
};
|
||||
|
||||
interface MetroCacheStore {
|
||||
v: 5;
|
||||
buildNumber: number;
|
||||
findIndex: Record<string, {
|
||||
_?: 1;
|
||||
[id: number]: 1 | void;
|
||||
} | undefined>;
|
||||
polyfillCache: Record<string, {
|
||||
_?: 1;
|
||||
[id: number]: 1 | void;
|
||||
} | undefined>;
|
||||
findIndex: Record<string, ModulesIndexer | undefined>;
|
||||
polyfillCache: Record<string, ModulesIndexer | undefined>;
|
||||
assetsCache: Record<string, number>;
|
||||
}
|
||||
|
||||
const BUNNY_METRO_CACHE_KEY = "__bunny_metro_cache_key_v5__";
|
||||
|
||||
let metroCache = null as unknown as MetroCacheStore;
|
||||
|
||||
export function getMetroCache() {
|
||||
return metroCache;
|
||||
}
|
||||
|
||||
export function getFuncUniqCall() {
|
||||
const { stack } = new Error();
|
||||
return SparkMD5.hash(stack!);
|
||||
}
|
||||
|
||||
function buildInitCache() {
|
||||
metroCache = {
|
||||
v: 5,
|
||||
|
|
@ -62,9 +55,7 @@ export async function initMetroCache() {
|
|||
}
|
||||
}
|
||||
|
||||
const saveCache = throttle(() => {
|
||||
MMKVManager.setItem(BUNNY_METRO_CACHE_KEY, JSON.stringify(metroCache));
|
||||
});
|
||||
const saveCache = throttle(() => MMKVManager.setItem(BUNNY_METRO_CACHE_KEY, JSON.stringify(metroCache)));
|
||||
|
||||
export function registerModuleFindCacheId(uniqueId: string, moduleId: number, all: boolean) {
|
||||
(metroCache.findIndex[uniqueId] ??= { _: undefined })[moduleId] = 1;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { createSimpleFilter } from "@metro/filters";
|
||||
import { find } from "@metro/finders";
|
||||
import { Dispatcher } from "@metro/types";
|
||||
import { findByProps } from "@metro/utils";
|
||||
import { createSimpleFilter, findByProps } from "@metro/utils";
|
||||
|
||||
// Discord
|
||||
export const constants = findByProps("Fonts", "Permissions");
|
||||
|
|
|
|||
|
|
@ -1,63 +1,5 @@
|
|||
|
||||
type Indexable = Record<PropertyKey, any>;
|
||||
type ModuleExports = any;
|
||||
|
||||
type FilterCheckDef<A extends unknown[]> = <M extends Indexable>(args: A, m: M) => boolean;
|
||||
|
||||
export interface FilterFn<A extends unknown[]> {
|
||||
(m: any): boolean;
|
||||
filter: FilterCheckDef<A>;
|
||||
args: A;
|
||||
isDefault: boolean;
|
||||
serialized: string;
|
||||
}
|
||||
|
||||
export interface FilterDef<A extends unknown[]> {
|
||||
(...args: A): FilterFn<A>;
|
||||
byDefault(...args: A): FilterFn<A>;
|
||||
serializer(args: A): string;
|
||||
}
|
||||
|
||||
export function createFilterDefinition<A extends unknown[]>(
|
||||
fn: FilterCheckDef<A>,
|
||||
serializer: (args: A) => string
|
||||
): FilterDef<A> {
|
||||
function createHolder<T extends Function>(func: T, args: A, isDefault: boolean) {
|
||||
return Object.assign(func, {
|
||||
filter: fn,
|
||||
args,
|
||||
isDefault,
|
||||
serialized: [
|
||||
isDefault && "default::",
|
||||
serializer(args)
|
||||
].filter(Boolean).join("")
|
||||
});
|
||||
}
|
||||
|
||||
const curried = (...args: A) => createHolder((m: ModuleExports) => fn(args, m), args, false);
|
||||
const curriedDefault = (...args: A) => {
|
||||
function filter(m: ModuleExports) {
|
||||
return m.__esModule && m.default ? fn(args, m.default) : false;
|
||||
}
|
||||
return createHolder(filter, args, true);
|
||||
};
|
||||
|
||||
return Object.assign(curried, {
|
||||
byDefault: curriedDefault,
|
||||
serializer
|
||||
});
|
||||
}
|
||||
|
||||
export function createSimpleFilter(
|
||||
filter: (m: ModuleExports) => boolean,
|
||||
id: string
|
||||
) {
|
||||
return createFilterDefinition(
|
||||
(_, m) => filter(m),
|
||||
() => `dynamic::${id}`
|
||||
)();
|
||||
}
|
||||
|
||||
import { metroModules } from "./modules";
|
||||
import { createFilterDefinition } from "./utils";
|
||||
|
||||
export const byProps = createFilterDefinition<string[]>(
|
||||
(props, m) => props.length === 0 ? m[props[0]] : props.every(p => m[p]),
|
||||
|
|
@ -84,6 +26,11 @@ export const byStoreName = createFilterDefinition<[string]>(
|
|||
name => `bunny.metro.byStoreName(${name})`
|
||||
);
|
||||
|
||||
export const byFilePath = createFilterDefinition<[string]>(
|
||||
([path], _, id) => metroModules[id]?.__filePath === path,
|
||||
path => `bunny.metro.byFilePath(${path})`
|
||||
);
|
||||
|
||||
export const byMutableProp = createFilterDefinition<[string]>(
|
||||
([prop], m) => m?.[prop] && !Object.getOwnPropertyDescriptor(m, prop)?.get,
|
||||
prop => `bunny.metro.byMutableProp(${prop})`
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { registerModuleFindCacheId, registerModuleFindFinished } from "./caches";
|
||||
import { FilterFn } from "./filters";
|
||||
import { getModules } from "./modules";
|
||||
import { FilterFn } from "./utils";
|
||||
|
||||
function testExports<A extends unknown[]>(moduleExports: any, filter: FilterFn<A>) {
|
||||
if (moduleExports.default && moduleExports.__esModule && filter(moduleExports.default))
|
||||
function testExports<A extends unknown[]>(moduleExports: any, moduleId: number, filter: FilterFn<A>) {
|
||||
if (moduleExports.default && moduleExports.__esModule && filter(moduleExports.default, moduleId))
|
||||
return moduleExports.default;
|
||||
if (filter(moduleExports))
|
||||
if (filter(moduleExports, moduleId))
|
||||
return moduleExports;
|
||||
}
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ function testExports<A extends unknown[]>(moduleExports: any, filter: FilterFn<A
|
|||
*/
|
||||
export function find<A extends unknown[]>(filter: FilterFn<A>) {
|
||||
for (const [id, moduleExports] of getModules(filter.serialized, false)) {
|
||||
const testedExports = testExports(moduleExports, filter);
|
||||
const testedExports = testExports(moduleExports, id, filter);
|
||||
if (testedExports !== undefined) {
|
||||
registerModuleFindCacheId(filter.serialized, id, false);
|
||||
return testedExports;
|
||||
|
|
@ -33,7 +33,7 @@ export function findAll<A extends unknown[]>(filter: FilterFn<A>) {
|
|||
const foundExports: any[] = [];
|
||||
|
||||
for (const [id, moduleExports] of getModules(filter.serialized, true)) {
|
||||
const testedExports = testExports(moduleExports, filter);
|
||||
const testedExports = testExports(moduleExports, id, filter);
|
||||
if (testedExports !== undefined) {
|
||||
foundExports.push(testedExports);
|
||||
registerModuleFindCacheId(filter.serialized, id, true);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
import { getMetroCache } from "@metro/caches";
|
||||
import { Metro } from "@metro/types";
|
||||
import { instead } from "spitroast";
|
||||
import { before, instead } from "spitroast";
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const modules: Metro.ModuleList = window.modules;
|
||||
export const metroModules: Metro.ModuleList = window.modules;
|
||||
const metroRequire: Metro.Require = window.__r;
|
||||
|
||||
metroRequire(0);
|
||||
|
||||
const blacklistedIds = new Set<String>();
|
||||
const noopHandler = () => undefined;
|
||||
const functionToString = Function.prototype.toString;
|
||||
|
||||
let patchedInspectSource = false;
|
||||
let patchedImportTracker = false;
|
||||
let _importingModuleId: string | null = null;
|
||||
|
||||
/** Makes the module associated with the specified ID non-enumberable. */
|
||||
function blacklistModule(id: string) {
|
||||
Object.defineProperty(modules, id, { enumerable: false });
|
||||
Object.defineProperty(metroModules, id, { enumerable: false });
|
||||
blacklistedIds.add(id);
|
||||
}
|
||||
|
||||
|
|
@ -25,8 +23,8 @@ function isBadExports(exports: any) {
|
|||
return !exports || exports === window || exports["<!@ pylix was here :fuyusquish: !@>"] === null;
|
||||
}
|
||||
|
||||
for (const id in modules) {
|
||||
const metroModule = modules[id];
|
||||
for (const id in metroModules) {
|
||||
const metroModule = metroModules[id];
|
||||
|
||||
if (metroModule!.factory) {
|
||||
instead("factory", metroModule, ((args: Parameters<Metro.FactoryFn>, origFunc: Metro.FactoryFn) => {
|
||||
|
|
@ -95,6 +93,14 @@ function onModuleRequire(moduleExports: any, id: Metro.ModuleID) {
|
|||
});
|
||||
}
|
||||
|
||||
if (!patchedImportTracker && moduleExports.fileFinishedImporting) {
|
||||
before("fileFinishedImporting", moduleExports, ([filePath]) => {
|
||||
if (_importingModuleId == null || !filePath) return;
|
||||
metroModules[_importingModuleId]!.__filePath = filePath;
|
||||
});
|
||||
patchedImportTracker = true;
|
||||
}
|
||||
|
||||
// Funny infinity recursion caused by a race condition
|
||||
if (!patchedInspectSource && window["__core-js_shared__"]) {
|
||||
const inspect = (f: unknown) => typeof f === "function" && functionToString.apply(f, []);
|
||||
|
|
@ -105,9 +111,9 @@ function onModuleRequire(moduleExports: any, id: Metro.ModuleID) {
|
|||
// Explosion (no, I can't explain this, don't ask) ((hi rosie))
|
||||
if (moduleExports.findHostInstance_DEPRECATED) {
|
||||
const numberedId = Number(id);
|
||||
const prevExports = modules[numberedId - 1]?.publicModule.exports;
|
||||
const prevExports = metroModules[numberedId - 1]?.publicModule.exports;
|
||||
const inc = prevExports.default?.reactProfilingEnabled ? 1 : -1;
|
||||
if (!modules[numberedId + inc]?.isInitialized) {
|
||||
if (!metroModules[numberedId + inc]?.isInitialized) {
|
||||
blacklistModule(String(numberedId + inc));
|
||||
}
|
||||
}
|
||||
|
|
@ -127,9 +133,10 @@ export function getImportingModuleId() {
|
|||
}
|
||||
|
||||
export function requireModule(id: Metro.ModuleID) {
|
||||
if (!metroModules[0]?.isInitialized) metroRequire(0);
|
||||
if (blacklistedIds.has(String(id))) return undefined;
|
||||
|
||||
if (modules[id]?.isInitialized && !modules[id]?.hasError) {
|
||||
if (metroModules[id]?.isInitialized && !metroModules[id]?.hasError) {
|
||||
return metroRequire(id);
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +164,7 @@ export function* getModules(uniqueId: string, all = false) {
|
|||
let cache = getMetroCache().findIndex[uniqueId];
|
||||
if (all && !cache?._) cache = undefined;
|
||||
|
||||
for (const id in cache ?? modules) {
|
||||
for (const id in cache ?? metroModules) {
|
||||
const exports = requireModule(id);
|
||||
if (isBadExports(exports)) continue;
|
||||
yield [id, exports];
|
||||
|
|
@ -166,7 +173,7 @@ export function* getModules(uniqueId: string, all = false) {
|
|||
|
||||
export function* getCachedPolyfillModules(name: string) {
|
||||
const cache = getMetroCache().polyfillCache[name];
|
||||
for (const id in cache ?? modules) {
|
||||
for (const id in cache ?? metroModules) {
|
||||
const exports = requireModule(id);
|
||||
if (isBadExports(exports)) continue;
|
||||
yield [id, exports];
|
||||
|
|
|
|||
|
|
@ -45,22 +45,43 @@ export namespace Metro {
|
|||
error?: any;
|
||||
/** Set to undefined once module is initialized */
|
||||
factory: FactoryFn | undefined;
|
||||
/** If factory thrown any error */
|
||||
/**
|
||||
* If factory thrown any error
|
||||
* */
|
||||
hasError: boolean;
|
||||
/** Only available on Discord's development environment, will never be defined on release builds */
|
||||
/**
|
||||
* Only available on Discord's development environment, will never be defined on release builds
|
||||
* */
|
||||
hot?: HotModuleReloadingData | undefined;
|
||||
/** Cached `import *` imports in Metro, always an empty object as Bunny prevents outdated import cache */
|
||||
/**
|
||||
* Cached `import *` imports in Metro, always an empty object as Bunny prevents outdated import cache
|
||||
* */
|
||||
importedAll: any;
|
||||
/** Cached `import module from "./module"` imports in Metro, always an empty object as Bunny prevents outdated import cache */
|
||||
/**
|
||||
* Cached `import module from "./module"` imports in Metro, always an empty object as Bunny prevents outdated import cache
|
||||
* */
|
||||
importedDefault: any;
|
||||
/** If factory has been successfully called */
|
||||
/**
|
||||
* Whether factory has been successfully called
|
||||
* */
|
||||
isInitialized: boolean;
|
||||
/** Only available on Discord's development environment, will never be defined on release builds */
|
||||
/**
|
||||
* Only available on Discord's development environment, will never be defined on release builds
|
||||
* */
|
||||
path?: string | undefined;
|
||||
/** Acts as CJS module in the bundler */
|
||||
/**
|
||||
* Acts as CJS module in the bundler
|
||||
* */
|
||||
publicModule: Module;
|
||||
/** Only available on Discord's development environment, will never be defined on release builds */
|
||||
/**
|
||||
* Only available on Discord's development environment, will never be defined on release builds
|
||||
* */
|
||||
verboseName?: string | undefined;
|
||||
|
||||
/**
|
||||
* This is set by us. Should be available for all Discord's tsx modules!
|
||||
*/
|
||||
__filePath?: string;
|
||||
}
|
||||
|
||||
export type ModuleList = Record<ModuleID, ModuleDefinition | Nullish>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,63 @@
|
|||
import { byDisplayName, byName, byProps, byStoreName, byTypeName } from "./filters";
|
||||
import { byDisplayName, byFilePath, byName, byProps, byStoreName, byTypeName } from "./filters";
|
||||
import { find, findAll } from "./finders";
|
||||
|
||||
type ModuleExports = any;
|
||||
type FilterCheckDef<A extends unknown[]> = (args: A, m: any, id: number) => boolean;
|
||||
|
||||
export interface FilterFn<A extends unknown[]> {
|
||||
(m: any, id: number): boolean;
|
||||
filter: FilterCheckDef<A>;
|
||||
args: A;
|
||||
isDefault: boolean;
|
||||
serialized: string;
|
||||
}
|
||||
|
||||
export interface FilterDef<A extends unknown[]> {
|
||||
(...args: A): FilterFn<A>;
|
||||
byDefault(...args: A): FilterFn<A>;
|
||||
serializer(args: A): string;
|
||||
}
|
||||
|
||||
export function createFilterDefinition<A extends unknown[]>(
|
||||
fn: FilterCheckDef<A>,
|
||||
serializer: (args: A) => string
|
||||
): FilterDef<A> {
|
||||
function createHolder<T extends Function>(func: T, args: A, isDefault: boolean) {
|
||||
return Object.assign(func, {
|
||||
filter: fn,
|
||||
args,
|
||||
isDefault,
|
||||
serialized: [
|
||||
isDefault && "default::",
|
||||
serializer(args)
|
||||
].filter(Boolean).join("")
|
||||
});
|
||||
}
|
||||
|
||||
const curried = (...args: A) => createHolder((m: ModuleExports, id: number) => fn(args, m, id), args, false);
|
||||
const curriedDefault = (...args: A) => {
|
||||
function filter(m: ModuleExports, id: number) {
|
||||
return m.__esModule && m.default ? fn(args, m.default, id) : false;
|
||||
}
|
||||
return createHolder(filter, args, true);
|
||||
};
|
||||
|
||||
return Object.assign(curried, {
|
||||
byDefault: curriedDefault,
|
||||
serializer
|
||||
});
|
||||
}
|
||||
|
||||
export function createSimpleFilter(
|
||||
filter: (m: ModuleExports) => boolean,
|
||||
id: string
|
||||
) {
|
||||
return createFilterDefinition(
|
||||
(_, m) => filter(m),
|
||||
() => `dynamic::${id}`
|
||||
)();
|
||||
}
|
||||
|
||||
export const findByProps = (...props: string[]) => find(byProps(...props));
|
||||
export const findByPropsAll = (...props: string[]) => findAll(byProps(...props));
|
||||
|
||||
|
|
@ -14,3 +71,4 @@ export const findByTypeName = (name: string, expDefault = true) => find(expDefau
|
|||
export const findByTypeNameAll = (name: string, expDefault = true) => findAll(expDefault ? byTypeName(name) : byTypeName.byDefault(name));
|
||||
|
||||
export const findByStoreName = (name: string) => find(byStoreName(name));
|
||||
export const findByFilePath = (path: string) => find(byFilePath(path));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue