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
This commit is contained in:
parent
1e8b83d2e6
commit
43f73f675f
|
@ -19,6 +19,7 @@ const StyleSheet = require('StyleSheet');
|
||||||
const Text = require('Text');
|
const Text = require('Text');
|
||||||
const TouchableHighlight = require('TouchableHighlight');
|
const TouchableHighlight = require('TouchableHighlight');
|
||||||
const View = require('View');
|
const View = require('View');
|
||||||
|
const WebSocketInterceptor = require('WebSocketInterceptor');
|
||||||
const XHRInterceptor = require('XHRInterceptor');
|
const XHRInterceptor = require('XHRInterceptor');
|
||||||
|
|
||||||
const LISTVIEW_CELL_HEIGHT = 15;
|
const LISTVIEW_CELL_HEIGHT = 15;
|
||||||
|
@ -28,6 +29,7 @@ const SEPARATOR_THICKNESS = 2;
|
||||||
let nextXHRId = 0;
|
let nextXHRId = 0;
|
||||||
|
|
||||||
type NetworkRequestInfo = {
|
type NetworkRequestInfo = {
|
||||||
|
type?: string,
|
||||||
url?: string,
|
url?: string,
|
||||||
method?: string,
|
method?: string,
|
||||||
status?: number,
|
status?: number,
|
||||||
|
@ -40,6 +42,10 @@ type NetworkRequestInfo = {
|
||||||
responseURL?: string,
|
responseURL?: string,
|
||||||
responseType?: string,
|
responseType?: string,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
closeReason?: string,
|
||||||
|
messages?: string,
|
||||||
|
serverClose?: Object,
|
||||||
|
serverError?: Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,6 +70,8 @@ class NetworkOverlay extends React.Component {
|
||||||
) => ReactElement<any>;
|
) => ReactElement<any>;
|
||||||
_renderScrollComponent: (props: Object) => ReactElement<any>;
|
_renderScrollComponent: (props: Object) => ReactElement<any>;
|
||||||
_closeButtonClicked: () => void;
|
_closeButtonClicked: () => void;
|
||||||
|
// Map of `socketId` -> `index in `_requests``.
|
||||||
|
_socketIdMap: Object;
|
||||||
// Map of `xhr._index` -> `index in `_requests``.
|
// Map of `xhr._index` -> `index in `_requests``.
|
||||||
_xhrIdMap: {[key: number]: number};
|
_xhrIdMap: {[key: number]: number};
|
||||||
|
|
||||||
|
@ -92,15 +100,16 @@ class NetworkOverlay extends React.Component {
|
||||||
this._renderRow = this._renderRow.bind(this);
|
this._renderRow = this._renderRow.bind(this);
|
||||||
this._renderScrollComponent = this._renderScrollComponent.bind(this);
|
this._renderScrollComponent = this._renderScrollComponent.bind(this);
|
||||||
this._closeButtonClicked = this._closeButtonClicked.bind(this);
|
this._closeButtonClicked = this._closeButtonClicked.bind(this);
|
||||||
|
this._socketIdMap = {};
|
||||||
this._xhrIdMap = {};
|
this._xhrIdMap = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
_enableInterception(): void {
|
_enableXHRInterception(): void {
|
||||||
if (XHRInterceptor.isInterceptorEnabled()) {
|
if (XHRInterceptor.isInterceptorEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Show the network request item in listView as soon as it was opened.
|
// Show the XHR request item in listView as soon as it was opened.
|
||||||
XHRInterceptor.setOpenCallback(function(method, url, xhr) {
|
XHRInterceptor.setOpenCallback((method, url, xhr) => {
|
||||||
// Generate a global id for each intercepted xhr object, add this id
|
// Generate a global id for each intercepted xhr object, add this id
|
||||||
// to the xhr object as a private `_index` property to identify it,
|
// to the xhr object as a private `_index` property to identify it,
|
||||||
// so that we can distinguish different xhr objects in callbacks.
|
// so that we can distinguish different xhr objects in callbacks.
|
||||||
|
@ -109,6 +118,7 @@ class NetworkOverlay extends React.Component {
|
||||||
this._xhrIdMap[xhr._index] = xhrIndex;
|
this._xhrIdMap[xhr._index] = xhrIndex;
|
||||||
|
|
||||||
const _xhr: NetworkRequestInfo = {
|
const _xhr: NetworkRequestInfo = {
|
||||||
|
'type': 'XMLHttpRequest',
|
||||||
'method': method,
|
'method': method,
|
||||||
'url': url
|
'url': url
|
||||||
};
|
};
|
||||||
|
@ -119,9 +129,9 @@ class NetworkOverlay extends React.Component {
|
||||||
{dataSource: this._listViewDataSource.cloneWithRows(this._requests)},
|
{dataSource: this._listViewDataSource.cloneWithRows(this._requests)},
|
||||||
this._scrollToBottom(),
|
this._scrollToBottom(),
|
||||||
);
|
);
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
XHRInterceptor.setRequestHeaderCallback(function(header, value, xhr) {
|
XHRInterceptor.setRequestHeaderCallback((header, value, xhr) => {
|
||||||
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
||||||
if (xhrIndex === -1) {
|
if (xhrIndex === -1) {
|
||||||
return;
|
return;
|
||||||
|
@ -132,19 +142,19 @@ class NetworkOverlay extends React.Component {
|
||||||
}
|
}
|
||||||
networkInfo.requestHeaders[header] = value;
|
networkInfo.requestHeaders[header] = value;
|
||||||
this._genDetailViewItem(xhrIndex);
|
this._genDetailViewItem(xhrIndex);
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
XHRInterceptor.setSendCallback(function(data, xhr) {
|
XHRInterceptor.setSendCallback((data, xhr) => {
|
||||||
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
||||||
if (xhrIndex === -1) {
|
if (xhrIndex === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._requests[xhrIndex].dataSent = data;
|
this._requests[xhrIndex].dataSent = data;
|
||||||
this._genDetailViewItem(xhrIndex);
|
this._genDetailViewItem(xhrIndex);
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
XHRInterceptor.setHeaderReceivedCallback(
|
XHRInterceptor.setHeaderReceivedCallback(
|
||||||
function(type, size, responseHeaders, xhr) {
|
(type, size, responseHeaders, xhr) => {
|
||||||
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
||||||
if (xhrIndex === -1) {
|
if (xhrIndex === -1) {
|
||||||
return;
|
return;
|
||||||
|
@ -154,18 +164,17 @@ class NetworkOverlay extends React.Component {
|
||||||
networkInfo.responseSize = size;
|
networkInfo.responseSize = size;
|
||||||
networkInfo.responseHeaders = responseHeaders;
|
networkInfo.responseHeaders = responseHeaders;
|
||||||
this._genDetailViewItem(xhrIndex);
|
this._genDetailViewItem(xhrIndex);
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
XHRInterceptor.setResponseCallback(
|
XHRInterceptor.setResponseCallback((
|
||||||
function(
|
|
||||||
status,
|
status,
|
||||||
timeout,
|
timeout,
|
||||||
response,
|
response,
|
||||||
responseURL,
|
responseURL,
|
||||||
responseType,
|
responseType,
|
||||||
xhr,
|
xhr,
|
||||||
) {
|
) => {
|
||||||
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
const xhrIndex = this._getRequestIndexByXHRID(xhr._index);
|
||||||
if (xhrIndex === -1) {
|
if (xhrIndex === -1) {
|
||||||
return;
|
return;
|
||||||
|
@ -177,19 +186,107 @@ class NetworkOverlay extends React.Component {
|
||||||
networkInfo.responseURL = responseURL;
|
networkInfo.responseURL = responseURL;
|
||||||
networkInfo.responseType = responseType;
|
networkInfo.responseType = responseType;
|
||||||
this._genDetailViewItem(xhrIndex);
|
this._genDetailViewItem(xhrIndex);
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fire above callbacks.
|
// Fire above callbacks.
|
||||||
XHRInterceptor.enableInterception();
|
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() {
|
componentDidMount() {
|
||||||
this._enableInterception();
|
this._enableXHRInterception();
|
||||||
|
this._enableWebSocketInterception();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
XHRInterceptor.disableInterception();
|
XHRInterceptor.disableInterception();
|
||||||
|
WebSocketInterceptor.disableInterception();
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderRow(
|
_renderRow(
|
||||||
|
@ -218,7 +315,7 @@ class NetworkOverlay extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
<View style={methodCellViewStyle}>
|
<View style={methodCellViewStyle}>
|
||||||
<Text style={styles.cellText} numberOfLines={1}>
|
<Text style={styles.cellText} numberOfLines={1}>
|
||||||
{rowData.method}
|
{this._getTypeShortName(rowData.type)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</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
|
* Generate a list of views containing network request information for
|
||||||
* a XHR object, to be shown in the detail scrollview. This function
|
* 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.
|
// 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});
|
this.setState({newDetailInfo: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,7 +491,7 @@ class NetworkOverlay extends React.Component {
|
||||||
<Text style={styles.cellText} numberOfLines={1}>URL</Text>
|
<Text style={styles.cellText} numberOfLines={1}>URL</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.methodTitleCellView}>
|
<View style={styles.methodTitleCellView}>
|
||||||
<Text style={styles.cellText} numberOfLines={1}>Method</Text>
|
<Text style={styles.cellText} numberOfLines={1}>Type</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>}
|
</View>}
|
||||||
</View>
|
</View>
|
||||||
|
@ -510,10 +618,10 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
},
|
},
|
||||||
closeButton: {
|
closeButton: {
|
||||||
|
marginTop: 5,
|
||||||
backgroundColor: '#888',
|
backgroundColor: '#888',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
right: 0,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
/**
|
||||||
|
* 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 WebSocketInterceptor
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const RCTWebSocketModule = require('NativeModules').WebSocketModule;
|
||||||
|
const NativeEventEmitter = require('NativeEventEmitter');
|
||||||
|
const base64 = require('base64-js');
|
||||||
|
|
||||||
|
const originalRCTWebSocketConnect = RCTWebSocketModule.connect;
|
||||||
|
const originalRCTWebSocketSend = RCTWebSocketModule.send;
|
||||||
|
const originalRCTWebSocketSendBinary = RCTWebSocketModule.sendBinary;
|
||||||
|
const originalRCTWebSocketClose = RCTWebSocketModule.close;
|
||||||
|
|
||||||
|
let eventEmitter: NativeEventEmitter;
|
||||||
|
let subscriptions: Array<EventSubscription>;
|
||||||
|
|
||||||
|
let closeCallback;
|
||||||
|
let sendCallback;
|
||||||
|
let connectCallback;
|
||||||
|
let onOpenCallback;
|
||||||
|
let onMessageCallback;
|
||||||
|
let onErrorCallback;
|
||||||
|
let onCloseCallback;
|
||||||
|
|
||||||
|
let isInterceptorEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A network interceptor which monkey-patches RCTWebSocketModule methods
|
||||||
|
* to gather all websocket network requests/responses, in order to show
|
||||||
|
* their information in the React Native inspector development tool.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const WebSocketInterceptor = {
|
||||||
|
/**
|
||||||
|
* Invoked when RCTWebSocketModule.close(...) is called.
|
||||||
|
*/
|
||||||
|
setCloseCallback(callback) {
|
||||||
|
closeCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when RCTWebSocketModule.send(...) or sendBinary(...) is called.
|
||||||
|
*/
|
||||||
|
setSendCallback(callback) {
|
||||||
|
sendCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when RCTWebSocketModule.connect(...) is called.
|
||||||
|
*/
|
||||||
|
setConnectCallback(callback) {
|
||||||
|
connectCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when event "websocketOpen" happens.
|
||||||
|
*/
|
||||||
|
setOnOpenCallback(callback) {
|
||||||
|
onOpenCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when event "websocketMessage" happens.
|
||||||
|
*/
|
||||||
|
setOnMessageCallback(callback) {
|
||||||
|
onMessageCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when event "websocketFailed" happens.
|
||||||
|
*/
|
||||||
|
setOnErrorCallback(callback) {
|
||||||
|
onErrorCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when event "websocketClosed" happens.
|
||||||
|
*/
|
||||||
|
setOnCloseCallback(callback) {
|
||||||
|
onCloseCallback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
isInterceptorEnabled() {
|
||||||
|
return isInterceptorEnabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
_unregisterEvents() {
|
||||||
|
subscriptions.forEach(e => e.remove());
|
||||||
|
subscriptions = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listeners to the RCTWebSocketModule events to intercept them.
|
||||||
|
*/
|
||||||
|
_registerEvents() {
|
||||||
|
subscriptions = [
|
||||||
|
eventEmitter.addListener('websocketMessage', ev => {
|
||||||
|
if (onMessageCallback) {
|
||||||
|
onMessageCallback(
|
||||||
|
ev.id,
|
||||||
|
(ev.type === 'binary') ?
|
||||||
|
WebSocketInterceptor._arrayBufferToString(ev.data) : ev.data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
eventEmitter.addListener('websocketOpen', ev => {
|
||||||
|
if (onOpenCallback) {
|
||||||
|
onOpenCallback(ev.id);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
eventEmitter.addListener('websocketClosed', ev => {
|
||||||
|
if (onCloseCallback) {
|
||||||
|
onCloseCallback(ev.id, {code: ev.code, reason: ev.reason});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
eventEmitter.addListener('websocketFailed', ev => {
|
||||||
|
if (onErrorCallback) {
|
||||||
|
onErrorCallback(ev.id, {message: ev.message});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
enableInterception() {
|
||||||
|
if (isInterceptorEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eventEmitter = new NativeEventEmitter(RCTWebSocketModule);
|
||||||
|
WebSocketInterceptor._registerEvents();
|
||||||
|
|
||||||
|
// Override `connect` method for all RCTWebSocketModule requests
|
||||||
|
// to intercept the request url, protocols, options and socketId,
|
||||||
|
// then pass them through the `connectCallback`.
|
||||||
|
RCTWebSocketModule.connect = function(url, protocols, options, socketId) {
|
||||||
|
if (connectCallback) {
|
||||||
|
connectCallback(url, protocols, options, socketId);
|
||||||
|
}
|
||||||
|
originalRCTWebSocketConnect.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override `send` method for all RCTWebSocketModule requests to intercept
|
||||||
|
// the data sent, then pass them through the `sendCallback`.
|
||||||
|
RCTWebSocketModule.send = function(data, socketId) {
|
||||||
|
if (sendCallback) {
|
||||||
|
sendCallback(data, socketId);
|
||||||
|
}
|
||||||
|
originalRCTWebSocketSend.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override `sendBinary` method for all RCTWebSocketModule requests to
|
||||||
|
// intercept the data sent, then pass them through the `sendCallback`.
|
||||||
|
RCTWebSocketModule.sendBinary = function(data, socketId) {
|
||||||
|
if (sendCallback) {
|
||||||
|
sendCallback(WebSocketInterceptor._arrayBufferToString(data), socketId);
|
||||||
|
}
|
||||||
|
originalRCTWebSocketSendBinary.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override `close` method for all RCTWebSocketModule requests to intercept
|
||||||
|
// the close information, then pass them through the `closeCallback`.
|
||||||
|
RCTWebSocketModule.close = function() {
|
||||||
|
if (closeCallback) {
|
||||||
|
if (arguments.length === 3) {
|
||||||
|
closeCallback(arguments[0], arguments[1], arguments[2]);
|
||||||
|
} else {
|
||||||
|
closeCallback(null, null, arguments[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalRCTWebSocketClose.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
isInterceptorEnabled = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_arrayBufferToString(data) {
|
||||||
|
const value = base64.toByteArray(data).buffer;
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '(no value)';
|
||||||
|
}
|
||||||
|
if (typeof ArrayBuffer !== 'undefined' &&
|
||||||
|
typeof Uint8Array !== 'undefined' &&
|
||||||
|
value instanceof ArrayBuffer) {
|
||||||
|
return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Unpatch RCTWebSocketModule methods and remove the callbacks.
|
||||||
|
disableInterception() {
|
||||||
|
if (!isInterceptorEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isInterceptorEnabled = false;
|
||||||
|
RCTWebSocketModule.send = originalRCTWebSocketSend;
|
||||||
|
RCTWebSocketModule.sendBinary = originalRCTWebSocketSendBinary;
|
||||||
|
RCTWebSocketModule.close = originalRCTWebSocketClose;
|
||||||
|
RCTWebSocketModule.connect = originalRCTWebSocketConnect;
|
||||||
|
|
||||||
|
connectCallback = null;
|
||||||
|
closeCallback = null;
|
||||||
|
sendCallback = null;
|
||||||
|
onOpenCallback = null;
|
||||||
|
onMessageCallback = null;
|
||||||
|
onCloseCallback = null;
|
||||||
|
onErrorCallback = null;
|
||||||
|
|
||||||
|
WebSocketInterceptor._unregisterEvents();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = WebSocketInterceptor;
|
Loading…
Reference in New Issue