1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Add top-level error page (#7889)

Any error caught during a React component render or lifecycle method
will now be caught by the top-level error boundary, which shows the
user this new error page. The error page will display a simple error
message, and will show the details of the error in a collapsible
section.

The caught error is also reported to Sentry.

In development the error will be re-thrown to make it easier to see on
the console, but it is not re-thrown in production.
This commit is contained in:
Mark Stacey 2020-01-24 17:11:02 -04:00 committed by GitHub
parent 86c6280bc3
commit 560be4b4e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 190 additions and 12 deletions

View File

@ -531,6 +531,38 @@
"enterPasswordContinue": {
"message": "Enter password to continue"
},
"errorCode": {
"message": "Code: $1",
"description": "Displayed error code for debugging purposes. $1 is the error code"
},
"errorDetails": {
"message": "Error Details",
"description": "Title for collapsible section that displays error details for debugging purposes"
},
"errorMessage": {
"message": "Message: $1",
"description": "Displayed error message for debugging purposes. $1 is the error message"
},
"errorName": {
"message": "Code: $1",
"description": "Displayed error name for debugging purposes. $1 is the error name"
},
"errorPageTitle": {
"message": "MetaMask encountered an error",
"description": "Title of generic error page"
},
"errorPageMessage": {
"message": "Try again by reloading the page, or contact support at support@metamask.io",
"description": "Message displayed on generic error page in the fullscreen or notification UI"
},
"errorPagePopupMessage": {
"message": "Try again by closing and reopening the popup, or contact support at support@metamask.io",
"description": "Message displayed on generic error page in the popup UI"
},
"errorStack": {
"message": "Stack:",
"description": "Title for error stack, which is displayed for debugging purposes"
},
"ethereumPublicAddress": {
"message": "Ethereum Public Address"
},

View File

@ -0,0 +1,74 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
class ErrorPage extends PureComponent {
static contextTypes = {
t: PropTypes.func.isRequired,
}
static propTypes = {
error: PropTypes.object.isRequired,
}
renderErrorDetail (content) {
return (
<li>
<p>
{content}
</p>
</li>
)
}
renderErrorStack (title, stack) {
return (
<li>
<span>
{title}
</span>
<pre className="error-page__stack">
{stack}
</pre>
</li>
)
}
render () {
const { error } = this.props
const { t } = this.context
const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
return (
<section className="error-page">
<h1 className="error-page__header">
{t('errorPageTitle')}
</h1>
<h2 className="error-page__subheader">
{
isPopup
? t('errorPagePopupMessage')
: t('errorPageMessage')
}
</h2>
<section className="error-page__details">
<details>
<summary>
{t('errorDetails')}
</summary>
<ul>
{ error.message ? this.renderErrorDetail(t('errorMessage', [error.message])) : null }
{ error.code ? this.renderErrorDetail(t('errorCode', [error.code])) : null }
{ error.name ? this.renderErrorDetail(t('errorName', [error.name])) : null }
{ error.stack ? this.renderErrorStack(t('errorStack'), error.stack) : null }
</ul>
</details>
</section>
</section>
)
}
}
export default ErrorPage

View File

@ -0,0 +1 @@
export { default } from './error.component'

View File

@ -0,0 +1,41 @@
.error-page {
display: flex;
flex-flow: column nowrap;
align-items: center;
font-family: Roboto;
font-style: normal;
font-weight: normal;
padding: 35px 10px 10px 10px;
height: 100%;
&__header {
display: flex;
justify-content: center;
font-size: 42px;
padding: 10px 0;
text-align: center;
}
&__subheader {
font-size: 19px;
padding: 10px 0;
width: 100%;
max-width: 720px;
text-align: center;
}
&__details {
font-size: 18px;
overflow-y: auto;
width: 100%;
max-width: 720px;
padding-top: 10px;
}
&__stack {
overflow-x: auto;
background-color: #eee;
}
}

View File

@ -1,25 +1,53 @@
import React from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
import * as Sentry from '@sentry/browser'
import ErrorPage from './error'
import Routes from './routes'
import I18nProvider from '../helpers/higher-order-components/i18n-provider'
import MetaMetricsProvider from '../helpers/higher-order-components/metametrics/metametrics.provider'
const Index = props => {
const { store } = props
class Index extends PureComponent {
state = {}
return (
<Provider store={store}>
<HashRouter hashType="noslash">
<MetaMetricsProvider>
static getDerivedStateFromError (error) {
return { error }
}
componentDidCatch (error) {
Sentry.captureException(error)
}
render () {
const { error, errorId } = this.state
const { store } = this.props
if (error) {
return (
<Provider store={store}>
<I18nProvider>
<Routes />
<ErrorPage
error={error}
errorId={errorId}
/>
</I18nProvider>
</MetaMetricsProvider>
</HashRouter>
</Provider>
)
</Provider>
)
}
return (
<Provider store={store}>
<HashRouter hashType="noslash">
<MetaMetricsProvider>
<I18nProvider>
<Routes />
</I18nProvider>
</MetaMetricsProvider>
</HashRouter>
</Provider>
)
}
}
Index.propTypes = {

View File

@ -2,6 +2,8 @@
@import 'add-token/index';
@import 'error/index';
@import 'send/send';
@import 'confirm-add-token/index';