Browse Source

Merge pull request #634 from gre/make-errors-reported-on-init-crash

Make errors reported on init crash
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
a3a8160670
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      src/components/AppError.js
  2. 97
      src/components/RenderError.js
  3. 85
      src/components/ThrowBlock.js
  4. 2
      src/components/TranslatedError.js
  5. 16
      src/components/TriggerAppReady.js
  6. 5
      src/components/layout/Default.js
  7. 3
      src/renderer/init.js

23
src/components/AppError.js

@ -0,0 +1,23 @@
// @flow
import React from 'react'
import { ThemeProvider } from 'styled-components'
import { I18nextProvider } from 'react-i18next'
import theme from 'styles/theme'
import i18n from 'renderer/i18n/electron'
import TriggerAppReady from './TriggerAppReady'
import RenderError from './RenderError'
// Like App except it just render an error
const App = ({ language, error }: { error: Error, language: string }) => (
<I18nextProvider i18n={i18n} initialLanguage={language}>
<ThemeProvider theme={theme}>
<RenderError disableExport error={error}>
<TriggerAppReady />
</RenderError>
</ThemeProvider>
</I18nextProvider>
)
export default App

97
src/components/RenderError.js

@ -0,0 +1,97 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { shell, remote } from 'electron'
import qs from 'querystring'
import { translate } from 'react-i18next'
import { rgba } from 'styles/helpers'
import db from 'helpers/db'
import type { T } from 'types/common'
import ExportLogsBtn from 'components/ExportLogsBtn'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import TranslatedError from './TranslatedError'
type Props = {
error: Error,
t: T,
disableExport?: boolean,
children?: *,
}
const Container = styled(Box).attrs({
grow: true,
align: 'center',
justify: 'center',
bg: 'lightGraphite',
color: 'alertRed',
ff: 'Museo Sans|Bold',
flow: 2,
})``
const Inner = styled(Box).attrs({
p: 2,
bg: p => rgba(p.theme.colors.alertRed, 0.05),
borderRadius: 1,
})`
border: ${p => `1px solid ${rgba(p.theme.colors.alertRed, 0.1)}`};
`
class RenderError extends PureComponent<Props> {
handleCreateIssue = () => {
const { error } = this.props
if (!error) {
return
}
const q = qs.stringify({
title: `Error: ${error.message}`,
body: `Error was thrown:
\`\`\`
${error.stack}
\`\`\`
`,
})
shell.openExternal(`https://github.com/LedgerHQ/ledger-live-desktop/issues/new?${q}`)
}
handleRestart = () => {
remote.app.relaunch()
remote.app.exit()
}
handleReset = () => {
db.resetAll()
this.handleRestart()
}
render() {
const { error, t, disableExport, children } = this.props
return (
<Container>
<Inner>
<TranslatedError error={error} />
</Inner>
<Box horizontal flow={2}>
<Button primary onClick={this.handleRestart}>
{t('app:crash.restart')}
</Button>
<Button danger onClick={this.handleReset}>
{t('app:crash.reset')}
</Button>
{!disableExport ? <ExportLogsBtn /> : null}
<Button primary onClick={this.handleCreateIssue}>
{t('app:crash.createTicket')}
</Button>
</Box>
{children}
</Container>
)
}
}
export default translate()(RenderError)

85
src/components/ThrowBlock.js

