Skip to content

Instantly share code, notes, and snippets.

@sirlancelot
Last active May 20, 2021 18:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sirlancelot/633f54883af182d26d66147449aefd1e to your computer and use it in GitHub Desktop.
Save sirlancelot/633f54883af182d26d66147449aefd1e to your computer and use it in GitHub Desktop.
Monkey Patch Method
/**
* Patch a method on an instance. Returns a function which will undo the patch.
*
* Note: The function signature of the provided override does not match the
* original method. This is by design in order to create a more predictable
* function signature.
*
* @param {{ [x: string]: Function }} instance
* @param {keyof instance} method
* @param {(ctx: any, args: IArguments, original: Function) => any} override
* @returns {Function} Revert monkey-patching
*/
export function monkeyPatchMethod(instance, method, override) {
const descriptor = Object.getOwnPropertyDescriptor(instance, method)
const original = instance[method]
// If `descriptor` is undefined, then we're likely trying to monkey-patch a
// method somewhere in the prototype chain. This is okay, but we should try to
// make the patch as resiliant as possible by making it non-assignable and
// non-enumerable. If a descriptor was found, then allow its configuration to
// override ours.
Object.defineProperty(instance, method, {
configurable: true,
enumerable: false,
writable: false,
...descriptor,
value: function patchedMethod() {
return override(this, arguments, original)
},
})
// When reverting the patch, use the original property descriptor, otherwise
// just delete the patched property, allowing property access to continue
// along the prototype chain as normal.
return function unpatchMethod() {
if (descriptor) Object.defineProperty(instance, method, descriptor)
else delete instance[method]
}
}
@sirlancelot
Copy link
Author

Example usage:

// WARNING: Don't this for built-ins, this just for demonstration.
const unpatch = monkeyPatchMethod(window, "addEventListener", (ctx, args, original) => {
  const [type, listener, options] = args
  // ...
  return original.apply(ctx, args)
})

window.addEventListener("click", () => {}) // calls the monkey-patched method

// later...
unpatch()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment