feat(lib/utils): add objects and functions.objectDefineProperty

This commit is contained in:
PalmDevs 2025-04-12 14:18:13 +07:00
parent 7452be9d9a
commit 3b6627531e
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
3 changed files with 68 additions and 3 deletions

View file

@ -15,11 +15,14 @@
"./logger": {
"default": "./src/logger.ts"
},
"./types": {
"types": "./src/types.d.ts"
},
"./proxy": {
"default": "./src/proxy.ts"
},
"./objects": {
"default": "./src/objects.ts"
},
"./types": {
"types": "./src/types.d.ts"
}
}
}

View file

@ -3,3 +3,4 @@
export const objectFreeze = Object.freeze
export const objectSeal = Object.seal
export const objectPreventExtensions = Object.preventExtensions
export const objectDefineProperty = Object.defineProperty

61
lib/utils/src/objects.ts Normal file
View file

@ -0,0 +1,61 @@
import { objectDefineProperty as defineProperty } from './functions'
// 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 defineProperty(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))
defineProperty(proto, prop, {
configurable: true,
set(value) {
if (this === proto) {
_intercepting.set(prop, { configurable: true, value })
return
}
defineProperty(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) defineProperty(proto, prop, desc)
_intercepting.delete(prop)
}
}