diff --git a/internals/caches.ts b/internals/caches.ts deleted file mode 100644 index b5192b8..0000000 --- a/internals/caches.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { fileExists, readFile, writeFile } from "@lib/api/native/fs"; -import { NativeClientInfoModule } from "@lib/api/native/modules"; -import { debounce } from "es-toolkit"; - -import { ModuleFlags, ModulesMapInternal } from "./enums"; - -const CACHE_VERSION = 102; -const BUNNY_METRO_CACHE_PATH = "caches/metro_modules.json"; - -type ModulesMap = { - [flag in number | `_${ModulesMapInternal}`]?: ModuleFlags; -}; - -let _metroCache = null as unknown as ReturnType; - -export const getMetroCache = () => _metroCache; - -function buildInitCache() { - const cache = { - _v: CACHE_VERSION, - _buildNumber: NativeClientInfoModule.getConstants().Build, - _modulesCount: Object.keys(window.modules).length, - flagsIndex: {} as Record, - findIndex: {} as Record, - polyfillIndex: {} as Record - } as const; - - // Force load all modules so useful modules are pre-cached. Add a minor - // delay so the cache is initialized before the modules are loaded. - setTimeout(() => { - for (const id in window.modules) { - require("./modules").requireModule(id); - } - }, 100); - - _metroCache = cache; - return cache; -} - -/** @internal */ -export async function initMetroCache() { - if (!await fileExists(BUNNY_METRO_CACHE_PATH)) return void buildInitCache(); - const rawCache = await readFile(BUNNY_METRO_CACHE_PATH); - - try { - _metroCache = JSON.parse(rawCache); - if (_metroCache._v !== CACHE_VERSION) { - _metroCache = null!; - throw "cache invalidated; cache version outdated"; - } - if (_metroCache._buildNumber !== NativeClientInfoModule.getConstants().Build) { - _metroCache = null!; - throw "cache invalidated; version mismatch"; - } - if (_metroCache._modulesCount !== Object.keys(window.modules).length) { - _metroCache = null!; - throw "cache invalidated; modules count mismatch"; - } - } catch { - buildInitCache(); - } -} - -const saveCache = debounce(() => { - writeFile(BUNNY_METRO_CACHE_PATH, JSON.stringify(_metroCache)); -}, 1000); - -function extractExportsFlags(moduleExports: any) { - if (!moduleExports) return undefined; - - const bit = ModuleFlags.EXISTS; - return bit; -} - -/** @internal */ -export function indexExportsFlags(moduleId: number, moduleExports: any) { - const flags = extractExportsFlags(moduleExports); - if (flags && flags !== ModuleFlags.EXISTS) { - _metroCache.flagsIndex[moduleId] = flags; - } -} - -/** @internal */ -export function indexBlacklistFlag(id: number) { - _metroCache.flagsIndex[id] |= ModuleFlags.BLACKLISTED; -} - -/** @internal */ -export function indexAssetModuleFlag(id: number) { - _metroCache.flagsIndex[id] |= ModuleFlags.ASSET; -} - -/** @internal */ -export function getCacherForUniq(uniq: string, allFind: boolean) { - const indexObject = _metroCache.findIndex[uniq] ??= {}; - - return { - cacheId(moduleId: number, exports: any) { - indexObject[moduleId] ??= extractExportsFlags(exports); - - saveCache(); - }, - // Finish may not be called by single find - finish(notFound: boolean) { - if (allFind) indexObject[`_${ModulesMapInternal.FULL_LOOKUP}`] = 1; - if (notFound) indexObject[`_${ModulesMapInternal.NOT_FOUND}`] = 1; - - saveCache(); - } - }; -} - -/** @internal */ -export function getPolyfillModuleCacher(name: string) { - const indexObject = _metroCache.polyfillIndex[name] ??= {}; - - return { - getModules() { - return require("@metro/internals/modules").getCachedPolyfillModules(name); - }, - cacheId(moduleId: number) { - indexObject[moduleId] = 1; - saveCache(); - }, - finish() { - indexObject[`_${ModulesMapInternal.FULL_LOOKUP}`] = 1; - saveCache(); - } - }; -} diff --git a/internals/enums.ts b/internals/enums.ts deleted file mode 100644 index 71cf9de..0000000 --- a/internals/enums.ts +++ /dev/null @@ -1,10 +0,0 @@ -export enum ModuleFlags { - EXISTS = 1 << 0, - BLACKLISTED = 1 << 1, - ASSET = 1 << 2, -} - -export enum ModulesMapInternal { - FULL_LOOKUP, - NOT_FOUND -} diff --git a/internals/modules.ts b/internals/modules.ts deleted file mode 100644 index f62020f..0000000 --- a/internals/modules.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { - getMetroCache, - indexBlacklistFlag, - indexExportsFlags, -} from "@metro/internals/caches"; -import { Metro } from "@metro/types"; - -import { ModuleFlags, ModulesMapInternal } from "./enums"; - -const { before, instead } = require("spitroast"); - -export const metroModules: Metro.ModuleList = window.modules; -const metroRequire = (id: string | number) => window.__r(+id); - -// eslint-disable-next-line func-call-spacing -const moduleSubscriptions = new Map void>>(); -const blacklistedIds = new Set(); -const noopHandler = () => undefined; -const functionToString = Function.prototype.toString; - -let patchedInspectSource = false; -let patchedImportTracker = false; -let patchedNativeComponentRegistry = false; -let _importingModuleId: number = -1; - -for (const key in metroModules) { - const id = Number(key); - const metroModule = metroModules[id]; - - const cache = getMetroCache().flagsIndex[id]; - if (cache & ModuleFlags.BLACKLISTED) { - blacklistModule(id); - continue; - } - - if (metroModule!.factory) { - instead("factory", metroModule, (( - args: Parameters, - origFunc: Metro.FactoryFn, - ) => { - const originalImportingId = _importingModuleId; - _importingModuleId = id; - - const { 1: metroRequire, 4: moduleObject } = args; - - args[2 /* metroImportDefault */] = (id) => { - const exps = metroRequire(id); - return exps && exps.__esModule ? exps.default : exps; - }; - - args[3 /* metroImportAll */] = (id) => { - const exps = metroRequire(id); - if (exps && exps.__esModule) return exps; - - const importAll: Record = {}; - if (exps) Object.assign(importAll, exps); - importAll.default = exps; - return importAll; - }; - - origFunc(...args); - if (!isBadExports(moduleObject.exports)) { - onModuleRequire(moduleObject.exports, id); - } else { - blacklistModule(id); - } - - _importingModuleId = originalImportingId; - }) as any); // If only spitroast had better types - } -} - -/** Makes the module associated with the specified ID non-enumberable. */ -function blacklistModule(id: number) { - Object.defineProperty(metroModules, id, { enumerable: false }); - blacklistedIds.add(id); - indexBlacklistFlag(Number(id)); -} - -function isBadExports(exports: any) { - return ( - !exports || - exports === window || - exports[""] === - null || - (exports.__proto__ === Object.prototype && - Reflect.ownKeys(exports).length === 0) || - exports.default?.[Symbol.toStringTag] === "IntlMessagesProxy" - ); -} - -function onModuleRequire(moduleExports: any, id: Metro.ModuleID) { - indexExportsFlags(id, moduleExports); - - // Temporary - moduleExports.initSentry &&= () => undefined; - if (moduleExports.default?.track && moduleExports.default.trackMaker) - moduleExports.default.track = () => Promise.resolve(); - - if (moduleExports.registerAsset) { - require("@lib/api/assets/patches").patchAssets(moduleExports); - } - - // There are modules registering the same native component - if ( - !patchedNativeComponentRegistry && - [ - "customBubblingEventTypes", - "customDirectEventTypes", - "register", - "get", - ].every((x) => moduleExports[x]) - ) { - instead("register", moduleExports, ([name, cb]: any, origFunc: any) => { - try { - return origFunc(name, cb); - } catch { - return name; - } - }); - - patchedNativeComponentRegistry = true; - } - - // Hook DeveloperExperimentStore - if ( - moduleExports?.default?.constructor?.displayName === - "DeveloperExperimentStore" - ) { - moduleExports.default = new Proxy(moduleExports.default, { - get(target, property, receiver) { - if (property === "isDeveloper") { - // Hopefully won't explode accessing it here :3 - const { settings } = require("@lib/api/settings"); - return settings.enableDiscordDeveloperSettings ?? false; - } - - return Reflect.get(target, property, receiver); - }, - }); - } - - if (!patchedImportTracker && moduleExports.fileFinishedImporting) { - before("fileFinishedImporting", moduleExports, ([filePath]: [string]) => { - if (_importingModuleId === -1 || !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, []); - window["__core-js_shared__"].inspectSource = inspect; - patchedInspectSource = true; - } - - // - if (moduleExports.findHostInstance_DEPRECATED) { - const prevExports = metroModules[id - 1]?.publicModule.exports; - const inc = prevExports.default?.reactProfilingEnabled ? 1 : -1; - if (!metroModules[id + inc]?.isInitialized) { - blacklistModule(id + inc); - } - } - - // Hindi timestamps moment - if (moduleExports.isMoment) { - instead( - "defineLocale", - moduleExports, - (args: [string], orig: (lcl: string) => string) => { - const origLocale = moduleExports.locale(); - orig(...args); - moduleExports.locale(origLocale); - }, - ); - } - - const subs = moduleSubscriptions.get(Number(id)); - if (subs) { - subs.forEach((s) => s()); - moduleSubscriptions.delete(Number(id)); - } -} - -export function getImportingModuleId() { - return _importingModuleId; -} - -export function subscribeModule(id: number, cb: () => void): () => void { - const subs = moduleSubscriptions.get(id) ?? new Set(); - - subs.add(cb); - moduleSubscriptions.set(id, subs); - - return () => subs.delete(cb); -} - -export function requireModule(id: Metro.ModuleID) { - if (!metroModules[0]?.isInitialized) metroRequire(0); - if (blacklistedIds.has(id)) return undefined; - - if (Number(id) === -1) return require("@metro/polyfills/redesign"); - - if (metroModules[id]?.isInitialized && !metroModules[id]?.hasError) { - return metroRequire(id); - } - - // Disable Internal RN error reporting logic - const originalHandler = ErrorUtils.getGlobalHandler(); - ErrorUtils.setGlobalHandler(noopHandler); - - let moduleExports; - try { - moduleExports = metroRequire(id); - } catch { - blacklistModule(id); - moduleExports = undefined; - } - - // Done initializing! Now, revert our hacks - ErrorUtils.setGlobalHandler(originalHandler); - - return moduleExports; -} - -export function* getModules(uniq: string, all = false) { - yield [-1, require("@metro/polyfills/redesign")]; - - let cache = getMetroCache().findIndex[uniq]; - if (all && !cache?.[`_${ModulesMapInternal.FULL_LOOKUP}`]) cache = undefined; - if (cache?.[`_${ModulesMapInternal.NOT_FOUND}`]) return; - - for (const id in cache) { - if (id[0] === "_") continue; - const exports = requireModule(Number(id)); - if (isBadExports(exports)) continue; - yield [id, exports]; - } - - for (const id in metroModules) { - const exports = requireModule(Number(id)); - if (isBadExports(exports)) continue; - yield [id, exports]; - } -} - -export function* getCachedPolyfillModules(name: string) { - const cache = getMetroCache().polyfillIndex[name]!; - - for (const id in cache) { - const exports = requireModule(Number(id)); - if (isBadExports(exports)) continue; - yield [id, exports]; - } - - if (!cache[`_${ModulesMapInternal.FULL_LOOKUP}`]) { - for (const id in metroModules) { - const exports = requireModule(Number(id)); - if (isBadExports(exports)) continue; - yield [id, exports]; - } - } -}