ServerDrawer: make drawer content-swap timing-independent
This commit is contained in:
parent
5ad56932fc
commit
62192d5035
1 changed files with 61 additions and 27 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { React } from "@vendetta/metro/common";
|
||||
import { rawFind } from "../lib/rawFind";
|
||||
import ServerDrawerSheet from "../components/ServerDrawerSheet";
|
||||
import { registerIntercept } from "../lib/createElementIntercept";
|
||||
import { registerIntercept, registerTypeDetector } from "../lib/createElementIntercept";
|
||||
|
||||
const TAG = "[ServerDrawer]";
|
||||
|
||||
|
|
@ -17,47 +17,81 @@ function getGestureContext(): any {
|
|||
return cachedGestureContext;
|
||||
}
|
||||
|
||||
export function patchExpanded(
|
||||
cleanups: (() => void)[]
|
||||
): boolean {
|
||||
const mod = rawFind((m) => m?.type?.displayName === "QuestDockContentExpanded" || m?.type?.name === "QuestDockContentExpanded");
|
||||
// Memo-wrapped components have no own .name/.displayName - only their nested .type does (confirmed
|
||||
// live for GuildsBar). Checked in addition to the direct check, not instead of it.
|
||||
function isNamed(name: string) {
|
||||
return (type: any) => type?.name === name || type?.displayName === name ||
|
||||
type?.type?.name === name || type?.type?.displayName === name;
|
||||
}
|
||||
|
||||
function ServerDrawerSheetWrapper() {
|
||||
return <ServerDrawerSheet gestureContext={getGestureContext()} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Two independent mechanisms, both active. Mutating the found module's own `.type` property
|
||||
* (matched here by isNamed, same nested-memo-aware check GuildsBar's patch uses) makes the drawer
|
||||
* render immediately if the target module happens to already be registered in Metro by the time
|
||||
* this runs. But that registration only happens once Discord's own code actually renders Quest
|
||||
* Dock's expanded content at least once - which itself depends on questDockContext.ts's Context
|
||||
* value force having already taken effect, and on Discord's own render pass actually happening
|
||||
* within this plugin's ~10s retry window. That's a real boot-time race, confirmed live: identical
|
||||
* code gave different results (drawer showing vs not) across genuinely clean reinstalls, pointing
|
||||
* at timing rather than a deterministic bug.
|
||||
*
|
||||
* registerTypeDetector has no such timing dependency - it inspects the `type` argument passed
|
||||
* directly to createElement/jsx at the moment something actually creates an element with it,
|
||||
* whenever that happens, early or late. Combining both means the drawer shows as fast as possible
|
||||
* when the race is won, and still shows correctly even when it's lost.
|
||||
*/
|
||||
export function patchExpanded(cleanups: (() => void)[]): boolean {
|
||||
registerTypeDetector(isNamed("QuestDockContentExpanded"), (real) => {
|
||||
registerIntercept(real, ServerDrawerSheetWrapper);
|
||||
console.log(TAG, "PATCH: QuestDockContentExpanded replaced (type detector)");
|
||||
});
|
||||
|
||||
const mod = rawFind((m) => isNamed("QuestDockContentExpanded")(m?.type));
|
||||
if (!mod?.type) {
|
||||
console.log(TAG, "WARN: QuestDockContentExpanded not found (will retry)");
|
||||
console.log(TAG, "WARN: QuestDockContentExpanded module not found yet (will retry)");
|
||||
return false;
|
||||
}
|
||||
|
||||
const orig = mod.type;
|
||||
|
||||
registerIntercept(orig, ServerDrawerSheet, { gestureContext: getGestureContext() });
|
||||
|
||||
mod.type = function ServerDrawerPatch() {
|
||||
return <ServerDrawerSheet gestureContext={getGestureContext()} />;
|
||||
};
|
||||
registerIntercept(orig, ServerDrawerSheetWrapper);
|
||||
mod.type = ServerDrawerSheetWrapper;
|
||||
cleanups.push(() => { mod.type = orig; });
|
||||
console.log(TAG, "PATCH: QuestDockContentExpanded replaced");
|
||||
console.log(TAG, "PATCH: QuestDockContentExpanded replaced (module mutation)");
|
||||
return true;
|
||||
}
|
||||
|
||||
export function patchEmpty(
|
||||
name: string,
|
||||
cleanups: (() => void)[]
|
||||
): boolean {
|
||||
const mod = rawFind((m) => m?.type?.displayName === name || m?.type?.name === name);
|
||||
export function patchEmpty(name: string, cleanups: (() => void)[]): boolean {
|
||||
registerTypeDetector(isNamed(name), (real) => {
|
||||
registerIntercept(real, function EmptyPatch() { return null; });
|
||||
console.log(TAG, `PATCH: ${name} replaced (type detector)`);
|
||||
});
|
||||
|
||||
const mod = rawFind((m) => isNamed(name)(m?.type));
|
||||
if (!mod?.type) {
|
||||
console.log(TAG, `WARN: ${name} not found`);
|
||||
console.log(TAG, `WARN: ${name} module not found yet (will retry)`);
|
||||
return false;
|
||||
}
|
||||
const orig = mod.type;
|
||||
const originalComponent = orig;
|
||||
|
||||
mod.type = function EmptyPatch() {
|
||||
const orig = mod.type;
|
||||
function EmptyPatch() {
|
||||
return null;
|
||||
};
|
||||
|
||||
if (originalComponent) {
|
||||
Object.defineProperties(mod.type, Object.getOwnPropertyDescriptors(originalComponent));
|
||||
mod.type.displayName = name;
|
||||
}
|
||||
mod.type = EmptyPatch;
|
||||
|
||||
try {
|
||||
Object.defineProperties(mod.type, Object.getOwnPropertyDescriptors(orig));
|
||||
} catch {
|
||||
// Some property descriptors can't be copied onto another function (e.g. non-configurable
|
||||
// ones on certain memo/forwardRef wrappers) - fine, this is a best-effort compatibility
|
||||
// shim, not something correctness depends on.
|
||||
}
|
||||
mod.type.displayName = name;
|
||||
|
||||
cleanups.push(() => { mod.type = orig; });
|
||||
console.log(TAG, `PATCH: ${name} replaced (module mutation)`);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue