Add UI and logic for handling worker errors
This commit is contained in:
68
ui/providers/error-boundary-provider.tsx
Normal file
68
ui/providers/error-boundary-provider.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { Colors, Text, Toast, Toaster } from "@blueprintjs/core";
|
||||
import * as React from "react";
|
||||
|
||||
const CREATE_ISSUE_URL = "https://github.com/nilaymaj/esolang-park/issues/new";
|
||||
|
||||
const styles = {
|
||||
errorTitle: {
|
||||
fontWeight: "bold",
|
||||
whiteSpace: "pre-wrap" as "pre-wrap",
|
||||
},
|
||||
callStack: {
|
||||
padding: 10,
|
||||
borderRadius: 10,
|
||||
background: Colors.RED1,
|
||||
whiteSpace: "pre-wrap" as "pre-wrap",
|
||||
border: "1px solid " + Colors.RED4,
|
||||
},
|
||||
};
|
||||
|
||||
const ErrorBoundaryContext = React.createContext<{
|
||||
throwError: (error: Error) => void;
|
||||
}>({ throwError: () => {} });
|
||||
|
||||
/** Context provider for error handling */
|
||||
export const ErrorBoundaryProvider = (props: { children: React.ReactNode }) => {
|
||||
const [error, setError] = React.useState<Error | null>(null);
|
||||
|
||||
return (
|
||||
<ErrorBoundaryContext.Provider value={{ throwError: setError }}>
|
||||
<Toaster>
|
||||
{error && (
|
||||
<Toast
|
||||
icon="error"
|
||||
intent="danger"
|
||||
onDismiss={() => setError(null)}
|
||||
message={
|
||||
<Text>
|
||||
<p>An unexpected error occurred:</p>
|
||||
<pre style={styles.errorTitle}>{error.message}</pre>
|
||||
<pre style={styles.callStack}>{error.stack}</pre>
|
||||
<p>
|
||||
Please{" "}
|
||||
<a href={CREATE_ISSUE_URL} target="_blank">
|
||||
create an issue on GitHub
|
||||
</a>{" "}
|
||||
with:
|
||||
</p>
|
||||
<ul>
|
||||
<li>The language you were using</li>
|
||||
<li>The code you tried to run</li>
|
||||
<li>The error details and call stack above</li>
|
||||
</ul>
|
||||
<p>
|
||||
Once done, refresh the page to reload the IDE. If needed,{" "}
|
||||
<b>copy your code before refreshing the page</b>.
|
||||
</p>
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Toaster>
|
||||
{props.children}
|
||||
</ErrorBoundaryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/** Utility hook to access error boundary */
|
||||
export const useErrorBoundary = () => React.useContext(ErrorBoundaryContext);
|
@ -5,6 +5,7 @@ import {
|
||||
WorkerResponseData,
|
||||
} from "../engines/worker-constants";
|
||||
import { WorkerParseError, WorkerRuntimeError } from "../engines/worker-errors";
|
||||
import { useErrorBoundary } from "./providers/error-boundary-provider";
|
||||
|
||||
/** Possible states for the worker to be in */
|
||||
type WorkerState =
|
||||
@ -26,6 +27,7 @@ type WorkerState =
|
||||
export const useExecController = <RS>(langName: string) => {
|
||||
const workerRef = React.useRef<Worker | null>(null);
|
||||
const [workerState, setWorkerState] = React.useState<WorkerState>("loading");
|
||||
const errorBoundary = useErrorBoundary();
|
||||
|
||||
/**
|
||||
* Type-safe wrapper to abstract request-response cycle into
|
||||
@ -77,6 +79,20 @@ export const useExecController = <RS>(langName: string) => {
|
||||
(async () => {
|
||||
if (workerRef.current) throw new Error("Tried to reinitialize worker");
|
||||
workerRef.current = new Worker(`../workers/${langName}.js`);
|
||||
// Add event listener for bubbling errors to main thread
|
||||
workerRef.current.addEventListener(
|
||||
"message",
|
||||
(event: MessageEvent<WorkerResponseData<RS, any>>) => {
|
||||
if (event.data.type !== "error") return;
|
||||
const plainError = event.data.error;
|
||||
const err = new Error(
|
||||
`[Worker] ${plainError.name}: ${plainError.message}`
|
||||
);
|
||||
err.stack = event.data.error.stack;
|
||||
errorBoundary.throwError(err);
|
||||
// throw err;
|
||||
}
|
||||
);
|
||||
const res = await requestWorker({ type: "Init" });
|
||||
if (res.type === "ack" && res.data === "init") setWorkerState("empty");
|
||||
else throwUnexpectedRes("init", res);
|
||||
|
Reference in New Issue
Block a user