fix(web): implement offline detection (#1065)

* xtreme connected edition

* change fallbackRender to fallbackComponent on ErrorBoundary

* call healthz endpoint when error is 500

* display custom offline message

* fix eslint indentation for switchCase

* Update ErrorPage.tsx

* check against error.cause

---------

Co-authored-by: Fabricio Silva <hi@fabricio.dev>
This commit is contained in:
Kyle Sanderson 2023-09-10 08:39:58 -07:00 committed by GitHub
parent d187daa566
commit a1a16adbab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 25 deletions

View file

@ -19,7 +19,7 @@ module.exports = {
// Allow only double quotes and backticks // Allow only double quotes and backticks
quotes: ["error", "double"], quotes: ["error", "double"],
// Warn if a line isn't indented with a multiple of 2 // Warn if a line isn't indented with a multiple of 2
indent: ["warn", 2], indent: ["warn", 2, { "SwitchCase": 1 }],
// Don't enforce any particular brace style // Don't enforce any particular brace style
curly: "off", curly: "off",
// Allow only vars starting with _ to be ununsed vars // Allow only vars starting with _ to be ununsed vars
@ -64,7 +64,7 @@ module.exports = {
"@typescript-eslint/quotes": ["error", "double"], "@typescript-eslint/quotes": ["error", "double"],
semi: "off", semi: "off",
"@typescript-eslint/semi": ["warn", "always"], "@typescript-eslint/semi": ["warn", "always"],
indent: ["warn", 2], indent: ["warn", 2, { "SwitchCase": 1 }],
"@typescript-eslint/indent": "off", "@typescript-eslint/indent": "off",
"@typescript-eslint/comma-dangle": "warn", "@typescript-eslint/comma-dangle": "warn",
"keyword-spacing": "off", "keyword-spacing": "off",

View file

@ -22,7 +22,7 @@ const queryClient = new QueryClient({
// delay = Math.min(1000 * 2 ** attemptIndex, 30000) // delay = Math.min(1000 * 2 ** attemptIndex, 30000)
retry: true, retry: true,
useErrorBoundary: true, useErrorBoundary: true,
suspense: true, suspense: true
}, },
mutations: { mutations: {
onError: (error) => { onError: (error) => {
@ -47,7 +47,7 @@ export function App() {
return ( return (
<ErrorBoundary <ErrorBoundary
onReset={reset} onReset={reset}
fallbackRender={ErrorPage} FallbackComponent={ErrorPage}
> >
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Portal> <Portal>

View file

@ -20,7 +20,7 @@ interface HttpConfig {
function encodeRFC3986URIComponent(str: string): string { function encodeRFC3986URIComponent(str: string): string {
return encodeURIComponent(str).replace( return encodeURIComponent(str).replace(
/[!'()*]/g, /[!'()*]/g,
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
); );
} }
@ -82,27 +82,27 @@ export async function HttpClient<T = unknown>(
const response = await window.fetch(`${baseUrl()}${endpoint}`, init); const response = await window.fetch(`${baseUrl()}${endpoint}`, init);
switch (response.status) { switch (response.status) {
case 204: case 204:
// 204 contains no data, but indicates success // 204 contains no data, but indicates success
return Promise.resolve<T>({} as T); return Promise.resolve<T>({} as T);
case 401: case 401:
// Remove auth info from localStorage // Remove auth info from localStorage
AuthContext.reset(); AuthContext.reset();
// Show an error toast to notify the user what occurred // Show an error toast to notify the user what occurred
return Promise.reject(new Error(`[401] Unauthorized: "${endpoint}"`)); return Promise.reject(new Error(`[401] Unauthorized: "${endpoint}"`));
case 404: case 404:
return Promise.reject(new Error(`[404] Not found: "${endpoint}"`)); return Promise.reject(new Error(`[404] Not found: "${endpoint}"`));
case 500: case 500:
const health = await window.fetch(`${baseUrl()}api/healthz/liveness`); const health = await window.fetch(`${baseUrl()}api/healthz/liveness`);
if (!health.ok) { if (!health.ok) {
return Promise.reject( return Promise.reject(
new Error(`[500] Offline (Internal server error): "${endpoint}"`, { cause: "OFFLINE" }) new Error(`[500] Offline (Internal server error): "${endpoint}"`, { cause: "OFFLINE" })
); );
} }
break; break;
default: default:
break; break;
} }
const isJson = response.headers.get("Content-Type")?.includes("application/json"); const isJson = response.headers.get("Content-Type")?.includes("application/json");

View file

@ -17,11 +17,20 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
} }
}); });
const parseTitle = () => {
switch (error?.cause) {
case "OFFLINE":
return "Connection to Autobrr failed! Check the application state and verify your connectivity.";
default:
return "We caught an unrecoverable error!";
}
};
return ( return (
<div className="min-h-screen flex flex-col justify-center py-12 px-2 sm:px-6 lg:px-8"> <div className="min-h-screen flex flex-col justify-center py-12 px-2 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-screen-md md:max-w-screen-lg lg:max-w-screen-xl"> <div className="sm:mx-auto sm:w-full sm:max-w-screen-md md:max-w-screen-lg lg:max-w-screen-xl">
<h1 className="text-3xl font-bold leading-6 text-gray-900 dark:text-gray-200 mt-4 mb-3"> <h1 className="text-3xl font-bold leading-6 text-gray-900 dark:text-gray-200 mt-4 mb-3">
We caught an unrecoverable error! {parseTitle()}
</h1> </h1>
<h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4"> <h3 className="text-xl leading-6 text-gray-700 dark:text-gray-400 mb-4">
Please consider reporting this error to our Please consider reporting this error to our
@ -67,7 +76,7 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
</pre> </pre>
) : null} ) : null}
<span className="block text-gray-800 mb-2 text-md"> <span className="block text-gray-800 mb-2 text-md">
You can try resetting the page state using the button provided below. You can try resetting the page state using the button provided below or restarting your autobrr application.
However, this is not guaranteed to fix the error. However, this is not guaranteed to fix the error.
</span> </span>
<button <button