301 lines
6.6 KiB
JavaScript
301 lines
6.6 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This source code is licensed under the BSD-style license found in the
|
||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||
|
*
|
||
|
* @providesModule NetworkAgent
|
||
|
* @flow
|
||
|
*/
|
||
|
'use strict';
|
||
|
|
||
|
const InspectorAgent = require('InspectorAgent');
|
||
|
const JSInspector = require('JSInspector');
|
||
|
const Map = require('Map');
|
||
|
const XMLHttpRequest = require('XMLHttpRequest');
|
||
|
|
||
|
import type EventSender from 'InspectorAgent';
|
||
|
|
||
|
type RequestId = string;
|
||
|
|
||
|
type LoaderId = string;
|
||
|
type FrameId = string;
|
||
|
type Timestamp = number;
|
||
|
|
||
|
type Headers = Object;
|
||
|
|
||
|
// 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: Object) {
|
||
|
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: Object) {
|
||
|
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: Object): string {
|
||
|
const contentType = headers['Content-Type'] || '';
|
||
|
return contentType.split(';')[0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type EnableArgs = {
|
||
|
maxResourceBufferSize?: number,
|
||
|
maxTotalBufferSize?: number
|
||
|
};
|
||
|
|
||
|
class NetworkAgent extends InspectorAgent {
|
||
|
static DOMAIN = '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;
|