/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ import {FlexColumn, Button, styled, Text, FlexRow, Spacer} from '../ui'; import React, {Component} from 'react'; import {setExportStatusComponent, unsetShare} from '../reducers/application'; import {reportPlatformFailures} from '../utils/metrics'; import CancellableExportStatus from './CancellableExportStatus'; import {performance} from 'perf_hooks'; import {Logger} from '../fb-interfaces/Logger'; import {Idler} from '../utils/Idler'; import { exportStoreToFile, EXPORT_FLIPPER_TRACE_EVENT, displayFetchMetadataErrors, } from '../utils/exportData'; import ShareSheetErrorList from './ShareSheetErrorList'; import ShareSheetPendingDialog from './ShareSheetPendingDialog'; import {ReactReduxContext} from 'react-redux'; import {MiddlewareAPI} from '../reducers/index'; const Container = styled(FlexColumn)({ padding: 20, width: 500, }); const ErrorMessage = styled(Text)({ display: 'block', marginTop: 6, wordBreak: 'break-all', whiteSpace: 'pre-line', lineHeight: 1.35, }); const Title = styled(Text)({ marginBottom: 6, }); const InfoText = styled(Text)({ lineHeight: 1.35, marginBottom: 15, }); type Props = { onHide: () => void; file: string; logger: Logger; }; type State = { fetchMetaDataErrors: { [plugin: string]: Error; } | null; result: | { kind: 'success'; } | { kind: 'error'; error: Error; } | { kind: 'pending'; }; statusUpdate: string | null; runInBackground: boolean; }; export default class ShareSheetExportFile extends Component { static contextType = ReactReduxContext; get store(): MiddlewareAPI { return this.context.store; } state: State = { fetchMetaDataErrors: null, result: {kind: 'pending'}, statusUpdate: null, runInBackground: false, }; idler = new Idler(); dispatchAndUpdateToolBarStatus(msg: string) { this.store.dispatch( setExportStatusComponent( { this.idler.cancel(); this.store.dispatch(unsetShare()); }} />, ), ); } async componentDidMount() { const mark = 'shareSheetExportFile'; performance.mark(mark); try { if (!this.props.file) { return; } const {fetchMetaDataErrors} = await reportPlatformFailures( exportStoreToFile( this.props.file, this.store, false, this.idler, (msg: string) => { if (this.state.runInBackground) { this.dispatchAndUpdateToolBarStatus(msg); } else { this.setState({statusUpdate: msg}); } }, ), `${EXPORT_FLIPPER_TRACE_EVENT}:UI_FILE`, ); if (this.state.runInBackground) { new Notification('Shareable Flipper Export created', { body: `Saved to ${this.props.file}`, requireInteraction: true, }); } this.setState({fetchMetaDataErrors, result: {kind: 'success'}}); this.store.dispatch(unsetShare()); this.props.logger.trackTimeSince(mark, 'export:file-success'); } catch (err) { const result: { kind: 'error'; error: Error; } = { kind: 'error', error: err, }; if (!this.state.runInBackground) { // Show the error in UI. this.setState({result}); } this.store.dispatch(unsetShare()); this.props.logger.trackTimeSince(mark, 'export:file-error', result); throw err; } } renderSuccess() { const {title, errorArray} = displayFetchMetadataErrors( this.state.fetchMetaDataErrors, ); return ( {({store}) => ( Data Exported Successfully When sharing your Flipper data, consider that the captured data might contain sensitive information like access tokens used in network requests. )} ); } renderError(result: {kind: 'error'; error: Error}) { return ( {({store}) => ( Error {result.error.message || 'File could not be saved.'} )} ); } renderPending(statusUpdate: string | null) { return ( {({store}) => ( this.cancelAndHide(store)} onRunInBackground={() => { this.setState({runInBackground: true}); if (statusUpdate) { this.dispatchAndUpdateToolBarStatus(statusUpdate); } this.props.onHide(); }} /> )} ); } cancelAndHide(store: MiddlewareAPI) { store.dispatch(unsetShare()); this.props.onHide(); this.idler.cancel(); } render() { const {result, statusUpdate} = this.state; switch (result.kind) { case 'success': return this.renderSuccess(); case 'error': return this.renderError(result); case 'pending': return this.renderPending(statusUpdate); } } }