296 lines
6.5 KiB
JavaScript
296 lines
6.5 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
*
|
||
|
* This source code is licensed under the MIT license found in the
|
||
|
* LICENSE file in the root directory of this source tree.
|
||
|
*
|
||
|
* @format
|
||
|
* @flow strict-local
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
import type EventSender from './InspectorAgent';
|
||
|
|
||
|
const XMLHttpRequest = require('../Network/XMLHttpRequest');
|
||
|
const InspectorAgent = require('./InspectorAgent');
|
||
|
const JSInspector = require('./JSInspector');
|
||
|
|
||
|
type RequestId = string;
|
||
|
|
||
|
type LoaderId = string;
|
||
|
type FrameId = string;
|
||
|
type Timestamp = number;
|
||
|
|
||
|
type Headers = {[string]: string};
|
||
|
|
||
|
// We don't currently care about this
|
||
|
type ResourceTiming = null;
|
||
|
|
||
|
type ResourceType =
|
||
|
| 'Document'
|
||
|
| 'Stylesheet'
|
||
|
| 'Image'
|
||
|
| 'Media'
|
||
|
| 'Font'
|
||
|
| 'Script'
|
||
|
| 'TextTrack'
|
||
|
| 'XHR'
|
||
|
| 'Fetch'
|
||
|
| 'EventSource'
|
||
|
| 'WebSocket'
|
||
|
| 'Manifest'
|
||
|
| 'Other';
|
||
|
|
||
|
type SecurityState =
|
||
|
| 'unknown'
|
||
|
| 'neutral'
|
||
|
| 'insecure'
|
||
|
| 'warning'
|
||
|
| 'secure'
|
||
|
| 'info';
|
||
|
type BlockedReason =
|
||
|
| 'csp'
|
||
|
| 'mixed-content'
|
||
|
| 'origin'
|
||
|
| 'inspector'
|
||
|
| 'subresource-filter'
|
||
|
| 'other';
|
||
|
|
||
|
type StackTrace = null;
|
||
|
|
||
|
type Initiator = {
|
||
|
type: 'script' | 'other',
|
||
|
stackTrace?: StackTrace,
|
||
|
url?: string,
|
||
|
lineNumber?: number,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type ResourcePriority = 'VeryLow' | 'Low' | 'Medium' | 'High' | 'VeryHigh';
|
||
|
|
||
|
type Request = {
|
||
|
url: string,
|
||
|
method: string,
|
||
|
headers: Headers,
|
||
|
postData?: string,
|
||
|
mixedContentType?: 'blockable' | 'optionally-blockable' | 'none',
|
||
|
initialPriority: ResourcePriority,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type Response = {
|
||
|
url: string,
|
||
|
status: number,
|
||
|
statusText: string,
|
||
|
headers: Headers,
|
||
|
headersText?: string,
|
||
|
mimeType: string,
|
||
|
requestHeaders?: Headers,
|
||
|
requestHeadersText?: string,
|
||
|
connectionReused: boolean,
|
||
|
connectionId: number,
|
||
|
fromDiskCache?: boolean,
|
||
|
encodedDataLength: number,
|
||
|
timing?: ResourceTiming,
|
||
|
securityState: SecurityState,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type RequestWillBeSentEvent = {
|
||
|
requestId: RequestId,
|
||
|
frameId: FrameId,
|
||
|
loaderId: LoaderId,
|
||
|
documentURL: string,
|
||
|
request: Request,
|
||
|
timestamp: Timestamp,
|
||
|
initiator: Initiator,
|
||
|
redirectResponse?: Response,
|
||
|
// This is supposed to be optional but the inspector crashes without it,
|
||
|
// see https://bugs.chromium.org/p/chromium/issues/detail?id=653138
|
||
|
type: ResourceType,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type ResponseReceivedEvent = {
|
||
|
requestId: RequestId,
|
||
|
frameId: FrameId,
|
||
|
loaderId: LoaderId,
|
||
|
timestamp: Timestamp,
|
||
|
type: ResourceType,
|
||
|
response: Response,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type DataReceived = {
|
||
|
requestId: RequestId,
|
||
|
timestamp: Timestamp,
|
||
|
dataLength: number,
|
||
|
encodedDataLength: number,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type LoadingFinishedEvent = {
|
||
|
requestId: RequestId,
|
||
|
timestamp: Timestamp,
|
||
|
encodedDataLength: number,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
type LoadingFailedEvent = {
|
||
|
requestId: RequestId,
|
||
|
timestamp: Timestamp,
|
||
|
type: ResourceType,
|
||
|
errorText: string,
|
||
|
canceled?: boolean,
|
||
|
blockedReason?: BlockedReason,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
class Interceptor {
|
||
|
_agent: NetworkAgent;
|
||
|
_requests: Map<string, string>;
|
||
|
|
||
|
constructor(agent: NetworkAgent) {
|
||
|
this._agent = agent;
|
||
|
this._requests = new Map();
|
||
|
}
|
||
|
|
||
|
getData(requestId: string): ?string {
|
||
|
return this._requests.get(requestId);
|
||
|
}
|
||
|
|
||
|
requestSent(id: number, url: string, method: string, headers: Headers) {
|
||
|
const requestId = String(id);
|
||
|
this._requests.set(requestId, '');
|
||
|
|
||
|
const request: Request = {
|
||
|
url,
|
||
|
method,
|
||
|
headers,
|
||
|
initialPriority: 'Medium',
|
||
|
};
|
||
|
const event: RequestWillBeSentEvent = {
|
||
|
requestId,
|
||
|
documentURL: '',
|
||
|
frameId: '1',
|
||
|
loaderId: '1',
|
||
|
request,
|
||
|
timestamp: JSInspector.getTimestamp(),
|
||
|
initiator: {
|
||
|
// TODO(blom): Get stack trace
|
||
|
// If type is 'script' the inspector will try to execute
|
||
|
// `stack.callFrames[0]`
|
||
|
type: 'other',
|
||
|
},
|
||
|
type: 'Other',
|
||
|
};
|
||
|
this._agent.sendEvent('requestWillBeSent', event);
|
||
|
}
|
||
|
|
||
|
responseReceived(id: number, url: string, status: number, headers: Headers) {
|
||
|
const requestId = String(id);
|
||
|
const response: Response = {
|
||
|
url,
|
||
|
status,
|
||
|
statusText: String(status),
|
||
|
headers,
|
||
|
// TODO(blom) refined headers, can we get this?
|
||
|
requestHeaders: {},
|
||
|
mimeType: this._getMimeType(headers),
|
||
|
connectionReused: false,
|
||
|
connectionId: -1,
|
||
|
encodedDataLength: 0,
|
||
|
securityState: 'unknown',
|
||
|
};
|
||
|
|
||
|
const event: ResponseReceivedEvent = {
|
||
|
requestId,
|
||
|
frameId: '1',
|
||
|
loaderId: '1',
|
||
|
timestamp: JSInspector.getTimestamp(),
|
||
|
type: 'Other',
|
||
|
response,
|
||
|
};
|
||
|
this._agent.sendEvent('responseReceived', event);
|
||
|
}
|
||
|
|
||
|
dataReceived(id: number, data: string) {
|
||
|
const requestId = String(id);
|
||
|
const existingData = this._requests.get(requestId) || '';
|
||
|
this._requests.set(requestId, existingData.concat(data));
|
||
|
const event: DataReceived = {
|
||
|
requestId,
|
||
|
timestamp: JSInspector.getTimestamp(),
|
||
|
dataLength: data.length,
|
||
|
encodedDataLength: data.length,
|
||
|
};
|
||
|
this._agent.sendEvent('dataReceived', event);
|
||
|
}
|
||
|
|
||
|
loadingFinished(id: number, encodedDataLength: number) {
|
||
|
const event: LoadingFinishedEvent = {
|
||
|
requestId: String(id),
|
||
|
timestamp: JSInspector.getTimestamp(),
|
||
|
encodedDataLength: encodedDataLength,
|
||
|
};
|
||
|
this._agent.sendEvent('loadingFinished', event);
|
||
|
}
|
||
|
|
||
|
loadingFailed(id: number, error: string) {
|
||
|
const event: LoadingFailedEvent = {
|
||
|
requestId: String(id),
|
||
|
timestamp: JSInspector.getTimestamp(),
|
||
|
type: 'Other',
|
||
|
errorText: error,
|
||
|
};
|
||
|
this._agent.sendEvent('loadingFailed', event);
|
||
|
}
|
||
|
|
||
|
_getMimeType(headers: Headers): string {
|
||
|
const contentType = headers['Content-Type'] || '';
|
||
|
return contentType.split(';')[0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type EnableArgs = {
|
||
|
maxResourceBufferSize?: number,
|
||
|
maxTotalBufferSize?: number,
|
||
|
...
|
||
|
};
|
||
|
|
||
|
class NetworkAgent extends InspectorAgent {
|
||
|
static DOMAIN: $TEMPORARY$string<'Network'> = 'Network';
|
||
|
|
||
|
_sendEvent: EventSender;
|
||
|
_interceptor: ?Interceptor;
|
||
|
|
||
|
enable({maxResourceBufferSize, maxTotalBufferSize}: EnableArgs) {
|
||
|
this._interceptor = new Interceptor(this);
|
||
|
XMLHttpRequest.setInterceptor(this._interceptor);
|
||
|
}
|
||
|
|
||
|
disable() {
|
||
|
XMLHttpRequest.setInterceptor(null);
|
||
|
this._interceptor = null;
|
||
|
}
|
||
|
|
||
|
getResponseBody({requestId}: {requestId: RequestId, ...}): {
|
||
|
body: ?string,
|
||
|
base64Encoded: boolean,
|
||
|
...
|
||
|
} {
|
||
|
return {body: this.interceptor().getData(requestId), base64Encoded: false};
|
||
|
}
|
||
|
|
||
|
interceptor(): Interceptor {
|
||
|
if (this._interceptor) {
|
||
|
return this._interceptor;
|
||
|
} else {
|
||
|
throw Error('_interceptor can not be null');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = NetworkAgent;
|