@ -1,49 +1,16 @@
// @flow // @flow
import logger from 'logger' import logger from 'logger'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import styled from 'styled-components' import RenderError from 'components/RenderError'
import { shell, remote } from 'electron'
import qs from 'querystring'
import { translate } from 'react-i18next'
import { rgba } from 'styles/helpers'
import db from 'helpers/db'
import type { T } from 'types/common'
import ExportLogsBtn from 'components/ExportLogsBtn'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import TranslatedError from './TranslatedError'
type Props = { type Props = {
children: any, children: any,
t: T,
} }
type State = { type State = {
error: ?Error, error: ?Error,
} }
const Container = styled(Box).attrs({
grow: true,
align: 'center',
justify: 'center',
bg: 'lightGraphite',
color: 'alertRed',
ff: 'Museo Sans|Bold',
flow: 2,
})``
const Inner = styled(Box).attrs({
p: 2,
bg: p => rgba(p.theme.colors.alertRed, 0.05),
borderRadius: 1,
})`
border: ${p => `1px solid ${rgba(p.theme.colors.alertRed, 0.1)}`};
`
class ThrowBlock extends PureComponent<Props, State> { class ThrowBlock extends PureComponent<Props, State> {
state = { state = {
error: null, error: null,
@ -54,59 +21,13 @@ class ThrowBlock extends PureComponent<Props, State> {
this.setState({ error }) this.setState({ error })
} }
handleCreateIssue = () => {
const { error } = this.state
if (!error) {
return
}
const q = qs.stringify({
title: `Error: ${error.message}`,
body: `Error was thrown:
\`\`\`
${error.stack}
\`\`\`
`,
})
shell.openExternal(`https://github.com/LedgerHQ/ledger-live-desktop/issues/new?${q}`)
}
handleRestart = () => {
remote.app.relaunch()
remote.app.exit()
}
handleReset = () => {
db.resetAll()
this.handleRestart()
}
render() { render() {
const { error } = this.state const { error } = this.state
const { t } = this.props
if (error) { if (error) {
return ( return <RenderError error={error} />
<Container>
<Inner>
<TranslatedError error={error} />
</Inner>
<Box horizontal flow={2}>
<Button primary onClick={this.handleRestart}>
{t('app:crash.restart')}
</Button>
<Button danger onClick={this.handleReset}>
{t('app:crash.reset')}
</Button>
<ExportLogsBtn />
<Button primary onClick={this.handleCreateIssue}>
{t('app:crash.createTicket')}
</Button>
</Box>
</Container>
)
} }
return this.props.children return this.props.children
} }
} }
export default translate()(ThrowBlock) export default ThrowBlock

2
src/components/TranslatedError.js

@ -23,7 +23,7 @@ class TranslatedError extends PureComponent<Props> {
if (translation) { if (translation) {
return translation return translation
} }
logger.warn('TranslatedError: no transation!', error.name, error) logger.warn(`TranslatedError: no translation for '${error.name}'`, error)
return error.message || error.name || t('errors:generic') return error.message || error.name || t('errors:generic')
} }
} }

16
src/components/TriggerAppReady.js

@ -0,0 +1,16 @@
// @flow
import { PureComponent } from 'react'
export default class TriggerAppReady extends PureComponent<{}> {
componentDidMount() {
window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300)))
}
componentWillUnmount() {
clearTimeout(this._timeout)
}
_timeout: *
render() {
return null
}
}

5
src/components/layout/Default.js

@ -19,6 +19,7 @@ import ExchangePage from 'components/ExchangePage'
import SettingsPage from 'components/SettingsPage' import SettingsPage from 'components/SettingsPage'
import LibcoreBusyIndicator from 'components/LibcoreBusyIndicator' import LibcoreBusyIndicator from 'components/LibcoreBusyIndicator'
import DeviceBusyIndicator from 'components/DeviceBusyIndicator' import DeviceBusyIndicator from 'components/DeviceBusyIndicator'
import TriggerAppReady from 'components/TriggerAppReady'
import AppRegionDrag from 'components/AppRegionDrag' import AppRegionDrag from 'components/AppRegionDrag'
import IsUnlocked from 'components/IsUnlocked' import IsUnlocked from 'components/IsUnlocked'
@ -41,7 +42,6 @@ type Props = {
class Default extends Component<Props> { class Default extends Component<Props> {
componentDidMount() { componentDidMount() {
window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300)))
window.addEventListener('keydown', this.kbShortcut) window.addEventListener('keydown', this.kbShortcut)
} }
@ -59,7 +59,6 @@ class Default extends Component<Props> {
} }
componentWillUnmount() { componentWillUnmount() {
clearTimeout(this._timeout)
window.removeEventListener('keydown', this.kbShortcut) // Prevents adding multiple listeners when hot reloading window.removeEventListener('keydown', this.kbShortcut) // Prevents adding multiple listeners when hot reloading
} }
@ -69,12 +68,12 @@ class Default extends Component<Props> {
} }
} }
_timeout = undefined
_scrollContainer = null _scrollContainer = null
render() { render() {
return ( return (
<Fragment> <Fragment>
<TriggerAppReady />
{process.platform === 'darwin' && <AppRegionDrag />} {process.platform === 'darwin' && <AppRegionDrag />}
<IsUnlocked> <IsUnlocked>

3
src/renderer/init.js

@ -27,6 +27,7 @@ import hardReset from 'helpers/hardReset'
import sentry from 'sentry/browser' import sentry from 'sentry/browser'
import App from 'components/App' import App from 'components/App'
import AppError from 'components/AppError'
import 'styles/global' import 'styles/global'
@ -99,5 +100,5 @@ function r(Comp) {
init().catch(e => { init().catch(e => {
// for now we make the app crash instead of pending forever. later we can render the error OR try to recover, but probably this is unrecoverable cases. // for now we make the app crash instead of pending forever. later we can render the error OR try to recover, but probably this is unrecoverable cases.
logger.error(e) logger.error(e)
process.exit(1) r(<AppError error={e} language="en" />)
}) })

Loading…
Cancel
Save