From 6af355ea2a2939b0fff0b32c4a5980e67c0d2246 Mon Sep 17 00:00:00 2001 From: kmmiio99o <164557248+kmmiio99o@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:59:57 +0200 Subject: [PATCH] Expose collectAllPluginIds and improve error UI --- .../components/ErrorBoundaryScreen.tsx | 646 ++++++++++-------- 1 file changed, 354 insertions(+), 292 deletions(-) diff --git a/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx b/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx index ca48fe2..0ccd8be 100644 --- a/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx +++ b/src/core/ui/reporter/components/ErrorBoundaryScreen.tsx @@ -4,15 +4,20 @@ 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"; -import { Codeblock, ErrorBoundary } from "@lib/ui/components"; +import { Codeblock } from "@lib/ui/components"; import { createStyles } from "@lib/ui/styles"; import { tokens } from "@metro/common"; +import { showToast } from "@lib/ui/toasts"; import { Button, Card, SafeAreaProvider, SafeAreaView, Text, + TableRowGroup, + TableRow, + TableSwitchRow, + Stack, } from "@metro/common/components"; import { ScrollView, View } from "react-native"; @@ -22,7 +27,10 @@ import { getBisectBatches, enablePlugin, getPluginSettingsComponent, + isPluginEnabled, + pluginSettings, } from "@lib/addons/plugins"; +import { getCorePlugins } from "@core/plugins"; import ErrorComponentStackCard from "./ErrorComponentStackCard"; import ErrorStackCard from "./ErrorStackCard"; import { NavigationNative } from "@metro/common"; @@ -37,6 +45,74 @@ const useStyles = createStyles({ }, }); +/** + * Module-level helper to collect all known plugin IDs (core + registered externals + installed keys). + * Exported and attached to window so external callers (or code executing before React mounts) + * can call it without needing the React component to mount first. + */ +export function collectAllPluginIdsModule(): string[] { + const ids = new Set(); + // registeredPlugins (includes externals known from repos) + try { + if ( + typeof registeredPlugins !== "undefined" && + registeredPlugins && + typeof (registeredPlugins as any).keys === "function" + ) { + for (const k of registeredPlugins.keys()) ids.add(k); + } + } catch {} + // installed plugin settings (may include plugins not registered yet) + try { + if (typeof pluginSettings !== "undefined" && pluginSettings) { + for (const k of Object.keys(pluginSettings || {})) ids.add(k); + } + } catch {} + return Array.from(ids); +} + +// Provide a global alias for callers that expect the global to exist early. +// Use both spellings seen in various callers. +// Provide global aliases (some callers used plural/singular variants) +// Ensure a safe, early no-op fallback is present on the global object so +// callers that run before this module is evaluated won't throw a ReferenceError. +(function () { + try { + const g = + typeof globalThis !== "undefined" + ? globalThis + : typeof window !== "undefined" + ? window + : {}; + // If the global already has a function, don't overwrite it. + if (typeof (g as any).collectAllPluginsIds !== "function") { + Object.defineProperty(g, "collectAllPluginsIds", { + configurable: true, + writable: true, + enumerable: false, + value: collectAllPluginIdsModule, + }); + } + if (typeof (g as any).collectAllPluginIds !== "function") { + Object.defineProperty(g, "collectAllPluginIds", { + configurable: true, + writable: true, + enumerable: false, + value: collectAllPluginIdsModule, + }); + } + } catch (e) { + // Best-effort only — avoid throwing during module evaluation + try { + console.error( + "[ShiggyCord] failed to install global collectAllPluginIds alias", + e, + ); + } catch {} + } +})(); + +// Component entrypoint export default function ErrorBoundaryScreen(props: { error: Error; rerender: () => void; @@ -52,317 +128,303 @@ export default function ErrorBoundaryScreen(props: { const [bisectIndex, setBisectIndex] = React.useState(0); const [showBisectUI, setShowBisectUI] = React.useState(false); + // Use a safe fallback so renders don't throw if state hasn't been initialized yet + const batches = bisectBatches ?? []; + const currentBatch = batches[bisectIndex] ?? []; + return ( - - - - - Uh oh. - - A crash occurred while rendering a component. This could be caused - by a plugin, ShiggyCord, or Discord itself. - - - {debugInfo.os.name}; {debugInfo.discord.build} ( - {debugInfo.discord.version}); {debugInfo.bunny.version} - - - - {props.error.message} + + + + Uh oh. + + A crash occurred while rendering a component. This could be caused + by a plugin, ShiggyCord, or Discord itself. + + + {debugInfo.os.name}; {debugInfo.discord.build} ( + {debugInfo.discord.version}); {debugInfo.bunny.version} + + + + {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; + {(() => { + // 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}) - - -