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
This commit is contained in:
parent
6a1783210b
commit
1709043a12
|
@ -177,7 +177,7 @@ navigator.product = 'ReactNative';
|
|||
defineProperty(navigator, 'geolocation', () => require('Geolocation'));
|
||||
|
||||
// Set up collections
|
||||
// We can't make these lazy because `Map` checks for `global.Map` (which would
|
||||
// We can't make these lazy because `Map` checks for `global.Map` (which wouldc
|
||||
// not exist if it were lazily defined).
|
||||
defineProperty(global, 'Map', () => require('Map'), true);
|
||||
defineProperty(global, 'Set', () => require('Set'), true);
|
||||
|
@ -195,6 +195,12 @@ if (__DEV__) {
|
|||
require('react-transform-hmr');
|
||||
}
|
||||
|
||||
// Set up inspector
|
||||
if (__DEV__) {
|
||||
const JSInspector = require('JSInspector');
|
||||
JSInspector.registerAgent(require('NetworkAgent'));
|
||||
}
|
||||
|
||||
// Just to make sure the JS gets packaged up. Wait until the JS environment has
|
||||
// been initialized before requiring them.
|
||||
require('RCTDeviceEventEmitter');
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
/**
|
||||
* 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;
|
|
@ -11,9 +11,9 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const EventTarget = require('event-target-shim');
|
||||
const RCTNetworking = require('RCTNetworking');
|
||||
|
||||
const EventTarget = require('event-target-shim');
|
||||
const base64 = require('base64-js');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const warning = require('fbjs/lib/warning');
|
||||
|
@ -21,6 +21,28 @@ const warning = require('fbjs/lib/warning');
|
|||
type ResponseType = '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text';
|
||||
type Response = ?Object | string;
|
||||
|
||||
type XHRInterceptor = {
|
||||
requestSent: (
|
||||
id: number,
|
||||
url: string,
|
||||
method: string,
|
||||
headers: Object) => void,
|
||||
responseReceived: (
|
||||
id: number,
|
||||
url: string,
|
||||
status: number,
|
||||
headers: Object) => void,
|
||||
dataReceived: (
|
||||
id: number,
|
||||
data: string) => void,
|
||||
loadingFinished: (
|
||||
id: number,
|
||||
encodedDataLength: number) => void,
|
||||
loadingFailed: (
|
||||
id: number,
|
||||
error: string) => void,
|
||||
};
|
||||
|
||||
const UNSENT = 0;
|
||||
const OPENED = 1;
|
||||
const HEADERS_RECEIVED = 2;
|
||||
|
@ -68,6 +90,8 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
static LOADING: number = LOADING;
|
||||
static DONE: number = DONE;
|
||||
|
||||
static _interceptor: ?XHRInterceptor = null;
|
||||
|
||||
UNSENT: number = UNSENT;
|
||||
OPENED: number = OPENED;
|
||||
HEADERS_RECEIVED: number = HEADERS_RECEIVED;
|
||||
|
@ -109,6 +133,10 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
_trackingName: string = 'unknown';
|
||||
_incrementalEvents: boolean = false;
|
||||
|
||||
static setInterceptor(interceptor: ?XHRInterceptor) {
|
||||
XMLHttpRequest._interceptor = interceptor;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._reset();
|
||||
|
@ -224,6 +252,12 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
// exposed for testing
|
||||
__didCreateRequest(requestId: number): void {
|
||||
this._requestId = requestId;
|
||||
|
||||
XMLHttpRequest._interceptor && XMLHttpRequest._interceptor.requestSent(
|
||||
requestId,
|
||||
this._url || '',
|
||||
this._method || 'GET',
|
||||
this._headers);
|
||||
}
|
||||
|
||||
// exposed for testing
|
||||
|
@ -257,6 +291,12 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
} else {
|
||||
delete this.responseURL;
|
||||
}
|
||||
|
||||
XMLHttpRequest._interceptor && XMLHttpRequest._interceptor.responseReceived(
|
||||
requestId,
|
||||
responseURL || this._url || '',
|
||||
status,
|
||||
responseHeaders || {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,6 +307,10 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
this._response = response;
|
||||
this._cachedResponse = undefined; // force lazy recomputation
|
||||
this.setReadyState(this.LOADING);
|
||||
|
||||
XMLHttpRequest._interceptor && XMLHttpRequest._interceptor.dataReceived(
|
||||
requestId,
|
||||
response);
|
||||
}
|
||||
|
||||
__didReceiveIncrementalData(
|
||||
|
@ -283,6 +327,11 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
} else {
|
||||
this._response += responseText;
|
||||
}
|
||||
|
||||
XMLHttpRequest._interceptor && XMLHttpRequest._interceptor.dataReceived(
|
||||
requestId,
|
||||
responseText);
|
||||
|
||||
this.setReadyState(this.LOADING);
|
||||
this.__didReceiveDataProgress(requestId, progress, total);
|
||||
}
|
||||
|
@ -322,6 +371,16 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
|||
this._clearSubscriptions();
|
||||
this._requestId = null;
|
||||
this.setReadyState(this.DONE);
|
||||
|
||||
if (error) {
|
||||
XMLHttpRequest._interceptor && XMLHttpRequest._interceptor.loadingFailed(
|
||||
requestId,
|
||||
error);
|
||||
} else {
|
||||
XMLHttpRequest._interceptor && XMLHttpRequest._interceptor.loadingFinished(
|
||||
requestId,
|
||||
this._response.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue