diff --git a/src/client/AppNext.tsx b/src/client/AppNext.tsx
index 31dde983..1f6981a3 100644
--- a/src/client/AppNext.tsx
+++ b/src/client/AppNext.tsx
@@ -18,6 +18,7 @@ import { NAV_LINKS, SideNavItems } from './components/SideNav';
import { LogsFetchable } from './components/LogsNext';
import { SplitLayout } from './components/layouts/SplitLayout';
import { ComponentDetailedRoutable } from './components/msComponent/MSComponentDetailed';
+import { MSErrorBoundary } from './components/ErrorBoundary';
function NoMatch() {
const location = useLocation();
@@ -73,11 +74,11 @@ const routesNested: RouteObject[] = [
Component: Layout,
children: [ {
index: true,
- element: ,
+ element: ,
},
{
path: "components/:componentId",
- element:
+ element:
},
{
path: "*",
diff --git a/src/client/components/ActivityTimeline.tsx b/src/client/components/ActivityTimeline.tsx
index 1d1809a8..1569847d 100644
--- a/src/client/components/ActivityTimeline.tsx
+++ b/src/client/components/ActivityTimeline.tsx
@@ -20,6 +20,7 @@ import { MSCollapsible } from "./MSCollapsible";
import { TimelineErrorIcon } from "./timeline/TimelineIcon";
import { Muted } from "./Typography";
import { PlayApiCommonDetailed } from "../../core/Api";
+import { MSErrorBoundary } from "./ErrorBoundary";
export interface ActivityDetailProps {
@@ -103,6 +104,7 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
}
return (
+
@@ -241,5 +243,6 @@ export const ActivityTimeline = (props: ActivityDetailProps) => {
) : null}
+
)
}
\ No newline at end of file
diff --git a/src/client/components/AppHeader.tsx b/src/client/components/AppHeader.tsx
index 90b03132..fb58d36e 100644
--- a/src/client/components/AppHeader.tsx
+++ b/src/client/components/AppHeader.tsx
@@ -16,6 +16,7 @@ import { useSSEContext, useSSEStatus } from "@flamefrontend/sse-runtime-react";
import { ToggleTip } from "./ToggleTip";
import { ErrorLike } from "../../core/Atomic";
import { ErrorAlert } from "./ErrorAlert";
+import { MSErrorBoundary } from "./ErrorBoundary";
export const AppTitle = (props: { fetchable?: boolean } = {}) => {
const {
@@ -152,7 +153,7 @@ export const RightHeaderFloatingLogs = (props: {streamable?: boolean}) => {
-
+
diff --git a/src/client/components/ErrorAlert.tsx b/src/client/components/ErrorAlert.tsx
index 7437adad..6ca26a66 100644
--- a/src/client/components/ErrorAlert.tsx
+++ b/src/client/components/ErrorAlert.tsx
@@ -3,20 +3,21 @@ import { Fragment } from 'react';
import { ErrorLike } from '../../core/Atomic';
import { ChakraCodeBlock } from './CodeBlock';
import { ChakraClip } from './ChakraClipboard';
+import { ErrorIsh, isErrorIsh } from '../../core/ErrorUtils';
export interface ErrorAlertProps {
- error: ErrorLike
+ error: ErrorIsh
status?: "error" | "info" | "warning" | "success" | "neutral"
}
export const ErrorAlert = (props: ErrorAlertProps) => {
- if(props.error === undefined || props.error === null) {
+ if(!isErrorIsh(props.error)) {
return null;
}
let causes: ErrorData[] = [];
- if(props.error.cause !== undefined && typeof props.error.cause === 'object' && props.error.cause !== null) {
- causes = walkError(props.error.cause as ErrorLike);
+ if(isErrorIsh(props.error.cause)) {
+ causes = walkError(props.error.cause);
}
return (
@@ -51,7 +52,7 @@ interface ErrorData {
stack?: string
}
-const walkError = (err: ErrorLike, errors: ErrorData[] = []): ErrorData[] => {
+const walkError = (err: ErrorIsh, errors: ErrorData[] = []): ErrorData[] => {
const thisErr: ErrorData = {
name: err.name,
code: 'code' in err ? err.code : undefined,
diff --git a/src/client/components/ErrorBoundary.tsx b/src/client/components/ErrorBoundary.tsx
index 4f59aacd..09d9ebe9 100644
--- a/src/client/components/ErrorBoundary.tsx
+++ b/src/client/components/ErrorBoundary.tsx
@@ -13,7 +13,7 @@ export const MSErrorBoundary = (props: React.PropsWithChildren) => {
Error while render
-
+
diff --git a/src/client/components/MSCollapsible.tsx b/src/client/components/MSCollapsible.tsx
index 143381c3..71db76cb 100644
--- a/src/client/components/MSCollapsible.tsx
+++ b/src/client/components/MSCollapsible.tsx
@@ -1,6 +1,7 @@
import { Collapsible, Stack, Box, useBreakpointValue } from "@chakra-ui/react"
import { ComponentProps, PropsWithChildren, useState, useEffect, useMemo } from "react";
import { LuChevronRight } from "react-icons/lu"
+import { MSErrorBoundary } from "./ErrorBoundary";
//padding="0" borderWidth="0px"
@@ -127,7 +128,9 @@ export const MSCollapsible = (props: MSCollapsibleProps) => {
+
{props.children}
+
diff --git a/src/client/components/PlayData.tsx b/src/client/components/PlayData.tsx
index a460cd2d..3908f094 100644
--- a/src/client/components/PlayData.tsx
+++ b/src/client/components/PlayData.tsx
@@ -9,6 +9,7 @@ import { TextMuted } from './TextMuted.js';
import { formatNumber } from '../../core/DataUtils.js';
import { Muted } from './Typography.js';
import { ArtistCreditTags } from './ArtistCreditDisplay.js';
+import { MSErrorBoundary } from './ErrorBoundary.js';
const EmptyPlayData = () => {
return (
@@ -71,19 +72,21 @@ export const PlayData = (props?: PlayInfoProps) => {
return (
- {code}
-
-
- Initial
- Final
-
-
- {codeMode ? : }
-
-
- {codeMode ? : }
-
-
+
+ {code}
+
+
+ Initial
+ Final
+
+
+ {codeMode ? : }
+
+
+ {codeMode ? : }
+
+
+
);
}
diff --git a/src/client/components/msComponent/MSComponentDetailed.tsx b/src/client/components/msComponent/MSComponentDetailed.tsx
index 65f6a860..032265ba 100644
--- a/src/client/components/msComponent/MSComponentDetailed.tsx
+++ b/src/client/components/msComponent/MSComponentDetailed.tsx
@@ -25,6 +25,7 @@ import dayjs from "dayjs";
import { shortTodayAwareFormat } from "../../../core/TimeUtils.js";
import { durationToHuman } from "../../../backend/utils.js";
import { tanQueries } from "../../queries/index.js";
+import { MSErrorBoundary } from "../ErrorBoundary.js";
export const MSComponentHeading = (props: { data?: Pick, fetchable?: boolean }) => {
if (props.data === undefined) {
@@ -111,6 +112,7 @@ export const ComponentDetailedDesktop = (props: {data?: ComponentCommonApiJson,
}
}
return (
+
@@ -135,6 +137,7 @@ export const ComponentDetailedDesktop = (props: {data?: ComponentCommonApiJson,
{isComponentTypeSource(props.data.mode) ? 'Plays' : 'Scrobbles'}
+
)
}
diff --git a/src/client/components/msComponent/MSComponentList.tsx b/src/client/components/msComponent/MSComponentList.tsx
index 2e558781..59978493 100644
--- a/src/client/components/msComponent/MSComponentList.tsx
+++ b/src/client/components/msComponent/MSComponentList.tsx
@@ -7,6 +7,7 @@ import ky from 'ky';
import { baseUrl } from "../../utils";
import { ErrorAlert } from "../ErrorAlert";
import { tanQueries } from "../../queries";
+import { MSErrorBoundary } from "../ErrorBoundary";
export interface ComponentListProps {
components: ComponentsApiJson[]
@@ -33,7 +34,7 @@ export const MSComponentList = (props: ComponentListProps) => {
return x.mode === 'source';
}
return x.mode === 'client';
- }).map(x => props.fetchable ? : )}
+ }).map(x => props.fetchable ? : )}
)
diff --git a/src/client/components/msComponent/MSComponentSummary.tsx b/src/client/components/msComponent/MSComponentSummary.tsx
index 988901db..3ae22b5e 100644
--- a/src/client/components/msComponent/MSComponentSummary.tsx
+++ b/src/client/components/msComponent/MSComponentSummary.tsx
@@ -16,6 +16,7 @@ import {
} from "@flamefrontend/sse-runtime-react";
import { CountLiveIndicator, DeadLetterIndicator, QueuedIndicator } from "./Stats.js";
import { ComponentStateBadge } from "../Badges.js";
+import { MSErrorBoundary } from "../ErrorBoundary.js";
const presentPlayersContainerProps: ComponentProps = {
paddingTop: '2',
@@ -43,7 +44,9 @@ export const MSComponentSummary = (props: { data: ComponentCommonApiJson, fetcha
);
}
- return (
+ return (
+
+
@@ -67,7 +70,8 @@ export const MSComponentSummary = (props: { data: ComponentCommonApiJson, fetcha
{body}
- )
+
+ )
}
const QuickStatsSource = (props: { data: ComponentCommonApiJson, streamable?: boolean }) => {
diff --git a/src/core/ErrorUtils.ts b/src/core/ErrorUtils.ts
index 320c5290..c8b21a98 100644
--- a/src/core/ErrorUtils.ts
+++ b/src/core/ErrorUtils.ts
@@ -4,6 +4,21 @@ import { ErrorLike, isErrorLike } from 'serialize-error';
export type ErrorIsh = Error | MarkOptional;
+export const isErrorIsh = (val: unknown): val is ErrorIsh => {
+ if(val === undefined || val === null || typeof val !== 'object') {
+ return false;
+ }
+ if(!('message' in val)) {
+ return false;
+ }
+ if('cause' in val) {
+ if(!isErrorIsh(val.cause)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/**
* Adapted from https://github.com/voxpelli/pony-cause
* */