diff --git a/lib/actions/report-actions.js b/lib/actions/report-actions.js index b898bbdee..9b9eeae95 100644 --- a/lib/actions/report-actions.js +++ b/lib/actions/report-actions.js @@ -1,50 +1,41 @@ // @flow import type { ClientReportCreationRequest, ReportCreationResponse, } from '../types/report-types.js'; import { sendReports as callSendReports } from '../utils/reports-service.js'; const sendReportActionTypes = Object.freeze({ started: 'SEND_REPORT_STARTED', success: 'SEND_REPORT_SUCCESS', failed: 'SEND_REPORT_FAILED', }); -const sendReport = - (): (( - request: ClientReportCreationRequest, - ) => Promise) => - async request => { - const { - reportIDs: [id], - } = await callSendReports([request]); - if (!id) { - throw new Error('Server did not return report ID'); - } - return { id }; - }; +const sendReport: ( + request: ClientReportCreationRequest, +) => Promise = async request => { + const { + reportIDs: [id], + } = await callSendReports([request]); + + if (!id) { + throw new Error('Server did not return report ID'); + } + return { id }; +}; const sendReportsActionTypes = Object.freeze({ started: 'SEND_REPORTS_STARTED', success: 'SEND_REPORTS_SUCCESS', failed: 'SEND_REPORTS_FAILED', }); -const sendReports = - (): (( - reports: $ReadOnlyArray, - ) => Promise) => - async clientReports => { - await callSendReports(clientReports); - }; const queueReportsActionType = 'QUEUE_REPORTS'; export { sendReportActionTypes, sendReport, sendReportsActionTypes, - sendReports, queueReportsActionType, }; diff --git a/lib/socket/report-handler.react.js b/lib/socket/report-handler.react.js index ce453fe7a..31e928ef8 100644 --- a/lib/socket/report-handler.react.js +++ b/lib/socket/report-handler.react.js @@ -1,97 +1,89 @@ // @flow import * as React from 'react'; -import { - sendReportsActionTypes, - sendReports, -} from '../actions/report-actions.js'; +import { sendReportsActionTypes } from '../actions/report-actions.js'; import { queuedReports as queuedReportsSelector } from '../selectors/socket-selectors.js'; import { type ClientReportCreationRequest, type ClearDeliveredReportsPayload, } from '../types/report-types.js'; import { type DispatchActionPromise, useDispatchActionPromise, - useServerCall, } from '../utils/action-utils.js'; import { useSelector } from '../utils/redux-utils.js'; +import { sendReports } from '../utils/reports-service.js'; type BaseProps = { +canSendReports: boolean, }; type Props = { ...BaseProps, +queuedReports: $ReadOnlyArray, +dispatchActionPromise: DispatchActionPromise, - +sendReports: ( - reports: $ReadOnlyArray, - ) => Promise, }; class ReportHandler extends React.PureComponent { componentDidMount() { if (this.props.canSendReports) { this.dispatchSendReports(this.props.queuedReports); } } componentDidUpdate(prevProps: Props) { if (!this.props.canSendReports) { return; } const couldSend = prevProps.canSendReports; const curReports = this.props.queuedReports; if (!couldSend) { this.dispatchSendReports(curReports); return; } const prevReports = prevProps.queuedReports; if (curReports !== prevReports) { const prevResponses = new Set(prevReports); const newResponses = curReports.filter( response => !prevResponses.has(response), ); this.dispatchSendReports(newResponses); } } render() { return null; } dispatchSendReports(reports: $ReadOnlyArray) { if (reports.length === 0) { return; } this.props.dispatchActionPromise( sendReportsActionTypes, this.sendReports(reports), ); } async sendReports(reports: $ReadOnlyArray) { - await this.props.sendReports(reports); + await sendReports(reports); return ({ reports }: ClearDeliveredReportsPayload); } } const ConnectedReportHandler: React.ComponentType = React.memo(function ConnectedReportHandler(props) { const queuedReports = useSelector(queuedReportsSelector); - const callSendReports = useServerCall(sendReports); const dispatchActionPromise = useDispatchActionPromise(); return ( ); }); export default ConnectedReportHandler; diff --git a/native/crash.react.js b/native/crash.react.js index c45e47c7f..1ecbf9cff 100644 --- a/native/crash.react.js +++ b/native/crash.react.js @@ -1,300 +1,290 @@ // @flow import Icon from '@expo/vector-icons/FontAwesome.js'; import Clipboard from '@react-native-clipboard/clipboard'; import invariant from 'invariant'; import _shuffle from 'lodash/fp/shuffle.js'; import * as React from 'react'; import { View, Text, Platform, StyleSheet, ScrollView, ActivityIndicator, } from 'react-native'; import { sendReportActionTypes, sendReport, } from 'lib/actions/report-actions.js'; import { logOutActionTypes, logOut } from 'lib/actions/user-actions.js'; import { preRequestUserStateSelector } from 'lib/selectors/account-selectors.js'; import type { LogOutResult } from 'lib/types/account-types.js'; -import { - type ErrorData, - type ClientReportCreationRequest, - type ReportCreationResponse, - reportTypes, -} from 'lib/types/report-types.js'; +import { type ErrorData, reportTypes } from 'lib/types/report-types.js'; import type { PreRequestUserState } from 'lib/types/session-types.js'; import { actionLogger } from 'lib/utils/action-logger.js'; import { type DispatchActionPromise, useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils.js'; import { generateReportID, useIsReportEnabled, } from 'lib/utils/report-utils.js'; import { sanitizeReduxReport, type ReduxCrashReport, } from 'lib/utils/sanitization.js'; import sleep from 'lib/utils/sleep.js'; import Button from './components/button.react.js'; import ConnectedStatusBar from './connected-status-bar.react.js'; import { commCoreModule } from './native-modules.js'; import { persistConfig, codeVersion } from './redux/persist.js'; import { useSelector } from './redux/redux-utils.js'; import { wipeAndExit } from './utils/crash-utils.js'; const errorTitles = ['Oh no!!', 'Womp womp womp...']; type BaseProps = { +errorData: $ReadOnlyArray, }; type Props = { ...BaseProps, // Redux state +preRequestUserState: PreRequestUserState, // Redux dispatch functions +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs - +sendReport: ( - request: ClientReportCreationRequest, - ) => Promise, +logOut: (preRequestUserState: PreRequestUserState) => Promise, +crashReportingEnabled: boolean, }; type State = { +errorReportID: ?string, +doneWaiting: boolean, }; class Crash extends React.PureComponent { errorTitle = _shuffle(errorTitles)[0]; constructor(props) { super(props); this.state = { errorReportID: null, doneWaiting: !props.crashReportingEnabled, }; } componentDidMount() { if (this.state.doneWaiting) { return; } this.props.dispatchActionPromise(sendReportActionTypes, this.sendReport()); this.timeOut(); } async timeOut() { // If it takes more than 10s, give up and let the user exit await sleep(10000); this.setState({ doneWaiting: true }); } render() { const errorText = [...this.props.errorData] .reverse() .map(errorData => errorData.error.message) .join('\n'); let crashID; if (!this.state.doneWaiting) { crashID = ; } else if (this.state.doneWaiting && this.state.errorReportID) { crashID = ( Crash report ID: {this.state.errorReportID} ); } else { crashID = ( Crash reporting can be enabled in the Profile tab. ); } const buttonStyle = { opacity: Number(this.state.doneWaiting) }; return ( {this.errorTitle} I’m sorry, but the app crashed. {crashID} Here’s some text that’s probably not helpful: {errorText} ); } async sendReport() { const sanitizedReduxReport: ReduxCrashReport = sanitizeReduxReport({ preloadedState: actionLogger.preloadedState, currentState: actionLogger.currentState, actions: actionLogger.actions, }); - const result = await this.props.sendReport({ + const result = await sendReport({ type: reportTypes.ERROR, platformDetails: { platform: Platform.OS, codeVersion, stateVersion: persistConfig.version, }, errors: this.props.errorData.map(data => ({ errorMessage: data.error.message, stack: data.error.stack, componentStack: data.info && data.info.componentStack, })), ...sanitizedReduxReport, id: generateReportID(), }); this.setState({ errorReportID: result.id, doneWaiting: true, }); } onPressKill = () => { if (!this.state.doneWaiting) { return; } commCoreModule.terminate(); }; onPressWipe = async () => { if (!this.state.doneWaiting) { return; } this.props.dispatchActionPromise(logOutActionTypes, this.logOutAndExit()); }; async logOutAndExit() { try { await this.props.logOut(this.props.preRequestUserState); } catch (e) {} await wipeAndExit(); } onCopyCrashReportID = () => { invariant(this.state.errorReportID, 'should be set'); Clipboard.setString(this.state.errorReportID); }; } const styles = StyleSheet.create({ button: { backgroundColor: '#FF0000', borderRadius: 5, marginHorizontal: 10, paddingHorizontal: 10, paddingVertical: 5, }, buttonText: { color: 'white', fontSize: 16, }, buttons: { flexDirection: 'row', }, container: { alignItems: 'center', backgroundColor: 'white', flex: 1, justifyContent: 'center', }, copyCrashReportIDButtonText: { color: '#036AFF', }, crashID: { flexDirection: 'row', paddingBottom: 12, paddingTop: 2, }, crashIDText: { color: 'black', paddingRight: 8, }, errorReportID: { flexDirection: 'row', height: 20, }, errorReportIDText: { color: 'black', paddingRight: 8, }, errorText: { color: 'black', fontFamily: Platform.select({ ios: 'Menlo', default: 'monospace', }), }, header: { color: 'black', fontSize: 24, paddingBottom: 24, }, scrollView: { flex: 1, marginBottom: 24, marginTop: 12, maxHeight: 200, paddingHorizontal: 50, }, text: { color: 'black', paddingBottom: 12, }, }); const ConnectedCrash: React.ComponentType = React.memo( function ConnectedCrash(props: BaseProps) { const preRequestUserState = useSelector(preRequestUserStateSelector); const dispatchActionPromise = useDispatchActionPromise(); - const callSendReport = useServerCall(sendReport); const callLogOut = useServerCall(logOut); const crashReportingEnabled = useIsReportEnabled('crashReports'); return ( ); }, ); export default ConnectedCrash;