- Retrieve turbo modules from `global.__turboModuleProxy` - Use `getConstants()` to retrieve constants of native modules - Defer some calls from the native bridge/JSI that could break the app when bundle index is not initialized properly Tested on Android 291.0 (291200), not tested on older versions (without New Architecture)
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
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<typeof buildInitCache>;
|
|
|
|
export const getMetroCache = () => _metroCache;
|
|
|
|
function buildInitCache() {
|
|
const cache = {
|
|
_v: CACHE_VERSION,
|
|
_buildNumber: NativeClientInfoModule.getConstants().Build,
|
|
_modulesCount: Object.keys(window.modules).length,
|
|
flagsIndex: {} as Record<string, number>,
|
|
findIndex: {} as Record<string, ModulesMap | undefined>,
|
|
polyfillIndex: {} as Record<string, ModulesMap | undefined>
|
|
} 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();
|
|
}
|
|
};
|
|
}
|