import * as React from 'react'; import { useContext, useState } from 'react'; import { View, StyleSheet, Image } from 'react-native'; import { Subheading } from 'react-native-paper'; import { getTime, getShortDate, isLongText, addEllipsis, getStatus, duration, getGeneralElementsAsArray, getRequestHeadersElementsAsArray, getResponseHeadersElementsAsArray, excludedAttributesForExport, } from '../Utils/helpers'; import { Text, Title } from './Text'; import { tag } from './Status'; import { ThemeContext } from '../Theme'; import { NRequest } from '../Core/Objects/NRequest'; import { RNRequest } from '../Core/Objects/RNRequest'; import { EnumStatus } from '../types'; import url from 'url'; import { ViewMoreButton } from './ViewMoreButton'; import Clipboard from '@react-native-clipboard/clipboard'; import ClipboardButton from './ClipboardButton'; import { MockRequestButton } from './Mocking/MockRequestButton'; import { MockResponse } from './Mocking/utils'; export interface IProps { testId?: string; item: NRequest | RNRequest; onPressViewMoreRequest: (value: boolean) => void; onPressViewMoreResponse: (value: boolean) => void; setSnackBarMessage: (value: string) => void; setSnackBarVisibility: (value: boolean) => void; onEditMockResponse: (mockResponse: MockResponse, update: false) => void; } // This component is specif to request (React-Native or Native). // If you need the componant which handle the Redux action, see // ActionDetails.tsx export const RequestDetails: React.FC = (props: IProps) => { const theme = useContext(ThemeContext); const [onErrorImage, setOnErrorImage] = useState(false); let _color: string = theme.reduxColor; const _temp = getStatus(props.item.status); if (_temp === EnumStatus.Success) _color = theme.successColor; if (_temp === EnumStatus.Warning) _color = theme.warningColor; if (_temp === EnumStatus.Failed) _color = theme.failedColor; const urlObject = url.parse(props.item.url, true); const hostname = urlObject.host || ''; const _params = Object.entries(urlObject.query); const _generalElements = getGeneralElementsAsArray(props.item); const _requestHeadersElements = getRequestHeadersElementsAsArray(props.item); const _responseHeadersElements = getResponseHeadersElementsAsArray(props.item); // The elements are generated here for _generalElements, _requestHeadersElements and _responseHeadersElements // There is a filter with excludedAttributesForExport to avoid duplicate elements // For example, status is present in the root of props.item but also in _requestHeadersElements // that's why status is listed in excludedAttributesForExport const _renderItems = (listOfItems: Array<[string, any]>) => { return listOfItems .filter((item: Array) => item.length > 1) // To be sure that item has at least two element .filter((item: Array) => !excludedAttributesForExport.includes(item[0])) .filter((item: Array) => item[1] && item[1].length > 0) .map((item: Array, index: number) => { return ( {typeof item[1] === 'string' && item[1].startsWith('data:image/') ? ( _renderImage(item[1]) ) : ( <> {item[0]} {item[0] === 'url' && _copyClipbutton(item[1], 'URL has been copied to clipboard')} {item[1]} )} ); }); }; // If an item contains a base64 image. This one will be displayed directly instead of plain text const _renderImage = (source: string) => { return ( <> {onErrorImage ? ( _renderErrorMessage() ) : ( setOnErrorImage(true)} /> )} ); }; const _copyToClipboard = (text: string): void => { if (typeof text === 'string') Clipboard.setString(text); }; const _copyClipbutton = (text: string = '', toastMessage: string = '') => { return ( { _copyToClipboard(text); props.setSnackBarMessage(toastMessage); props.setSnackBarVisibility(true); }} /> ); }; const _renderErrorMessage = () => { return An error occur, cannot load the image; }; return ( {/* METHOD + STATUS CODE */} {props.item.method} {tag(_color, props.item.status.toString())} props.onEditMockResponse( { url: props.item.url, method: props.item.method, statusCode: props.item.status, headers: JSON.stringify(props.item.responseHeaders), response: props.item.response, }, false, ) } /> Hostname : {hostname} Started at : {`${getShortDate(props.item.startTime)} - ${getTime(props.item.startTime)} ( ${duration( props.item.startTime, props.item.endTime, )}ms )`} {typeof props.item.timeout === 'number' && ( Timeout : {props.item?.timeout.toString()} )} {_generalElements.length > 0 && ( <> Request Info {_renderItems(_generalElements)} )} {_params.length > 0 && ( <> Request Params {_renderItems(_params)} )} {_requestHeadersElements.length > 0 && ( <> Request Headers {_renderItems(_requestHeadersElements)} )} {props.item.dataSent?.length > 0 && props.item.dataSent !== 'undefined' && props.item.dataSent !== 'null' && ( <> Request Data {addEllipsis(props.item?.dataSent)} {isLongText(props.item?.dataSent) && ( props.onPressViewMoreRequest(true)} /> )} )} {_responseHeadersElements.length > 0 && ( <> Response Headers {_renderItems(_responseHeadersElements)} )} {props.item?.response?.length > 0 && props.item.response !== 'undefined' && props.item.response !== 'null' && ( <> Response Body {_copyClipbutton(props.item?.response, 'Response has been copied to clipboard')} {addEllipsis(props.item?.response)} {isLongText(props.item?.response) && ( props.onPressViewMoreResponse(true)} /> )} )} ); }; export default RequestDetails; const styles = StyleSheet.create({ attribute: { marginBottom: 10, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, itemContainer: { paddingHorizontal: 16, marginBottom: 10, }, line: { flexDirection: 'row', paddingHorizontal: 16, marginBottom: 10, alignItems: 'center', }, subheading: { paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10, }, attribtuesContainer: { justifyContent: 'flex-start', paddingHorizontal: 16, paddingVertical: 8, }, button: { justifyContent: 'center', alignItems: 'center', height: 56, width: 56, borderLeftWidth: 1, }, });