This commit is contained in:
Nuckyz 2024-05-23 03:10:59 -03:00
parent bc367b1d2a
commit 40a1f48267
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
4 changed files with 47 additions and 46 deletions

View file

@ -18,3 +18,4 @@
export * as Common from "./common"; export * as Common from "./common";
export * from "./webpack"; export * from "./webpack";
export * from "./wreq.d";

View file

@ -8,20 +8,19 @@ import { Logger } from "@utils/Logger";
import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc"; import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types"; import { PatchReplacement } from "@utils/types";
import { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins"; import { patches } from "../plugins";
import { _initWebpack, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from "."; import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, moduleListeners, subscriptions, WebpackRequire, wreq } from ".";
const logger = new Logger("WebpackInterceptor", "#8caaee"); const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/); const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
const modulesProxyhandler: ProxyHandler<any> = { const modulesProxyhandler: ProxyHandler<WebpackRequire["m"]> = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName => ...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
[propName, (target: any, ...args: any[]) => Reflect[propName](target, ...args)] [propName, (target: WebpackRequire["m"], ...args: any[]) => Reflect[propName](target, ...args)]
)), )),
get: (target, p: string) => { get: (target, p) => {
const mod = Reflect.get(target, p); const mod = Reflect.get(target, p);
// If the property is not a module id, return the value of it without trying to patch // If the property is not a module id, return the value of it without trying to patch
@ -48,7 +47,7 @@ const modulesProxyhandler: ProxyHandler<any> = {
Object.defineProperty(Function.prototype, "O", { Object.defineProperty(Function.prototype, "O", {
configurable: true, configurable: true,
set(onChunksLoaded: any) { set(onChunksLoaded: WebpackRequire["O"]) {
// When using react devtools or other extensions, or even when discord loads the sentry, we may also catch their webpack here. // When using react devtools or other extensions, or even when discord loads the sentry, we may also catch their webpack here.
// This ensures we actually got the right one // This ensures we actually got the right one
// this.e (wreq.e) is the method for loading a chunk, and only the main webpack has it // this.e (wreq.e) is the method for loading a chunk, and only the main webpack has it
@ -59,14 +58,14 @@ Object.defineProperty(Function.prototype, "O", {
delete (Function.prototype as any).O; delete (Function.prototype as any).O;
const originalOnChunksLoaded = onChunksLoaded; const originalOnChunksLoaded = onChunksLoaded;
onChunksLoaded = function (this: unknown, result: any, chunkIds: string[], callback: () => any, priority: number) { onChunksLoaded = function (result, chunkIds, callback, priority) {
if (callback != null && initCallbackRegex.test(callback.toString())) { if (callback != null && initCallbackRegex.test(callback.toString())) {
Object.defineProperty(this, "O", { Object.defineProperty(this, "O", {
value: originalOnChunksLoaded, value: originalOnChunksLoaded,
configurable: true configurable: true
}); });
const wreq = this as WebpackInstance; const wreq = this;
const originalCallback = callback; const originalCallback = callback;
callback = function (this: unknown) { callback = function (this: unknown) {
@ -85,7 +84,7 @@ Object.defineProperty(Function.prototype, "O", {
} }
originalOnChunksLoaded.apply(this, arguments as any); originalOnChunksLoaded.apply(this, arguments as any);
}; } as WebpackRequire["O"];
onChunksLoaded.toString = originalOnChunksLoaded.toString.bind(originalOnChunksLoaded); onChunksLoaded.toString = originalOnChunksLoaded.toString.bind(originalOnChunksLoaded);
} }
@ -131,7 +130,7 @@ Object.defineProperty(Function.prototype, "m", {
let webpackNotInitializedLogged = false; let webpackNotInitializedLogged = false;
function patchFactory(id: string, mod: (module: any, exports: any, require: WebpackInstance) => void) { function patchFactory(id: string | number, mod: ModuleFactory) {
for (const factoryListener of factoryListeners) { for (const factoryListener of factoryListeners) {
try { try {
factoryListener(mod); factoryListener(mod);
@ -255,7 +254,7 @@ function patchFactory(id: string, mod: (module: any, exports: any, require: Webp
if (!patch.all) patches.splice(i--, 1); if (!patch.all) patches.splice(i--, 1);
} }
function patchedFactory(module: any, exports: any, require: WebpackInstance) { const patchedFactory: ModuleFactory = (module, exports, require) => {
if (wreq == null && IS_DEV) { if (wreq == null && IS_DEV) {
if (!webpackNotInitializedLogged) { if (!webpackNotInitializedLogged) {
webpackNotInitializedLogged = true; webpackNotInitializedLogged = true;
@ -312,7 +311,7 @@ function patchFactory(id: string, mod: (module: any, exports: any, require: Webp
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback); logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
} }
} }
} };
patchedFactory.toString = originalMod.toString.bind(originalMod); patchedFactory.toString = originalMod.toString.bind(originalMod);
// @ts-ignore // @ts-ignore

View file

@ -20,9 +20,9 @@ import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact"; import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import type { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
import { ModuleExports, ModuleFactory, WebpackRequire } from "./wreq";
const logger = new Logger("Webpack"); const logger = new Logger("Webpack");
@ -33,8 +33,8 @@ export let _resolveReady: () => void;
*/ */
export const onceReady = new Promise<void>(r => _resolveReady = r); export const onceReady = new Promise<void>(r => _resolveReady = r);
export let wreq: WebpackInstance; export let wreq: WebpackRequire;
export let cache: WebpackInstance["c"]; export let cache: WebpackRequire["c"];
export type FilterFn = (mod: any) => boolean; export type FilterFn = (mod: any) => boolean;
@ -68,14 +68,14 @@ export const filters = {
} }
}; };
export type CallbackFn = (mod: any, id: string) => void; export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
export const subscriptions = new Map<FilterFn, CallbackFn>(); export const subscriptions = new Map<FilterFn, CallbackFn>();
export const moduleListeners = new Set<CallbackFn>(); export const moduleListeners = new Set<CallbackFn>();
export const factoryListeners = new Set<(factory: (module: any, exports: any, require: WebpackInstance) => void) => void>(); export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
export const beforeInitListeners = new Set<(wreq: WebpackInstance) => void>(); export const beforeInitListeners = new Set<(wreq: WebpackRequire) => void>();
export function _initWebpack(webpackRequire: WebpackInstance) { export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire; wreq = webpackRequire;
cache = webpackRequire.c; cache = webpackRequire.c;
} }
@ -500,6 +500,7 @@ export function search(...filters: Array<string | RegExp>) {
const factories = wreq.m; const factories = wreq.m;
outer: outer:
for (const id in factories) { for (const id in factories) {
// @ts-ignore
const factory = factories[id].original ?? factories[id]; const factory = factories[id].original ?? factories[id];
const str: string = factory.toString(); const str: string = factory.toString();
for (const filter of filters) { for (const filter of filters) {

54
src/webpack/wreq.d.ts vendored
View file

@ -4,46 +4,46 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
type AnyRecord = Record<PropertyKey, any>; export type AnyRecord = Record<PropertyKey, any>;
type ModuleExports = any; export type ModuleExports = any;
type Module = { export type Module = {
id: PropertyKey; id: PropertyKey;
loaded: boolean; loaded: boolean;
exports: ModuleExports; exports: ModuleExports;
}; };
/** exports ({@link ModuleExports}) can be anything, however initially it is always an empty object */ /** exports ({@link ModuleExports}) can be anything, however initially it is always an empty object */
type ModuleFactory = (module: Module, exports: AnyRecord, require: WebpackRequire) => void; export type ModuleFactory = (module: Module, exports: AnyRecord, require: WebpackRequire) => void;
type AsyncModuleBody = ( export type AsyncModuleBody = (
handleDependencies: (deps: Promise<any>[]) => Promise<any[]> & (() => void) handleDependencies: (deps: Promise<any>[]) => Promise<any[]> & (() => void)
) => Promise<void>; ) => Promise<void>;
type ChunkHandlers = { export type ChunkHandlers = {
/** /**
* Ensures the js file for this chunk is loaded, or starts to load if it's not * Ensures the js file for this chunk is loaded, or starts to load if it's not
* @param chunkId The chunk id * @param chunkId The chunk id
* @param promises The promises array to add the loading promise to. * @param promises The promises array to add the loading promise to.
*/ */
j: (chunkId: string | number, promises: Promise<void[]>) => void, j: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void,
/** /**
* Ensures the css file for this chunk is loaded, or starts to load if it's not * Ensures the css file for this chunk is loaded, or starts to load if it's not
* @param chunkId The chunk id * @param chunkId The chunk id
* @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too. * @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too.
*/ */
css: (chunkId: string | number, promises: Promise<void[]>) => void, css: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void,
}; };
type ScriptLoadDone = (event: Event) => void; export type ScriptLoadDone = (event: Event) => void;
type OnChunksLoaded = ((result: any, chunkIds: (string | number)[] | undefined, callback: () => any, priority: number) => any) & { export type OnChunksLoaded = ((this: WebpackRequire, result: any, chunkIds: PropertyKey[] | undefined | null, callback: () => any, priority: number) => any) & {
/** Check if a chunk has been loaded */ /** Check if a chunk has been loaded */
j: (chunkId: string | number) => boolean; j: (chunkId: PropertyKey) => boolean;
}; };
type WebpackRequire = ((moduleId: PropertyKey) => Module) & { export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
m: Record<PropertyKey, ModuleFactory>; m: Record<PropertyKey, ModuleFactory>;
/** The module cache, where all modules which have been WebpackRequire'd are stored */ /** The module cache, where all modules which have been WebpackRequire'd are stored */
@ -52,12 +52,12 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this: * Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this:
* @example * @example
* const fromObject = { a: 1 }; * const fromObject = { a: 1 };
* Object.defineProperty(to, "a", { * Object.defineProperty(fromObject, "a", {
* get: () => fromObject.a * get: () => fromObject["a"]
* }); * });
* @returns fromObject * @returns fromObject
*/ */
es: (fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord; es: (this: WebpackRequire, fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord;
/** /**
* Creates an async module. The body function must be a async function. * Creates an async module. The body function must be a async function.
* "module.exports" will be decorated with an AsyncModulePromise. * "module.exports" will be decorated with an AsyncModulePromise.
@ -65,9 +65,9 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* To handle async dependencies correctly do this inside the body: "([a, b, c] = await handleDependencies([a, b, c]));". * To handle async dependencies correctly do this inside the body: "([a, b, c] = await handleDependencies([a, b, c]));".
* If "hasAwaitAfterDependencies" is truthy, "handleDependencies()" must be called at the end of the body function. * If "hasAwaitAfterDependencies" is truthy, "handleDependencies()" must be called at the end of the body function.
*/ */
a: (module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void; a: (this: WebpackRequire, module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void;
/** getDefaultExport function for compatibility with non-harmony modules */ /** getDefaultExport function for compatibility with non-harmony modules */
n: (module: Module) => () => ModuleExports; n: (this: WebpackRequire, module: Module) => () => ModuleExports;
/** /**
* Create a fake namespace object, useful for faking an __esModule with a default export. * Create a fake namespace object, useful for faking an __esModule with a default export.
* *
@ -81,7 +81,7 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* *
* mode & (8|1): Behave like require * mode & (8|1): Behave like require
*/ */
t: (value: any, mode: number) => any; t: (this: WebpackRequire, value: any, mode: number) => any;
/** /**
* Define property getters. For every prop in "definiton", set a getter in "exports" for the value in "definitiion", like this: * Define property getters. For every prop in "definiton", set a getter in "exports" for the value in "definitiion", like this:
* @example * @example
@ -91,22 +91,22 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Object.defineProperty(exports, key, { get: definition[key] } * Object.defineProperty(exports, key, { get: definition[key] }
* } * }
*/ */
d: (exports: AnyRecord, definiton: AnyRecord) => void; d: (this: WebpackRequire, exports: AnyRecord, definiton: AnyRecord) => void;
/** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */ /** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
f: ChunkHandlers; f: ChunkHandlers;
/** /**
* The ensure chunk function, it ensures a chunk is loaded, or loads if needed. * The ensure chunk function, it ensures a chunk is loaded, or loads if needed.
* Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded. * Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded.
*/ */
e: (chunkId: string | number) => Promise<void[]>; e: (this: WebpackRequire, chunkId: PropertyKey) => Promise<void[]>;
/** Get the filename name for the css part of a chunk */ /** Get the filename name for the css part of a chunk */
k: (chunkId: string | number) => `${chunkId}.css`; k: (this: WebpackRequire, chunkId: PropertyKey) => `${chunkId}.css`;
/** Get the filename for the js part of a chunk */ /** Get the filename for the js part of a chunk */
u: (chunkId: string | number) => string; u: (this: WebpackRequire, chunkId: PropertyKey) => string;
/** The global object, will likely always be the window */ /** The global object, will likely always be the window */
g: Window; g: Window;
/** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */ /** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */
hmd: (module: Module) => any; hmd: (this: WebpackRequire, module: Module) => any;
/** Shorthand for Object.prototype.hasOwnProperty */ /** Shorthand for Object.prototype.hasOwnProperty */
o: typeof Object.prototype.hasOwnProperty; o: typeof Object.prototype.hasOwnProperty;
/** /**
@ -114,11 +114,11 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* "done" will be attached to existing scripts loading if src === url or data-webpack === `${uniqueName}:${key}`, * "done" will be attached to existing scripts loading if src === url or data-webpack === `${uniqueName}:${key}`,
* so it will be called when that existing script finishes loading. * so it will be called when that existing script finishes loading.
*/ */
l: (url: string, done: ScriptLoadDone, key?: string | number, chunkId?: string | number) => void; l: (this: WebpackRequire, url: string, done: ScriptLoadDone, key?: string | number, chunkId?: PropertyKey) => void;
/** Defines __esModule on the exports, marking ES Modules compatibility as true */ /** Defines __esModule on the exports, marking ES Modules compatibility as true */
r: (exports: AnyRecord) => void; r: (this: WebpackRequire, exports: AnyRecord) => void;
/** Node.js module decorator. Decorates a module as a Node.js module */ /** Node.js module decorator. Decorates a module as a Node.js module */
nmd: (module: Module) => any; nmd: (this: WebpackRequire, module: Module) => any;
/** /**
* Register deferred code which will be executed when the passed chunks are loaded. * Register deferred code which will be executed when the passed chunks are loaded.
* *
@ -135,7 +135,7 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports" * Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports"
* @returns The exports argument, but now assigned with the exports of the wasm instance * @returns The exports argument, but now assigned with the exports of the wasm instance
*/ */
v: (exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise<any>; v: (this: WebpackRequire, exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise<any>;
/** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */ /** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
p: string; p: string;
/** Document baseURI or WebWorker location.href */ /** Document baseURI or WebWorker location.href */