ServerDrawer: patch the JSX runtime too, not just createElement
This commit is contained in:
parent
7a28a6cea7
commit
61d47954fa
1 changed files with 58 additions and 30 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import { React } from "@vendetta/metro/common";
|
import { React } from "@vendetta/metro/common";
|
||||||
|
import { find } from "@vendetta/metro";
|
||||||
|
|
||||||
interface Intercept {
|
interface Intercept {
|
||||||
replacement: React.ComponentType<any>;
|
replacement: React.ComponentType<any>;
|
||||||
|
|
@ -12,7 +13,6 @@ interface PropsIntercept {
|
||||||
|
|
||||||
const intercepts = new Map<React.ComponentType<any>, Intercept>();
|
const intercepts = new Map<React.ComponentType<any>, Intercept>();
|
||||||
const propsIntercepts: PropsIntercept[] = [];
|
const propsIntercepts: PropsIntercept[] = [];
|
||||||
let origCreateElement: typeof React.createElement | null = null;
|
|
||||||
let isPatched = false;
|
let isPatched = false;
|
||||||
|
|
||||||
export function registerIntercept(
|
export function registerIntercept(
|
||||||
|
|
@ -34,52 +34,80 @@ export function registerPropsIntercept(predicate: (props: any) => boolean, repla
|
||||||
propsIntercepts.push({ predicate, replacement });
|
propsIntercepts.push({ predicate, replacement });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns a replacement type if this element should be intercepted, `null` to render nothing, or `undefined` to pass through unchanged. */
|
||||||
|
function resolveReplacement(type: any, props: any): { type: any; props: any } | null | undefined {
|
||||||
|
if (props) {
|
||||||
|
for (const { predicate, replacement } of propsIntercepts) {
|
||||||
|
try {
|
||||||
|
if (predicate(props)) {
|
||||||
|
return replacement ? { type: replacement, props } : null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// A bad predicate shouldn't block every other element from rendering.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = intercepts.get(type);
|
||||||
|
if (entry) {
|
||||||
|
return { type: entry.replacement, props: entry.extraProps ? { ...props, ...entry.extraProps } : props };
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function patchCreateElement(cleanups: (() => void)[]) {
|
export function patchCreateElement(cleanups: (() => void)[]) {
|
||||||
if (isPatched) return;
|
if (isPatched) return;
|
||||||
|
|
||||||
// Guard against React.createElement being null in some environments
|
const origCreateElement = React.createElement;
|
||||||
origCreateElement = React.createElement;
|
|
||||||
if (!origCreateElement) {
|
if (!origCreateElement) {
|
||||||
console.warn("[ServerDrawer] React.createElement is null, skipping patch");
|
console.warn("[ServerDrawer] React.createElement is null, skipping patch");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const patched = function (type: any, props: any, ...rest: any[]) {
|
const patchedCreateElement = function (type: any, props: any, ...rest: any[]) {
|
||||||
if (!origCreateElement) {
|
const resolved = resolveReplacement(type, props);
|
||||||
// Fallback to original createElement if somehow null (should not happen)
|
if (resolved === null) return null;
|
||||||
return React.createElement(type, props, ...rest);
|
if (resolved) return origCreateElement.call(React, resolved.type, resolved.props, ...rest);
|
||||||
}
|
|
||||||
if (props) {
|
|
||||||
for (const { predicate, replacement } of propsIntercepts) {
|
|
||||||
try {
|
|
||||||
if (predicate(props)) {
|
|
||||||
return replacement ? origCreateElement.call(React, replacement, props, ...rest) : null;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// A bad predicate shouldn't block every other element from rendering.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = intercepts.get(type);
|
|
||||||
if (entry) {
|
|
||||||
const newProps = entry.extraProps
|
|
||||||
? { ...props, ...entry.extraProps }
|
|
||||||
: props;
|
|
||||||
return origCreateElement.call(React, entry.replacement, newProps, ...rest);
|
|
||||||
}
|
|
||||||
return origCreateElement.call(React, type, props, ...rest);
|
return origCreateElement.call(React, type, props, ...rest);
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(patched, origCreateElement);
|
Object.assign(patchedCreateElement, origCreateElement);
|
||||||
React.createElement = patched as typeof React.createElement;
|
React.createElement = patchedCreateElement as typeof React.createElement;
|
||||||
isPatched = true;
|
isPatched = true;
|
||||||
|
|
||||||
|
// Modern React/RN builds (Discord's included) compile JSX through the automatic runtime
|
||||||
|
// (jsx/jsxs from react/jsx-runtime) instead of React.createElement - patching only
|
||||||
|
// createElement misses every one of Discord's own render calls entirely, which is why
|
||||||
|
// hideGuildsBar's intercept never actually did anything even though it registered correctly.
|
||||||
|
// Found by shape (jsx/jsxs/Fragment together), not by name, for the same reason everything
|
||||||
|
// else in this file avoids name-based lookups.
|
||||||
|
const jsxRuntime = find((m: any) => typeof m?.jsx === "function" && typeof m?.jsxs === "function" && "Fragment" in m);
|
||||||
|
const restoreJsx: (() => void)[] = [];
|
||||||
|
|
||||||
|
if (jsxRuntime) {
|
||||||
|
for (const key of ["jsx", "jsxs"] as const) {
|
||||||
|
const orig = jsxRuntime[key];
|
||||||
|
if (typeof orig !== "function") continue;
|
||||||
|
|
||||||
|
jsxRuntime[key] = function (type: any, props: any, ...rest: any[]) {
|
||||||
|
const resolved = resolveReplacement(type, props);
|
||||||
|
if (resolved === null) return null;
|
||||||
|
if (resolved) return orig.call(this, resolved.type, resolved.props, ...rest);
|
||||||
|
return orig.call(this, type, props, ...rest);
|
||||||
|
};
|
||||||
|
restoreJsx.push(() => { jsxRuntime[key] = orig; });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("[ServerDrawer] jsx-runtime module not found, only classic createElement calls will be intercepted");
|
||||||
|
}
|
||||||
|
|
||||||
cleanups.push(() => {
|
cleanups.push(() => {
|
||||||
if (isPatched && origCreateElement) {
|
if (isPatched) {
|
||||||
React.createElement = origCreateElement;
|
React.createElement = origCreateElement;
|
||||||
isPatched = false;
|
isPatched = false;
|
||||||
}
|
}
|
||||||
|
restoreJsx.forEach((fn) => fn());
|
||||||
intercepts.clear();
|
intercepts.clear();
|
||||||
propsIntercepts.length = 0;
|
propsIntercepts.length = 0;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue