@dynatrace/react-native-plugin 2.345.1 → 2.347.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,8 +35,8 @@ If you want to start using this plugin and are not a Dynatrace customer yet, hea
35
35
  ## Agent Versions
36
36
  These agent versions are configured in this plugin:
37
37
 
38
- * Android Agent: 8.345.1.1003
39
- * iOS Agent: 8.345.1.1003
38
+ * Android Agent: 8.347.1.1003
39
+ * iOS Agent: 8.347.1.1004
40
40
 
41
41
  ## Quick Setup
42
42
 
@@ -56,6 +56,7 @@ These agent versions are configured in this plugin:
56
56
  * [Send Exception Event](#send-exception-event)
57
57
  * [Send HTTP Request Event](#send-http-request-event)
58
58
  * [View Monitoring](#view-monitoring)
59
+ * [App start user action](#app-start-user-action)
59
60
  * [React Native Symbolication](#react-native-symbolication)
60
61
  * [RUM Classic](#rum-classic)
61
62
  * [Monitor a Component](#monitor-a-component)
@@ -653,6 +654,38 @@ Touches inside one of the 2 screens now produce:
653
654
  >
654
655
  > **Note:** The static property has no effect when a component is rendered as JSX. In that case the name is taken from either the `dtActionName` prop passed to it (option 1) or the name of the JSX component.
655
656
 
657
+ ### App start user action
658
+
659
+ OneAgent automatically creates a user action for each app start. The action starts at the first measured React Native startup phase (`runJsBundleStart`) and ends when `contentAppeared` fires — once the JavaScript bundle has loaded and the first content is displayed. The action groups all web requests, navigations, and errors that occur during startup.
660
+
661
+ **Startup phases captured (in order):**
662
+
663
+ | Phase | Android | iOS |
664
+ |---|---|---|
665
+ | `downloadStart` / `downloadEnd` | Dev mode only (Metro bundle download) | Not captured |
666
+ | `runJsBundleStart` / `runJsBundleEnd` | Yes | Yes |
667
+ | `contentAppeared` | Yes | Yes |
668
+
669
+ > **Note:** Unlike Android and iOS native agents, the React Native plugin does not distinguish cold, warm, and hot start types — the native SDK sets the start type when it receives the event.
670
+
671
+ **Controlling the app start user action**
672
+
673
+ The app start user action is part of automatic user action detection and is controlled at runtime with `setAutomaticUserActionDetection`:
674
+
675
+ ```ts
676
+ import { Dynatrace } from '@dynatrace/react-native-plugin';
677
+
678
+ // Disable all automatic user actions including the app start user action
679
+ Dynatrace.setAutomaticUserActionDetection(false);
680
+
681
+ // Re-enable
682
+ Dynatrace.setAutomaticUserActionDetection(true);
683
+ ```
684
+
685
+ This call applies to all automatic user actions (app start, touch/press interactions). To disable only touch interactions while keeping the app start user action, use `react.userInteraction: false` in `dynatrace.config.js` instead.
686
+
687
+ For more details, see the [App start user action documentation](https://docs.dynatrace.com/docs/shortlink/react-native-app-performance#app-start-user-action).
688
+
656
689
  ### React Native Symbolication
657
690
 
658
691
  Dynatrace can automatically symbolicate JavaScript stack traces captured by the plugin using sourcemaps. This allows you to view human-readable file names, line numbers, and column information in your crash reports.
@@ -2053,6 +2086,13 @@ If you are struggling with a problem, submit a support ticket to Dynatrace (supp
2053
2086
 
2054
2087
  In Progress
2055
2088
 
2089
+ 2.347.1
2090
+ * Updated Android (8.347.1.1003) & iOS Agent (8.347.1.1004)
2091
+ * Renamed `registerEventTrigger` to `registerNavigationEventTrigger` for clarity
2092
+ * Fixed re-configure failure to properly clear the table reload trigger
2093
+ * Updated `xmldom` dependency in Session Replay to a non-vulnerable version (DEM-32539)
2094
+
2095
+
2056
2096
  2.345.1
2057
2097
  * Updated Android (8.345.1.1003) & iOS Agent (8.345.1.1003)
2058
2098
  * Renamed `monitorNavigation` to `useNavigationMonitor`; React Navigation screen changes are now detected automatically, stamped with `start_time` and `duration`, and trigger Session Replay capture after the incoming screen settles
@@ -85,7 +85,7 @@ repositories {
85
85
  }
86
86
 
87
87
  dependencies {
88
- implementation 'com.dynatrace.agent:agent-android:8.345.1.1003'
88
+ implementation 'com.dynatrace.agent:agent-android:8.347.1.1003'
89
89
  implementation "com.facebook.react:react-native:${safeExtGet('reactNative', '+')}"
90
90
  }
91
91
 
@@ -1,6 +1,9 @@
1
1
  package com.dynatrace.android.agent
2
2
 
3
3
  import android.location.Location
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import android.view.Choreographer
4
7
  import com.dynatrace.android.agent.conf.DataCollectionLevel
5
8
  import com.dynatrace.android.agent.conf.DynatraceConfigurationBuilder
6
9
  import com.dynatrace.android.agent.conf.UserPrivacyOptions
@@ -46,6 +49,7 @@ class DynatraceRNBridgeImpl(
46
49
  private var lastConfiguration: MutableMap<String, Any>? = null
47
50
  private var hasInternalListener: Boolean = false
48
51
  private var listenersCount: Int = 0
52
+ private val navGeneration = java.util.concurrent.atomic.AtomicInteger(0)
49
53
 
50
54
  fun getConstants(): Map<String, Any> {
51
55
  return constants;
@@ -472,8 +476,28 @@ class DynatraceRNBridgeImpl(
472
476
  }
473
477
  }
474
478
 
479
+ // NOTE: This callback is observational only — it measures the first rendered frame for
480
+ // diagnostic tooling (LOAD TIME display). It does not defer stopView(); deferring the
481
+ // view-end to the rendered frame to fix TTID under-reporting (DEM-32277) is a separate task.
475
482
  fun startViewInternal(fields: ReadableMap) {
483
+ val imNanos = System.nanoTime()
484
+ val gen = navGeneration.incrementAndGet()
476
485
  HybridBridge.startView(JSONObject(DynatraceUtils.toHashMap(fields)))
486
+ Handler(Looper.getMainLooper()).post {
487
+ Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
488
+ // Discard stale callbacks superseded by a newer navigation.
489
+ if (navGeneration.get() != gen) return@postFrameCallback
490
+ val frameEpochMs = System.currentTimeMillis()
491
+ val deltaMs = (frameTimeNanos - imNanos) / 1_000_000.0
492
+ val params = Arguments.createMap().apply {
493
+ putDouble("frameEndMs", frameEpochMs.toDouble())
494
+ putDouble("deltaMs", deltaMs)
495
+ }
496
+ reactApplicationContext
497
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
498
+ .emit("DTNavigationFrame", params)
499
+ }
500
+ }
477
501
  }
478
502
 
479
503
  fun stopViewInternal() {
@@ -1,4 +1,4 @@
1
1
  // TEMPLATE: plugin-gradle.template
2
2
  dependencies {
3
- classpath 'com.dynatrace.tools.android:gradle-plugin:8.345.1.1003'
3
+ classpath 'com.dynatrace.tools.android:gradle-plugin:8.347.1.1003'
4
4
  }
@@ -1 +1 @@
1
- var _templateObject,_templateObject2,_templateObject3,_templateObject4,_templateObject5,_templateObject6,_interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault"),_taggedTemplateLiteral2=_interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral"));function _createForOfIteratorHelper(e,t){var n,r,a,i,o="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(o)return a=!(r=!0),{s:function(){o=o.call(e)},n:function(){var e=o.next();return r=e.done,e},e:function(e){a=!0,n=e},f:function(){try{r||null==o.return||o.return()}finally{if(a)throw n}}};if(Array.isArray(e)||(o=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return o&&(e=o),i=0,{s:t=function(){},n:function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var n;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}Object.defineProperty(exports,"__esModule",{value:!0});var reactOptions,fspath=require("path"),fs=require("fs"),core_1=require("@babel/core"),generator_1=require("@babel/generator"),PathsConstants_1=require("../scripts/PathsConstants"),Config_1=require("../scripts/Config"),GetValuesFromPackage_1=require("../lib/core/util/GetValuesFromPackage"),Types_1=require("./model/Types"),INSTRUMENTATION_LIBS="@dynatrace/react-native-plugin/instrumentation/libs",COMPONENTS_EXCLUDED_FROM_UIA=new Set(["Fragment","React.Fragment"]),hasJSX=function(e){var t=!1;return e.traverse({"JSXElement|JSXFragment":function(){t=!0}}),t},instrumentUserInteraction=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"registerComponent"})&&(t="".concat(INSTRUMENTATION_LIBS,"/UserInteraction"),e.body.body.unshift(core_1.template.statement('componentProvider = require("'.concat(t,'").wrapProvider(componentProvider);'))()))}})},instrumentAppRegistry=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"runApplication"})&&e.body.body.unshift(core_1.template.statement(_templateObject=_templateObject||(0,_taggedTemplateLiteral2.default)(['require("@dynatrace/react-native-plugin").ApplicationHandler.startup();']))())}})},instrumentExceptionsManager=function(e){var t,n=null!=(t=null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.reportFatalErrorAsCrash)&&t,r=(null==reactOptions?void 0:reactOptions.autoStart)&&(null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.enabled);e.traverse({FunctionDeclaration:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"handleException"})&&(t=core_1.template.statement(_templateObject2=_templateObject2||(0,_taggedTemplateLiteral2.default)(['\n setTimeout(() => BODY, require("@dynatrace/react-native-plugin/lib/core/ErrorHandler")\n .reportErrorToDynatrace(e, isFatal, CRASH, AUTO));\n ']))({BODY:core_1.types.cloneNode(e.node.body),CRASH:core_1.types.booleanLiteral(n),AUTO:core_1.types.booleanLiteral(!!r)}),e.node.body.body=[t])}})},instrumentCssInterop=function(e){e.traverse({CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&("react/jsx-runtime"===e.value?e.value="@dynatrace/react-native-plugin/jsx-runtime":"react/jsx-dev-runtime"===e.value&&(e.value="@dynatrace/react-native-plugin/jsx-dev-runtime"))}})},instrumentNavigation=function(e){e.traverse({VariableDeclarator:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"getRootState"})&&(t=core_1.template.statement(_templateObject3=_templateObject3||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).useNavigationMonitor(getRootState);\n "]))({PATH:core_1.types.stringLiteral("".concat(INSTRUMENTATION_LIBS,"/react-navigation/ReactNavigation"))}),e.parentPath.insertAfter(t))}})},instrumentReactCreateElement=function(e){var t=core_1.template.statement(_templateObject4=_templateObject4||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).instrumentCreateElement(module.exports);\n "]))({PATH:core_1.types.stringLiteral("@dynatrace/react-native-plugin/instrumentation/jsx/ElementHelper")});e.pushContainer("body",t)},instrumentComponents=function(e,n){e.traverse({FunctionDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.FunctionalComponent))},ClassDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.ClassComponent))},"FunctionExpression|ArrowFunctionExpression":function(e){var t;hasJSX(e)&&(t=e.parent,void 0!==(t=core_1.types.isVariableDeclarator(t)&&core_1.types.isIdentifier(t.id)?t.id.name:core_1.types.isAssignmentExpression(t)&&core_1.types.isIdentifier(t.left)?t.left.name:void 0))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t,Types_1.Types.FunctionalComponent))}})},instrumentLifecycle=function(e){instrumentComponents(e,function(e,t){return core_1.template.statement(_templateObject5=_templateObject5||(0,_taggedTemplateLiteral2.default)(["NAME._dtInfo = { type: TYPE, name: 'NAME_STR' }"]))({NAME:core_1.types.identifier(e),TYPE:core_1.types.numericLiteral(t),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentComponentNames=function(e){instrumentComponents(e,function(e){return core_1.template.statement(_templateObject6=_templateObject6||(0,_taggedTemplateLiteral2.default)(["NAME.dtName = 'NAME_STR'"]))({NAME:core_1.types.identifier(e),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentInput=function(e){var u=new Map([["react-native",{proxy:"".concat(INSTRUMENTATION_LIBS,"/react-native/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","Button","RefreshControl","Text","Pressable","Switch"])}],["react-native-gesture-handler",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/gesture-handler/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","RectButton","BorderlessButton","BaseButton"])}],["@react-native-picker/picker",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/Picker"),components:new Set(["Picker","PickerIOS"])}]]);e.traverse({ImportDeclaration:function(e){if(void 0===e.node.processed){var t=e.node.source;if(u.has(t.value)){var r=u.get(t.value),n=e.node.specifiers,a=[],i=[];if(n.forEach(function(e,t){var n;core_1.types.isImportSpecifier(e)&&(n=core_1.types.isIdentifier(e.imported)?e.imported.name:e.imported.value,r.components.has(n)||(a.push(e),i.push(t)))}),a.length!==n.length){var o,s=_createForOfIteratorHelper([].concat(i).reverse());try{for(s.s();!(o=s.n()).done;){var c=o.value;n.splice(c,1)}}catch(e){s.e(e)}finally{s.f()}0<a.length&&((t=core_1.types.importDeclaration(a,core_1.types.stringLiteral(t.value))).processed=!0,e.insertBefore(t)),e.node.source=core_1.types.stringLiteral(r.proxy),e.scope.crawl()}}}},CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&u.has(e.value)&&(e.value=u.get(e.value).proxy)}})},instrumentJsxNames=function(e){e.traverse({JSXOpeningElement:function(e){var t=e.node.name,t=core_1.types.isJSXIdentifier(t)?t.name:core_1.types.isJSXMemberExpression(t)?(0,generator_1.default)(t).code:void 0;t&&!COMPONENTS_EXCLUDED_FROM_UIA.has(t)&&e.node.attributes.unshift(core_1.types.jsxAttribute(core_1.types.jsxIdentifier("dtName"),core_1.types.stringLiteral(t)))}})},instrumentConfigurationPreset=function(e){var t,n=(0,GetValuesFromPackage_1.getHostAppBundleInfo)(PathsConstants_1.default.getPackageJsonFile()),r={getLifecycleUpdate:reactOptions.lifecycle.includeUpdate,getLogLevel:reactOptions.debug?0:1,getBundleName:null!=(t=reactOptions.bundleName)?t:null==n?void 0:n.name,getBundleVersion:null!=(t=reactOptions.bundleVersion)?t:null==n?void 0:n.version,getActionNamePrivacy:reactOptions.input.actionNamePrivacy,getActionNamePreference:reactOptions.input.actionNamePreference,getActionNameAlgorithm:reactOptions.input.actionNameAlgorithm,isErrorHandlerEnabled:reactOptions.errorHandler.enabled,isReportFatalErrorAsCrash:reactOptions.errorHandler.reportFatalErrorAsCrash,isAutoStartupEnabled:reactOptions.autoStart};e.traverse({ClassMethod:function(e){var t;core_1.types.isIdentifier(e.node.key)&&void 0!==(t=r[e.node.key.name])&&(e=e.node.body.body[0],core_1.types.isReturnStatement(e))&&(e.argument=core_1.types.valueToNode(t))}})},createFilenameMatcher=function(r){return{isModule:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.includes(fspath.join("node_modules",e)+fspath.sep)})},isFile:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.endsWith(e)})}}},captureOriginalCode=function(e){return!0!==reactOptions.debugBabelPlugin?"":(0,generator_1.default)(e.node).code},isConfigurationPresetTarget=function(e){return e.isModule("@dynatrace")&&e.isFile("ConfigurationPreset.js")},isAppRegistryTarget=function(e){return e.isModule("react-native")&&e.isFile("AppRegistry.js","AppRegistryImpl.js")},isExceptionsManagerTarget=function(e){return e.isModule("react-native")&&e.isFile("ExceptionsManager.js")},isCssInteropTarget=function(e){return e.isModule("react-native-css-interop")&&e.isFile("jsx-runtime.js","jsx-dev-runtime.js")},isNavigationTarget=function(e){return reactOptions.navigation.enabled&&e.isModule("@react-navigation")&&e.isFile("BaseNavigationContainer.js","BaseNavigationContainer.tsx")},isReactIndexTarget=function(e){return e.isModule("react")&&e.isFile("index.js")},shouldInstrumentLifecycleForFile=function(e,t,n){var r,a,n=n.isModule("react-native")&&n.isFile("renderApplication.js");return(null==(a=null==(r=reactOptions.lifecycle)?void 0:r.instrument)?void 0:a.call(r,t))&&(!t.includes("node_modules")||n)&&hasJSX(e)},shouldInstrumentInputForFile=function(e,t){var n,r,t=t.isModule("@react-navigation","react-native-drawer-layout");return(null==(r=null==(n=reactOptions.input)?void 0:n.instrument)?void 0:r.call(n,e))&&(!e.includes("node_modules")||t)},shouldInstrumentUserInteractionNames=function(e){return reactOptions.userInteraction&&!e.includes("node_modules")},instrumentByModuleAndFile=function(e,t,n){isConfigurationPresetTarget(n)&&instrumentConfigurationPreset(e),isAppRegistryTarget(n)&&(reactOptions.autoStart&&instrumentAppRegistry(e),reactOptions.userInteraction)&&instrumentUserInteraction(e),isExceptionsManagerTarget(n)&&instrumentExceptionsManager(e),isCssInteropTarget(n)&&instrumentCssInterop(e),isNavigationTarget(n)&&instrumentNavigation(e),isReactIndexTarget(n)&&instrumentReactCreateElement(e),shouldInstrumentLifecycleForFile(e,t,n)&&instrumentLifecycle(e),shouldInstrumentInputForFile(t,n)&&instrumentInput(e),shouldInstrumentUserInteractionNames(t)&&(instrumentJsxNames(e),instrumentComponentNames(e))},writeDebugOutputIfChanged=function(e,t,n,r){!0===reactOptions.debugBabelPlugin&&(e=(0,generator_1.default)(e.node).code)!==r&&(r=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),n)+".dtx"),fs.mkdirSync(fspath.dirname(r),{recursive:!0}),fs.writeFileSync(r,e),t.set("fileDidChange",!0))};exports.default=function(){return{visitor:{Program:function(e,t){reactOptions=(0,Config_1.readConfigDefault)().react;var n,r,a=t.filename;void 0!==a&&(n=createFilenameMatcher(a),r=captureOriginalCode(e),instrumentByModuleAndFile(e,a,n),writeDebugOutputIfChanged(e,t,a,r))}},post:function(e){var t;!0===reactOptions.debugBabelPlugin&&!0===this.get("fileDidChange")&&(t=(0,generator_1.default)(e.ast).code,e=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),e.opts.filename)+".dtx.downstream"),fs.mkdirSync(fspath.dirname(e),{recursive:!0}),fs.writeFileSync(e,t))}}};
1
+ var _templateObject,_templateObject2,_templateObject3,_templateObject4,_templateObject5,_templateObject6,_interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault"),_taggedTemplateLiteral2=_interopRequireDefault(require("@babel/runtime/helpers/taggedTemplateLiteral"));function _createForOfIteratorHelper(e,t){var n,r,a,i,o="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(o)return a=!(r=!0),{s:function(){o=o.call(e)},n:function(){var e=o.next();return r=e.done,e},e:function(e){a=!0,n=e},f:function(){try{r||null==o.return||o.return()}finally{if(a)throw n}}};if(Array.isArray(e)||(o=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return o&&(e=o),i=0,{s:t=function(){},n:function(){return i>=e.length?{done:!0}:{done:!1,value:e[i++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var n;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}Object.defineProperty(exports,"__esModule",{value:!0});var reactOptions,fspath=require("path"),fs=require("fs"),core_1=require("@babel/core"),generator_1=require("@babel/generator"),PathsConstants_1=require("../scripts/PathsConstants"),Config_1=require("../scripts/Config"),GetValuesFromPackage_1=require("../lib/core/util/GetValuesFromPackage"),Types_1=require("./model/Types"),INSTRUMENTATION_LIBS="@dynatrace/react-native-plugin/instrumentation/libs",COMPONENTS_EXCLUDED_FROM_UIA=new Set(["Fragment","React.Fragment"]),hasJSX=function(e){var t=!1;return e.traverse({"JSXElement|JSXFragment":function(){t=!0}}),t},instrumentUserInteraction=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"registerComponent"})&&(t="".concat(INSTRUMENTATION_LIBS,"/UserInteraction"),e.body.body.unshift(core_1.template.statement('componentProvider = require("'.concat(t,'").wrapProvider(componentProvider);'))()))}})},instrumentAppRegistry=function(e){e.traverse({"FunctionDeclaration|ObjectMethod":function(e){var e=e.node,t=core_1.types.isFunctionDeclaration(e)?e.id:e.key;core_1.types.isIdentifier(t,{name:"runApplication"})&&e.body.body.unshift(core_1.template.statement(_templateObject=_templateObject||(0,_taggedTemplateLiteral2.default)(['require("@dynatrace/react-native-plugin").ApplicationHandler.startup();']))())}})},instrumentExceptionsManager=function(e){var t,n=null!=(t=null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.reportFatalErrorAsCrash)&&t,r=(null==reactOptions?void 0:reactOptions.autoStart)&&(null==(t=null==reactOptions?void 0:reactOptions.errorHandler)?void 0:t.enabled);e.traverse({FunctionDeclaration:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"handleException"})&&(t=core_1.template.statement(_templateObject2=_templateObject2||(0,_taggedTemplateLiteral2.default)(['\n setTimeout(() => BODY, require("@dynatrace/react-native-plugin/lib/core/ErrorHandler")\n .reportErrorToDynatrace(e, isFatal, CRASH, AUTO));\n ']))({BODY:core_1.types.cloneNode(e.node.body),CRASH:core_1.types.booleanLiteral(n),AUTO:core_1.types.booleanLiteral(!!r)}),e.node.body.body=[t])}})},instrumentCssInterop=function(e){e.traverse({CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&("react/jsx-runtime"===e.value?e.value="@dynatrace/react-native-plugin/jsx-runtime":"react/jsx-dev-runtime"===e.value&&(e.value="@dynatrace/react-native-plugin/jsx-dev-runtime"))}})},instrumentNavigation=function(e){e.traverse({VariableDeclarator:function(e){var t;core_1.types.isIdentifier(e.node.id,{name:"getRootState"})&&(t=core_1.template.statement(_templateObject3=_templateObject3||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).useNavigationMonitor(getRootState);\n "]))({PATH:core_1.types.stringLiteral("".concat(INSTRUMENTATION_LIBS,"/react-navigation/ReactNavigation"))}),e.parentPath.insertAfter(t))}})},instrumentReactCreateElement=function(e){var t=core_1.template.statement(_templateObject4=_templateObject4||(0,_taggedTemplateLiteral2.default)(["\n require(PATH).instrumentCreateElement(module.exports);\n "]))({PATH:core_1.types.stringLiteral("@dynatrace/react-native-plugin/instrumentation/jsx/ElementHelper")});e.pushContainer("body",t)},instrumentComponents=function(e,n){e.traverse({FunctionDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.FunctionalComponent))},ClassDeclaration:function(e){var t;hasJSX(e)&&(t=e.node,core_1.types.isIdentifier(t.id))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t.id.name,Types_1.Types.ClassComponent))},"FunctionExpression|ArrowFunctionExpression":function(e){var t;hasJSX(e)&&(t=e.parent,void 0!==(t=core_1.types.isVariableDeclarator(t)&&core_1.types.isIdentifier(t.id)?t.id.name:core_1.types.isAssignmentExpression(t)&&core_1.types.isIdentifier(t.left)?t.left.name:void 0))&&null!=(e=e.getStatementParent())&&e.insertAfter(n(t,Types_1.Types.FunctionalComponent))}})},instrumentLifecycle=function(e){instrumentComponents(e,function(e,t){return core_1.template.statement(_templateObject5=_templateObject5||(0,_taggedTemplateLiteral2.default)(["NAME._dtInfo = { type: TYPE, name: 'NAME_STR' }"]))({NAME:core_1.types.identifier(e),TYPE:core_1.types.numericLiteral(t),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentComponentNames=function(e){instrumentComponents(e,function(e){return core_1.template.statement(_templateObject6=_templateObject6||(0,_taggedTemplateLiteral2.default)(["NAME.dtName = 'NAME_STR'"]))({NAME:core_1.types.identifier(e),NAME_STR:core_1.types.stringLiteral(e)})})},instrumentInput=function(e){var u=new Map([["react-native",{proxy:"".concat(INSTRUMENTATION_LIBS,"/react-native/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","Button","RefreshControl","Text","Pressable","Switch","FlatList","SectionList"])}],["react-native-gesture-handler",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/gesture-handler/"),components:new Set(["TouchableHighlight","TouchableNativeFeedback","TouchableOpacity","TouchableWithoutFeedback","RectButton","BorderlessButton","BaseButton"])}],["@react-native-picker/picker",{proxy:"".concat(INSTRUMENTATION_LIBS,"/community/Picker"),components:new Set(["Picker","PickerIOS"])}]]);e.traverse({ImportDeclaration:function(e){if(void 0===e.node.processed){var t=e.node.source;if(u.has(t.value)){var r=u.get(t.value),n=e.node.specifiers,a=[],i=[];if(n.forEach(function(e,t){var n;core_1.types.isImportSpecifier(e)&&(n=core_1.types.isIdentifier(e.imported)?e.imported.name:e.imported.value,r.components.has(n)||(a.push(e),i.push(t)))}),a.length!==n.length){var o,s=_createForOfIteratorHelper([].concat(i).reverse());try{for(s.s();!(o=s.n()).done;){var c=o.value;n.splice(c,1)}}catch(e){s.e(e)}finally{s.f()}0<a.length&&((t=core_1.types.importDeclaration(a,core_1.types.stringLiteral(t.value))).processed=!0,e.insertBefore(t)),e.node.source=core_1.types.stringLiteral(r.proxy),e.scope.crawl()}}}},CallExpression:function(e){core_1.types.isIdentifier(e.node.callee,{name:"require"})&&(e=e.node.arguments[0],core_1.types.isStringLiteral(e))&&u.has(e.value)&&(e.value=u.get(e.value).proxy)}})},instrumentJsxNames=function(e){e.traverse({JSXOpeningElement:function(e){var t=e.node.name,t=core_1.types.isJSXIdentifier(t)?t.name:core_1.types.isJSXMemberExpression(t)?(0,generator_1.default)(t).code:void 0;t&&!COMPONENTS_EXCLUDED_FROM_UIA.has(t)&&e.node.attributes.unshift(core_1.types.jsxAttribute(core_1.types.jsxIdentifier("dtName"),core_1.types.stringLiteral(t)))}})},instrumentConfigurationPreset=function(e){var t,n=(0,GetValuesFromPackage_1.getHostAppBundleInfo)(PathsConstants_1.default.getPackageJsonFile()),r={getLifecycleUpdate:reactOptions.lifecycle.includeUpdate,getLogLevel:reactOptions.debug?0:1,getBundleName:null!=(t=reactOptions.bundleName)?t:null==n?void 0:n.name,getBundleVersion:null!=(t=reactOptions.bundleVersion)?t:null==n?void 0:n.version,getActionNamePrivacy:reactOptions.input.actionNamePrivacy,getActionNamePreference:reactOptions.input.actionNamePreference,getActionNameAlgorithm:reactOptions.input.actionNameAlgorithm,isErrorHandlerEnabled:reactOptions.errorHandler.enabled,isReportFatalErrorAsCrash:reactOptions.errorHandler.reportFatalErrorAsCrash,isAutoStartupEnabled:reactOptions.autoStart};e.traverse({ClassMethod:function(e){var t;core_1.types.isIdentifier(e.node.key)&&void 0!==(t=r[e.node.key.name])&&(e=e.node.body.body[0],core_1.types.isReturnStatement(e))&&(e.argument=core_1.types.valueToNode(t))}})},createFilenameMatcher=function(r){return{isModule:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.includes(fspath.join("node_modules",e)+fspath.sep)})},isFile:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.some(function(e){return r.endsWith(e)})}}},captureOriginalCode=function(e){return!0!==reactOptions.debugBabelPlugin?"":(0,generator_1.default)(e.node).code},isConfigurationPresetTarget=function(e){return e.isModule("@dynatrace")&&e.isFile("ConfigurationPreset.js")},isAppRegistryTarget=function(e){return e.isModule("react-native")&&e.isFile("AppRegistry.js","AppRegistryImpl.js")},isExceptionsManagerTarget=function(e){return e.isModule("react-native")&&e.isFile("ExceptionsManager.js")},isCssInteropTarget=function(e){return e.isModule("react-native-css-interop")&&e.isFile("jsx-runtime.js","jsx-dev-runtime.js")},isNavigationTarget=function(e){return reactOptions.navigation.enabled&&e.isModule("@react-navigation")&&e.isFile("BaseNavigationContainer.js","BaseNavigationContainer.tsx")},isReactIndexTarget=function(e){return e.isModule("react")&&e.isFile("index.js")},shouldInstrumentLifecycleForFile=function(e,t,n){var r,a,n=n.isModule("react-native")&&n.isFile("renderApplication.js");return(null==(a=null==(r=reactOptions.lifecycle)?void 0:r.instrument)?void 0:a.call(r,t))&&(!t.includes("node_modules")||n)&&hasJSX(e)},shouldInstrumentInputForFile=function(e,t){var n,r,t=t.isModule("@react-navigation","react-native-drawer-layout");return(null==(r=null==(n=reactOptions.input)?void 0:n.instrument)?void 0:r.call(n,e))&&(!e.includes("node_modules")||t)},shouldInstrumentUserInteractionNames=function(e){return reactOptions.userInteraction&&!e.includes("node_modules")},instrumentByModuleAndFile=function(e,t,n){isConfigurationPresetTarget(n)&&instrumentConfigurationPreset(e),isAppRegistryTarget(n)&&(reactOptions.autoStart&&instrumentAppRegistry(e),reactOptions.userInteraction)&&instrumentUserInteraction(e),isExceptionsManagerTarget(n)&&instrumentExceptionsManager(e),isCssInteropTarget(n)&&instrumentCssInterop(e),isNavigationTarget(n)&&instrumentNavigation(e),isReactIndexTarget(n)&&instrumentReactCreateElement(e),shouldInstrumentLifecycleForFile(e,t,n)&&instrumentLifecycle(e),shouldInstrumentInputForFile(t,n)&&instrumentInput(e),shouldInstrumentUserInteractionNames(t)&&(instrumentJsxNames(e),instrumentComponentNames(e))},writeDebugOutputIfChanged=function(e,t,n,r){!0===reactOptions.debugBabelPlugin&&(e=(0,generator_1.default)(e.node).code)!==r&&(r=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),n)+".dtx"),fs.mkdirSync(fspath.dirname(r),{recursive:!0}),fs.writeFileSync(r,e),t.set("fileDidChange",!0))};exports.default=function(){return{visitor:{Program:function(e,t){reactOptions=(0,Config_1.readConfigDefault)().react;var n,r,a=t.filename;void 0!==a&&(n=createFilenameMatcher(a),r=captureOriginalCode(e),instrumentByModuleAndFile(e,a,n),writeDebugOutputIfChanged(e,t,a,r))}},post:function(e){var t;!0===reactOptions.debugBabelPlugin&&!0===this.get("fileDidChange")&&(t=(0,generator_1.default)(e.ast).code,e=fspath.join(PathsConstants_1.default.getBuildPath(),fspath.relative(PathsConstants_1.default.getApplicationPath(),e.opts.filename)+".dtx.downstream"),fs.mkdirSync(fspath.dirname(e),{recursive:!0}),fs.writeFileSync(e,t))}}};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SectionList = exports.FlatList = exports.useTableReloadOnChange = void 0;
4
+ const react_native_1 = require("react-native");
5
+ const React = require("react");
6
+ const TableReloadTracker_1 = require("../../../lib/next/triggers/TableReloadTracker");
7
+ const ConsoleLogger_1 = require("../../../lib/core/logging/ConsoleLogger");
8
+ const LogMessages_1 = require("../../../lib/core/logging/LogMessages");
9
+ const DEBOUNCE_MS = 100;
10
+ const logger = new ConsoleLogger_1.ConsoleLogger('TableReload');
11
+ const _useIsFocused = (() => {
12
+ try {
13
+ return require('@react-navigation/native').useIsFocused;
14
+ }
15
+ catch (_a) {
16
+ return () => true;
17
+ }
18
+ })();
19
+ function useTableReloadOnChange(value, label) {
20
+ const isFocused = _useIsFocused();
21
+ const isFocusedRef = React.useRef(isFocused);
22
+ const isMounted = React.useRef(false);
23
+ const pendingReload = React.useRef(false);
24
+ const timer = React.useRef(null);
25
+ React.useEffect(() => {
26
+ isFocusedRef.current = isFocused;
27
+ }, [isFocused]);
28
+ const fireAfterDebounce = React.useCallback(() => {
29
+ if (timer.current !== null)
30
+ clearTimeout(timer.current);
31
+ timer.current = setTimeout(() => {
32
+ timer.current = null;
33
+ if (!isFocusedRef.current) {
34
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_DEBOUNCE_LOST_FOCUS, {
35
+ label,
36
+ });
37
+ pendingReload.current = true;
38
+ return;
39
+ }
40
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_DEBOUNCE_ELAPSED, { label });
41
+ (0, TableReloadTracker_1.callTableReloadTrigger)(Date.now());
42
+ }, DEBOUNCE_MS);
43
+ }, [label]);
44
+ React.useEffect(() => {
45
+ if (!isMounted.current) {
46
+ isMounted.current = true;
47
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_MOUNTED, {
48
+ label,
49
+ length: Array.isArray(value) ? String(value.length) : 'null',
50
+ });
51
+ return;
52
+ }
53
+ const length = Array.isArray(value) ? String(value.length) : 'null';
54
+ if (!isFocused) {
55
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_DATA_CHANGED_NOT_FOCUSED, {
56
+ label,
57
+ length,
58
+ });
59
+ pendingReload.current = true;
60
+ return;
61
+ }
62
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_DATA_CHANGED, { label, length });
63
+ pendingReload.current = false;
64
+ fireAfterDebounce();
65
+ return () => {
66
+ if (timer.current !== null) {
67
+ clearTimeout(timer.current);
68
+ timer.current = null;
69
+ }
70
+ };
71
+ }, [value]);
72
+ React.useEffect(() => {
73
+ if (!isFocused || !pendingReload.current)
74
+ return;
75
+ pendingReload.current = false;
76
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_FOCUS_RESTORED, { label });
77
+ fireAfterDebounce();
78
+ return () => {
79
+ if (timer.current !== null) {
80
+ clearTimeout(timer.current);
81
+ timer.current = null;
82
+ }
83
+ };
84
+ }, [isFocused, fireAfterDebounce, label]);
85
+ }
86
+ exports.useTableReloadOnChange = useTableReloadOnChange;
87
+ exports.FlatList = React.forwardRef(function DynatraceFlatList(props, ref) {
88
+ useTableReloadOnChange(props.data, 'FlatList');
89
+ return React.createElement(react_native_1.FlatList, Object.assign({ ref: ref }, props));
90
+ });
91
+ exports.SectionList = React.forwardRef(function DynatraceSectionList(props, ref) {
92
+ useTableReloadOnChange(props.sections, 'SectionList');
93
+ return React.createElement(react_native_1.SectionList, Object.assign({ ref: ref }, props));
94
+ });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Switch = exports.Pressable = exports.Text = exports.Button = exports.TouchableWithoutFeedback = exports.TouchableOpacity = exports.TouchableNativeFeedback = exports.TouchableHighlight = exports.RefreshControl = void 0;
3
+ exports.SectionList = exports.FlatList = exports.Switch = exports.Pressable = exports.Text = exports.Button = exports.TouchableWithoutFeedback = exports.TouchableOpacity = exports.TouchableNativeFeedback = exports.TouchableHighlight = exports.RefreshControl = void 0;
4
4
  var RefreshControl_1 = require("./RefreshControl");
