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 * from "./webpack";
export * from "./wreq.d";

View file

@ -8,20 +8,19 @@ import { Logger } from "@utils/Logger";
import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types";
import { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer";
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 initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
const modulesProxyhandler: ProxyHandler<any> = {
const modulesProxyhandler: ProxyHandler<WebpackRequire["m"]> = {
...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);
// 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", {
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.
// 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
@ -59,14 +58,14 @@ Object.defineProperty(Function.prototype, "O", {
delete (Function.prototype as any).O;
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())) {
Object.defineProperty(this, "O", {
value: originalOnChunksLoaded,
configurable: true
});
const wreq = this as WebpackInstance;
const wreq = this;
const originalCallback = callback;
callback = function (this: unknown) {
@ -85,7 +84,7 @@ Object.defineProperty(Function.prototype, "O", {
}
originalOnChunksLoaded.apply(this, arguments as any);
};
} as WebpackRequire["O"];
onChunksLoaded.toString = originalOnChunksLoaded.toString.bind(originalOnChunksLoaded);
}
@ -131,7 +130,7 @@ Object.defineProperty(Function.prototype, "m", {
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) {
try {
factoryListener(mod);
@ -255,7 +254,7 @@ function patchFactory(id: string, mod: (module: any, exports: any, require: Webp
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 (!webpackNotInitializedLogged) {
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);
}
}
}
};
patchedFactory.toString = originalMod.toString.bind(originalMod);
// @ts-ignore

View file

@ -20,9 +20,9 @@ import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
import type { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer";
import { ModuleExports, ModuleFactory, WebpackRequire } from "./wreq";
const logger = new Logger("Webpack");
@ -33,8 +33,8 @@ export let _resolveReady: () => void;
*/
export const onceReady = new Promise<void>(r => _resolveReady = r);
export let wreq: WebpackInstance;
export let cache: WebpackInstance["c"];
export let wreq: WebpackRequire;
export let cache: WebpackRequire["c"];
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 moduleListeners = new Set<CallbackFn>();
export const factoryListeners = new Set<(factory: (module: any, exports: any, require: WebpackInstance) => void) => void>();
export const beforeInitListeners = new Set<(wreq: WebpackInstance) => void>();
export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
export const beforeInitListeners = new Set<(wreq: WebpackRequire) => void>();
export function _initWebpack(webpackRequire: WebpackInstance) {
export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
cache = webpackRequire.c;
}
@ -500,6 +500,7 @@ export function search(...filters: Array<string | RegExp>) {
const factories = wreq.m;
outer:
for (const id in factories) {
// @ts-ignore
const factory = factories[id].original ?? factories[id];
const str: string = factory.toString();
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
*/
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;
loaded: boolean;
exports: ModuleExports;
};
/** 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)
) => Promise<void>;
type ChunkHandlers = {
export type ChunkHandlers = {
/**
* Ensures the js file for this chunk is loaded, or starts to load if it's not
* @param chunkId The chunk id
* @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
* @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.
*/
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 */
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) */
m: Record<PropertyKey, ModuleFactory>;
/** 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:
* @example
* const fromObject = { a: 1 };
* Object.defineProperty(to, "a", {
* get: () => fromObject.a
* Object.defineProperty(fromObject, "a", {
* get: () => fromObject["a"]
* });
* @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.
* "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]));".
* 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 */
n: (module: Module) => () => ModuleExports;
n: (this: WebpackRequire, module: Module) => () => ModuleExports;
/**
* 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
*/
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:
* @example
@ -91,22 +91,22 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* 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 */
f: ChunkHandlers;
/**
* 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.
*/
e: (chunkId: string | number) => Promise<void[]>;
e: (this: WebpackRequire, chunkId: PropertyKey) => Promise<void[]>;
/** 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 */
u: (chunkId: string | number) => string;
u: (this: WebpackRequire, chunkId: PropertyKey) => string;
/** The global object, will likely always be the window */
g: Window;
/** 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 */
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}`,
* 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 */
r: (exports: AnyRecord) => void;
r: (this: WebpackRequire, exports: AnyRecord) => void;
/** 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.
*
@ -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"
* @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 */
p: string;
/** Document baseURI or WebWorker location.href */