Merge branch 'dev'
This commit is contained in:
commit
3f5643931c
4 changed files with 1 additions and 262 deletions
|
|
@ -1,112 +0,0 @@
|
||||||
import { instead } from "@lib/api/patcher";
|
|
||||||
import { logger } from "@lib/utils/logger";
|
|
||||||
import { _lazyContextSymbol } from "@metro/lazy";
|
|
||||||
import { requireModule } from "@metro/internals/modules";
|
|
||||||
import { LazyModuleContext } from "@metro/types";
|
|
||||||
import { findByNameLazy } from "@metro/wrappers";
|
|
||||||
|
|
||||||
function log(...messages: any[]) {
|
|
||||||
try {
|
|
||||||
logger.warn("[ShiggyCord] patchAnimatedValue:", ...messages);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discord's bundled React Native throws `AnimatedValue: Attempting to set value
|
|
||||||
* to undefined` when a `undefined` value reaches an `Animated.Value` — either
|
|
||||||
* through `setValue()` (which calls `_updateValue`) or through the constructor.
|
|
||||||
* Some of Discord's own flows (and third-party plugins) hit this during
|
|
||||||
* animation setup, which bubbles up to the React ErrorBoundary and makes
|
|
||||||
* ShiggyCord render its custom crash screen.
|
|
||||||
*
|
|
||||||
* Fix the root cause instead of masking the symptom:
|
|
||||||
* - `_updateValue` is a prototype method, so one patch covers every instance no
|
|
||||||
* matter which module captured the class reference.
|
|
||||||
* - The constructor is guarded by swapping the public `Animated.Value`
|
|
||||||
* reference (and the module's `default` export) for a patched proxy so that
|
|
||||||
* `new Animated.Value(undefined)` can no longer throw.
|
|
||||||
*/
|
|
||||||
export default function patchAnimatedValue() {
|
|
||||||
const unpatchers: Array<() => boolean> = [];
|
|
||||||
|
|
||||||
const install = (Class: any, source: string) => {
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
!Class?.prototype ||
|
|
||||||
typeof Class.prototype._updateValue !== "function"
|
|
||||||
) {
|
|
||||||
log(`${source}: resolved value is not AnimatedValue, skipping`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setValue(undefined) → _updateValue(undefined). Keep the last value.
|
|
||||||
unpatchers.push(
|
|
||||||
instead(
|
|
||||||
"_updateValue",
|
|
||||||
Class.prototype,
|
|
||||||
function (this: any, args: any[], origFunc: Function) {
|
|
||||||
if (args[0] === undefined) args[0] = this?._value ?? 0;
|
|
||||||
return origFunc(...args);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Constructor guard: coerce non-number to 0 so `new Animated.Value(undefined)` can't throw.
|
|
||||||
const guard = (args: any[], origFunc: Function) => {
|
|
||||||
if (typeof args[0] !== "number") args[0] = 0;
|
|
||||||
return origFunc(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. Module exports (`require(...).default`).
|
|
||||||
try {
|
|
||||||
const ctxt: LazyModuleContext | undefined =
|
|
||||||
findByNameLazy("AnimatedValue")[_lazyContextSymbol];
|
|
||||||
if (ctxt?.moduleId != null) {
|
|
||||||
const moduleExports = requireModule(ctxt.moduleId);
|
|
||||||
if (moduleExports?.default === Class) {
|
|
||||||
unpatchers.push(instead("default", moduleExports, guard));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
// 2. The public `Animated.Value` namespace reference.
|
|
||||||
try {
|
|
||||||
const ReactNative: any = require("react-native");
|
|
||||||
const Animated = ReactNative?.Animated;
|
|
||||||
if (Animated && Animated.Value === Class) {
|
|
||||||
unpatchers.push(instead("Value", Animated, guard));
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
log(`${source}: patched _updateValue + constructor guard`);
|
|
||||||
} catch (e) {
|
|
||||||
log(`${source}: patch failed`, e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Primary: Discord's own RN Animated namespace (most reliable, no full scan).
|
|
||||||
try {
|
|
||||||
const ReactNative: any = require("react-native");
|
|
||||||
install(ReactNative?.Animated?.Value, "react-native.Animated.Value");
|
|
||||||
if (unpatchers.length > 0) {
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log("react-native.Animated path failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: byName lookup.
|
|
||||||
try {
|
|
||||||
const ctxt: LazyModuleContext =
|
|
||||||
findByNameLazy("AnimatedValue")[_lazyContextSymbol];
|
|
||||||
if (ctxt) {
|
|
||||||
ctxt.getExports((Class: any) => install(Class, "findByNameLazy"));
|
|
||||||
} else {
|
|
||||||
log("no lazy context found");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log("findByNameLazy path failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { byProps } from "@metro/filters";
|
|
||||||
import { findExports } from "@metro/finders";
|
|
||||||
import { findByStoreNameLazy } from "@metro/wrappers";
|
|
||||||
import { instead } from "@lib/api/patcher";
|
|
||||||
import { logger } from "@lib/utils/logger";
|
|
||||||
import { isSemanticColor, resolveSemanticColor, semanticColors } from "@ui/color";
|
|
||||||
|
|
||||||
const ThemeStore = findByStoreNameLazy("ThemeStore");
|
|
||||||
|
|
||||||
function log(...messages: any[]) {
|
|
||||||
try {
|
|
||||||
logger.warn("[ShiggyCord] patchSafeCard:", ...messages);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cardHealthy = false;
|
|
||||||
|
|
||||||
const FALLBACK_STYLE = {
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 16,
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveCardBackground(): string | undefined {
|
|
||||||
try {
|
|
||||||
const color = semanticColors?.CARD_BACKGROUND_DEFAULT;
|
|
||||||
if (isSemanticColor(color)) return resolveSemanticColor(color);
|
|
||||||
if (typeof color === "string") return color;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function patchSafeCard() {
|
|
||||||
const unpatchers: Array<() => boolean> = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const cardModule = findExports(byProps("Card"));
|
|
||||||
const origCard = cardModule?.Card;
|
|
||||||
if (typeof origCard !== "function") {
|
|
||||||
log("no Card function on the resolved module, skipping");
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
log("resolved design Card from first-match module");
|
|
||||||
|
|
||||||
let cardBackground = resolveCardBackground();
|
|
||||||
|
|
||||||
const unsubscribeTheme = ThemeStore?.addChangeListener?.(() => {
|
|
||||||
const resolved = resolveCardBackground();
|
|
||||||
if (resolved) cardBackground = resolved;
|
|
||||||
});
|
|
||||||
if (typeof unsubscribeTheme === "function") {
|
|
||||||
unpatchers.push(() => {
|
|
||||||
unsubscribeTheme();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class CardProbeBoundary extends React.Component<
|
|
||||||
{
|
|
||||||
fallback: React.ReactNode;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
},
|
|
||||||
{ hasError: boolean }
|
|
||||||
> {
|
|
||||||
state = { hasError: false };
|
|
||||||
|
|
||||||
static getDerivedStateFromError() {
|
|
||||||
return { hasError: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(error: unknown) {
|
|
||||||
try {
|
|
||||||
log("Card render threw, using View fallback", error);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) return this.props.fallback;
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HealthyProbe extends React.Component<{
|
|
||||||
onHealthy: () => void;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}> {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.onHealthy();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SafeCard = function SafeCard(props: any) {
|
|
||||||
if (cardHealthy) return React.createElement(origCard, props);
|
|
||||||
|
|
||||||
return React.createElement(
|
|
||||||
CardProbeBoundary,
|
|
||||||
{
|
|
||||||
fallback: React.createElement(
|
|
||||||
View,
|
|
||||||
{
|
|
||||||
style: [
|
|
||||||
FALLBACK_STYLE,
|
|
||||||
...(cardBackground
|
|
||||||
? [{ backgroundColor: cardBackground }]
|
|
||||||
: []),
|
|
||||||
props?.style,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
props?.children,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
React.createElement(
|
|
||||||
HealthyProbe,
|
|
||||||
{
|
|
||||||
onHealthy: () => {
|
|
||||||
cardHealthy = true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
React.createElement(origCard, props),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
unpatchers.push(
|
|
||||||
instead("Card", cardModule, (args: any[]) => SafeCard(args[0] ?? {})),
|
|
||||||
);
|
|
||||||
|
|
||||||
log("patched design Card (adaptive probe on first mount)");
|
|
||||||
} catch (e) {
|
|
||||||
log("patch failed", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => unpatchers.forEach((unpatch) => unpatch());
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import patchAnimatedValue from "@core/debug/patches/patchAnimatedValue";
|
|
||||||
import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary";
|
import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary";
|
||||||
import patchSafeCard from "@core/debug/patches/patchSafeCard";
|
|
||||||
import initFixes from "@core/fixes";
|
import initFixes from "@core/fixes";
|
||||||
import { initFetchI18nStrings } from "@core/i18n";
|
import { initFetchI18nStrings } from "@core/i18n";
|
||||||
import initSettings from "@core/ui/settings";
|
import initSettings from "@core/ui/settings";
|
||||||
|
|
@ -32,8 +30,6 @@ export default async () => {
|
||||||
["patchCommands", () => patchCommands()],
|
["patchCommands", () => patchCommands()],
|
||||||
["patchJsx", () => patchJsx()],
|
["patchJsx", () => patchJsx()],
|
||||||
["patchErrorBoundary", () => patchErrorBoundary()],
|
["patchErrorBoundary", () => patchErrorBoundary()],
|
||||||
["patchAnimatedValue", () => patchAnimatedValue()],
|
|
||||||
["patchSafeCard", () => patchSafeCard()],
|
|
||||||
["initVendettaObject", () => initVendettaObject()],
|
["initVendettaObject", () => initVendettaObject()],
|
||||||
["initFetchI18nStrings", () => initFetchI18nStrings()],
|
["initFetchI18nStrings", () => initFetchI18nStrings()],
|
||||||
["initSettings", () => initSettings()],
|
["initSettings", () => initSettings()],
|
||||||
|
|
|
||||||
|
|
@ -117,17 +117,13 @@ for (const [id, moduleExports] of cacher.getModules()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportsKeysLength = Reflect.ownKeys(actualExports).length;
|
const exportsKeysLength = Reflect.ownKeys(actualExports).length;
|
||||||
if (_source[prop] && exportsKeysLength >= _source[prop]) {
|
if (_source[prop] && exportsKeysLength <= _source[prop]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_module[prop] = actualExports[prop];
|
_module[prop] = actualExports[prop];
|
||||||
_source[prop] = Reflect.ownKeys(actualExports).length;
|
_source[prop] = Reflect.ownKeys(actualExports).length;
|
||||||
cacher.cacheId(id);
|
cacher.cacheId(id);
|
||||||
|
|
||||||
if (exportsKeysLength === 1) {
|
|
||||||
redesignProps.delete(prop);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue