From affd527bc13503aa906286c0d23f99bc64fe5550 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 26 May 2024 05:19:52 -0300 Subject: [PATCH] Make factory wrapper a little more future proof --- src/utils/misc.tsx | 5 +++++ src/webpack/patchWebpack.ts | 45 ++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index 5bf2c2398..089bd541c 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -102,3 +102,8 @@ export function pluralise(amount: number, singular: string, plural = singular + /** Unconfigurable properties for proxies */ export const UNCONFIGURABLE_PROPERTIES = ["arguments", "caller", "prototype"]; + +export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) { + if (args.some(arg => arg == null)) return ""; + return strings.reduce((acc, str, i) => `${acc}${str}${args[i] ?? ""}`, ""); +} diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index a35da9177..6aeee236f 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -6,6 +6,7 @@ import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; +import { interpolateIfDefined } from "@utils/misc"; import { canonicalizeReplacement } from "@utils/patches"; import { PatchReplacement } from "@utils/types"; @@ -105,8 +106,8 @@ Reflect.defineProperty(Function.prototype, "m", { // This ensures we actually got the right ones const { stack } = new Error(); if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) { - const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? ""; - logger.info("Found Webpack module factories in", fileName); + const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1]; + logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`); // setImmediate to clear this property setter if this is not the main Webpack // If this is the main Webpack, wreq.m will always be set before the timeout runs @@ -117,7 +118,7 @@ Reflect.defineProperty(Function.prototype, "m", { set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) { if (bundlePath !== "/assets/") return; - logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`); + logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire"); _initWebpack(this); clearTimeout(setterTimeout); @@ -160,8 +161,6 @@ Reflect.defineProperty(Function.prototype, "m", { } }); -let webpackNotInitializedLogged = false; - function patchFactory(id: PropertyKey, factory: ModuleFactory) { const originalFactory = factory; @@ -287,7 +286,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) { if (!patch.all) patches.splice(i--, 1); } - const patchedFactory: PatchedModuleFactory = function (module, exports, require) { + const patchedFactory: PatchedModuleFactory = function (...args: Parameters) { for (const moduleFactories of allModuleFactories) { Reflect.defineProperty(moduleFactories, id, { value: patchedFactory.$$vencordOriginal, @@ -297,38 +296,44 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) { }); } - if (wreq == null && IS_DEV) { - if (!webpackNotInitializedLogged) { - webpackNotInitializedLogged = true; - logger.error("WebpackRequire was not initialized, running modules without patches instead."); - } + // eslint-disable-next-line prefer-const + let [module, exports, require] = args; - return originalFactory.call(this, module, exports, require); + // Make sure the require argument is actually the WebpackRequire functioin + if (wreq == null && String(require).includes("exports:{}")) { + const { stack } = new Error(); + const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1]; + logger.warn( + "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" + + `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` + + ")" + ); + _initWebpack(require); } let factoryReturn: unknown; try { - factoryReturn = factory.call(this, module, exports, require); + factoryReturn = factory.apply(this, args); } catch (err) { - // Just rethrow Discord errors + // Just re-throw Discord errors if (factory === originalFactory) throw err; - logger.error("Error in patched module", err); - return originalFactory.call(this, module, exports, require); + logger.error("Error in patched module factory", err); + return originalFactory.apply(this, args); } // Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it - exports = module.exports; + exports = module?.exports; if (exports == null) return factoryReturn; // There are (at the time of writing) 11 modules exporting the window // Make these non enumerable to improve webpack search performance - if (exports === window && require.c) { + if (exports === window && require?.c) { Reflect.defineProperty(require.c, id, { value: require.c[id], configurable: true, - enumerable: false, - writable: true + writable: true, + enumerable: false }); return factoryReturn; }