diff --git a/.gitignore b/.gitignore index 4033aaa3b..efad193fc 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ android/keystores/debug.keystore # .idea run configurations /.run/* +.yarn/ +examples/default/.yarn/ +examples/default/ios/.xcode.env.local diff --git a/android/native.gradle b/android/native.gradle index faa3246cd..d9c7a2f5b 100644 --- a/android/native.gradle +++ b/android/native.gradle @@ -1,5 +1,5 @@ project.ext.instabug = [ - version: '15.0.1' + version: '15.0.2.7020723-SNAPSHOT' ] dependencies { diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java index d75b4f75b..f6ef387c1 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java @@ -1,6 +1,8 @@ package com.instabug.reactlibrary; +import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod; + import android.os.SystemClock; import android.util.Log; @@ -16,17 +18,13 @@ import com.instabug.apm.networking.APMNetworkLogger; import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; import com.instabug.reactlibrary.utils.EventEmitterModule; -import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; import com.instabug.reactlibrary.utils.MainThreadHandler; import java.lang.reflect.Method; - import java.util.HashMap; import javax.annotation.Nonnull; -import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod; - public class RNInstabugAPMModule extends EventEmitterModule { public RNInstabugAPMModule(ReactApplicationContext reactApplicationContext) { @@ -211,7 +209,6 @@ public void run() { * Starts an execution trace * * @param name string name of the trace. - * * @deprecated see {@link #startFlow(String)} */ @Deprecated @@ -242,7 +239,6 @@ public void run() { * @param id String id of the trace. * @param key attribute key * @param value attribute value. Null to remove attribute - * * @deprecated see {@link #setFlowAttribute} */ @Deprecated @@ -264,7 +260,6 @@ public void run() { * Ends a trace * * @param id string id of the trace. - * * @deprecated see {@link #endFlow} */ @Deprecated @@ -318,73 +313,73 @@ public void run() { }); } - /** - * The `networkLogAndroid` function logs network-related information using APMNetworkLogger in a React - * Native module. - * - * @param requestStartTime The `requestStartTime` parameter in the `networkLogAndroid` method - * represents the timestamp when the network request started. It is of type `double` and is passed as - * a parameter to log network-related information. - * @param requestDuration The `requestDuration` parameter in the `networkLogAndroid` method represents - * the duration of the network request in milliseconds. It indicates the time taken for the request to - * complete from the moment it was initiated until the response was received. This parameter helps in - * measuring the performance of network requests and identifying any potential - * @param requestHeaders requestHeaders is a string parameter that contains the headers of the network - * request. It typically includes information such as the content type, authorization token, and any - * other headers that were sent with the request. - * @param requestBody The `requestBody` parameter in the `networkLogAndroid` method represents the - * body of the HTTP request being logged. It contains the data that is sent as part of the request to - * the server. This could include form data, JSON payload, XML data, or any other content that is - * being transmitted - * @param requestBodySize The `requestBodySize` parameter in the `networkLogAndroid` method represents - * the size of the request body in bytes. It is a double value that indicates the size of the request - * body being sent in the network request. This parameter is used to log information related to the - * network request, including details - * @param requestMethod The `requestMethod` parameter in the `networkLogAndroid` method represents the - * HTTP method used in the network request, such as GET, POST, PUT, DELETE, etc. It indicates the type - * of operation that the client is requesting from the server. - * @param requestUrl The `requestUrl` parameter in the `networkLogAndroid` method represents the URL - * of the network request being logged. It typically contains the address of the server to which the - * request is being made, along with any additional path or query parameters required for the request. - * This URL is essential for identifying the - * @param requestContentType The `requestContentType` parameter in the `networkLogAndroid` method - * represents the content type of the request being made. This could be values like - * "application/json", "application/xml", "text/plain", etc., indicating the format of the data being - * sent in the request body. It helps in specifying - * @param responseHeaders The `responseHeaders` parameter in the `networkLogAndroid` method represents - * the headers of the response received from a network request. These headers typically include - * information such as content type, content length, server information, and any other metadata - * related to the response. The `responseHeaders` parameter is expected to - * @param responseBody The `responseBody` parameter in the `networkLogAndroid` method represents the - * body of the response received from a network request. It contains the data or content sent back by - * the server in response to the request made by the client. This could be in various formats such as - * JSON, XML, HTML - * @param responseBodySize The `responseBodySize` parameter in the `networkLogAndroid` method - * represents the size of the response body in bytes. It is a double value that indicates the size of - * the response body received from the network request. This parameter is used to log information - * related to the network request and response, including - * @param statusCode The `statusCode` parameter in the `networkLogAndroid` method represents the HTTP - * status code of the network request/response. It indicates the status of the HTTP response, such as - * success (200), redirection (3xx), client errors (4xx), or server errors (5xx). This parameter is - * @param responseContentType The `responseContentType` parameter in the `networkLogAndroid` method - * represents the content type of the response received from the network request. It indicates the - * format of the data in the response, such as JSON, XML, HTML, etc. This information is useful for - * understanding how to parse and handle the - * @param errorDomain The `errorDomain` parameter in the `networkLogAndroid` method is used to specify - * the domain of an error, if any occurred during the network request. If there was no error, this - * parameter will be `null`. - * @param w3cAttributes The `w3cAttributes` parameter in the `networkLogAndroid` method is a - * ReadableMap object that contains additional attributes related to W3C external trace. It may - * include the following key-value pairs: - * @param gqlQueryName The `gqlQueryName` parameter in the `networkLogAndroid` method represents the - * name of the GraphQL query being executed. It is a nullable parameter, meaning it can be null if no - * GraphQL query name is provided. This parameter is used to log information related to GraphQL - * queries in the network logging - * @param serverErrorMessage The `serverErrorMessage` parameter in the `networkLogAndroid` method is - * used to pass any error message received from the server during network communication. This message - * can provide additional details about any errors that occurred on the server side, helping in - * debugging and troubleshooting network-related issues. - */ + /** + * The `networkLogAndroid` function logs network-related information using APMNetworkLogger in a React + * Native module. + * + * @param requestStartTime The `requestStartTime` parameter in the `networkLogAndroid` method + * represents the timestamp when the network request started. It is of type `double` and is passed as + * a parameter to log network-related information. + * @param requestDuration The `requestDuration` parameter in the `networkLogAndroid` method represents + * the duration of the network request in milliseconds. It indicates the time taken for the request to + * complete from the moment it was initiated until the response was received. This parameter helps in + * measuring the performance of network requests and identifying any potential + * @param requestHeaders requestHeaders is a string parameter that contains the headers of the network + * request. It typically includes information such as the content type, authorization token, and any + * other headers that were sent with the request. + * @param requestBody The `requestBody` parameter in the `networkLogAndroid` method represents the + * body of the HTTP request being logged. It contains the data that is sent as part of the request to + * the server. This could include form data, JSON payload, XML data, or any other content that is + * being transmitted + * @param requestBodySize The `requestBodySize` parameter in the `networkLogAndroid` method represents + * the size of the request body in bytes. It is a double value that indicates the size of the request + * body being sent in the network request. This parameter is used to log information related to the + * network request, including details + * @param requestMethod The `requestMethod` parameter in the `networkLogAndroid` method represents the + * HTTP method used in the network request, such as GET, POST, PUT, DELETE, etc. It indicates the type + * of operation that the client is requesting from the server. + * @param requestUrl The `requestUrl` parameter in the `networkLogAndroid` method represents the URL + * of the network request being logged. It typically contains the address of the server to which the + * request is being made, along with any additional path or query parameters required for the request. + * This URL is essential for identifying the + * @param requestContentType The `requestContentType` parameter in the `networkLogAndroid` method + * represents the content type of the request being made. This could be values like + * "application/json", "application/xml", "text/plain", etc., indicating the format of the data being + * sent in the request body. It helps in specifying + * @param responseHeaders The `responseHeaders` parameter in the `networkLogAndroid` method represents + * the headers of the response received from a network request. These headers typically include + * information such as content type, content length, server information, and any other metadata + * related to the response. The `responseHeaders` parameter is expected to + * @param responseBody The `responseBody` parameter in the `networkLogAndroid` method represents the + * body of the response received from a network request. It contains the data or content sent back by + * the server in response to the request made by the client. This could be in various formats such as + * JSON, XML, HTML + * @param responseBodySize The `responseBodySize` parameter in the `networkLogAndroid` method + * represents the size of the response body in bytes. It is a double value that indicates the size of + * the response body received from the network request. This parameter is used to log information + * related to the network request and response, including + * @param statusCode The `statusCode` parameter in the `networkLogAndroid` method represents the HTTP + * status code of the network request/response. It indicates the status of the HTTP response, such as + * success (200), redirection (3xx), client errors (4xx), or server errors (5xx). This parameter is + * @param responseContentType The `responseContentType` parameter in the `networkLogAndroid` method + * represents the content type of the response received from the network request. It indicates the + * format of the data in the response, such as JSON, XML, HTML, etc. This information is useful for + * understanding how to parse and handle the + * @param errorDomain The `errorDomain` parameter in the `networkLogAndroid` method is used to specify + * the domain of an error, if any occurred during the network request. If there was no error, this + * parameter will be `null`. + * @param w3cAttributes The `w3cAttributes` parameter in the `networkLogAndroid` method is a + * ReadableMap object that contains additional attributes related to W3C external trace. It may + * include the following key-value pairs: + * @param gqlQueryName The `gqlQueryName` parameter in the `networkLogAndroid` method represents the + * name of the GraphQL query being executed. It is a nullable parameter, meaning it can be null if no + * GraphQL query name is provided. This parameter is used to log information related to GraphQL + * queries in the network logging + * @param serverErrorMessage The `serverErrorMessage` parameter in the `networkLogAndroid` method is + * used to pass any error message received from the server during network communication. This message + * can provide additional details about any errors that occurred on the server side, helping in + * debugging and troubleshooting network-related issues. + */ @ReactMethod private void networkLogAndroid(final double requestStartTime, final double requestDuration, @@ -403,15 +398,15 @@ private void networkLogAndroid(final double requestStartTime, @Nullable final ReadableMap w3cAttributes, @Nullable final String gqlQueryName, @Nullable final String serverErrorMessage - ) { + ) { try { APMNetworkLogger networkLogger = new APMNetworkLogger(); final boolean hasError = errorDomain != null && !errorDomain.isEmpty(); final String errorMessage = hasError ? errorDomain : null; - Boolean isW3cHeaderFound=false; - Long partialId=null; - Long networkStartTimeInSeconds=null; + Boolean isW3cHeaderFound = false; + Long partialId = null; + Long networkStartTimeInSeconds = null; try { @@ -420,7 +415,7 @@ private void networkLogAndroid(final double requestStartTime, } if (!w3cAttributes.isNull("partialId")) { - partialId =(long) w3cAttributes.getDouble("partialId"); + partialId = (long) w3cAttributes.getDouble("partialId"); networkStartTimeInSeconds = (long) w3cAttributes.getDouble("networkStartTimeInSeconds"); } @@ -438,34 +433,54 @@ private void networkLogAndroid(final double requestStartTime, try { Method method = getMethod(Class.forName("com.instabug.apm.networking.APMNetworkLogger"), "log", long.class, long.class, String.class, String.class, long.class, String.class, String.class, String.class, String.class, String.class, long.class, int.class, String.class, String.class, String.class, String.class, APMCPNetworkLog.W3CExternalTraceAttributes.class); if (method != null) { - method.invoke( - networkLogger, - (long) requestStartTime * 1000, - (long) requestDuration, - requestHeaders, - requestBody, - (long) requestBodySize, - requestMethod, - requestUrl, - requestContentType, - responseHeaders, - responseBody, - (long)responseBodySize, - (int) statusCode, - responseContentType, - errorMessage, - gqlQueryName, - serverErrorMessage, - w3cExternalTraceAttributes - ); + method.invoke( + networkLogger, + (long) requestStartTime * 1000, + (long) requestDuration, + requestHeaders, + requestBody, + (long) requestBodySize, + requestMethod, + requestUrl, + requestContentType, + responseHeaders, + responseBody, + (long) responseBodySize, + (int) statusCode, + responseContentType, + errorMessage, + gqlQueryName, + serverErrorMessage, + w3cExternalTraceAttributes + ); } else { Log.e("IB-CP-Bridge", "APMNetworkLogger.log was not found by reflection"); } } catch (Throwable e) { e.printStackTrace(); } - } catch(Throwable e) { + } catch (Throwable e) { e.printStackTrace(); } } + + + /** + * Enables or disables screen rendering + * + * @param isEnabled boolean indicating enabled or disabled. + */ + @ReactMethod + public void setScreenRenderEnabled(boolean isEnabled) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + APM.setScreenRenderingEnabled(isEnabled); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } } diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java index 17f48656f..8507993ad 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java @@ -26,7 +26,6 @@ import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.uimanager.UIManagerModule; import com.instabug.apm.InternalAPM; -import com.instabug.apm.configuration.cp.APMFeature; import com.instabug.library.Feature; import com.instabug.library.Instabug; import com.instabug.library.InstabugColorTheme; @@ -35,12 +34,11 @@ import com.instabug.library.LogLevel; import com.instabug.library.ReproConfigurations; import com.instabug.library.core.InstabugCore; +import com.instabug.library.featuresflags.model.IBGFeatureFlag; import com.instabug.library.internal.crossplatform.CoreFeature; import com.instabug.library.internal.crossplatform.CoreFeaturesState; import com.instabug.library.internal.crossplatform.FeaturesStateListener; import com.instabug.library.internal.crossplatform.InternalCore; -import com.instabug.library.featuresflags.model.IBGFeatureFlag; -import com.instabug.library.internal.crossplatform.InternalCore; import com.instabug.library.internal.crossplatform.OnFeaturesUpdatedListener; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; @@ -48,11 +46,9 @@ import com.instabug.library.model.NetworkLog; import com.instabug.library.model.Report; import com.instabug.library.ui.onboarding.WelcomeMessage; -import com.instabug.library.util.InstabugSDKLogger; import com.instabug.reactlibrary.utils.ArrayUtil; import com.instabug.reactlibrary.utils.EventEmitterModule; import com.instabug.reactlibrary.utils.MainThreadHandler; - import com.instabug.reactlibrary.utils.RNTouchedViewExtractor; import org.json.JSONException; @@ -115,6 +111,7 @@ public void removeListeners(Integer count) { /** * Enables or disables Instabug functionality. + * * @param isEnabled A boolean to enable/disable Instabug. */ @ReactMethod @@ -1175,7 +1172,7 @@ public void invoke(@NonNull CoreFeaturesState featuresState) { params.putBoolean("isW3ExternalTraceIDEnabled", featuresState.isW3CExternalTraceIdEnabled()); params.putBoolean("isW3ExternalGeneratedHeaderEnabled", featuresState.isAttachingGeneratedHeaderEnabled()); params.putBoolean("isW3CaughtHeaderEnabled", featuresState.isAttachingCapturedHeaderEnabled()); - params.putInt("networkBodyLimit",featuresState.getNetworkLogCharLimit()); + params.putInt("networkBodyLimit", featuresState.getNetworkLogCharLimit()); sendEvent(Constants.IBG_ON_FEATURE_FLAGS_UPDATE_RECEIVED_CALLBACK, params); } @@ -1259,7 +1256,7 @@ public void run() { * Map between the exported JS constant and the arg key in {@link ArgsRegistry}. * The constant name and the arg key should match to be able to resolve the * constant with its actual value from the {@link ArgsRegistry} maps. - * + *

* This is a workaround, because RN cannot resolve enums in the constants map. */ @Override @@ -1290,23 +1287,25 @@ public void invoke() { } }); } + /** - * Enables or disables capturing network body. - * @param isEnabled A boolean to enable/disable capturing network body. - */ - @ReactMethod - public void setNetworkLogBodyEnabled(final boolean isEnabled) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Instabug.setNetworkLogBodyEnabled(isEnabled); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } + * Enables or disables capturing network body. + * + * @param isEnabled A boolean to enable/disable capturing network body. + */ + @ReactMethod + public void setNetworkLogBodyEnabled(final boolean isEnabled) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + Instabug.setNetworkLogBodyEnabled(isEnabled); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } /** * Sets the auto mask screenshots types. diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java index 85ca1384d..63445978f 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java @@ -205,5 +205,13 @@ public void testSetFlowAttribute() { APM.endUITrace(); } + @Test + public void given$setScreenRenderEnabled_whenQuery_thenShouldCallNativeApiWithEnabled() { + apmModule.setScreenRenderEnabled(true); + // then + verify(APM.class, times(1)); + APM.setScreenRenderingEnabled(true); + } + } diff --git a/examples/default/ios/InstabugTests/InstabugAPMTests.m b/examples/default/ios/InstabugTests/InstabugAPMTests.m index 949393adb..cac456550 100644 --- a/examples/default/ios/InstabugTests/InstabugAPMTests.m +++ b/examples/default/ios/InstabugTests/InstabugAPMTests.m @@ -177,6 +177,22 @@ - (void) testEndUITrace { OCMVerify([mock endUITrace]); } +- (void) testSetScreenRenderEnabled { + id mock = OCMClassMock([IBGAPM class]); + BOOL isEnabled = YES; + [self.instabugBridge setScreenRenderEnabled:isEnabled]; + + OCMVerify([mock setScreenRenderingEnabled:YES]); +} + +- (void) testSetScreenRenderDisabled { + id mock = OCMClassMock([IBGAPM class]); + BOOL isEnabled = NO; + + [self.instabugBridge setScreenRenderEnabled:isEnabled]; + + OCMVerify([mock setScreenRenderingEnabled:NO]); +} @end diff --git a/examples/default/ios/Podfile b/examples/default/ios/Podfile index e1dda08e3..7b4496b80 100644 --- a/examples/default/ios/Podfile +++ b/examples/default/ios/Podfile @@ -15,7 +15,7 @@ target 'InstabugExample' do config = use_native_modules! rn_maps_path = '../node_modules/react-native-maps' pod 'react-native-google-maps', :path => rn_maps_path - + pod 'Instabug', :podspec => 'https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.16/Instabug.podspec' # Flags change depending on the env values. flags = get_default_flags() diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index 0d7d7a1ff..6795679c7 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -31,7 +31,7 @@ PODS: - hermes-engine (0.75.4): - hermes-engine/Pre-built (= 0.75.4) - hermes-engine/Pre-built (0.75.4) - - Instabug (15.1.1) + - Instabug (15.1.16) - instabug-reactnative-ndk (0.1.0): - DoubleConversion - glog @@ -1626,7 +1626,7 @@ PODS: - ReactCommon/turbomodule/core - Yoga - RNInstabug (15.0.1): - - Instabug (= 15.1.1) + - Instabug (= 15.1.16) - React-Core - RNReanimated (3.16.1): - DoubleConversion @@ -1770,6 +1770,7 @@ DEPENDENCIES: - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - Instabug (from `https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.16/Instabug.podspec`) - instabug-reactnative-ndk (from `../node_modules/instabug-reactnative-ndk`) - OCMock - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -1850,7 +1851,6 @@ SPEC REPOS: trunk: - Google-Maps-iOS-Utils - GoogleMaps - - Instabug - OCMock - SocketRocket @@ -1868,6 +1868,8 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-08-15-RNv0.75.1-4b3bf912cc0f705b51b71ce1a5b8bd79b93a451b + Instabug: + :podspec: https://ios-releases.instabug.com/custom/faeture-screen_rendering-release/15.1.16/Instabug.podspec instabug-reactnative-ndk: :path: "../node_modules/instabug-reactnative-ndk" RCT-Folly: @@ -2022,7 +2024,7 @@ SPEC CHECKSUMS: Google-Maps-iOS-Utils: f77eab4c4326d7e6a277f8e23a0232402731913a GoogleMaps: 032f676450ba0779bd8ce16840690915f84e57ac hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0 - Instabug: 3e7af445c14d7823fcdecba223f09b5f7c0c6ce1 + Instabug: 9fcae5627558e1832a0f49c81bb26c20aaf8af7f instabug-reactnative-ndk: d765ac289d56e8896398d02760d9abf2562fc641 OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 @@ -2090,7 +2092,7 @@ SPEC CHECKSUMS: ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb RNGestureHandler: 511250b190a284388f9dd0d2e56c1df76f14cfb8 - RNInstabug: f17d4e6c679fbc921f2692c223a1c21395589cc5 + RNInstabug: 62ac32fd0a0ecb7720aa115fec391e74b8bd5a27 RNReanimated: f42a5044d121d68e91680caacb0293f4274228eb RNScreens: c7ceced6a8384cb9be5e7a5e88e9e714401fd958 RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d @@ -2098,6 +2100,6 @@ SPEC CHECKSUMS: SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 055f92ad73f8c8600a93f0e25ac0b2344c3b07e6 -PODFILE CHECKSUM: 837b933596e1616ff02cc206bb17dee4f611fdbc +PODFILE CHECKSUM: 06ff71958843b5a526fbd11ecc1aad124421d74f COCOAPODS: 1.14.0 diff --git a/examples/default/src/App.tsx b/examples/default/src/App.tsx index ceef8bc19..2fc62edbe 100644 --- a/examples/default/src/App.tsx +++ b/examples/default/src/App.tsx @@ -69,7 +69,7 @@ export const App: React.FC = () => { networkData.url = `${networkData.url}/JS/Obfuscated`; return networkData; }); - // NetworkLogger.setRequestFilterExpression('false'); + // APM.setScreenRenderEnabled(true); }); }); diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 090aa6587..0eafe75cb 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -22,7 +22,8 @@ import { import { GoogleMapsScreen } from '../screens/user-steps/GoogleMapsScreen'; import { LargeImageListScreen } from '../screens/user-steps/LargeImageListScreen'; import { APMScreen } from '../screens/apm/APMScreen'; -import { TracesScreen } from '../screens/apm/TracesScreen'; +import { ExecutionTraceScreen } from '../screens/apm/ExecutionTraceScreen'; +import { CustomUITraceScreen } from '../screens/apm/CustomUITraceScreen'; import { NetworkScreen } from '../screens/apm/NetworkScreen'; import { FlowsScreen } from '../screens/apm/FlowsScreen'; import { SessionReplayScreen } from '../screens/SessionReplayScreen'; @@ -31,6 +32,7 @@ import { HttpScreen } from '../screens/apm/HttpScreen'; import { WebViewsScreen } from '../screens/apm/webViews/WebViewsScreen'; import { FullWebViewsScreen } from '../screens/apm/webViews/FullWebViewsScreen'; import { PartialWebViewsScreen } from '../screens/apm/webViews/PartialWebViewsScreen'; +import ScreenRender from '../screens/apm/ScreenRender'; export type HomeStackParamList = { Home: undefined; @@ -43,7 +45,7 @@ export type HomeStackParamList = { BasicComponents: undefined; ScrollView: undefined; FlatList: undefined; - ComplexViews: undefined; + ComplexViews: { initialDepth?: number; initialBreadth?: number } | undefined; SectionList: undefined; Gestures: undefined; GoogleMapsScreen: undefined; @@ -57,10 +59,12 @@ export type HomeStackParamList = { APM: undefined; NetworkTraces: undefined; ExecutionTraces: undefined; + CustomUITraces: undefined; AppFlows: undefined; WebViews: undefined; FullWebViews: undefined; PartialWebViews: undefined; + ScreenRender: undefined; }; const HomeStack = createNativeStackNavigator(); @@ -140,7 +144,8 @@ export const HomeStackNavigator: React.FC = () => { - + + { component={PartialWebViewsScreen} options={{ title: 'PartialWebViews' }} /> + ); }; diff --git a/examples/default/src/screens/apm/APMScreen.tsx b/examples/default/src/screens/apm/APMScreen.tsx index 3652a95c5..7f647924d 100644 --- a/examples/default/src/screens/apm/APMScreen.tsx +++ b/examples/default/src/screens/apm/APMScreen.tsx @@ -34,10 +34,12 @@ export const APMScreen: React.FC APM.endAppLaunch()} /> navigation.navigate('NetworkTraces')} /> - navigation.navigate('ExecutionTraces')} /> + navigation.navigate('ExecutionTraces')} /> + navigation.navigate('CustomUITraces')} /> navigation.navigate('AppFlows')} /> navigation.navigate('WebViews')} /> navigation.navigate('ComplexViews')} /> + navigation.navigate('ScreenRender')} /> ); }; diff --git a/examples/default/src/screens/apm/CustomUITraceScreen.tsx b/examples/default/src/screens/apm/CustomUITraceScreen.tsx new file mode 100644 index 000000000..f03b13334 --- /dev/null +++ b/examples/default/src/screens/apm/CustomUITraceScreen.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { APM } from 'instabug-reactnative'; +import { ScrollView } from 'react-native'; +import { Section } from '../../components/Section'; +import { Screen } from '../../components/Screen'; +import { VStack } from 'native-base'; +import { InputField } from '../../components/InputField'; +import { CustomButton } from '../../components/CustomButton'; +import BackgroundTimer from 'react-native-background-timer'; + +export const CustomUITraceScreen: React.FC = () => { + const [traceName, setTraceName] = useState(''); + + function startUITrace() { + if (!traceName.trim()) { + console.log('Please enter a trace name before starting.'); + return; + } + APM.startUITrace(traceName); + + console.log(`UI trace "${traceName}" started.`); + } + + function startDelayedUITrace() { + if (!traceName.trim()) { + console.log('Please enter a trace name before starting.'); + return; + } + return BackgroundTimer.setTimeout(() => { + APM.startUITrace(traceName); + console.log(`Delayed UI trace "${traceName}" started.`); + }, 5000); + } + + function endUITrace() { + APM.endUITrace(); + console.log('UI trace ended.'); + } + + return ( + + +

+ + setTraceName(text)} + value={traceName} + /> + + + + +
+ + + ); +}; diff --git a/examples/default/src/screens/apm/TracesScreen.tsx b/examples/default/src/screens/apm/ExecutionTraceScreen.tsx similarity index 97% rename from examples/default/src/screens/apm/TracesScreen.tsx rename to examples/default/src/screens/apm/ExecutionTraceScreen.tsx index bd3e41838..c25c14cae 100644 --- a/examples/default/src/screens/apm/TracesScreen.tsx +++ b/examples/default/src/screens/apm/ExecutionTraceScreen.tsx @@ -8,7 +8,7 @@ import { InputField } from '../../components/InputField'; import { CustomButton } from '../../components/CustomButton'; import BackgroundTimer from 'react-native-background-timer'; -export const TracesScreen: React.FC = () => { +export const ExecutionTraceScreen: React.FC = () => { const [traceName, setTraceName] = useState(''); const [traceAttributeKey, setTraceAttributeKey] = useState(''); const [traceAttributeValue, setTraceAttributeValue] = useState(''); diff --git a/examples/default/src/screens/apm/ScreenRender.tsx b/examples/default/src/screens/apm/ScreenRender.tsx new file mode 100644 index 000000000..f9113a2c8 --- /dev/null +++ b/examples/default/src/screens/apm/ScreenRender.tsx @@ -0,0 +1,445 @@ +/* eslint-disable react-native/no-inline-styles */ +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import React, { useEffect, useRef, useState } from 'react'; +import { + Animated, + SafeAreaView, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; +import { APM } from 'instabug-reactnative'; + +// Custom Components +const ScreenRenderSwitch: React.FC = () => { + const [isEnabled, setIsEnabled] = useState(false); + + return ( + + Screen Render Monitoring + { + setIsEnabled(!isEnabled); + APM.setScreenRenderEnabled(isEnabled); + }}> + + + + ); +}; + +const AnimatedBox: React.FC<{ isBlocking: boolean; blockingIntensity: number }> = ({ + isBlocking, + blockingIntensity, +}) => { + const [counter, setCounter] = useState(0); + const [layoutThrasher, setLayoutThrasher] = useState(0); + const animatedValue = useRef(new Animated.Value(0)).current; + const intervalRef = useRef(null); + + // Continuous animation - Use native driver for native thread work + useEffect(() => { + const animation = Animated.loop( + Animated.sequence([ + Animated.timing(animatedValue, { + toValue: 1, + duration: 1000, + useNativeDriver: true, // Native driver for native thread + }), + Animated.timing(animatedValue, { + toValue: 0, + duration: 1000, + useNativeDriver: true, // Native driver for native thread + }), + ]), + ); + animation.start(); + + return () => animation.stop(); + }, [animatedValue]); + + // High frequency counter updates + useEffect(() => { + intervalRef.current = setInterval(() => { + setCounter((prev) => prev + 1); + + // Layout thrashing to block native thread + if (isBlocking) { + setLayoutThrasher((prev) => prev + 1); + } + }, 16); // ~60fps updates + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [isBlocking]); + + const translateX = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, 100], + }); + + const getStatusText = () => { + if (!isBlocking) { + return 'Running Smoothly'; + } + if (blockingIntensity === 1) { + return 'SLOW NATIVE RENDERING!'; + } + if (blockingIntensity === 2) { + return 'FROZEN NATIVE THREAD!'; + } + return 'BLOCKING NATIVE THREAD!'; + }; + + const getBoxColor = () => { + if (!isBlocking) { + return '#4ECDC4'; + } + if (blockingIntensity === 1) { + return '#FFB347'; + } // Orange for slow + if (blockingIntensity === 2) { + return '#FF6B6B'; + } // Red for frozen + return '#FF6B6B'; + }; + + // Generate many layout-heavy elements to stress native thread + const generateHeavyNativeElements = () => { + if (!isBlocking) { + return null; + } + + const elementCount = blockingIntensity === 1 ? 50 : 200; // More elements = more native work + + return Array.from({ length: elementCount }, (_, i) => ( + + )); + }; + + return ( + + Frame Counter: {counter} + + Status: {getStatusText()} + + + {/* Native thread heavy work area */} + + + + {blockingIntensity === 1 ? 'Slow!' : blockingIntensity === 2 ? 'Frozen!' : 'Smooth'} + + + + {/* Heavy native rendering elements */} + {generateHeavyNativeElements()} + + + {/* Additional native-heavy components */} + {isBlocking && ( + + {/* Multiple ScrollViews to stress native scrolling */} + + {Array.from({ length: 100 }, (_, i) => ( + + ))} + + + {/* Text that forces layout recalculation */} + + Layout Thrashing Text: {layoutThrasher} + + + )} + + ); +}; + +interface InstabugButtonProps { + text: string; + onPress: () => void; + disabled?: boolean; +} + +const InstabugButton: React.FC = ({ text, onPress, disabled }) => { + return ( + + {text} + + ); +}; + +// Main Component +const ScreenRenderPage: React.FC> = ({ + navigation, +}) => { + const [isBlocking, setIsBlocking] = useState(false); + const [blockingIntensity, setBlockingIntensity] = useState(0); // 0 = none, 1 = slow, 2 = frozen + const blockingTimeoutRef = useRef(null); + + const triggerFrozenFrames = (): void => { + setIsBlocking(true); + setBlockingIntensity(2); // Frozen frames mode + + // Clear any existing timeout + if (blockingTimeoutRef.current) { + clearTimeout(blockingTimeoutRef.current); + } + + // Stop blocking after 5 seconds + blockingTimeoutRef.current = setTimeout(() => { + setIsBlocking(false); + setBlockingIntensity(0); + }, 5000); + }; + + const navigateToComplexPage = (): void => { + navigation.navigate('ComplexViews', { initialDepth: 10, initialBreadth: 2 }); + }; + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (blockingTimeoutRef.current) { + clearTimeout(blockingTimeoutRef.current); + } + }; + }, []); + + return ( + + + + + + + + + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f5f5f5', + }, + scrollContent: { + padding: 20, + }, + title: { + fontSize: 24, + fontWeight: 'bold', + textAlign: 'center', + marginBottom: 20, + color: '#333', + }, + spacer: { + height: 16, + }, + largeSpacer: { + height: 50, + }, + buttonContainer: { + gap: 12, + }, + button: { + backgroundColor: '#007AFF', + paddingVertical: 12, + paddingHorizontal: 20, + borderRadius: 8, + alignItems: 'center', + marginVertical: 4, + }, + buttonText: { + color: 'white', + fontSize: 16, + fontWeight: '600', + }, + buttonDisabled: { + backgroundColor: '#ccc', + opacity: 0.7, + }, + buttonTextDisabled: { + color: '#888', + }, + switchContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 12, + paddingHorizontal: 16, + backgroundColor: 'white', + borderRadius: 8, + marginVertical: 8, + }, + switchLabel: { + fontSize: 16, + color: '#333', + }, + switch: { + width: 50, + height: 30, + borderRadius: 15, + backgroundColor: '#ccc', + justifyContent: 'center', + padding: 2, + }, + switchEnabled: { + backgroundColor: '#007AFF', + }, + switchThumb: { + width: 26, + height: 26, + borderRadius: 13, + backgroundColor: 'white', + alignSelf: 'flex-start', + }, + switchThumbEnabled: { + alignSelf: 'flex-end', + }, + animatedContainer: { + alignItems: 'center', + paddingVertical: 20, + backgroundColor: 'white', + borderRadius: 8, + marginVertical: 8, + }, + counterText: { + fontSize: 18, + fontWeight: 'bold', + color: '#333', + marginBottom: 15, + }, + animatedBox: { + width: 100, + height: 60, + backgroundColor: '#FF6B6B', + borderRadius: 8, + justifyContent: 'center', + alignItems: 'center', + transform: [{ scale: 1 }], + }, + animatedBoxActive: { + backgroundColor: '#4ECDC4', + transform: [{ scale: 1.1 }], + }, + animatedBoxText: { + color: 'white', + fontSize: 14, + fontWeight: '600', + textAlign: 'center', + }, + statusText: { + fontSize: 16, + color: '#666', + marginBottom: 10, + }, + statusTextAlert: { + color: '#FF6B6B', // Red for alert + fontWeight: 'bold', + }, + additionalElements: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + marginTop: 20, + }, + smallBox: { + width: 30, + height: 30, + margin: 5, + borderRadius: 15, + }, + nativeWorkArea: { + position: 'relative', + width: 200, + height: 200, + backgroundColor: '#f0f0f0', + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + marginVertical: 10, + borderWidth: 1, + borderColor: '#ccc', + }, + heavyNativeSection: { + marginTop: 20, + alignItems: 'center', + }, + miniScrollView: { + width: '100%', + height: 100, + backgroundColor: '#e0e0e0', + borderRadius: 8, + marginBottom: 10, + }, +}); + +export default ScreenRenderPage; diff --git a/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx b/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx index 0035d5775..b6fc337a6 100644 --- a/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx +++ b/examples/default/src/screens/user-steps/ComplexViewsScreen.tsx @@ -1,4 +1,6 @@ import React, { useRef, useState } from 'react'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../navigation/HomeStack'; import { Screen } from '../../components/Screen'; import { Section } from '../../components/Section'; @@ -7,10 +9,11 @@ import { Button } from 'react-native'; import { ScrollView, VStack } from 'native-base'; import { InputField } from '../../components/InputField'; -export const ComplexViewsScreen: React.FC = () => { - const initialDepth = 10; - const initialBreadth = 2; - +export const ComplexViewsScreen: React.FC< + NativeStackScreenProps +> = ({ route }) => { + const initialDepth = route.params?.initialDepth ?? 10; + const initialBreadth = route.params?.initialBreadth ?? 2; const depthRef = useRef(initialDepth); const breadthRef = useRef(initialBreadth); diff --git a/ios/RNInstabug/InstabugAPMBridge.h b/ios/RNInstabug/InstabugAPMBridge.h index 0a0ea397c..509e7fae3 100644 --- a/ios/RNInstabug/InstabugAPMBridge.h +++ b/ios/RNInstabug/InstabugAPMBridge.h @@ -27,6 +27,8 @@ - (void)startUITrace:(NSString *)name; - (void)endUITrace; +- (void)setScreenRenderEnabled:(BOOL)isEnabled; + extern NSMutableDictionary *traces; @end diff --git a/ios/RNInstabug/InstabugAPMBridge.m b/ios/RNInstabug/InstabugAPMBridge.m index c28c7f425..3fe3effe4 100644 --- a/ios/RNInstabug/InstabugAPMBridge.m +++ b/ios/RNInstabug/InstabugAPMBridge.m @@ -124,6 +124,10 @@ - (id) init [IBGAPM endUITrace]; } +// Enables or disables screen render. +RCT_EXPORT_METHOD(setScreenRenderEnabled:(BOOL)isEnabled) { + IBGAPM.screenRenderingEnabled = isEnabled; +} diff --git a/ios/native.rb b/ios/native.rb index 41f497687..a62fd4577 100644 --- a/ios/native.rb +++ b/ios/native.rb @@ -1,4 +1,4 @@ -$instabug = { :version => '15.1.1' } +$instabug = { :version => '15.1.16' } def use_instabug! (spec = nil) version = $instabug[:version] diff --git a/src/modules/APM.ts b/src/modules/APM.ts index 92d401389..d81d3cbb5 100644 --- a/src/modules/APM.ts +++ b/src/modules/APM.ts @@ -139,3 +139,11 @@ export const endUITrace = () => { export const _ibgSleep = () => { NativeAPM.ibgSleep(); }; + +/** + * Enables or disables Screen Render feature + * @param isEnabled + */ +export const setScreenRenderEnabled = (isEnabled: boolean) => { + NativeAPM.setScreenRenderEnabled(isEnabled); +}; diff --git a/src/native/NativeAPM.ts b/src/native/NativeAPM.ts index 9fa30b702..67c7d8cd1 100644 --- a/src/native/NativeAPM.ts +++ b/src/native/NativeAPM.ts @@ -48,6 +48,9 @@ export interface ApmNativeModule extends NativeModule { startUITrace(name: string): void; endUITrace(): void; ibgSleep(): void; + + // Screen Rendering // + setScreenRenderEnabled(isEnabled: boolean): void; } export const NativeAPM = NativeModules.IBGAPM; diff --git a/test/mocks/mockAPM.ts b/test/mocks/mockAPM.ts index 27644c694..beb0b068b 100644 --- a/test/mocks/mockAPM.ts +++ b/test/mocks/mockAPM.ts @@ -17,6 +17,7 @@ const mockAPM: ApmNativeModule = { endAppLaunch: jest.fn(), ibgSleep: jest.fn(), networkLogAndroid: jest.fn(), + setScreenRenderEnabled: jest.fn(), }; export default mockAPM; diff --git a/test/modules/APM.spec.ts b/test/modules/APM.spec.ts index cf932d25c..39d5df4be 100644 --- a/test/modules/APM.spec.ts +++ b/test/modules/APM.spec.ts @@ -155,4 +155,11 @@ describe('APM Module', () => { expect(NativeAPM.ibgSleep).toBeCalledTimes(1); expect(NativeAPM.ibgSleep).toBeCalledWith(); }); + + it('should call the native method setScreenRenderEnabled', () => { + APM.setScreenRenderEnabled(true); + + expect(NativeAPM.setScreenRenderEnabled).toBeCalledTimes(1); + expect(NativeAPM.setScreenRenderEnabled).toBeCalledWith(true); + }); });