Permalink
Browse files

Enable websocket interception in RN network inspector tool

Summary:
This diff enables network inspection for WebSocket APIs, so by now XMLHttpRequest, Fetch and WebSocket are all supported. Android and iOS are both supported.

This diff monkey-patches the RCTWebSocketModule which WebSocket API builds on, and now it is able to intercept all WebSocket requests when app is running. The intercepted information of a WebSocket includes url, protocols, status, messages (sent and received), close reason, server close event and server error information, etc.

Reviewed By: davidaurelio

Differential Revision: D3641770

fbshipit-source-id: 393df0da74ed95b1fd60e38b0d67ed61b3dd5ff3
  • Loading branch information...
Siqi Liu Facebook Github Bot 5
Siqi Liu authored and Facebook Github Bot 5 committed Aug 2, 2016
1 parent 1e8b83d commit 43f73f675fd3f02b004b40e2b910ba4d7a52ea7f
Showing with 346 additions and 19 deletions.
  1. +127 −19 Libraries/Inspector/NetworkOverlay.js
  2. +219 −0 Libraries/Utilities/WebSocketInterceptor.js
@@ -19,6 +19,7 @@ const StyleSheet = require('StyleSheet');
const Text = require('Text');
const TouchableHighlight = require('TouchableHighlight');
const View = require('View');
const WebSocketInterceptor = require('WebSocketInterceptor');
const XHRInterceptor = require('XHRInterceptor');
const LISTVIEW_CELL_HEIGHT = 15;
@@ -28,6 +29,7 @@ const SEPARATOR_THICKNESS = 2;
let nextXHRId = 0;
type NetworkRequestInfo = {
type?: string,
url?: string,
method?: string,
status?: number,
@@ -40,6 +42,10 @@ type NetworkRequestInfo = {
responseURL?: string,
responseType?: string,
timeout?: number,
closeReason?: string,
messages?: string,
serverClose?: Object,
serverError?: Object,
};
/**
@@ -64,6 +70,8 @@ class NetworkOverlay extends React.Component {
) => ReactElement<any>;
_renderScrollComponent: (props: Object) => ReactElement<any>;
_closeButtonClicked: () => void;
// Map of `socketId` -> `index in `_requests``.
_socketIdMap: Object;
// Map of `xhr._index` -> `index in `_requests``.
_xhrIdMap: {[key: number]: number};
@@ -92,15 +100,16 @@ class NetworkOverlay extends React.Component {
this._renderRow = this._renderRow.bind(this);
this._renderScrollComponent = this._renderScrollComponent.bind(this);
this._closeButtonClicked = this._closeButtonClicked.bind(this);
this._socketIdMap = {};
this._xhrIdMap = {};
}
_enableInterception(): void {
_enableXHRInterception(): void {
if (XHRInterceptor.isInterceptorEnabled()) {
return;
}
// Show the network request item in listView as soon as it was opened.
XHRInterceptor.setOpenCallback(function(method, url, xhr) {
// Show the XHR request item in listView as soon as it was opened.
XHRInterceptor.setOpenCallback((method, url, xhr) => {
// Generate a global id for each intercepted xhr object, add this id
// to the xhr object as a private `_index` property to identify it,
// so that we can distinguish different xhr objects in callbacks.
@@ -109,6 +118,7 @@ class NetworkOverlay extends React.Component {
this._xhrIdMap[xhr._index] = xhrIndex;
const _xhr: NetworkRequestInfo = {
'type': 'XMLHttpRequest',
'method': method,
'url': url
};
@@ -119,9 +129,9 @@ class NetworkOverlay extends React.Component {
{dataSource: this._listViewDataSource.cloneWithRows(this._requests)},
this._scrollToBottom(),
);
}.bind(this));
});
XHRInterceptor.setRequestHeaderCallback(function(header, value, xhr) {
XHRInterceptor.setRequestHeaderCallback((header, value, xhr) => {
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
if (xhrIndex === -1) {
return;
@@ -132,19 +142,19 @@ class NetworkOverlay extends React.Component {
}
networkInfo.requestHeaders[header] = value;
this._genDetailViewItem(xhrIndex);
}.bind(this));
});
XHRInterceptor.setSendCallback(function(data, xhr) {
XHRInterceptor.setSendCallback((data, xhr) => {
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
if (xhrIndex === -1) {
return;
}
this._requests[xhrIndex].dataSent = data;
this._genDetailViewItem(xhrIndex);
}.bind(this));
});
XHRInterceptor.setHeaderReceivedCallback(
function(type, size, responseHeaders, xhr) {
(type, size, responseHeaders, xhr) => {
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
if (xhrIndex === -1) {
return;
@@ -154,18 +164,17 @@ class NetworkOverlay extends React.Component {
networkInfo.responseSize = size;
networkInfo.responseHeaders = responseHeaders;
this._genDetailViewItem(xhrIndex);
}.bind(this)
}
);
XHRInterceptor.setResponseCallback(
function(
XHRInterceptor.setResponseCallback((
status,
timeout,
response,
responseURL,
responseType,
xhr,
) {
) => {
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
if (xhrIndex === -1) {
return;
@@ -177,19 +186,107 @@ class NetworkOverlay extends React.Component {
networkInfo.responseURL = responseURL;
networkInfo.responseType = responseType;
this._genDetailViewItem(xhrIndex);
}.bind(this)
}
);
// Fire above callbacks.
XHRInterceptor.enableInterception();
}
_enableWebSocketInterception(): void {
if (WebSocketInterceptor.isInterceptorEnabled()) {
return;
}
// Show the WebSocket request item in listView when 'connect' is called.
WebSocketInterceptor.setConnectCallback(
(url, protocols, options, socketId) => {
const socketIndex = this._requests.length;
this._socketIdMap[socketId] = socketIndex;
const _webSocket: NetworkRequestInfo = {
'type': 'WebSocket',
'url': url,
'protocols': protocols,
};
this._requests.push(_webSocket);
this._detailViewItems.push([]);
this._genDetailViewItem(socketIndex);
this.setState(
{dataSource: this._listViewDataSource.cloneWithRows(this._requests)},
this._scrollToBottom(),
);
}
);
WebSocketInterceptor.setCloseCallback(
(statusCode, closeReason, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
if (statusCode !== null && closeReason !== null) {
this._requests[socketIndex].status = statusCode;
this._requests[socketIndex].closeReason = closeReason;
}
this._genDetailViewItem(socketIndex);
}
);
WebSocketInterceptor.setSendCallback((data, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
if (!this._requests[socketIndex].messages) {
this._requests[socketIndex].messages = '';
}
this._requests[socketIndex].messages +=
'Sent: ' + JSON.stringify(data) + '\n';
this._genDetailViewItem(socketIndex);
});
WebSocketInterceptor.setOnMessageCallback((socketId, message) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
if (!this._requests[socketIndex].messages) {
this._requests[socketIndex].messages = '';
}
this._requests[socketIndex].messages +=
'Received: ' + JSON.stringify(message) + '\n';
this._genDetailViewItem(socketIndex);
});
WebSocketInterceptor.setOnCloseCallback((socketId, message) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
this._requests[socketIndex].serverClose = message;
this._genDetailViewItem(socketIndex);
});
WebSocketInterceptor.setOnErrorCallback((socketId, message) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
this._requests[socketIndex].serverError = message;
this._genDetailViewItem(socketIndex);
});
// Fire above callbacks.
WebSocketInterceptor.enableInterception();
}
componentDidMount() {
this._enableInterception();
this._enableXHRInterception();
this._enableWebSocketInterception();
}
componentWillUnmount() {
XHRInterceptor.disableInterception();
WebSocketInterceptor.disableInterception();
}
_renderRow(
@@ -218,7 +315,7 @@ class NetworkOverlay extends React.Component {
</View>
<View style={methodCellViewStyle}>
<Text style={styles.cellText} numberOfLines={1}>
{rowData.method}
{this._getTypeShortName(rowData.type)}
</Text>
</View>
</View>
@@ -331,6 +428,16 @@ class NetworkOverlay extends React.Component {
}
}
_getTypeShortName(type: any): string {
if (type === 'XMLHttpRequest') {
return 'XHR';
} else if (type === 'WebSocket') {
return 'WS';
}
return '';
}
/**
* Generate a list of views containing network request information for
* a XHR object, to be shown in the detail scrollview. This function
@@ -354,7 +461,8 @@ class NetworkOverlay extends React.Component {
);
}
// Re-render if this network request is showing in the detail view.
if (this.state.detailRowID != null && Number(this.state.detailRowID) === index) {
if (this.state.detailRowID != null &&
Number(this.state.detailRowID) === index) {
this.setState({newDetailInfo: true});
}
}
@@ -383,7 +491,7 @@ class NetworkOverlay extends React.Component {
<Text style={styles.cellText} numberOfLines={1}>URL</Text>
</View>
<View style={styles.methodTitleCellView}>
<Text style={styles.cellText} numberOfLines={1}>Method</Text>
<Text style={styles.cellText} numberOfLines={1}>Type</Text>
</View>
</View>}
</View>
@@ -510,10 +618,10 @@ const styles = StyleSheet.create({
fontSize: 10,
},
closeButton: {
marginTop: 5,
backgroundColor: '#888',
justifyContent: 'center',
alignItems: 'center',
right: 0,
},
});
Oops, something went wrong.

1 comment on commit 43f73f6

@akshaysmurthy

This comment has been minimized.

Show comment
Hide comment
@akshaysmurthy

akshaysmurthy Aug 22, 2016

Awesome, thanks!

Awesome, thanks!

Please sign in to comment.