instabug-reactnative
Version:
React Native plugin for integrating the Instabug SDK
209 lines (208 loc) • 8.61 kB
JavaScript
import InstabugConstants from '../utils/InstabugConstants';
import xhr from '../utils/XhrNetworkInterceptor';
import { InstabugRNConfig } from '../utils/config';
import { Logger } from '../utils/logger';
import { NativeInstabug } from '../native/NativeInstabug';
import { isContentTypeNotAllowed, registerFilteringAndObfuscationListener, registerFilteringListener, registerObfuscationListener, reportNetworkLog, } from '../utils/InstabugUtils';
import { NativeNetworkLogger, NativeNetworkLoggerEvent, NetworkListenerType, NetworkLoggerEmitter, } from '../native/NativeNetworkLogger';
import { Platform } from 'react-native';
let _networkDataObfuscationHandler;
let _requestFilterExpression = 'false';
let _isNativeInterceptionEnabled = false;
let _networkListener = null;
let hasFilterExpression = false;
function getPortFromUrl(url) {
const portMatch = url.match(/:(\d+)(?=\/|$)/);
return portMatch ? portMatch[1] : null;
}
/**
* Sets whether network logs should be sent with bug reports.
* It is enabled by default.
* @param isEnabled
*/
export const setEnabled = (isEnabled) => {
if (isEnabled) {
xhr.enableInterception();
xhr.setOnDoneCallback(async (network) => {
// eslint-disable-next-line no-new-func
const predicate = Function('network', 'return ' + _requestFilterExpression);
if (!predicate(network)) {
const MAX_NETWORK_BODY_SIZE_IN_BYTES = await NativeInstabug.getNetworkBodyMaxSize();
try {
if (_networkDataObfuscationHandler) {
network = await _networkDataObfuscationHandler(network);
}
if (__DEV__) {
const urlPort = getPortFromUrl(network.url);
if (urlPort === InstabugRNConfig.metroDevServerPort) {
return;
}
}
if (network.requestBodySize > MAX_NETWORK_BODY_SIZE_IN_BYTES) {
network.requestBody = `${InstabugConstants.MAX_REQUEST_BODY_SIZE_EXCEEDED_MESSAGE}${MAX_NETWORK_BODY_SIZE_IN_BYTES / 1024} Kb`;
Logger.warn('IBG-RN:', `${InstabugConstants.MAX_REQUEST_BODY_SIZE_EXCEEDED_MESSAGE}${MAX_NETWORK_BODY_SIZE_IN_BYTES / 1024} Kb`);
}
if (network.responseBodySize > MAX_NETWORK_BODY_SIZE_IN_BYTES) {
network.responseBody = `${InstabugConstants.MAX_RESPONSE_BODY_SIZE_EXCEEDED_MESSAGE}${MAX_NETWORK_BODY_SIZE_IN_BYTES / 1024} Kb`;
Logger.warn('IBG-RN:', `${InstabugConstants.MAX_RESPONSE_BODY_SIZE_EXCEEDED_MESSAGE}${MAX_NETWORK_BODY_SIZE_IN_BYTES / 1024} Kb`);
}
if (network.requestBody && isContentTypeNotAllowed(network.requestContentType)) {
network.requestBody = `Body is omitted because content type ${network.requestContentType} isn't supported`;
Logger.warn(`IBG-RN: The request body for the network request with URL ${network.url} has been omitted because the content type ${network.requestContentType} isn't supported.`);
}
if (network.responseBody && isContentTypeNotAllowed(network.contentType)) {
network.responseBody = `Body is omitted because content type ${network.contentType} isn't supported`;
Logger.warn(`IBG-RN: The response body for the network request with URL ${network.url} has been omitted because the content type ${network.contentType} isn't supported.`);
}
reportNetworkLog(network);
}
catch (e) {
Logger.error(e);
}
}
});
}
else {
xhr.disableInterception();
}
};
/**
* @internal
* Sets whether enabling or disabling native network interception.
* It is disabled by default.
* @param isEnabled
*/
export const setNativeInterceptionEnabled = (isEnabled) => {
_isNativeInterceptionEnabled = isEnabled;
};
export const getNetworkDataObfuscationHandler = () => _networkDataObfuscationHandler;
export const getRequestFilterExpression = () => _requestFilterExpression;
export const hasRequestFilterExpression = () => hasFilterExpression;
/**
* Obfuscates any response data.
* @param handler
*/
export const setNetworkDataObfuscationHandler = (handler) => {
_networkDataObfuscationHandler = handler;
if (_isNativeInterceptionEnabled && Platform.OS === 'ios') {
if (hasFilterExpression) {
registerFilteringAndObfuscationListener(_requestFilterExpression);
}
else {
registerObfuscationListener();
}
}
};
/**
* Omit requests from being logged based on either their request or response details
* @param expression
*/
export const setRequestFilterExpression = (expression) => {
_requestFilterExpression = expression;
hasFilterExpression = true;
if (_isNativeInterceptionEnabled && Platform.OS === 'ios') {
if (_networkDataObfuscationHandler) {
registerFilteringAndObfuscationListener(_requestFilterExpression);
}
else {
registerFilteringListener(_requestFilterExpression);
}
}
};
/**
* Returns progress in terms of totalBytesSent and totalBytesExpectedToSend a network request.
* @param handler
*/
export const setProgressHandlerForRequest = (handler) => {
xhr.setOnProgressCallback(handler);
};
export const apolloLinkRequestHandler = (operation, forward) => {
try {
operation.setContext((context) => {
const newHeaders = context.headers ?? {};
newHeaders[InstabugConstants.GRAPHQL_HEADER] = operation.operationName;
return { headers: newHeaders };
});
}
catch (e) {
Logger.error(e);
}
return forward(operation);
};
/**
* Sets whether network body logs will be captured or not.
* @param isEnabled
*/
export const setNetworkLogBodyEnabled = (isEnabled) => {
NativeInstabug.setNetworkLogBodyEnabled(isEnabled);
};
/**
* @internal
* Exported for internal/testing purposes only.
*/
export const resetNetworkListener = () => {
if (process.env.NODE_ENV === 'test') {
_networkListener = null;
NativeNetworkLogger.resetNetworkLogsListener();
}
else {
Logger.error(`${InstabugConstants.IBG_APM_TAG}: The \`resetNetworkListener()\` method is intended solely for testing purposes.`);
}
};
/**
* @internal
* Exported for internal/testing purposes only.
*/
export const registerNetworkLogsListener = (type, handler) => {
if (Platform.OS === 'ios') {
// remove old listeners
if (NetworkLoggerEmitter.listenerCount(NativeNetworkLoggerEvent.NETWORK_LOGGER_HANDLER) > 0) {
NetworkLoggerEmitter.removeAllListeners(NativeNetworkLoggerEvent.NETWORK_LOGGER_HANDLER);
}
if (_networkListener == null) {
// set new listener.
_networkListener = type;
}
else {
// attach a new listener to the existing one.
_networkListener = NetworkListenerType.both;
}
}
NetworkLoggerEmitter.addListener(NativeNetworkLoggerEvent.NETWORK_LOGGER_HANDLER, (networkSnapshot) => {
// Mapping the data [Native -> React-Native].
const { id, url, requestHeader, requestBody, responseHeader, response, responseCode } = networkSnapshot;
const networkSnapshotObj = {
id: id,
url: url,
requestBody: requestBody,
requestHeaders: requestHeader,
method: '',
responseBody: response,
responseCode: responseCode,
responseHeaders: responseHeader,
contentType: '',
duration: 0,
requestBodySize: 0,
responseBodySize: 0,
errorDomain: '',
errorCode: 0,
startTime: 0,
serverErrorMessage: '',
requestContentType: '',
isW3cHeaderFound: true,
networkStartTimeInSeconds: 0,
partialId: 0,
w3cCaughtHeader: '',
w3cGeneratedHeader: '',
};
if (handler) {
handler(networkSnapshotObj);
}
});
if (Platform.OS === 'ios') {
NativeNetworkLogger.registerNetworkLogsListener(_networkListener ?? NetworkListenerType.both);
}
else {
NativeNetworkLogger.registerNetworkLogsListener();
}
};