Skip to content

Commit

Permalink
ErrorBoundary: include service worker state (#398)
Browse files Browse the repository at this point in the history
  • Loading branch information
incognitojam authored Jul 10, 2023
1 parent bc80933 commit 8cfae84
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 80 deletions.
10 changes: 7 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { getZoom, getClipsNav } from './url';
import { isDemo } from './demo';
import store, { history } from './store';

import ErrorFallback from './components/ErrorFallback';

const Explorer = lazy(() => import('./components/explorer'));
const AnonymousLanding = lazy(() => import('./components/anonymous'));

Expand Down Expand Up @@ -120,9 +122,11 @@ class App extends Component {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<Suspense fallback={this.renderLoading()}>
{ showLogin ? this.anonymousRoutes() : this.authRoutes() }
</Suspense>
<Sentry.ErrorBoundary fallback={props => <ErrorFallback {...props} />}>
<Suspense fallback={this.renderLoading()}>
{ showLogin ? this.anonymousRoutes() : this.authRoutes() }
</Suspense>
</Sentry.ErrorBoundary>
</ConnectedRouter>
</Provider>
);
Expand Down
92 changes: 92 additions & 0 deletions src/components/ErrorFallback.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useEffect, useRef, useState } from 'react';

import { Check, ContentCopy } from '../icons';

const ErrorFallback = ({ error, componentStack }) => {
const [swInfo, setSwInfo] = useState('');
const [copied, setCopied] = useState(false);
const copiedInterval = useRef(null);

console.log({ error, componentStack });

useEffect(() => {
if ('serviceWorker' in navigator) {
setSwInfo('loading...');
navigator.serviceWorker.getRegistrations().then((registrations) => {
if (registrations.length === 0) {
setSwInfo('none');
return;
}
const serviceWorkers = [];
for (const registration of registrations) {
serviceWorkers.push(`${registration.scope} ${registration.active?.state}`);
}
setSwInfo(serviceWorkers.join('; '));
}).catch((err) => {
setSwInfo(err.toString());
});
} else {
setSwInfo('not supported');
}
}, []);

const information = `connect version: ${import.meta.env.VITE_APP_GIT_SHA || 'unknown'}
Build timestamp: ${import.meta.env.VITE_APP_BUILD_TIMESTAMP || 'unknown'}
URL: ${window.location.href}
Browser: ${window.navigator.userAgent}
Window: ${window.innerWidth}x${window.innerHeight}
Service workers: ${swInfo}
${error.toString()}${componentStack}`;

const copyError = async () => {
if (copiedInterval.current) {
clearTimeout(copiedInterval.current);
}
try {
await navigator.clipboard.writeText('```' + information + '```');
setCopied(true);
copiedInterval.current = setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};

return (
<div className="m-4">
<div className=" prose prose-invert">
<h2>Oops!</h2>
<p>Something went wrong. Please reload the page.</p>
<p>
If you continue to have problems, let us know on <a href="https://discord.comma.ai" target="_blank">Discord</a>{` `}
in the <span className="whitespace-nowrap"><strong>#connect-feedback</strong></span> channel.
</p>
<p>
<a className="bg-blue-600 rounded-md px-4 py-2 font-bold hover:bg-blue-500 transition-colors no-underline" href="">
Reload
</a>
</p>
</div>
<details className="mt-8">
<summary>Show debugging information</summary>
<div className="relative bg-black rounded-xl mt-2 overflow-hidden">
<pre className="select-all overflow-x-auto px-4 pt-4 pb-2">
{information}
</pre>
<button
className={`absolute right-2 top-2 flex rounded-md pl-2 pr-2 py-2 text-white font-bold transition-colors ${copied ? 'bg-green-500' : 'bg-gray-700 hover:bg-gray-600'}`}
onClick={copyError}
aria-label={copied ? 'Copied' : 'Copy error'}
>
{copied ? <Check /> : <ContentCopy />}
<span className="ml-1">{copied ? 'Copied' : 'Copy error'}</span>
</button>
</div>
</details>
</div>
);
};

export default ErrorFallback;
88 changes: 11 additions & 77 deletions src/components/explorer.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, lazy, Suspense, useMemo, useRef, useState } from 'react';
import React, { Component, lazy, Suspense } from 'react';
import { connect } from 'react-redux';
import Obstruction from 'obstruction';
import localforage from 'localforage';
Expand All @@ -17,12 +17,11 @@ import AppDrawer from './AppDrawer';
import PullDownReload from './utils/PullDownReload';

import { analyticsEvent, selectDevice, updateDevice } from '../actions';
import ResizeHandler from './ResizeHandler';
import init from '../actions/startup';
import Colors from '../colors';
import { Check, ContentCopy } from '../icons';
import { verifyPairToken, pairErrorToMessage } from '../utils';
import { play, pause } from '../timeline/playback';
import init from '../actions/startup';
import ResizeHandler from './ResizeHandler';

const ClipView = lazy(() => import('./ClipView'));
const DriveView = lazy(() => import('./DriveView'));
Expand Down Expand Up @@ -62,69 +61,6 @@ const styles = (theme) => ({
},
});

const ErrorFallback = ({ error, componentStack }) => {
const [copied, setCopied] = useState(false);
const copiedInterval = useRef(null);

const version = import.meta.env.VITE_APP_GIT_SHA || 'unknown';
const userAgent = window.navigator.userAgent;
const platform = window.navigator.platform;

const information = useMemo(() => `connect version: ${version}
URL: ${window.location.href}
Device: ${platform}
Browser: ${userAgent}
Window: ${window.innerWidth}x${window.innerHeight}
${error.toString()}${componentStack}`, [version, userAgent, platform, error, componentStack]);

const copyError = async () => {
if (copiedInterval.current) {
clearTimeout(copiedInterval.current);
}
try {
await navigator.clipboard.writeText('```' + information + '```');
setCopied(true);
copiedInterval.current = setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};

return (
<div className="m-4 prose prose-invert">
<h2>Oops!</h2>
<p>Something went wrong. Please reload the page.</p>
<p>
If you continue to have problems, let us know on <a href="https://discord.comma.ai" target="_blank">Discord</a>{` `}
in the <span className="whitespace-nowrap"><strong>#connect-feedback</strong></span> channel.
</p>
<p>
<a className="bg-blue-600 rounded-md px-4 py-2 font-bold hover:bg-blue-500 transition-colors no-underline" href="">
Reload
</a>
</p>
<details>
<summary>Show debugging information</summary>
<div className="relative">
<pre className="select-all">
{information}
</pre>
<button
className={`absolute right-1 top-1 flex rounded-md pl-2 pr-2 py-1 text-white font-bold transition-colors ${copied ? 'bg-green-500' : 'bg-gray-700 hover:bg-gray-600'}`}
onClick={copyError}
aria-label={copied ? 'Copied' : 'Copy error'}
>
{copied ? <Check /> : <ContentCopy />}
<span className="ml-1">{copied ? 'Copied' : 'Copy error'}</span>
</button>
</div>
</details>
</div>
);
};

class ExplorerApp extends Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -291,16 +227,14 @@ class ExplorerApp extends Component {
style={ drawerStyles }
/>
<div className={ classes.window } style={ containerStyles }>
<Sentry.ErrorBoundary fallback={props => <ErrorFallback {...props} />}>
<Suspense fallback={this.renderLoading()}>
{ noDevicesUpsell
? <NoDeviceUpsell />
: (clips
? <ClipView />
: (zoom ? <DriveView /> : <Dashboard />)
) }
</Suspense>
</Sentry.ErrorBoundary>
<Suspense fallback={this.renderLoading()}>
{ noDevicesUpsell
? <NoDeviceUpsell />
: (clips
? <ClipView />
: (zoom ? <DriveView /> : <Dashboard />)
) }
</Suspense>
</div>
<IosPwaPopup />
<Modal open={ Boolean(pairLoading || pairError || pairDongleId) } onClose={ this.closePair }>
Expand Down

0 comments on commit 8cfae84

Please sign in to comment.