Add error boundary component to catch Web UI crashes
parent
39c8a71df8
commit
922d05864f
|
@ -0,0 +1,92 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hasError: false,
|
||||||
|
stackTrace: undefined,
|
||||||
|
componentStack: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, info) {
|
||||||
|
this.setState({
|
||||||
|
hasError: true,
|
||||||
|
stackTrace: error.stack,
|
||||||
|
componentStack: info && info.componentStack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReload(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { hasError, stackTrace, componentStack } = this.state;
|
||||||
|
|
||||||
|
if (!hasError) return this.props.children;
|
||||||
|
|
||||||
|
let debugInfo = '';
|
||||||
|
if (stackTrace) {
|
||||||
|
debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```';
|
||||||
|
}
|
||||||
|
if (componentStack) {
|
||||||
|
if (debugInfo) {
|
||||||
|
debugInfo += '\n\n\n';
|
||||||
|
}
|
||||||
|
debugInfo += 'React component stack\n---------------------\n\n```\n' + componentStack.toString() + '\n```';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div tabIndex='-1'>
|
||||||
|
<div className='error-boundary'>
|
||||||
|
<h1><FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." /></h1>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage id='web_app_crash.content' defaultMessage='You could try any of the following:' />
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<FormattedMessage
|
||||||
|
id='web_app_crash.report_issue'
|
||||||
|
defaultMessage='Report a bug in the {issuetracker}'
|
||||||
|
values={{ issuetracker: <a href='https://github.com/glitch-soc/mastodon/issues' rel='noopener' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }}
|
||||||
|
/>
|
||||||
|
{ debugInfo !== '' && (
|
||||||
|
<details>
|
||||||
|
<summary><FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' /></summary>
|
||||||
|
<textarea
|
||||||
|
className='web_app_crash-stacktrace'
|
||||||
|
value={debugInfo}
|
||||||
|
rows='10'
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</details>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<FormattedMessage
|
||||||
|
id='web_app_crash.reload_page'
|
||||||
|
defaultMessage='{reload} the current page'
|
||||||
|
values={{ reload: <a href='#' onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></a> }}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<FormattedMessage
|
||||||
|
id='web_app_crash.change_your_settings'
|
||||||
|
defaultMessage='Change your {settings}'
|
||||||
|
values={{ settings: <a href='/settings/preferences'><FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /></a> }}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { getLocale } from 'locales';
|
import { getLocale } from 'locales';
|
||||||
import initialState from 'flavours/glitch/util/initial_state';
|
import initialState from 'flavours/glitch/util/initial_state';
|
||||||
|
import ErrorBoundary from 'flavours/glitch/components/error_boundary';
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
|
@ -61,11 +62,13 @@ export default class Mastodon extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<IntlProvider locale={locale} messages={messages}>
|
<IntlProvider locale={locale} messages={messages}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<ErrorBoundary>
|
||||||
<BrowserRouter basename='/web'>
|
<BrowserRouter basename='/web'>
|
||||||
<ScrollContext>
|
<ScrollContext>
|
||||||
<Route path='/' component={UI} />
|
<Route path='/' component={UI} />
|
||||||
</ScrollContext>
|
</ScrollContext>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</ErrorBoundary>
|
||||||
</Provider>
|
</Provider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
.error-boundary {
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primary-text-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: disc;
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.web_app_crash-stacktrace {
|
||||||
|
width: 100%;
|
||||||
|
resize: none;
|
||||||
|
white-space: pre;
|
||||||
|
font-family: $font-monospace, monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1263,3 +1263,4 @@ noscript {
|
||||||
@import 'lists';
|
@import 'lists';
|
||||||
@import 'emoji_picker';
|
@import 'emoji_picker';
|
||||||
@import 'local_settings';
|
@import 'local_settings';
|
||||||
|
@import 'error_boundary';
|
||||||
|
|
Loading…
Reference in New Issue