From 9c8db576f5cea498c70d00a0764d7f3c6c9cef65 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 27 May 2024 17:21:27 -0400 Subject: error boundary now properly reports steam errors --- frontend/src/components/DeckyErrorBoundary.tsx | 278 ++++++++++----------- frontend/src/components/DeckyToasterState.tsx | 3 +- frontend/src/components/PluginView.tsx | 4 +- .../src/components/QuickAccessVisibleState.tsx | 4 +- frontend/src/components/TitleView.tsx | 4 +- 5 files changed, 145 insertions(+), 148 deletions(-) (limited to 'frontend/src/components') diff --git a/frontend/src/components/DeckyErrorBoundary.tsx b/frontend/src/components/DeckyErrorBoundary.tsx index a851b2e1..6fe234b7 100644 --- a/frontend/src/components/DeckyErrorBoundary.tsx +++ b/frontend/src/components/DeckyErrorBoundary.tsx @@ -1,17 +1,14 @@ import { sleep } from '@decky/ui'; -import { ErrorInfo, FunctionComponent, useReducer, useState } from 'react'; +import { FunctionComponent, useEffect, useReducer, useState } from 'react'; import { uninstallPlugin } from '../plugin'; -import { doRestart, doShutdown } from '../updater'; - -interface ReactErrorInfo { - error: Error; - info: ErrorInfo; -} +import { VerInfo, doRestart, doShutdown } from '../updater'; +import { ValveReactErrorInfo, getLikelyErrorSourceFromValveReactError } from '../utils/errors'; interface DeckyErrorBoundaryProps { - error: ReactErrorInfo; + error: ValveReactErrorInfo; errorKey: string; + identifier: string; reset: () => void; } @@ -21,32 +18,6 @@ declare global { } } -const pluginErrorRegex = /\(http:\/\/localhost:1337\/plugins\/(.*)\//; -const pluginSourceMapErrorRegex = /\(decky:\/\/decky\/plugin\/(.*)\//; -const legacyPluginErrorRegex = /\(decky:\/\/decky\/legacy_plugin\/(.*)\/index.js/; - -function getLikelyErrorSource(error: ReactErrorInfo): [source: string, wasPlugin: boolean] { - const pluginMatch = error.error.stack?.match(pluginErrorRegex); - if (pluginMatch) { - return [decodeURIComponent(pluginMatch[1]), true]; - } - - const pluginMatchViaMap = error.error.stack?.match(pluginSourceMapErrorRegex); - if (pluginMatchViaMap) { - return [decodeURIComponent(pluginMatchViaMap[1]), true]; - } - - const legacyPluginMatch = error.error.stack?.match(legacyPluginErrorRegex); - if (legacyPluginMatch) { - return [decodeURIComponent(legacyPluginMatch[1]), true]; - } - - if (error.error.stack?.includes('http://localhost:1337/')) { - return ['the Decky frontend', false]; - } - return ['Steam', false]; -} - export const startSSH = DeckyBackend.callable('utilities/start_ssh'); export const starrCEFForwarding = DeckyBackend.callable('utilities/allow_remote_debugging'); @@ -55,146 +26,171 @@ function ipToString(ip: number) { } // Intentionally not localized since we can't really trust React here -const DeckyErrorBoundary: FunctionComponent = ({ error, reset }) => { +const DeckyErrorBoundary: FunctionComponent = ({ error, identifier, reset }) => { const [actionLog, addLogLine] = useReducer((log: string, line: string) => (log += '\n' + line), ''); const [actionsEnabled, setActionsEnabled] = useState(true); const [debugAllowed, setDebugAllowed] = useState(true); - const [errorSource, wasCausedByPlugin] = getLikelyErrorSource(error); - + // Intentionally doesn't use DeckyState. + const [versionInfo, setVersionInfo] = useState(); + const [errorSource, wasCausedByPlugin] = getLikelyErrorSourceFromValveReactError(error); + useEffect(() => { + DeckyPluginLoader.updateVersion().then(setVersionInfo); + }, []); return ( -
-

+ +
- ⚠️ An error occured rendering this content. -

-

This error likely occured in {getLikelyErrorSource(error)}.

- {actionLog?.length > 0 && ( -
+        

+ ⚠️ An error occured rendering this content. +

+
           
-            Running actions...
-            {actionLog}
+            {identifier && `Error Reference: ${identifier}`}
+            {versionInfo?.current && `\nDecky Version: ${versionInfo.current}`}
           
         
- )} - {actionsEnabled && ( - <> -

Actions:

-

Use the touch screen.

-
- - -
-
- - -
- {debugAllowed && ( +

This error likely occured in {errorSource}.

+ {actionLog?.length > 0 && ( +
+            
+              Running actions...
+              {actionLog}
+            
+          
+ )} + {actionsEnabled && ( + <> +

Actions:

+

Use the touch screen.

+
- )} - {wasCausedByPlugin && (
- {'\n'} +
- )} - - )} + {debugAllowed && ( +
+ +
+ )} + {wasCausedByPlugin && ( +
+ {'\n'} + +
+ )} + + )} -
-        
-          {error.error.stack}
-          {'\n\n'}
-          Component Stack:
-          {error.info.componentStack}
-        
-      
-
+
+          
+            {error.error.stack}
+            {'\n\n'}
+            Component Stack:
+            {error.info.componentStack}
+          
+        
+ + ); }; diff --git a/frontend/src/components/DeckyToasterState.tsx b/frontend/src/components/DeckyToasterState.tsx index 8d0a5d45..715ed76d 100644 --- a/frontend/src/components/DeckyToasterState.tsx +++ b/frontend/src/components/DeckyToasterState.tsx @@ -1,5 +1,5 @@ import type { ToastData } from '@decky/api'; -import { FC, createContext, useContext, useEffect, useState } from 'react'; +import { FC, ReactNode, createContext, useContext, useEffect, useState } from 'react'; interface PublicDeckyToasterState { toasts: Set; @@ -41,6 +41,7 @@ export const useDeckyToasterState = () => useContext(DeckyToasterContext); interface Props { deckyToasterState: DeckyToasterState; + children: ReactNode; } export const DeckyToasterStateContextProvider: FC = ({ children, deckyToasterState }) => { diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx index ce20ac4a..997e576b 100644 --- a/frontend/src/components/PluginView.tsx +++ b/frontend/src/components/PluginView.tsx @@ -1,5 +1,5 @@ import { ButtonItem, Focusable, PanelSection, PanelSectionRow } from '@decky/ui'; -import { VFC, useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FaEyeSlash } from 'react-icons/fa'; @@ -9,7 +9,7 @@ import NotificationBadge from './NotificationBadge'; import { useQuickAccessVisible } from './QuickAccessVisibleState'; import TitleView from './TitleView'; -const PluginView: VFC = () => { +const PluginView: FC = () => { const { hiddenPlugins } = useDeckyState(); const { plugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } = useDeckyState(); const visible = useQuickAccessVisible(); diff --git a/frontend/src/components/QuickAccessVisibleState.tsx b/frontend/src/components/QuickAccessVisibleState.tsx index 1bfe0e65..f5c05061 100644 --- a/frontend/src/components/QuickAccessVisibleState.tsx +++ b/frontend/src/components/QuickAccessVisibleState.tsx @@ -1,10 +1,10 @@ -import { FC, createContext, useContext, useState } from 'react'; +import { FC, ReactNode, createContext, useContext, useState } from 'react'; const QuickAccessVisibleState = createContext(false); export const useQuickAccessVisible = () => useContext(QuickAccessVisibleState); -export const QuickAccessVisibleStateProvider: FC<{ tab: any }> = ({ children, tab }) => { +export const QuickAccessVisibleStateProvider: FC<{ tab: any; children: ReactNode }> = ({ children, tab }) => { const initial = tab.initialVisibility; const [visible, setVisible] = useState(initial); // HACK but i can't think of a better way to do this diff --git a/frontend/src/components/TitleView.tsx b/frontend/src/components/TitleView.tsx index c49e6df6..8b45aae4 100644 --- a/frontend/src/components/TitleView.tsx +++ b/frontend/src/components/TitleView.tsx @@ -1,5 +1,5 @@ import { DialogButton, Focusable, Router, staticClasses } from '@decky/ui'; -import { CSSProperties, VFC } from 'react'; +import { CSSProperties, FC } from 'react'; import { useTranslation } from 'react-i18next'; import { BsGearFill } from 'react-icons/bs'; import { FaArrowLeft, FaStore } from 'react-icons/fa'; @@ -14,7 +14,7 @@ const titleStyles: CSSProperties = { top: '0px', }; -const TitleView: VFC = () => { +const TitleView: FC = () => { const { activePlugin, closeActivePlugin } = useDeckyState(); const { t } = useTranslation(); -- cgit v1.2.3