/** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { ResourceSnapshot } from '@trace/snapshot'; import * as React from 'react'; import './networkResourceDetails.css'; import { TabbedPane } from '@web/components/tabbedPane'; import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper'; import type { Language } from '@web/components/codeMirrorWrapper'; import { ToolbarButton } from '@web/components/toolbarButton'; export const NetworkResourceDetails: React.FunctionComponent<{ resource: ResourceSnapshot; onClose: () => void; }> = ({ resource, onClose }) => { const [selectedTab, setSelectedTab] = React.useState('request'); return ]} tabs={[ { id: 'request', title: 'Request', render: () => , }, { id: 'response', title: 'Response', render: () => , }, { id: 'body', title: 'Body', render: () => , }, ]} selectedTab={selectedTab} setSelectedTab={setSelectedTab} />; }; const RequestTab: React.FunctionComponent<{ resource: ResourceSnapshot; }> = ({ resource }) => { const [requestBody, setRequestBody] = React.useState<{ text: string, language?: Language } | null>(null); React.useEffect(() => { const readResources = async () => { if (resource.request.postData) { const requestContentTypeHeader = resource.request.headers.find(q => q.name === 'Content-Type'); const requestContentType = requestContentTypeHeader ? requestContentTypeHeader.value : ''; const language = mimeTypeToHighlighter(requestContentType); if (resource.request.postData._sha1) { const response = await fetch(`sha1/${resource.request.postData._sha1}`); setRequestBody({ text: formatBody(await response.text(), requestContentType), language }); } else { setRequestBody({ text: formatBody(resource.request.postData.text, requestContentType), language }); } } else { setRequestBody(null); } }; readResources(); }, [resource]); return
General
{`URL: ${resource.request.url}`}
{`Method: ${resource.request.method}`}
{`Status Code: ${ resource.response.status >= 200 && resource.response.status < 400 ? `🟢 ${resource.response.status} ${resource.response.statusText}` : `🔴 ${resource.response.status} ${resource.response.statusText}` }`}
Request Headers
{resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}
{requestBody &&
Request Body
} {requestBody && }
; }; const ResponseTab: React.FunctionComponent<{ resource: ResourceSnapshot; }> = ({ resource }) => { return
Response Headers
{resource.response.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}
; }; const BodyTab: React.FunctionComponent<{ resource: ResourceSnapshot; }> = ({ resource }) => { const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, language?: Language } | null>(null); React.useEffect(() => { const readResources = async () => { if (resource.response.content._sha1) { const useBase64 = resource.response.content.mimeType.includes('image'); const response = await fetch(`sha1/${resource.response.content._sha1}`); if (useBase64) { const blob = await response.blob(); const reader = new FileReader(); const eventPromise = new Promise(f => reader.onload = f); reader.readAsDataURL(blob); setResponseBody({ dataUrl: (await eventPromise).target.result }); } else { const formattedBody = formatBody(await response.text(), resource.response.content.mimeType); const language = mimeTypeToHighlighter(resource.response.content.mimeType); setResponseBody({ text: formattedBody, language }); } } }; readResources(); }, [resource]); return
{!resource.response.content._sha1 &&
Response body is not available for this request.
} {responseBody && responseBody.dataUrl && } {responseBody && responseBody.text && }
; }; function formatBody(body: string | null, contentType: string): string { if (body === null) return 'Loading...'; const bodyStr = body; if (bodyStr === '') return ''; if (contentType.includes('application/json')) { try { return JSON.stringify(JSON.parse(bodyStr), null, 2); } catch (err) { return bodyStr; } } if (contentType.includes('application/x-www-form-urlencoded')) return decodeURIComponent(bodyStr); return bodyStr; } function mimeTypeToHighlighter(mimeType: string): Language | undefined { if (mimeType.includes('javascript') || mimeType.includes('json')) return 'javascript'; if (mimeType.includes('html')) return 'html'; if (mimeType.includes('css')) return 'css'; }