2022-10-22 01:17:06 +02:00
/ *
2025-02-08 00:07:17 +01:00
* Vencord , a Discord client mod
* Copyright ( c ) 2024 Vendicated , Nuckyz , and contributors
* SPDX - License - Identifier : GPL - 3.0 - or - later
* /
2022-10-22 01:17:06 +02:00
2025-02-08 00:07:17 +01:00
import { Settings } from "@api/Settings" ;
import { makeLazy } from "@utils/lazy" ;
2023-05-06 01:36:00 +02:00
import { Logger } from "@utils/Logger" ;
2025-02-08 00:07:17 +01:00
import { interpolateIfDefined } from "@utils/misc" ;
2024-06-28 04:37:10 +02:00
import { canonicalizeReplacement } from "@utils/patches" ;
2022-12-19 23:59:54 +01:00
import { PatchReplacement } from "@utils/types" ;
2022-11-28 13:37:55 +01:00
2025-02-08 00:07:17 +01:00
import { traceFunctionWithResults } from "../debug/Tracer" ;
2024-05-02 23:52:41 +02:00
import { patches } from "../plugins" ;
2025-02-08 00:07:17 +01:00
import { _initWebpack , _shouldIgnoreModule , AnyModuleFactory , AnyWebpackRequire , factoryListeners , findModuleId , MaybeWrappedModuleFactory , ModuleExports , moduleListeners , waitForSubscriptions , WebpackRequire , WrappedModuleFactory , wreq } from "." ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
export const SYM_ORIGINAL_FACTORY = Symbol ( "WebpackPatcher.originalFactory" ) ;
export const SYM_PATCHED_SOURCE = Symbol ( "WebpackPatcher.patchedSource" ) ;
export const SYM_PATCHED_BY = Symbol ( "WebpackPatcher.patchedBy" ) ;
/** A set with all the Webpack instances */
export const allWebpackInstances = new Set < AnyWebpackRequire > ( ) ;
export const patchTimings = [ ] as Array < [ plugin : string , moduleId : PropertyKey , match : string | RegExp , totalTime : number ] > ;
2022-08-29 02:25:27 +02:00
2025-02-08 00:07:17 +01:00
const logger = new Logger ( "WebpackInterceptor" , "#8caaee" ) ;
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
let wreqFallbackApplied = false ;
/ * * W h e t h e r w e s h o u l d b e p a t c h i n g f a c t o r i e s .
*
* This should be disabled if we start searching for the module to get the build number , and then resumed once it ' s done .
* * /
let shouldPatchFactories = true ;
2022-08-29 02:25:27 +02:00
2025-02-08 00:07:17 +01:00
export const getBuildNumber = makeLazy ( ( ) = > {
try {
shouldPatchFactories = false ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
try {
if ( wreq . m [ 128014 ] ? . toString ( ) . includes ( "Trying to open a changelog for an invalid build number" ) ) {
const hardcodedGetBuildNumber = wreq ( 128014 ) . b as ( ) = > number ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
if ( typeof hardcodedGetBuildNumber === "function" && typeof hardcodedGetBuildNumber ( ) === "number" ) {
return hardcodedGetBuildNumber ( ) ;
}
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
} catch { }
const moduleId = findModuleId ( "Trying to open a changelog for an invalid build number" ) ;
if ( moduleId == null ) {
return - 1 ;
2024-05-02 23:52:41 +02:00
}
2022-08-29 20:27:47 +02:00
2025-02-08 00:07:17 +01:00
const exports = Object . values < ModuleExports > ( wreq ( moduleId ) ) ;
if ( exports . length !== 1 || typeof exports [ 0 ] !== "function" ) {
return - 1 ;
}
const buildNumber = exports [ 0 ] ( ) ;
return typeof buildNumber === "number" ? buildNumber : - 1 ;
} catch {
return - 1 ;
} finally {
shouldPatchFactories = true ;
2024-05-02 23:52:41 +02:00
}
} ) ;
2025-02-08 00:07:17 +01:00
type Define = typeof Reflect . defineProperty ;
const define : Define = ( target , p , attributes ) = > {
if ( Object . hasOwn ( attributes , "value" ) ) {
attributes . writable = true ;
}
2024-06-28 23:17:38 +02:00
2025-02-08 00:07:17 +01:00
return Reflect . defineProperty ( target , p , {
configurable : true ,
enumerable : true ,
. . . attributes
} ) ;
} ;
export function getOriginalFactory ( id : PropertyKey , webpackRequire = wreq as AnyWebpackRequire ) {
const moduleFactory = webpackRequire . m [ id ] ;
return ( moduleFactory ? . [ SYM_ORIGINAL_FACTORY ] ? ? moduleFactory ) as AnyModuleFactory | undefined ;
}
export function getFactoryPatchedSource ( id : PropertyKey , webpackRequire = wreq as AnyWebpackRequire ) {
return webpackRequire . m [ id ] ? . [ SYM_PATCHED_SOURCE ] ;
}
export function getFactoryPatchedBy ( id : PropertyKey , webpackRequire = wreq as AnyWebpackRequire ) {
return webpackRequire . m [ id ] ? . [ SYM_PATCHED_BY ] ;
}
// wreq.m is the Webpack object containing module factories. It is pre-populated with module factories, and is also populated via webpackGlobal.push
// We use this setter to intercept when wreq.m is defined and apply the patching in its module factories.
// We wrap wreq.m with our proxy, which is responsible for patching the module factories when they are set, or defining getters for the patched versions.
// If this is the main Webpack, we also set up the internal references to WebpackRequire.
define ( Function . prototype , "m" , {
enumerable : false ,
set ( this : AnyWebpackRequire , originalModules : AnyWebpackRequire [ "m" ] ) {
define ( this , "m" , { value : originalModules } ) ;
// Ensure this is one of Discord main Webpack instances.
// We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here.
2024-05-05 18:58:23 +02:00
const { stack } = new Error ( ) ;
2025-02-08 00:07:17 +01:00
if ( ! stack ? . includes ( "http" ) || stack . match ( /at \d+? \(/ ) || ! String ( this ) . includes ( "exports:{}" ) ) {
2024-06-28 23:17:38 +02:00
return ;
}
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
const fileName = stack . match ( /\/assets\/(.+?\.js)/ ) ? . [ 1 ] ;
logger . info ( "Found Webpack module factories" + interpolateIfDefined ` in ${ fileName } ` ) ;
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
allWebpackInstances . add ( this ) ;
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
// Define a setter for the ensureChunk property of WebpackRequire. Only the main Webpack (which is the only that includes chunk loading) has this property.
2024-06-28 23:17:38 +02:00
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
2025-02-08 00:07:17 +01:00
define ( this , "e" , {
enumerable : false ,
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
set ( this : WebpackRequire , ensureChunk : WebpackRequire [ "e" ] ) {
define ( this , "e" , { value : ensureChunk } ) ;
2024-06-28 23:17:38 +02:00
clearTimeout ( setterTimeout ) ;
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
logger . info ( "Main WebpackInstance found" + interpolateIfDefined ` in ${ fileName } ` + ", initializing internal references to WebpackRequire" ) ;
2024-06-28 23:17:38 +02:00
_initWebpack ( this ) ;
}
2024-05-02 23:52:41 +02:00
} ) ;
2024-06-28 23:17:38 +02:00
// setImmediate to clear this property setter if this is not the main Webpack.
2025-02-08 00:07:17 +01:00
// If this is the main Webpack, wreq.e will always be set before the timeout runs.
const setterTimeout = setTimeout ( ( ) = > Reflect . deleteProperty ( this , "e" ) , 0 ) ;
// Patch the pre-populated factories
for ( const id in originalModules ) {
if ( updateExistingFactory ( originalModules , id , originalModules [ id ] , true ) ) {
continue ;
}
notifyFactoryListeners ( originalModules [ id ] ) ;
defineModulesFactoryGetter ( id , Settings . eagerPatches && shouldPatchFactories ? wrapAndPatchFactory ( id , originalModules [ id ] ) : originalModules [ id ] ) ;
}
define ( originalModules , Symbol . toStringTag , {
value : "ModuleFactories" ,
enumerable : false
} ) ;
// The proxy responsible for patching the module factories when they are set, or defining getters for the patched versions
const proxiedModuleFactories = new Proxy ( originalModules , moduleFactoriesHandler ) ;
/ *
If Webpack ever decides to set module factories using the variable of the modules object directly , instead of wreq . m , switch the proxy to the prototype
Reflect . setPrototypeOf ( originalModules , new Proxy ( originalModules , moduleFactoriesHandler ) ) ;
* /
define ( this , "m" , { value : proxiedModuleFactories } ) ;
2024-05-02 23:52:41 +02:00
}
} ) ;
2022-08-29 02:25:27 +02:00
2025-02-08 00:07:17 +01:00
const moduleFactoriesHandler : ProxyHandler < AnyWebpackRequire [ " m " ] > = {
/ *
If Webpack ever decides to set module factories using the variable of the modules object directly instead of wreq . m , we need to switch the proxy to the prototype
and that requires defining additional traps for keeping the object working
// Proxies on the prototype don't intercept "get" when the property is in the object itself. But in case it isn't we need to return undefined,
// to avoid Reflect.get having no effect and causing a stack overflow
get ( target , p , receiver ) {
return undefined ;
} ,
// Same thing as get
has ( target , p ) {
return false ;
} ,
* /
// The set trap for patching or defining getters for the module factories when new module factories are loaded
set ( target , p , newValue , receiver ) {
if ( updateExistingFactory ( target , p , newValue ) ) {
return true ;
}
notifyFactoryListeners ( newValue ) ;
defineModulesFactoryGetter ( p , Settings . eagerPatches && shouldPatchFactories ? wrapAndPatchFactory ( p , newValue ) : newValue ) ;
return true ;
}
} ;
/ * *
* Update a factory that exists in any Webpack instance with a new original factory .
*
* @target The module factories where this new original factory is being set
* @param id The id of the module
* @param newFactory The new original factory
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
* @returns Whether the original factory was updated , or false if it doesn ' t exist in any Webpack instance
* /
function updateExistingFactory ( moduleFactoriesTarget : AnyWebpackRequire [ "m" ] , id : PropertyKey , newFactory : AnyModuleFactory , ignoreExistingInTarget : boolean = false ) {
let existingFactory : TypedPropertyDescriptor < AnyModuleFactory > | undefined ;
let moduleFactoriesWithFactory : AnyWebpackRequire [ "m" ] | undefined ;
for ( const wreq of allWebpackInstances ) {
if ( ignoreExistingInTarget && wreq . m === moduleFactoriesTarget ) continue ;
if ( Object . hasOwn ( wreq . m , id ) ) {
existingFactory = Reflect . getOwnPropertyDescriptor ( wreq . m , id ) ;
moduleFactoriesWithFactory = wreq . m ;
break ;
}
}
if ( existingFactory != null ) {
// If existingFactory exists in any Webpack instance, it's either wrapped in defineModuleFactoryGetter, or it has already been required.
// So define the descriptor of it on this current Webpack instance (if it doesn't exist already), call Reflect.set with the new original,
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
if ( moduleFactoriesWithFactory !== moduleFactoriesTarget ) {
Reflect . defineProperty ( moduleFactoriesTarget , id , existingFactory ) ;
}
// Persist patched source and patched by in the new original factory, if the patched one has already been required
if ( IS_DEV && existingFactory . value != null ) {
newFactory [ SYM_PATCHED_SOURCE ] = existingFactory . value [ SYM_PATCHED_SOURCE ] ;
newFactory [ SYM_PATCHED_BY ] = existingFactory . value [ SYM_PATCHED_BY ] ;
}
return Reflect . set ( moduleFactoriesTarget , id , newFactory , moduleFactoriesTarget ) ;
}
return false ;
}
/ * *
* Notify all factory listeners .
*
* @param factory The original factory to notify for
* /
function notifyFactoryListeners ( factory : AnyModuleFactory ) {
for ( const factoryListener of factoryListeners ) {
2022-08-29 02:25:27 +02:00
try {
2025-02-08 00:07:17 +01:00
factoryListener ( factory ) ;
2022-08-29 02:25:27 +02:00
} catch ( err ) {
2025-02-08 00:07:17 +01:00
logger . error ( "Error in Webpack factory listener:\n" , err , factoryListener ) ;
2022-08-29 02:25:27 +02:00
}
}
2025-02-08 00:07:17 +01:00
}
2022-08-29 02:25:27 +02:00
2025-02-08 00:07:17 +01:00
/ * *
* Define the getter for returning the patched version of the module factory .
*
* If eagerPatches is enabled , the factory argument should already be the patched version , else it will be the original
* and only be patched when accessed for the first time .
*
* @param id The id of the module
* @param factory The original or patched module factory
* /
function defineModulesFactoryGetter ( id : PropertyKey , factory : MaybeWrappedModuleFactory ) {
const descriptor : PropertyDescriptor = {
get ( ) {
// SYM_ORIGINAL_FACTORY means the factory is already patched
if ( ! shouldPatchFactories || factory [ SYM_ORIGINAL_FACTORY ] != null ) {
return factory ;
}
2023-10-26 21:03:05 +02:00
2025-02-08 00:07:17 +01:00
return ( factory = wrapAndPatchFactory ( id , factory ) ) ;
} ,
set ( newFactory : MaybeWrappedModuleFactory ) {
if ( IS_DEV ) {
newFactory [ SYM_PATCHED_SOURCE ] = factory [ SYM_PATCHED_SOURCE ] ;
newFactory [ SYM_PATCHED_BY ] = factory [ SYM_PATCHED_BY ] ;
}
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
if ( factory [ SYM_ORIGINAL_FACTORY ] != null ) {
factory . toString = newFactory . toString . bind ( newFactory ) ;
factory [ SYM_ORIGINAL_FACTORY ] = newFactory ;
} else {
factory = newFactory ;
}
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
} ;
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object
// have the patched version
for ( const wreq of allWebpackInstances ) {
define ( wreq . m , id , descriptor ) ;
}
2022-08-29 02:25:27 +02:00
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
/ * *
* Wraps and patches a module factory .
*
* @param id The id of the module
* @param factory The original or patched module factory
* @returns The wrapper for the patched module factory
* /
function wrapAndPatchFactory ( id : PropertyKey , originalFactory : AnyModuleFactory ) {
const [ patchedFactory , patchedSource , patchedBy ] = patchFactory ( id , originalFactory ) ;
const wrappedFactory : WrappedModuleFactory = function ( . . . args ) {
// Restore the original factory in all the module factories objects. We want to make sure the original factory is restored properly, no matter what is the Webpack instance
for ( const wreq of allWebpackInstances ) {
define ( wreq . m , id , { value : wrappedFactory [ SYM_ORIGINAL_FACTORY ] } ) ;
}
// eslint-disable-next-line prefer-const
let [ module , exports , require ] = args ;
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
if ( wreq == null ) {
if ( ! wreqFallbackApplied ) {
wreqFallbackApplied = true ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
// Make sure the require argument is actually the WebpackRequire function
if ( typeof require === "function" && require . m != null ) {
const { stack } = new Error ( ) ;
const webpackInstanceFileName = stack ? . match ( /\/assets\/(.+?\.js)/ ) ? . [ 1 ] ;
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
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 as WebpackRequire ) ;
} else if ( IS_DEV ) {
2024-05-02 23:52:41 +02:00
logger . error ( "WebpackRequire was not initialized, running modules without patches instead." ) ;
2025-02-08 00:07:17 +01:00
return wrappedFactory [ SYM_ORIGINAL_FACTORY ] . apply ( this , args ) ;
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
} else if ( IS_DEV ) {
return wrappedFactory [ SYM_ORIGINAL_FACTORY ] . apply ( this , args ) ;
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
}
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
let factoryReturn : unknown ;
try {
// Call the patched factory
factoryReturn = patchedFactory . apply ( this , args ) ;
} catch ( err ) {
// Just re-throw Discord errors
if ( patchedFactory === wrappedFactory [ SYM_ORIGINAL_FACTORY ] ) {
throw err ;
2023-10-26 21:21:21 +02:00
}
2025-02-08 00:07:17 +01:00
logger . error ( "Error in patched module factory:\n" , err ) ;
return wrappedFactory [ SYM_ORIGINAL_FACTORY ] . apply ( this , args ) ;
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
exports = module . exports ;
if ( exports == null ) {
return factoryReturn ;
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
if ( typeof require === "function" ) {
const shouldIgnoreModule = _shouldIgnoreModule ( exports ) ;
2025-01-29 05:04:36 +01:00
2025-02-08 00:07:17 +01:00
if ( shouldIgnoreModule ) {
if ( require . c != null ) {
2024-06-19 03:04:15 +02:00
Object . defineProperty ( require . c , id , {
value : require.c [ id ] ,
enumerable : false ,
configurable : true ,
writable : true
} ) ;
}
2025-02-08 00:07:17 +01:00
return factoryReturn ;
2023-10-26 21:21:21 +02:00
}
2025-02-08 00:07:17 +01:00
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
for ( const callback of moduleListeners ) {
try {
callback ( exports , id ) ;
} catch ( err ) {
logger . error ( "Error in Webpack module listener:\n" , err , callback ) ;
}
}
for ( const [ filter , callback ] of waitForSubscriptions ) {
try {
if ( filter ( exports ) ) {
waitForSubscriptions . delete ( filter ) ;
2023-11-27 06:56:57 +01:00
callback ( exports , id ) ;
2025-02-08 00:07:17 +01:00
continue ;
2023-10-26 21:21:21 +02:00
}
2025-02-08 00:07:17 +01:00
if ( typeof exports !== "object" ) {
continue ;
}
2025-01-30 18:54:41 +01:00
2025-02-08 00:07:17 +01:00
for ( const exportKey in exports ) {
const exportValue = exports [ exportKey ] ;
2025-01-30 18:54:41 +01:00
2025-02-08 00:07:17 +01:00
if ( exportValue != null && filter ( exportValue ) ) {
waitForSubscriptions . delete ( filter ) ;
callback ( exportValue , id ) ;
break ;
2023-10-26 21:21:21 +02:00
}
}
2025-02-08 00:07:17 +01:00
} catch ( err ) {
logger . error ( "Error while firing callback for Webpack waitFor subscription:\n" , err , filter , callback ) ;
2023-10-26 21:21:21 +02:00
}
2025-02-08 00:07:17 +01:00
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
return factoryReturn ;
} ;
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
wrappedFactory . toString = originalFactory . toString . bind ( originalFactory ) ;
wrappedFactory [ SYM_ORIGINAL_FACTORY ] = originalFactory ;
if ( IS_DEV && patchedFactory !== originalFactory ) {
wrappedFactory [ SYM_PATCHED_SOURCE ] = patchedSource ;
wrappedFactory [ SYM_PATCHED_BY ] = patchedBy ;
originalFactory [ SYM_PATCHED_SOURCE ] = patchedSource ;
originalFactory [ SYM_PATCHED_BY ] = patchedBy ;
}
// @ts-expect-error Allow GC to get into action, if possible
originalFactory = undefined ;
return wrappedFactory ;
}
/ * *
* Patches a module factory .
*
* @param id The id of the module
* @param factory The original module factory
* @returns The patched module factory , the patched source of it , and the plugins that patched it
* /
function patchFactory ( id : PropertyKey , factory : AnyModuleFactory ) : [ patchedFactory : AnyModuleFactory , patchedSource : string , patchedBy : Set < string > ] {
// 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
let code : string = "0," + String ( factory ) ;
let patchedSource = code ;
let patchedFactory = factory ;
const patchedBy = new Set < string > ( ) ;
for ( let i = 0 ; i < patches . length ; i ++ ) {
const patch = patches [ i ] ;
const moduleMatches = typeof patch . find === "string"
? code . includes ( patch . find )
: ( patch . find . global && ( patch . find . lastIndex = 0 ) , patch . find . test ( code ) ) ;
if ( ! moduleMatches ) {
continue ;
2024-05-02 23:52:41 +02:00
}
2025-02-09 01:46:08 +01:00
// Reporter eagerly patches and cannot retrieve the build number because this code runs before the module for it is loaded
const buildNumber = IS_REPORTER ? - 1 : getBuildNumber ( ) ;
const shouldCheckBuildNumber = ! Settings . eagerPatches && buildNumber !== - 1 ;
2023-11-22 07:23:21 +01:00
2025-02-08 00:07:17 +01:00
if (
2025-02-09 01:46:08 +01:00
shouldCheckBuildNumber &&
2025-02-08 00:07:17 +01:00
( patch . fromBuild != null && buildNumber < patch . fromBuild ) ||
( patch . toBuild != null && buildNumber > patch . toBuild )
) {
continue ;
}
2023-11-22 07:23:21 +01:00
2025-02-08 00:07:17 +01:00
const executePatch = traceFunctionWithResults ( ` patch by ${ patch . plugin } ` , ( match : string | RegExp , replace : string ) = > {
if ( typeof match !== "string" && match . global ) {
match . lastIndex = 0 ;
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
return code . replace ( match , replace ) ;
} ) ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
const previousCode = code ;
const previousFactory = factory ;
let markedAsPatched = false ;
// We change all patch.replacement to array in plugins/index
for ( const replacement of patch . replacement as PatchReplacement [ ] ) {
if (
2025-02-09 01:46:08 +01:00
shouldCheckBuildNumber &&
2025-02-08 00:07:17 +01:00
( replacement . fromBuild != null && buildNumber < replacement . fromBuild ) ||
( replacement . toBuild != null && buildNumber > replacement . toBuild )
) {
continue ;
}
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
// TODO: remove once Vesktop has been updated to use addPatch
if ( patch . plugin === "Vesktop" ) {
canonicalizeReplacement ( replacement , "VCDP" ) ;
}
const lastCode = code ;
const lastFactory = factory ;
try {
const [ newCode , totalTime ] = executePatch ( replacement . match , replacement . replace as string ) ;
if ( IS_REPORTER ) {
patchTimings . push ( [ patch . plugin , id , replacement . match , totalTime ] ) ;
}
if ( newCode === code ) {
if ( ! patch . noWarn ) {
logger . warn ( ` Patch by ${ patch . plugin } had no effect (Module id is ${ String ( id ) } ): ${ replacement . match } ` ) ;
if ( IS_DEV ) {
logger . debug ( "Function Source:\n" , code ) ;
}
}
2024-05-02 23:52:41 +02:00
if ( patch . group ) {
2025-02-08 00:07:17 +01:00
logger . warn ( ` Undoing patch group ${ patch . find } by ${ patch . plugin } because replacement ${ replacement . match } had no effect ` ) ;
2024-05-02 23:52:41 +02:00
code = previousCode ;
2025-02-08 00:07:17 +01:00
patchedFactory = previousFactory ;
if ( markedAsPatched ) {
patchedBy . delete ( patch . plugin ) ;
}
2024-05-02 23:52:41 +02:00
break ;
}
2025-02-08 00:07:17 +01:00
continue ;
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
code = newCode ;
patchedSource = ` // Webpack Module ${ String ( id ) } - Patched by ${ [ . . . patchedBy , patch . plugin ] . join ( ", " ) } \ n ${ newCode } \ n//# sourceURL=WebpackModule ${ String ( id ) } ` ;
patchedFactory = ( 0 , eval ) ( patchedSource ) ;
2024-08-18 05:26:40 +02:00
2025-02-08 00:07:17 +01:00
if ( ! patchedBy . has ( patch . plugin ) ) {
patchedBy . add ( patch . plugin ) ;
markedAsPatched = true ;
}
} catch ( err ) {
logger . error ( ` Patch by ${ patch . plugin } errored (Module id is ${ String ( id ) } ): ${ replacement . match } \ n ` , err ) ;
if ( IS_DEV ) {
diffErroredPatch ( code , lastCode , lastCode . match ( replacement . match ) ! ) ;
}
if ( markedAsPatched ) {
patchedBy . delete ( patch . plugin ) ;
}
2024-08-20 09:13:21 +02:00
2025-02-08 00:07:17 +01:00
if ( patch . group ) {
logger . warn ( ` Undoing patch group ${ patch . find } by ${ patch . plugin } because replacement ${ replacement . match } errored ` ) ;
code = previousCode ;
patchedFactory = previousFactory ;
break ;
2024-08-20 09:13:21 +02:00
}
2025-02-08 00:07:17 +01:00
code = lastCode ;
patchedFactory = lastFactory ;
2024-08-20 09:13:21 +02:00
}
2024-08-18 05:26:40 +02:00
}
2025-02-08 00:07:17 +01:00
if ( ! patch . all ) {
patches . splice ( i -- , 1 ) ;
}
2023-10-26 21:21:21 +02:00
}
2025-02-08 00:07:17 +01:00
return [ patchedFactory , patchedSource , patchedBy ] ;
}
function diffErroredPatch ( code : string , lastCode : string , match : RegExpMatchArray ) {
const changeSize = code . length - lastCode . length ;
// Use 200 surrounding characters of context
const start = Math . max ( 0 , match . index ! - 200 ) ;
const end = Math . min ( lastCode . length , match . index ! + match [ 0 ] . length + 200 ) ;
// (changeSize may be negative)
const endPatched = end + changeSize ;
const context = lastCode . slice ( start , end ) ;
const patchedContext = code . slice ( start , endPatched ) ;
// Inline require to avoid including it in !IS_DEV builds
const diff = ( require ( "diff" ) as typeof import ( "diff" ) ) . diffWordsWithSpace ( context , patchedContext ) ;
let fmt = "%c %s " ;
const elements : string [ ] = [ ] ;
for ( const d of diff ) {
const color = d . removed
? "red"
: d . added
? "lime"
: "grey" ;
fmt += "%c%s" ;
elements . push ( "color:" + color , d . value ) ;
}
logger . errorCustomFmt ( . . . Logger . makeTitle ( "white" , "Before" ) , context ) ;
logger . errorCustomFmt ( . . . Logger . makeTitle ( "white" , "After" ) , patchedContext ) ;
const [ titleFmt , . . . titleElements ] = Logger . makeTitle ( "white" , "Diff" ) ;
logger . errorCustomFmt ( titleFmt + fmt , . . . titleElements , . . . elements ) ;
2023-10-26 21:21:21 +02:00
}