revenge-bundle-next/lib/utils/src/objects.ts
2025-05-09 00:04:36 +07:00

66 lines
2.2 KiB
TypeScript

// Retain reference to original functions
export const objectFreeze = Object.freeze
export const objectDefineProperty = Object.defineProperty
// Patch to prevent the use of Object.freeze
Object.freeze = (o: any) => o
// Heavily inspired by uwu/shelter
// https://github.com/uwu/shelter/blob/main/packages/shelter/src/exfiltrate.ts
// Patch to fix intercepting properties on Object.prototype
Object.defineProperty = (target, p, d) => {
if (target === Object.prototype && _intercepting.has(p)) {
_intercepting.set(p, d)
return target
}
return objectDefineProperty(target, p, d)
}
const _intercepting = new Map<PropertyKey, PropertyDescriptor | undefined>()
export type InterceptPropertyCallback = (target: any, value: any) => any
/**
* Intercept a property when set on an object to modify its value. **Only one callback can be set for a property at a time.**
*
* @param prop The property to intercept.
* @param callback The callback to call when the property is set, can return a non-null value to set the property to.
* @returns The function to unintercept the property.
*/
export function interceptProperty(prop: PropertyKey, callback: (target: object, value: any) => any) {
if (_intercepting.has(prop)) throw new Error(`Property ${String(prop)} is already being intercepted`)
const proto = Object.prototype
_intercepting.set(prop, Object.getOwnPropertyDescriptor(proto, prop))
objectDefineProperty(proto, prop, {
configurable: true,
set(value) {
if (this === proto) {
_intercepting.set(prop, { configurable: true, value })
return
}
objectDefineProperty(this, prop, {
configurable: true,
writable: true,
enumerable: true,
value: callback(this, value) ?? value,
})
},
get() {
const desc = _intercepting.get(prop)
return desc && (desc.value ?? desc.get?.call(this))
},
})
return () => {
// @ts-expect-error
delete proto[prop]
const desc = _intercepting.get(prop)
if (desc) objectDefineProperty(proto, prop, desc)
_intercepting.delete(prop)
}
}