/** * 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, Input, } from 'flipper'; import React, {Component} from 'react'; import {ReactReduxContext} from 'react-redux'; import {store} from '../store'; import { setExportStatusComponent, unsetShare, setExportURL, } from '../reducers/application'; import {Logger} from '../fb-interfaces/Logger'; import {Idler} from '../utils/Idler'; import { shareFlipperData, DataExportResult, DataExportError, } from '../fb-stubs/user'; import {exportStore, EXPORT_FLIPPER_TRACE_EVENT} from '../utils/exportData'; import {clipboard} from 'electron'; import ShareSheetErrorList from './ShareSheetErrorList'; import {reportPlatformFailures} from '../utils/metrics'; import CancellableExportStatus from './CancellableExportStatus'; import {performance} from 'perf_hooks'; import ShareSheetPendingDialog from './ShareSheetPendingDialog'; import {getInstance as getLogger} from '../fb-stubs/Logger'; import {resetSupportFormV2State} from '../reducers/supportForm'; export const SHARE_FLIPPER_TRACE_EVENT = 'share-flipper-link'; const Container = styled(FlexColumn)({ padding: 20, width: 500, }); const Copy = styled(Input)({ marginRight: 0, marginBottom: 15, }); const InfoText = styled(Text)({ lineHeight: 1.35, marginBottom: 15, }); const Title = styled(Text)({ marginBottom: 6, }); const ErrorMessage = styled(Text)({ display: 'block', marginTop: 6, wordBreak: 'break-all', whiteSpace: 'pre-line', lineHeight: 1.35, }); type Props = { onHide: () => any; logger: Logger; closeOnFinish: boolean; }; type State = { runInBackground: boolean; errorArray: Array; result: DataExportError | DataExportResult | null; statusUpdate: string | null; }; export default class ShareSheetExportUrl extends Component { state: State = { errorArray: [], result: null, statusUpdate: null, runInBackground: false, }; idler = new Idler(); dispatchAndUpdateToolBarStatus(msg: string) { store.dispatch( setExportStatusComponent( { this.idler.cancel(); store.dispatch(unsetShare()); }} />, ), ); } async componentDidMount() { const mark = 'shareSheetExportUrl'; performance.mark(mark); try { const statusUpdate = (msg: string) => { if (this.state.runInBackground) { this.dispatchAndUpdateToolBarStatus(msg); } else { this.setState({statusUpdate: msg}); } }; const {serializedString, errorArray} = await reportPlatformFailures( exportStore(store.getState(), this.idler, statusUpdate), `${EXPORT_FLIPPER_TRACE_EVENT}:UI_LINK`, ); const uploadMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:upload`; performance.mark(uploadMarker); statusUpdate('Uploading Flipper Trace...'); // TODO(T55169042): Implement error handling. Result is not tested // its result type right now, causing the upload indicator to stall forever. const result = await reportPlatformFailures( shareFlipperData(serializedString), `${SHARE_FLIPPER_TRACE_EVENT}`, ); getLogger().trackTimeSince(uploadMarker, uploadMarker, { plugins: store.getState().plugins.selectedPlugins, }); this.setState({errorArray, result}); const flipperUrl = (result as DataExportResult).flipperUrl; if (flipperUrl) { clipboard.writeText(String(flipperUrl)); store.dispatch(setExportURL(flipperUrl)); new Notification('Sharable Flipper trace created', { body: 'URL copied to clipboard', requireInteraction: true, }); } store.dispatch(resetSupportFormV2State()); this.props.logger.trackTimeSince(mark, 'export:url-success'); } catch (e) { if (!this.state.runInBackground) { const result: DataExportError = { error_class: 'EXPORT_ERROR', error: '', stacktrace: '', }; if (e instanceof Error) { result.error = e.message; result.stacktrace = e.stack || ''; } else { result.error = e; } this.setState({result}); } store.dispatch(unsetShare()); this.props.logger.trackTimeSince(mark, 'export:url-error'); } } sheetHidden: boolean = false; hideSheet = () => { this.sheetHidden = true; this.props.onHide(); this.idler.cancel(); }; componentDidUpdate() { const {result} = this.state; if (!result || !(result as DataExportResult).flipperUrl) { return; } if (!this.sheetHidden && this.props.closeOnFinish) { this.hideSheet(); } } cancelAndHide = (store: any) => () => { store.dispatch(unsetShare()); this.hideSheet(); }; renderPending(statusUpdate: string | null) { return ( {({store}) => ( { this.setState({runInBackground: true}); if (statusUpdate) { this.dispatchAndUpdateToolBarStatus(statusUpdate); } this.props.onHide(); }} /> )} ); } render() { const {result, statusUpdate, errorArray} = this.state; if (!result || !(result as DataExportResult).flipperUrl) { return this.renderPending(statusUpdate); } return ( {({store}) => ( <> {(result as DataExportResult).flipperUrl ? ( <> Data Upload Successful Flipper's data was successfully uploaded. This URL can be used to share with other Flipper users. Opening it will import the data from your trace. When sharing your Flipper link, consider that the captured data might contain sensitve information like access tokens used in network requests. ) : ( <> {(result as DataExportError).error_class || 'Error'} {(result as DataExportError).error || 'The data could not be uploaded'} )} )} ); } }