diff --git a/src/lib/api/patcher.ts b/src/lib/api/patcher.ts index 8caf4b9..8d13884 100644 --- a/src/lib/api/patcher.ts +++ b/src/lib/api/patcher.ts @@ -1,4 +1,4 @@ -import { getFindProxyInfo } from "@metro/proxy"; +import { getFindContext } from "@metro/proxy"; import { after as _after, before as _before, @@ -10,7 +10,7 @@ type ProxyPatchParameters = [func: string, parent: [P, (t: P) => any], ... function shim) => any>(fn: T) { function shimmed(this: any, ...args: Parameters) { - const proxyInfo = getFindProxyInfo(args[1]); + const proxyInfo = getFindContext(args[1]); if (proxyInfo && !proxyInfo.cache && proxyInfo.indexed) { let cancel = false; let unpatch = () => cancel = true; @@ -27,7 +27,7 @@ function shim(this: any, ...args: ProxyPatchParameters) { const [target, resolve] = args[1]; - const proxyInfo = getFindProxyInfo(target); + const proxyInfo = getFindContext(target); if (!proxyInfo) { args[1] = resolve(target); return shimmed(...args as unknown as Parameters); diff --git a/src/lib/utils/lazy.ts b/src/lib/utils/lazy.ts index eaeb4fe..c24cd47 100644 --- a/src/lib/utils/lazy.ts +++ b/src/lib/utils/lazy.ts @@ -1,54 +1,68 @@ -const factorySymbol = Symbol.for("bunny.lazy.factory"); -const cacheSymbol = Symbol.for("bunny.lazy.cache"); - +/* eslint-disable func-call-spacing */ const unconfigurable = ["arguments", "caller", "prototype"]; const isUnconfigurable = (key: PropertyKey) => typeof key === "string" && unconfigurable.includes(key); -// eslint-disable-next-line func-call-spacing const proxyToFactoryMap = new WeakMap any>(); +const dummyToFactoryMap = new WeakMap any>(); const lazyHandler: ProxyHandler = { + ...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(fnName => { + return [fnName, (target: any, ...args: any[]) => { + const resolved = dummyToFactoryMap.get(target)!(); + if (!resolved) throw new Error(`Trying to ${fnName} of ${typeof resolved}`); + // @ts-expect-error + return Reflect[fnName](resolved, ...args); + }]; + })), ownKeys: target => { - const cacheKeys = Reflect.ownKeys(target[factorySymbol]()); - unconfigurable.forEach(key => isUnconfigurable(key) && cacheKeys.push(key)); + const resolved = dummyToFactoryMap.get(target)!(); + if (!resolved) throw new Error(`Trying to ownKeys of ${typeof resolved}`); + + const cacheKeys = Reflect.ownKeys(dummyToFactoryMap.get(target)!()); + unconfigurable.forEach(key => !cacheKeys.includes(key) && cacheKeys.push(key)); return cacheKeys; }, getOwnPropertyDescriptor: (target, p) => { + const resolved = dummyToFactoryMap.get(target)!(); + if (!resolved) throw new Error(`Trying to getOwnPropertyDescriptor of ${typeof resolved}`); + if (isUnconfigurable(p)) return Reflect.getOwnPropertyDescriptor(target, p); - const descriptor = Reflect.getOwnPropertyDescriptor(target[factorySymbol](), p); + const descriptor = Reflect.getOwnPropertyDescriptor(dummyToFactoryMap.get(target)!(), p); if (descriptor) Object.defineProperty(target, p, descriptor); return descriptor; - } + }, }; -// Mirror all Reflect methods -Object.getOwnPropertyNames(Reflect).forEach(fnName => { - // @ts-ignore - lazyHandler[fnName] ??= (target: any, ...args: any[]) => { - // @ts-ignore - return Reflect[fnName](target[factorySymbol](), ...args); - }; -}); - /** * Lazy proxy that will only call the factory function when needed (when a property is accessed) * @param factory Factory function to create the object - * @param fallback A fallback value to return if the factory returns undefined + * @param asFunction Mock the proxy as a function * @returns A proxy that will call the factory function only when needed * @example const ChannelStore = proxyLazy(() => findByProps("getChannelId")); */ export function proxyLazy(factory: () => T, asFunction = true): T { + let cache: T; + const dummy = asFunction ? function () { } as any : {}; - dummy[factorySymbol] = () => { - return dummy[cacheSymbol] ??= factory(); - }; + const proxyFactory = () => cache ??= factory(); const proxy = new Proxy(dummy, lazyHandler) as T; - proxyToFactoryMap.set(proxy, dummy[factorySymbol]); + proxyToFactoryMap.set(proxy, proxyFactory); + dummyToFactoryMap.set(dummy, proxyFactory); + return proxy; } +/** + * Lazily destructure an object with all the properties being lazified. This assumes all the properties are either an object or a function + * @param factory Factory function which resolves to the object (and caches it) + * @param asFunction Mock the proxy as a function + * @example + * + * const { uuid4 } = lazyDestructure(() => findByProps("uuid4")) + * uuid4; // <- is a lazy proxy! + */ export function lazyDestructure>(factory: () => T, asFunction = false): T { const proxiedObject = proxyLazy(factory, asFunction); diff --git a/src/metro/proxy.ts b/src/metro/proxy.ts index 5993eba..fb9aa86 100644 --- a/src/metro/proxy.ts +++ b/src/metro/proxy.ts @@ -5,9 +5,9 @@ import { findExports } from "./finders"; import { metroModules, subscribeModule } from "./modules"; import type { FilterFn } from "./types"; -const proxyInfoMap = new WeakMap<{}, FindProxyInfo>(); +const proxyContextMap = new WeakMap>(); -interface FindProxyInfo { +interface FindProxyContext { filter: FilterFn; indexed: boolean; moduleId: number | undefined; @@ -24,8 +24,8 @@ function getIndexedSingleFind(filter: FilterFn) { return id ? Number(id) : void 0; } -function subscribeModuleForFind(proxy: any, callback: (exports: any) => void) { - const info = getFindProxyInfo(proxy); +function subscribeModuleOfFind(proxy: any, callback: (exports: any) => void) { + const info = getFindContext(proxy); if (!info) throw new Error("Subscribing a module for non-proxy-find"); if (!info.indexed) throw new Error("Attempting to subscribe to a non-indexed find"); @@ -34,37 +34,39 @@ function subscribeModuleForFind(proxy: any, callback: (exports: any) => void) { }); } -export function getFindProxyInfo(proxy: any): FindProxyInfo | void { - return proxyInfoMap.get(proxy) as unknown as FindProxyInfo; +export function getFindContext(proxy: any): FindProxyContext | void { + return proxyContextMap.get(proxy) as unknown as FindProxyContext; } export function createFindProxy(filter: FilterFn) { let cache: any = undefined; const moduleId = getIndexedSingleFind(filter); - const info: FindProxyInfo = { + const context: FindProxyContext = { filter, indexed: !!moduleId, moduleId, getExports(cb: (exports: any) => void) { if (!moduleId || metroModules[moduleId]?.isInitialized) { - return cb(findExports(filter)), () => { }; + return cb(this.unproxy()), () => { }; } return this.subscribe(cb); }, subscribe(cb: (exports: any) => void) { - return subscribeModuleForFind(proxy, cb); + return subscribeModuleOfFind(proxy, cb); }, get cache() { return cache; }, unproxy() { - return cache ??= findExports(filter); + cache ??= findExports(filter); + if (!cache) throw new Error(`${filter.uniq} is ${typeof cache}! (id ${context.moduleId ?? "unknown"})`); + return cache; } }; - const proxy = proxyLazy(() => cache ??= findExports(filter)); - proxyInfoMap.set(proxy, info as FindProxyInfo); + const proxy = proxyLazy(() => context.unproxy()); + proxyContextMap.set(proxy, context as FindProxyContext); return proxy; } diff --git a/src/metro/types.ts b/src/metro/types.ts index f0cc36a..d5e5817 100644 --- a/src/metro/types.ts +++ b/src/metro/types.ts @@ -138,7 +138,6 @@ export type FilterCheckDef = ( export interface FilterFn { (m: any, id: number, defaultCheck: boolean): boolean; filter: FilterCheckDef; - args: A; raw: boolean; uniq: string; } diff --git a/src/metro/utils.ts b/src/metro/utils.ts index d760546..318eea5 100644 --- a/src/metro/utils.ts +++ b/src/metro/utils.ts @@ -12,7 +12,6 @@ export function createFilterDefinition( return Object.assign(func, { filter: fn, raw, - args, uniq: [ raw && "raw::", uniqMaker(args)