2021-01-26 20:06:05 +01:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2022-09-20 18:41:51 -07:00
|
|
|
import type { ResourceSnapshot } from '@trace/snapshot';
|
2022-03-25 13:12:00 -08:00
|
|
|
import { Expandable } from '@web/components/expandable';
|
2021-01-26 20:06:05 +01:00
|
|
|
import * as React from 'react';
|
2022-03-25 13:12:00 -08:00
|
|
|
import './networkResourceDetails.css';
|
2023-06-02 13:00:27 -07:00
|
|
|
import type { Entry } from '@trace/har';
|
2021-01-26 20:06:05 +01:00
|
|
|
|
|
|
|
export const NetworkResourceDetails: React.FunctionComponent<{
|
2021-03-08 19:49:57 -08:00
|
|
|
resource: ResourceSnapshot,
|
2023-07-10 12:56:56 -07:00
|
|
|
highlighted: boolean,
|
|
|
|
}> = ({ resource, highlighted }) => {
|
2021-01-26 20:06:05 +01:00
|
|
|
const [expanded, setExpanded] = React.useState(false);
|
|
|
|
const [requestBody, setRequestBody] = React.useState<string | null>(null);
|
2021-03-08 19:49:57 -08:00
|
|
|
const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string } | null>(null);
|
2021-01-26 20:06:05 +01:00
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
setExpanded(false);
|
2023-07-10 12:56:56 -07:00
|
|
|
}, [resource]);
|
2021-01-26 20:06:05 +01:00
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
const readResources = async () => {
|
2021-08-24 13:17:58 -07:00
|
|
|
if (resource.request.postData) {
|
|
|
|
if (resource.request.postData._sha1) {
|
2021-10-13 10:07:29 -08:00
|
|
|
const response = await fetch(`sha1/${resource.request.postData._sha1}`);
|
2021-08-24 13:17:58 -07:00
|
|
|
const requestResource = await response.text();
|
|
|
|
setRequestBody(requestResource);
|
|
|
|
} else {
|
|
|
|
setRequestBody(resource.request.postData.text);
|
|
|
|
}
|
2021-01-26 20:06:05 +01:00
|
|
|
}
|
|
|
|
|
2021-08-24 13:17:58 -07:00
|
|
|
if (resource.response.content._sha1) {
|
|
|
|
const useBase64 = resource.response.content.mimeType.includes('image');
|
2021-10-13 10:07:29 -08:00
|
|
|
const response = await fetch(`sha1/${resource.response.content._sha1}`);
|
2021-03-08 19:49:57 -08:00
|
|
|
if (useBase64) {
|
|
|
|
const blob = await response.blob();
|
|
|
|
const reader = new FileReader();
|
|
|
|
const eventPromise = new Promise<any>(f => reader.onload = f);
|
|
|
|
reader.readAsDataURL(blob);
|
|
|
|
setResponseBody({ dataUrl: (await eventPromise).target.result });
|
|
|
|
} else {
|
|
|
|
setResponseBody({ text: await response.text() });
|
|
|
|
}
|
2021-01-26 20:06:05 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
readResources();
|
2021-08-24 13:17:58 -07:00
|
|
|
}, [expanded, resource]);
|
2021-01-26 20:06:05 +01:00
|
|
|
|
2023-06-02 13:00:27 -07:00
|
|
|
const { routeStatus, requestContentType, resourceName, contentType } = React.useMemo(() => {
|
|
|
|
const routeStatus = formatRouteStatus(resource);
|
|
|
|
const requestContentTypeHeader = resource.request.headers.find(q => q.name === 'Content-Type');
|
|
|
|
const requestContentType = requestContentTypeHeader ? requestContentTypeHeader.value : '';
|
2023-06-06 17:38:44 -07:00
|
|
|
const resourceName = resource.request.url.substring(resource.request.url.lastIndexOf('/'));
|
2023-06-02 13:00:27 -07:00
|
|
|
let contentType = resource.response.content.mimeType;
|
|
|
|
const charset = contentType.match(/^(.*);\s*charset=.*$/);
|
|
|
|
if (charset)
|
|
|
|
contentType = charset[1];
|
|
|
|
return { routeStatus, requestContentType, resourceName, contentType };
|
|
|
|
}, [resource]);
|
|
|
|
|
|
|
|
const renderTitle = React.useCallback(() => {
|
|
|
|
return <div className='network-request-title'>
|
2023-06-06 17:38:44 -07:00
|
|
|
{routeStatus && <div className={`network-request-title-status status-route ${routeStatus}`}>{routeStatus}</div> }
|
2023-06-02 13:00:27 -07:00
|
|
|
{resource.response._failureText && <div className={'network-request-title-status status-failure'}>{resource.response._failureText}</div>}
|
|
|
|
{!resource.response._failureText && <div className={'network-request-title-status ' + formatStatus(resource.response.status)}>{resource.response.status}</div>}
|
|
|
|
<div className='network-request-title-status'>{resource.request.method}</div>
|
|
|
|
<div className='network-request-title-url'>{resourceName}</div>
|
|
|
|
<div className='network-request-title-content-type'>{contentType}</div>
|
|
|
|
</div>;
|
|
|
|
}, [contentType, resource, resourceName, routeStatus]);
|
2022-01-07 11:22:01 -08:00
|
|
|
|
|
|
|
return <div
|
2023-07-10 12:56:56 -07:00
|
|
|
className={'network-request' + (highlighted ? ' highlighted' : '')}>
|
2023-03-13 22:19:31 -07:00
|
|
|
<Expandable expanded={expanded} setExpanded={setExpanded} title={ renderTitle() }>
|
2021-01-26 20:06:05 +01:00
|
|
|
<div className='network-request-details'>
|
2022-07-07 23:56:31 +05:30
|
|
|
<div className='network-request-details-time'>{resource.time}ms</div>
|
2021-05-13 16:07:38 -07:00
|
|
|
<div className='network-request-details-header'>URL</div>
|
2021-08-24 13:17:58 -07:00
|
|
|
<div className='network-request-details-url'>{resource.request.url}</div>
|
2021-05-13 16:07:38 -07:00
|
|
|
<div className='network-request-details-header'>Request Headers</div>
|
2021-08-24 13:17:58 -07:00
|
|
|
<div className='network-request-headers'>{resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}</div>
|
2021-05-13 16:07:38 -07:00
|
|
|
<div className='network-request-details-header'>Response Headers</div>
|
2021-08-24 13:17:58 -07:00
|
|
|
<div className='network-request-headers'>{resource.response.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}</div>
|
|
|
|
{resource.request.postData ? <div className='network-request-details-header'>Request Body</div> : ''}
|
|
|
|
{resource.request.postData ? <div className='network-request-body'>{formatBody(requestBody, requestContentType)}</div> : ''}
|
2021-05-13 16:07:38 -07:00
|
|
|
<div className='network-request-details-header'>Response Body</div>
|
2021-08-24 13:17:58 -07:00
|
|
|
{!resource.response.content._sha1 ? <div className='network-request-response-body'>Response body is not available for this request.</div> : ''}
|
2023-06-14 09:37:19 -07:00
|
|
|
{responseBody !== null && responseBody.dataUrl ? <img draggable='false' src={responseBody.dataUrl} /> : ''}
|
2021-08-24 13:17:58 -07:00
|
|
|
{responseBody !== null && responseBody.text ? <div className='network-request-response-body'>{formatBody(responseBody.text, resource.response.content.mimeType)}</div> : ''}
|
2021-01-26 20:06:05 +01:00
|
|
|
</div>
|
2022-03-29 20:06:11 -08:00
|
|
|
</Expandable>
|
2021-01-26 20:06:05 +01:00
|
|
|
</div>;
|
|
|
|
};
|
2023-06-02 13:00:27 -07:00
|
|
|
|
|
|
|
function formatStatus(status: number): string {
|
|
|
|
if (status >= 200 && status < 400)
|
|
|
|
return 'status-success';
|
|
|
|
if (status >= 400)
|
|
|
|
return 'status-failure';
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatBody(body: string | null, contentType: string): string {
|
|
|
|
if (body === null)
|
|
|
|
return 'Loading...';
|
|
|
|
|
|
|
|
const bodyStr = body;
|
|
|
|
if (bodyStr === '')
|
|
|
|
return '<Empty>';
|
|
|
|
|
|
|
|
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 formatRouteStatus(request: Entry): string {
|
|
|
|
if (request._wasAborted)
|
|
|
|
return 'aborted';
|
|
|
|
if (request._wasContinued)
|
|
|
|
return 'continued';
|
|
|
|
if (request._wasFulfilled)
|
|
|
|
return 'fulfilled';
|
2023-06-06 17:38:44 -07:00
|
|
|
if (request._apiRequest)
|
|
|
|
return 'api';
|
2023-06-02 13:00:27 -07:00
|
|
|
return '';
|
|
|
|
}
|