Vencord/src/utils/proxyInner.ts

92 lines
3.5 KiB
TypeScript
Raw Normal View History

2024-05-03 02:18:12 +00:00
/*
* Vencord, a Discord client mod
2024-05-26 23:41:28 +00:00
* Copyright (c) 2024 Vendicated, Nuckyz and contributors
2024-05-03 02:18:12 +00:00
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { UNCONFIGURABLE_PROPERTIES } from "./misc";
export const SYM_PROXY_INNER_GET = Symbol.for("vencord.proxyInner.get");
export const SYM_PROXY_INNER_VALUE = Symbol.for("vencord.proxyInner.innerValue");
2024-05-03 02:18:12 +00:00
const handler: ProxyHandler<any> = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
[propName, (target: any, ...args: any[]) => Reflect[propName](target[SYM_PROXY_INNER_GET](), ...args)]
2024-05-03 02:18:12 +00:00
)),
2024-05-24 01:52:32 +00:00
set: (target, p, value) => {
const innerTarget = target[SYM_PROXY_INNER_GET]();
2024-05-24 01:52:32 +00:00
return Reflect.set(innerTarget, p, value, innerTarget);
},
2024-05-03 02:18:12 +00:00
ownKeys: target => {
const keys = Reflect.ownKeys(target[SYM_PROXY_INNER_GET]());
for (const key of UNCONFIGURABLE_PROPERTIES) {
2024-05-03 02:18:12 +00:00
if (!keys.includes(key)) keys.push(key);
}
return keys;
},
getOwnPropertyDescriptor: (target, p) => {
if (typeof p === "string" && UNCONFIGURABLE_PROPERTIES.includes(p)) {
2024-05-03 02:18:12 +00:00
return Reflect.getOwnPropertyDescriptor(target, p);
2024-06-22 08:50:09 +00:00
}
2024-05-03 02:18:12 +00:00
const descriptor = Reflect.getOwnPropertyDescriptor(target[SYM_PROXY_INNER_GET](), p);
2024-05-03 02:18:12 +00:00
if (descriptor) Object.defineProperty(target, p, descriptor);
return descriptor;
}
};
/**
* A proxy which has an inner value that can be set later.
* When a property is accessed, the proxy looks for the property value in its inner value, and errors if it's not set.
*
* IMPORTANT:
* Destructuring at top level is not supported for proxyInner.
*
* @param err The error message to throw when the inner value is not set
* @param primitiveErr The error message to throw when the inner value is a primitive
2024-05-03 02:18:12 +00:00
* @returns A proxy which will act like the inner value when accessed
*/
export function proxyInner<T = any>(
errMsg = "Proxy inner value is undefined, setInnerValue was never called.",
primitiveErrMsg = "proxyInner called on a primitive value."
): [proxy: T, setInnerValue: (innerValue: T) => void] {
2024-06-22 09:14:43 +00:00
const proxyDummy = Object.assign(function () { }, {
[SYM_PROXY_INNER_GET]: function () {
if (proxyDummy[SYM_PROXY_INNER_VALUE] == null) {
throw new Error(errMsg);
2024-05-03 02:18:12 +00:00
}
return proxyDummy[SYM_PROXY_INNER_VALUE];
2024-05-03 02:18:12 +00:00
},
[SYM_PROXY_INNER_VALUE]: void 0 as T | undefined
2024-05-03 02:18:12 +00:00
});
const proxy = new Proxy(proxyDummy, {
2024-05-03 02:18:12 +00:00
...handler,
get(target, p, receiver) {
if (p === SYM_PROXY_INNER_GET || p === SYM_PROXY_INNER_VALUE) {
return Reflect.get(target, p, receiver);
}
2024-05-03 02:18:12 +00:00
const innerTarget = target[SYM_PROXY_INNER_GET]();
2024-05-03 02:18:12 +00:00
if (typeof innerTarget === "object" || typeof innerTarget === "function") {
2024-05-24 01:51:23 +00:00
return Reflect.get(innerTarget, p, innerTarget);
2024-05-03 02:18:12 +00:00
}
throw new Error(primitiveErrMsg);
2024-05-03 02:18:12 +00:00
}
});
function setInnerValue(innerValue: T) {
proxyDummy[SYM_PROXY_INNER_VALUE] = innerValue;
2024-05-03 02:18:12 +00:00
// Avoid binding toString if the inner value is null.
// This can happen if we are setting the inner value as another instance of proxyInner, which will cause that proxy to instantly evaluate and throw an error
2024-06-23 22:48:15 +00:00
if (typeof innerValue === "function" && innerValue[SYM_PROXY_INNER_VALUE] == null) {
proxy.toString = innerValue.toString.bind(innerValue);
2024-05-03 02:18:12 +00:00
}
}
return [proxy, setInnerValue];
2024-05-03 02:18:12 +00:00
}