5
5
  Object.defineProperty(exports, "RefreshControl", { enumerable: true, get: function () { return RefreshControl_1.RefreshControl; } });
6
6
  var Touchables_1 = require("./Touchables");
@@ -13,3 +13,6 @@ Object.defineProperty(exports, "Text", { enumerable: true, get: function () { re
13
13
  Object.defineProperty(exports, "Pressable", { enumerable: true, get: function () { return Touchables_1.Pressable; } });
14
14
  var Switch_1 = require("./Switch");
15
15
  Object.defineProperty(exports, "Switch", { enumerable: true, get: function () { return Switch_1.Switch; } });
16
+ var Lists_1 = require("./Lists");
17
+ Object.defineProperty(exports, "FlatList", { enumerable: true, get: function () { return Lists_1.FlatList; } });
18
+ Object.defineProperty(exports, "SectionList", { enumerable: true, get: function () { return Lists_1.SectionList; } });
@@ -5,6 +5,7 @@ const reactNative = require("react-native");
5
5
  const Touchables_1 = require("./react-native/Touchables");
6
6
  const RefreshControl_1 = require("./react-native/RefreshControl");
7
7
  const Switch_1 = require("./react-native/Switch");
8
+ const Lists_1 = require("./react-native/Lists");
8
9
  const dynatraceInstrument = {
9
10
  TouchableHighlight: Touchables_1.TouchableHighlight,
10
11
  TouchableNativeFeedback: Touchables_1.TouchableNativeFeedback,
@@ -15,6 +16,8 @@ const dynatraceInstrument = {
15
16
  Text: Touchables_1.Text,
16
17
  Pressable: Touchables_1.Pressable,
17
18
  Switch: Switch_1.Switch,
19
+ FlatList: Lists_1.FlatList,
20
+ SectionList: Lists_1.SectionList,
18
21
  };
19
22
  const dynatraceProxy = new Proxy(reactNative, {
20
23
  get: (target, property) => {
package/internal.d.ts CHANGED
@@ -1 +1,2 @@
1
- export declare function registerEventTrigger(fn: ((name: string, timestamp: number) => void) | null): void;
1
+ export declare function registerNavigationEventTrigger(fn: ((name: string, timestamp: number) => void) | null): void;
2
+ export declare function registerTableReloadTrigger(fn: ((timestamp: number) => void) | null): void;
package/internal.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerEventTrigger = exports.createElement = exports.ApplicationHandler = void 0;
3
+ exports.registerTableReloadTrigger = exports.registerNavigationEventTrigger = exports.createElement = exports.ApplicationHandler = void 0;
4
4
  var Application_1 = require("./lib/core/Application");
5
5
  Object.defineProperty(exports, "ApplicationHandler", { enumerable: true, get: function () { return Application_1.ApplicationHandler; } });
6
6
  var CreateElement_1 = require("./instrumentation/jsx/CreateElement");
7
7
  Object.defineProperty(exports, "createElement", { enumerable: true, get: function () { return CreateElement_1.createElement; } });
8
8
  var NavigationTracker_1 = require("./lib/next/navigation/NavigationTracker");
9
- Object.defineProperty(exports, "registerEventTrigger", { enumerable: true, get: function () { return NavigationTracker_1.registerEventTrigger; } });
9
+ Object.defineProperty(exports, "registerNavigationEventTrigger", { enumerable: true, get: function () { return NavigationTracker_1.registerNavigationEventTrigger; } });
10
+ var TableReloadTracker_1 = require("./lib/next/triggers/TableReloadTracker");
11
+ Object.defineProperty(exports, "registerTableReloadTrigger", { enumerable: true, get: function () { return TableReloadTracker_1.registerTableReloadTrigger; } });
@@ -11,6 +11,7 @@
11
11
  #endif
12
12
 
13
13
  #import <Foundation/Foundation.h>
14
+ #import <QuartzCore/QuartzCore.h>
14
15
  #import <CoreLocation/CoreLocation.h>
15
16
  #import <React/RCTPerformanceLogger.h>
16
17
  #import <React/RCTRootView.h>
@@ -178,7 +179,7 @@ int64_t getRunJSBundleEndTime() {
178
179
  }
179
180
 
180
181
  - (NSArray<NSString *> *)supportedEvents {
181
- return @[EmitAppMeasurementsIdentifier, EmitDynatraceConfiguration];
182
+ return @[EmitAppMeasurementsIdentifier, EmitDynatraceConfiguration, @"DTNavigationFrame"];
182
183
  }
183
184
 
184
185
  int64_t GetTimestampUnix()
@@ -834,9 +835,35 @@ RCT_EXPORT_METHOD(getCurrentConfiguration:(RCTPromiseResolveBlock)resolve reject
834
835
  }
835
836
  }
836
837
 
838
+ static _Atomic int32_t _navGeneration = 0;
839
+
840
+ // NOTE: This callback is observational only — it measures the first rendered frame for
841
+ // diagnostic tooling (LOAD TIME display). It does not defer stopView(); deferring the
842
+ // view-end to the rendered frame to fix TTID under-reporting (DEM-32277) is a separate task.
837
843
  RCT_EXPORT_METHOD(startViewInternal:(NSDictionary<NSString*, id>*) fields)
838
844
  {
845
+ int32_t gen = ++_navGeneration;
846
+ CFTimeInterval imCATime = CACurrentMediaTime();
847
+
839
848
  [HybridBridge startView:fields];
849
+
850
+ dispatch_async(dispatch_get_main_queue(), ^{
851
+ __block CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[NSBlockOperation blockOperationWithBlock:^{
852
+ [link invalidate];
853
+ link = nil;
854
+ if (_navGeneration != gen) return; // superseded by a newer navigation
855
+ CFTimeInterval frameCATime = CACurrentMediaTime();
856
+ uint64_t frameEpochMs = (uint64_t)([[NSDate date] timeIntervalSince1970] * 1000.0);
857
+ double deltaMs = (frameCATime - imCATime) * 1000.0;
858
+ if (hasListeners) {
859
+ [self sendEventWithName:@"DTNavigationFrame" body:@{
860
+ @"frameEndMs": @(frameEpochMs),
861
+ @"deltaMs": @(deltaMs)
862
+ }];
863
+ }
864
+ }] selector:@selector(main)];
865
+ [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
866
+ });
840
867
  }
841
868
 
842
869
  RCT_EXPORT_METHOD(stopViewInternal)
@@ -77,6 +77,7 @@ var LogMessage;
77
77
  LogMessage["KEY_SYNTAX_INVALID"] = "KEY_SYNTAX_INVALID";
78
78
  LogMessage["MODIFY_EVENT"] = "MODIFY_EVENT";
79
79
  LogMessage["MODIFY_EVENT_NULL"] = "MODIFY_EVENT_NULL";
80
+ LogMessage["NAVIGATION_FRAME_TIMING"] = "NAVIGATION_FRAME_TIMING";
80
81
  LogMessage["NAVIGATION_NOT_STARTED"] = "NAVIGATION_NOT_STARTED";
81
82
  LogMessage["PLUGIN_STARTED"] = "PLUGIN_STARTED";
82
83
  LogMessage["PROPERTY_LIMIT_EXCEEDED"] = "PROPERTY_LIMIT_EXCEEDED";
@@ -150,6 +151,15 @@ var LogMessage;
150
151
  LogMessage["WEB_REQUEST_TIMING_START_FAILED"] = "WEB_REQUEST_TIMING_START_FAILED";
151
152
  LogMessage["WEB_REQUEST_TIMING_STOP"] = "WEB_REQUEST_TIMING_STOP";
152
153
  LogMessage["WEB_REQUEST_TIMING_STOP_FAILED"] = "WEB_REQUEST_TIMING_STOP_FAILED";
154
+ LogMessage["TABLE_RELOAD_MOUNTED"] = "TABLE_RELOAD_MOUNTED";
155
+ LogMessage["TABLE_RELOAD_DATA_CHANGED"] = "TABLE_RELOAD_DATA_CHANGED";
156
+ LogMessage["TABLE_RELOAD_DATA_CHANGED_NOT_FOCUSED"] = "TABLE_RELOAD_DATA_CHANGED_NOT_FOCUSED";
157
+ LogMessage["TABLE_RELOAD_DEBOUNCE_ELAPSED"] = "TABLE_RELOAD_DEBOUNCE_ELAPSED";
158
+ LogMessage["TABLE_RELOAD_DEBOUNCE_LOST_FOCUS"] = "TABLE_RELOAD_DEBOUNCE_LOST_FOCUS";
159
+ LogMessage["TABLE_RELOAD_FOCUS_RESTORED"] = "TABLE_RELOAD_FOCUS_RESTORED";
160
+ LogMessage["TABLE_RELOAD_TRIGGER_REGISTERED"] = "TABLE_RELOAD_TRIGGER_REGISTERED";
161
+ LogMessage["TABLE_RELOAD_TRIGGER_SKIPPED"] = "TABLE_RELOAD_TRIGGER_SKIPPED";
162
+ LogMessage["TABLE_RELOAD_TRIGGER_FIRED"] = "TABLE_RELOAD_TRIGGER_FIRED";
153
163
  })(LogMessage = exports.LogMessage || (exports.LogMessage = {}));
154
164
  exports.LOG_MESSAGES = {
155
165
  [LogMessage.ACTION_CANCEL]: {
@@ -478,6 +488,10 @@ exports.LOG_MESSAGES = {
478
488
  logType: LogType_1.LogType.Warning,
479
489
  documentationUri: 'https://dt-url.net/7al37nc',
480
490
  },
491
+ [LogMessage.NAVIGATION_FRAME_TIMING]: {
492
+ template: ({ frameEndMs, deltaMs, }) => `[NavigationFrame]: frame_end=${frameEndMs} delta_ms=${deltaMs}`,
493
+ logType: LogType_1.LogType.Info,
494
+ },
481
495
  [LogMessage.NAVIGATION_NOT_STARTED]: {
482
496
  template: 'React Native plugin has not been started yet! Navigation will not be reported!',
483
497
  logType: LogType_1.LogType.Warning,
@@ -803,4 +817,40 @@ exports.LOG_MESSAGES = {
803
817
  logType: LogType_1.LogType.Warning,
804
818
  documentationUri: 'https://dt-url.net/4us39ur',
805
819
  },
820
+ [LogMessage.TABLE_RELOAD_MOUNTED]: {
821
+ template: ({ label, length }) => `${label} mounted — skipping initial value (length=${length})`,
822
+ logType: LogType_1.LogType.Info,
823
+ },
824
+ [LogMessage.TABLE_RELOAD_DATA_CHANGED]: {
825
+ template: ({ label, length }) => `${label} data changed — debouncing (length=${length})`,
826
+ logType: LogType_1.LogType.Info,
827
+ },
828
+ [LogMessage.TABLE_RELOAD_DATA_CHANGED_NOT_FOCUSED]: {
829
+ template: ({ label, length }) => `${label} data changed while not focused (length=${length}) — deferring until refocus`,
830
+ logType: LogType_1.LogType.Info,
831
+ },
832
+ [LogMessage.TABLE_RELOAD_DEBOUNCE_ELAPSED]: {
833
+ template: ({ label }) => `${label} debounce elapsed — firing tableReload`,
834
+ logType: LogType_1.LogType.Info,
835
+ },
836
+ [LogMessage.TABLE_RELOAD_DEBOUNCE_LOST_FOCUS]: {
837
+ template: ({ label }) => `${label} lost focus while debounce was running — deferring until refocus`,
838
+ logType: LogType_1.LogType.Info,
839
+ },
840
+ [LogMessage.TABLE_RELOAD_FOCUS_RESTORED]: {
841
+ template: ({ label }) => `${label} screen focused with pending reload — firing tableReload`,
842
+ logType: LogType_1.LogType.Info,
843
+ },
844
+ [LogMessage.TABLE_RELOAD_TRIGGER_REGISTERED]: {
845
+ template: ({ state }) => `TableReloadTracker: trigger ${state}`,
846
+ logType: LogType_1.LogType.Info,
847
+ },
848
+ [LogMessage.TABLE_RELOAD_TRIGGER_SKIPPED]: {
849
+ template: 'TableReloadTracker: tableReload skipped — no trigger registered (SR not enabled)',
850
+ logType: LogType_1.LogType.Info,
851
+ },
852
+ [LogMessage.TABLE_RELOAD_TRIGGER_FIRED]: {
853
+ template: 'TableReloadTracker: tableReload firing → bridge trackEvent("tableReload")',
854
+ logType: LogType_1.LogType.Info,
855
+ },
806
856
  };
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ALL_APP_START_KEYS = exports.EVENT_WHITELIST_SIZE = exports.MODIFY_EVENT_WHITELIST_NAMESPACE = exports.MODIFY_EVENT_WHITELIST_STRING_FIELDS = exports.MODIFY_EVENT_WHITELIST_FIELDS = exports.SEND_SESSION_PROPERTY_EVENT_WHITELIST_FIELDS = exports.SEND_EXCEPTION_EVENT_WHITELIST_FIELDS = exports.SEND_EVENT_WHITELIST_FIELDS = exports.SEND_SESSION_PROPERTY_EVENT_WHITELIST_NAMESPACES = exports.SEND_EXCEPTION_EVENT_WHITELIST_NAMESPACES = exports.SEND_EVENT_WHITELIST_NAMESPACES = exports.AllCharacteristicsKeys = exports.KEY_NAME_REGEX = exports.MAX_CUSTOM_EVENT_VALUE_LENGTH = exports.MAX_CUSTOM_EVENT_KEY_LENGTH = exports.MAX_CUSTOM_EVENT_FIELDS = exports.SPECIFICATION_VERSION = void 0;
4
- exports.SPECIFICATION_VERSION = '0.24.0';
4
+ exports.SPECIFICATION_VERSION = '1.0.0';
5
5
  exports.MAX_CUSTOM_EVENT_FIELDS = 50;
6
6
  exports.MAX_CUSTOM_EVENT_KEY_LENGTH = 100;
7
7
  exports.MAX_CUSTOM_EVENT_VALUE_LENGTH = 5000;
@@ -1,17 +1,25 @@
1
1
  "use strict";
2
+ var _a;
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._resetForTest = exports.getActivePath = exports.reportScreenResume = exports.reportScreenBackground = exports.reportScreenChange = exports.buildPathFromState = exports.registerEventTrigger = void 0;
4
+ exports._resetForTest = exports.getActivePath = exports.reportScreenResume = exports.reportScreenBackground = exports.reportScreenChange = exports.buildPathFromState = exports.registerNavigationEventTrigger = void 0;
4
5
  const ConsoleLogger_1 = require("../../core/logging/ConsoleLogger");
5
6
  const LogMessages_1 = require("../../core/logging/LogMessages");
6
7
  const ConfigurationHandler_1 = require("../../core/configuration/ConfigurationHandler");
7
8
  const DynatraceBridge_1 = require("../../core/DynatraceBridge");
8
9
  const BaseDataEventModifier_1 = require("../events/modifier/BaseDataEventModifier");
10
+ const DynatraceEventBus_1 = require("../DynatraceEventBus");
9
11
  const logger = new ConsoleLogger_1.ConsoleLogger('NavigationTracker');
12
+ (_a = (0, DynatraceEventBus_1.createEmitterRuntimeConfig)()) === null || _a === void 0 ? void 0 : _a.addListener('DTNavigationFrame', (event) => {
13
+ logger.debug(LogMessages_1.LogMessage.NAVIGATION_FRAME_TIMING, {
14
+ frameEndMs: Math.round(event.frameEndMs),
15
+ deltaMs: event.deltaMs.toFixed(1),
16
+ });
17
+ });
10
18
  let eventTrigger = null;
11
- const registerEventTrigger = (fn) => {
19
+ const registerNavigationEventTrigger = (fn) => {
12
20
  eventTrigger = fn;
13
21
  };
14
- exports.registerEventTrigger = registerEventTrigger;
22
+ exports.registerNavigationEventTrigger = registerNavigationEventTrigger;
15
23
  let activePath = '';
16
24
  let isBackgrounded = false;
17
25
  const buildPathFromState = (state) => {
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.callTableReloadTrigger = exports.registerTableReloadTrigger = void 0;
4
+ const ConsoleLogger_1 = require("../../core/logging/ConsoleLogger");
5
+ const LogMessages_1 = require("../../core/logging/LogMessages");
6
+ const logger = new ConsoleLogger_1.ConsoleLogger('TableReload');
7
+ let tableReloadTrigger = null;
8
+ const registerTableReloadTrigger = (fn) => {
9
+ tableReloadTrigger = fn;
10
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_TRIGGER_REGISTERED, {
11
+ state: fn ? 'registered (SR enabled)' : 'cleared (SR disabled)',
12
+ });
13
+ };
14
+ exports.registerTableReloadTrigger = registerTableReloadTrigger;
15
+ const callTableReloadTrigger = (timestamp) => {
16
+ if (tableReloadTrigger == null) {
17
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_TRIGGER_SKIPPED);
18
+ return;
19
+ }
20
+ logger.debug(LogMessages_1.LogMessage.TABLE_RELOAD_TRIGGER_FIRED);
21
+ tableReloadTrigger(timestamp);
22
+ };
23
+ exports.callTableReloadTrigger = callTableReloadTrigger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynatrace/react-native-plugin",
3
- "version": "2.345.1",
3
+ "version": "2.347.1",
4
4
  "description": "This plugin gives you the ability to use the Dynatrace Mobile agent in your react native application.",
5
5
  "main": "index.js",
6
6
  "types": "types.d.ts",
@@ -63,7 +63,8 @@
63
63
  "overrides": {
64
64
  "ws": "^8.21.1",
65
65
  "js-yaml": "^5.2.2",
66
- "brace-expansion": "5.0.9"
66
+ "brace-expansion": "5.0.9",
67
+ "@xmldom/xmldom": "^0.9.12"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@babel/plugin-transform-class-properties": "^7.27.1",
@@ -168,6 +169,7 @@
168
169
  "lib/next/provider/*.js",
169
170
  "lib/next/userAction/*.js",
170
171
  "lib/next/navigation/*.js",
172
+ "lib/next/triggers/*.js",
171
173
  "src/lib/core/interface/NativeDynatraceBridge.ts",
172
174
  "types.d.ts"
173
175
  ],
@@ -118,7 +118,7 @@ Pod::Spec.new do |s|
118
118
  #
119
119
 
120
120
  s.dependency "React"
121
- s.dependency 'Dynatrace', '~> 8.345.1.1003'
121
+ s.dependency 'Dynatrace', '~> 8.347.1.1004'
122
122
 
123
123
  # Allows for better compatibility for older and newer versions
124
124
  if defined?(install_modules_dependencies)