Add error boundary component to catch Web UI crashes
This commit is contained in:
		
							parent
							
								
									c0675a272e
								
							
						
					
					
						commit
						cb2822b68a
					
				
					 4 changed files with 133 additions and 5 deletions
				
			
		
							
								
								
									
										92
									
								
								app/javascript/flavours/glitch/components/error_boundary.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								app/javascript/flavours/glitch/components/error_boundary.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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 { getLocale } from 'locales';
 | 
			
		||||
import initialState from 'flavours/glitch/util/initial_state';
 | 
			
		||||
import ErrorBoundary from 'flavours/glitch/components/error_boundary';
 | 
			
		||||
 | 
			
		||||
const { localeData, messages } = getLocale();
 | 
			
		||||
addLocaleData(localeData);
 | 
			
		||||
| 
						 | 
				
			
			@ -61,11 +62,13 @@ export default class Mastodon extends React.PureComponent {
 | 
			
		|||
    return (
 | 
			
		||||
      <IntlProvider locale={locale} messages={messages}>
 | 
			
		||||
        <Provider store={store}>
 | 
			
		||||
          <BrowserRouter basename='/web'>
 | 
			
		||||
            <ScrollContext>
 | 
			
		||||
              <Route path='/' component={UI} />
 | 
			
		||||
            </ScrollContext>
 | 
			
		||||
          </BrowserRouter>
 | 
			
		||||
          <ErrorBoundary>
 | 
			
		||||
            <BrowserRouter basename='/web'>
 | 
			
		||||
              <ScrollContext>
 | 
			
		||||
                <Route path='/' component={UI} />
 | 
			
		||||
              </ScrollContext>
 | 
			
		||||
            </BrowserRouter>
 | 
			
		||||
          </ErrorBoundary>
 | 
			
		||||
        </Provider>
 | 
			
		||||
      </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 'emoji_picker';
 | 
			
		||||
@import 'local_settings';
 | 
			
		||||
@import 'error_boundary';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue