Alexander Blom 1709043a12 Add Network agent
Summary: Adds methods in XMLHttpRequest so that the agent can hook the events needed for the implementation, these are only enabled if the agent is enabled (which means that the inspector is connected), it is also stripped out in non-dev currently.

Reviewed By: davidaurelio

Differential Revision: D4021516

fbshipit-source-id: c0c00d588404012d20b744de74e5ecbe5c002a53
2016-11-02 12:29:15 -07:00

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;