diff --git a/README.md b/README.md index 3ec8585..8805e8a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ShiggyCord +# ShiggyCord [![Discord](https://img.shields.io/discord/1427396621905432699?style=social&logo=discord&label=ShiggyCord)](https://discord.gg/nQykFF9Ud6) An unofficial fork of ShiggyCord, made just for fun Idea taken from Rosiecord diff --git a/scripts/build.mjs b/scripts/build.mjs index 9c4f8b3..d608b33 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -77,7 +77,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"1.1.4-2"`, + version: `"1.1.4-3"`, }, }, }, diff --git a/src/core/debug/patches/patchErrorBoundary.tsx b/src/core/debug/patches/patchErrorBoundary.tsx index ccab32a..e6a8da6 100644 --- a/src/core/debug/patches/patchErrorBoundary.tsx +++ b/src/core/debug/patches/patchErrorBoundary.tsx @@ -4,18 +4,135 @@ import { _lazyContextSymbol } from "@metro/lazy"; import { LazyModuleContext } from "@metro/types"; import { findByNameLazy } from "@metro/wrappers"; -function getErrorBoundaryContext() { - const ctxt: LazyModuleContext = findByNameLazy("ErrorBoundary")[_lazyContextSymbol]; - return new Promise(resolve => ctxt.getExports(exp => resolve(exp.prototype))); +/** + * Try to resolve the ErrorBoundary prototype context in a resilient way. + * Some Discord versions / builds may expose modules under slightly different + * names; attempt lookup but fail gracefully if not found. + */ +function getErrorBoundaryContext(): Promise { + try { + const ctxt: LazyModuleContext = + findByNameLazy("ErrorBoundary")[_lazyContextSymbol]; + if (ctxt) { + return new Promise((resolve) => + ctxt.getExports((exp: any) => resolve(exp.prototype)), + ); + } + } catch (e) { + // silent + } + + // Best-effort fallback: resolve to null so callers can handle absence. + return Promise.reject(new Error("ErrorBoundary context not found")); } +/** + * Patch Discord's ErrorBoundary.render to return ShiggyCord's custom screen. + * Add defensive logging and fallback behavior so the patch won't break startup + * if lookup fails. Also register lightweight global handlers to capture + * uncaught errors for debugging. + */ export default function patchErrorBoundary() { - return after.await("render", getErrorBoundaryContext(), function (this: any) { - if (!this.state.error) return; + try { + console.log("[ShiggyCord] patchErrorBoundary: registering"); + } catch {} - return this.setState({ info: null, error: null })} - />; - }); + // Attempt to attach after the ErrorBoundary render. If the context lookup + // fails, the promise will reject and the patcher will not install the patch, + // which is preferable to throwing during startup. + const ctxPromise = getErrorBoundaryContext().catch((err) => { + try { + console.warn( + "[ShiggyCord] patchErrorBoundary: context lookup failed", + err, + ); + } catch {} + // Return null so after.await receives a thenable; the patcher wrapper will handle it. + return null; + }); + + const unpatch = after.await("render", ctxPromise, function (this: any) { + try { + if (!this || !this.state || !this.state.error) return; + try { + console.log( + "[ShiggyCord] patchErrorBoundary: rendering custom error screen", + this.state.error, + ); + } catch {} + return ( + { + try { + // common variant: some boundaries track `hasErr` or `error` + this.setState?.({ info: null, error: null, hasErr: false }); + } catch { + try { + this.setState?.({ error: null }); + } catch {} + } + }} + /> + ); + } catch (e) { + try { + console.error( + "[ShiggyCord] patchErrorBoundary: error while rendering custom screen", + e, + ); + } catch {} + // Fall back to original behavior by returning undefined + return; + } + }); + + // Best-effort: install global handlers to capture uncaught errors / promise rejections. + // These handlers do not attempt to mount UI directly (that can be fragile), but they + // store the last uncaught error for inspection and log details which helps debugging. + try { + const g: any = global as any; + if (g) { + // Wrap React Native's ErrorUtils if present + try { + const ErrorUtils = g.ErrorUtils; + if (ErrorUtils && typeof ErrorUtils.setGlobalHandler === "function") { + // preserve previous handler + const prev = + ErrorUtils.getGlobalHandler?.() ?? + ErrorUtils._globalHandler ?? + null; + ErrorUtils.setGlobalHandler((err: any, isFatal?: boolean) => { + try { + console.error("[ShiggyCord] global uncaught error:", err, { + isFatal, + }); + (window as any).__SHIGGY_LAST_UNCAUGHT_ERROR = err; + } catch {} + try { + if (typeof prev === "function") prev(err, isFatal); + } catch {} + }); + } + } catch {} + + // Listen for unhandled promise rejections if environment supports it. + try { + if (typeof g.addEventListener === "function") { + g.addEventListener("unhandledrejection", (ev: any) => { + try { + console.error( + "[ShiggyCord] unhandledrejection:", + ev?.reason ?? ev, + ); + (window as any).__SHIGGY_LAST_UNCAUGHT_ERROR = ev?.reason ?? ev; + } catch {} + }); + } + } catch {} + } + } catch {} + + return unpatch; } diff --git a/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx b/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx index a466cb8..ca48fe2 100644 --- a/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx +++ b/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx @@ -1,4 +1,6 @@ +import React from "react"; import { hasStack, isComponentStack } from "@core/ui/reporter/utils/isStack"; +import parseErrorStack from "@core/ui/reporter/utils/parseErrorStack"; import { getDebugInfo, toggleSafeMode } from "@lib/api/debug"; import { BundleUpdaterManager } from "@lib/api/native/modules"; import { settings } from "@lib/api/settings"; @@ -14,8 +16,16 @@ import { } from "@metro/common/components"; import { ScrollView, View } from "react-native"; +import { + registeredPlugins, + disablePlugin, + getBisectBatches, + enablePlugin, + getPluginSettingsComponent, +} from "@lib/addons/plugins"; import ErrorComponentStackCard from "./ErrorComponentStackCard"; import ErrorStackCard from "./ErrorStackCard"; +import { NavigationNative } from "@metro/common"; const useStyles = createStyles({ container: { @@ -33,6 +43,14 @@ export default function ErrorBoundaryScreen(props: { }) { const styles = useStyles(); const debugInfo = getDebugInfo(); + const navigation = NavigationNative.useNavigation(); + + // Bisect UI state (fixes ReferenceError when referenced below) + const [bisectBatches, setBisectBatches] = React.useState( + null, + ); + const [bisectIndex, setBisectIndex] = React.useState(0); + const [showBisectUI, setShowBisectUI] = React.useState(false); return ( @@ -51,6 +69,274 @@ export default function ErrorBoundaryScreen(props: { {props.error.message} + + {(() => { + // 1) Prefer explicit annotation on the error (set by plugin loader) or current runtime marker. + const explicitPluginId = + (props.error as any)?.pluginId ?? + (window as any)?.__SHIGGY_LAST_UNCAUGHT_ERROR?.pluginId ?? + (window as any)?.__SHIGGY_CURRENT_PLUGIN; + + if (explicitPluginId && registeredPlugins.has(explicitPluginId)) { + const manifest = registeredPlugins.get(explicitPluginId)!; + const name = + manifest.display?.name ?? manifest.name ?? explicitPluginId; + return ( + + + Suspected plugin + + This error is associated with a plugin based on runtime + context. + + + {name} ({explicitPluginId}) + + +