@grafana/faro-react-native 1.3.1 → 1.4.0

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.
Files changed (256) hide show
  1. package/README.md +176 -30
  2. package/android/build.gradle +17 -12
  3. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashIds.kt +44 -0
  4. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +134 -38
  5. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashSessionStore.kt +433 -0
  6. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +11 -2
  7. package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +128 -17
  8. package/android/src/main/java/com/grafana/faro/reactnative/FaroSessionProcess.kt +88 -0
  9. package/android/src/main/java/com/grafana/faro/reactnative/FrameMonitor.kt +34 -42
  10. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterBelowApi30Test.kt +21 -0
  11. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterTest.kt +59 -0
  12. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashSessionStoreTest.kt +268 -0
  13. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashTraceCacheTest.kt +52 -0
  14. package/android/src/test/java/com/grafana/faro/reactnative/FaroSessionProcessTest.kt +127 -0
  15. package/dist/cjs/config/getRNInstrumentations.js +6 -8
  16. package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
  17. package/dist/cjs/config/sampling.js.map +1 -1
  18. package/dist/cjs/config/types.js.map +1 -1
  19. package/dist/cjs/dataCollection/DataCollectionPolicy.js +1 -2
  20. package/dist/cjs/dataCollection/DataCollectionPolicy.js.map +1 -1
  21. package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
  22. package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
  23. package/dist/cjs/index.js +8 -3
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/instrumentations/anr/instrumentation.js +10 -1
  26. package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
  27. package/dist/cjs/instrumentations/appState/index.js +8 -2
  28. package/dist/cjs/instrumentations/appState/index.js.map +1 -1
  29. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +19 -6
  30. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
  31. package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +331 -0
  32. package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
  33. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
  34. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
  35. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
  36. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
  37. package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
  38. package/dist/cjs/instrumentations/errors/index.js +8 -3
  39. package/dist/cjs/instrumentations/errors/index.js.map +1 -1
  40. package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js +20 -31
  41. package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
  42. package/dist/cjs/instrumentations/frameMonitoring/types.js.map +1 -1
  43. package/dist/cjs/instrumentations/performance/index.js +2 -6
  44. package/dist/cjs/instrumentations/performance/index.js.map +1 -1
  45. package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js +48 -0
  46. package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
  47. package/dist/cjs/instrumentations/session/directSessionActivity.js +37 -0
  48. package/dist/cjs/instrumentations/session/directSessionActivity.js.map +1 -0
  49. package/dist/cjs/instrumentations/session/index.js +214 -45
  50. package/dist/cjs/instrumentations/session/index.js.map +1 -1
  51. package/dist/cjs/instrumentations/session/sessionActivity.js +43 -0
  52. package/dist/cjs/instrumentations/session/sessionActivity.js.map +1 -0
  53. package/dist/cjs/instrumentations/session/sessionAttributes.js +8 -8
  54. package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
  55. package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +199 -24
  56. package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
  57. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -13
  58. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  59. package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js +72 -0
  60. package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
  61. package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js +1 -1
  62. package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
  63. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js +124 -96
  64. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  65. package/dist/cjs/instrumentations/session/sessionManager/types.js.map +1 -1
  66. package/dist/cjs/instrumentations/session/sessionProcess.js +96 -0
  67. package/dist/cjs/instrumentations/session/sessionProcess.js.map +1 -0
  68. package/dist/cjs/instrumentations/startup/index.js +2 -6
  69. package/dist/cjs/instrumentations/startup/index.js.map +1 -1
  70. package/dist/cjs/navigation/utils.js +3 -1
  71. package/dist/cjs/navigation/utils.js.map +1 -1
  72. package/dist/cjs/transports/fetch/index.js.map +1 -1
  73. package/dist/cjs/transports/fetch/transport.js +56 -25
  74. package/dist/cjs/transports/fetch/transport.js.map +1 -1
  75. package/dist/cjs/transports/fetch/types.js.map +1 -1
  76. package/dist/cjs/transports/offline/ConnectivityService.js +1 -1
  77. package/dist/cjs/transports/offline/ConnectivityService.js.map +1 -1
  78. package/dist/cjs/transports/offline/OfflineCache.js +2 -2
  79. package/dist/cjs/transports/offline/OfflineCache.js.map +1 -1
  80. package/dist/cjs/transports/offline/transport.js +3 -4
  81. package/dist/cjs/transports/offline/transport.js.map +1 -1
  82. package/dist/cjs/userPersistence/UserPersistence.js +1 -2
  83. package/dist/cjs/userPersistence/UserPersistence.js.map +1 -1
  84. package/dist/esm/config/getRNInstrumentations.js +6 -8
  85. package/dist/esm/config/getRNInstrumentations.js.map +1 -1
  86. package/dist/esm/config/sampling.js.map +1 -1
  87. package/dist/esm/config/types.js.map +1 -1
  88. package/dist/esm/dataCollection/DataCollectionPolicy.js +1 -2
  89. package/dist/esm/dataCollection/DataCollectionPolicy.js.map +1 -1
  90. package/dist/esm/generated/faroRNPackageMeta.js +1 -1
  91. package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
  92. package/dist/esm/index.js +4 -2
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/instrumentations/anr/instrumentation.js +6 -1
  95. package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
  96. package/dist/esm/instrumentations/appState/index.js +8 -2
  97. package/dist/esm/instrumentations/appState/index.js.map +1 -1
  98. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +15 -4
  99. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
  100. package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +230 -0
  101. package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
  102. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
  103. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
  104. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
  105. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
  106. package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
  107. package/dist/esm/instrumentations/errors/index.js +8 -3
  108. package/dist/esm/instrumentations/errors/index.js.map +1 -1
  109. package/dist/esm/instrumentations/frameMonitoring/instrumentation.js +17 -32
  110. package/dist/esm/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
  111. package/dist/esm/instrumentations/frameMonitoring/types.js.map +1 -1
  112. package/dist/esm/instrumentations/performance/index.js +2 -6
  113. package/dist/esm/instrumentations/performance/index.js.map +1 -1
  114. package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js +34 -0
  115. package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
  116. package/dist/esm/instrumentations/session/directSessionActivity.js +32 -0
  117. package/dist/esm/instrumentations/session/directSessionActivity.js.map +1 -0
  118. package/dist/esm/instrumentations/session/index.js +207 -46
  119. package/dist/esm/instrumentations/session/index.js.map +1 -1
  120. package/dist/esm/instrumentations/session/sessionActivity.js +38 -0
  121. package/dist/esm/instrumentations/session/sessionActivity.js.map +1 -0
  122. package/dist/esm/instrumentations/session/sessionAttributes.js +7 -7
  123. package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
  124. package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +190 -27
  125. package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
  126. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -14
  127. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  128. package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js +56 -0
  129. package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
  130. package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js +1 -1
  131. package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
  132. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js +120 -44
  133. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  134. package/dist/esm/instrumentations/session/sessionManager/types.js.map +1 -1
  135. package/dist/esm/instrumentations/session/sessionProcess.js +89 -0
  136. package/dist/esm/instrumentations/session/sessionProcess.js.map +1 -0
  137. package/dist/esm/instrumentations/startup/index.js +2 -6
  138. package/dist/esm/instrumentations/startup/index.js.map +1 -1
  139. package/dist/esm/navigation/utils.js +2 -1
  140. package/dist/esm/navigation/utils.js.map +1 -1
  141. package/dist/esm/transports/fetch/index.js.map +1 -1
  142. package/dist/esm/transports/fetch/transport.js +38 -14
  143. package/dist/esm/transports/fetch/transport.js.map +1 -1
  144. package/dist/esm/transports/fetch/types.js.map +1 -1
  145. package/dist/esm/transports/offline/ConnectivityService.js +1 -1
  146. package/dist/esm/transports/offline/ConnectivityService.js.map +1 -1
  147. package/dist/esm/transports/offline/OfflineCache.js +2 -2
  148. package/dist/esm/transports/offline/OfflineCache.js.map +1 -1
  149. package/dist/esm/transports/offline/transport.js +3 -4
  150. package/dist/esm/transports/offline/transport.js.map +1 -1
  151. package/dist/esm/userPersistence/UserPersistence.js +1 -2
  152. package/dist/esm/userPersistence/UserPersistence.js.map +1 -1
  153. package/dist/types/config/getRNInstrumentations.d.ts +1 -2
  154. package/dist/types/config/sampling.d.ts +0 -3
  155. package/dist/types/config/types.d.ts +5 -3
  156. package/dist/types/dataCollection/DataCollectionPolicy.d.ts +1 -2
  157. package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
  158. package/dist/types/index.d.ts +4 -1
  159. package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
  160. package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
  161. package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +2 -2
  162. package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +2 -2
  163. package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
  164. package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +2 -2
  165. package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
  166. package/dist/types/instrumentations/performance/index.d.ts +0 -2
  167. package/dist/types/instrumentations/session/FaroSessionActivityBoundary.d.ts +8 -0
  168. package/dist/types/instrumentations/session/directSessionActivity.d.ts +13 -0
  169. package/dist/types/instrumentations/session/index.d.ts +20 -1
  170. package/dist/types/instrumentations/session/sessionActivity.d.ts +9 -0
  171. package/dist/types/instrumentations/session/sessionAttributes.d.ts +6 -3
  172. package/dist/types/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.d.ts +16 -3
  173. package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +9 -3
  174. package/dist/types/instrumentations/session/sessionManager/persistentSessionRecord.d.ts +4 -0
  175. package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +19 -7
  176. package/dist/types/instrumentations/session/sessionManager/types.d.ts +6 -0
  177. package/dist/types/instrumentations/session/sessionProcess.d.ts +17 -0
  178. package/dist/types/instrumentations/startup/index.d.ts +2 -6
  179. package/dist/types/navigation/utils.d.ts +1 -0
  180. package/dist/types/transports/fetch/index.d.ts +1 -1
  181. package/dist/types/transports/fetch/transport.d.ts +8 -1
  182. package/dist/types/transports/fetch/types.d.ts +5 -0
  183. package/dist/types/transports/offline/ConnectivityService.d.ts +1 -1
  184. package/dist/types/transports/offline/OfflineCache.d.ts +2 -2
  185. package/dist/types/transports/offline/transport.d.ts +3 -4
  186. package/dist/types/userPersistence/UserPersistence.d.ts +1 -2
  187. package/ios/FaroCrashReporter.swift +214 -21
  188. package/ios/FaroReactNative.swift +24 -0
  189. package/ios/FaroReactNativeModule.h +2 -1
  190. package/ios/FaroReactNativeModule.mm +121 -6
  191. package/ios/RefreshRateVitals.swift +34 -38
  192. package/package.json +7 -5
  193. package/src/config/getRNInstrumentations.ts +6 -8
  194. package/src/config/makeRNConfig.test.ts +6 -0
  195. package/src/config/sampling.ts +0 -3
  196. package/src/config/types.ts +5 -3
  197. package/src/dataCollection/DataCollectionPolicy.ts +1 -2
  198. package/src/generated/faroRNPackageMeta.ts +1 -1
  199. package/src/index.ts +8 -2
  200. package/src/instrumentations/anr/instrumentation.ts +8 -1
  201. package/src/instrumentations/appState/index.ts +10 -2
  202. package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
  203. package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
  204. package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
  205. package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
  206. package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
  207. package/src/instrumentations/crashReporting/types.ts +7 -0
  208. package/src/instrumentations/errors/index.ts +12 -4
  209. package/src/instrumentations/errors/instrumentation.test.ts +35 -0
  210. package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
  211. package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
  212. package/src/instrumentations/frameMonitoring/types.ts +4 -6
  213. package/src/instrumentations/performance/index.ts +2 -6
  214. package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
  215. package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
  216. package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
  217. package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
  218. package/src/instrumentations/session/directSessionActivity.ts +36 -0
  219. package/src/instrumentations/session/index.ts +269 -46
  220. package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
  221. package/src/instrumentations/session/sessionActivity.test.ts +68 -0
  222. package/src/instrumentations/session/sessionActivity.ts +58 -0
  223. package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
  224. package/src/instrumentations/session/sessionAttributes.ts +12 -6
  225. package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
  226. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
  227. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
  228. package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
  229. package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
  230. package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
  231. package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
  232. package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
  233. package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
  234. package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
  235. package/src/instrumentations/session/sessionManager/types.ts +8 -0
  236. package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
  237. package/src/instrumentations/session/sessionProcess.test.ts +136 -0
  238. package/src/instrumentations/session/sessionProcess.ts +114 -0
  239. package/src/instrumentations/session/startNewSession.test.ts +337 -0
  240. package/src/instrumentations/startup/index.ts +2 -6
  241. package/src/navigation/utils.ts +3 -1
  242. package/src/transports/fetch/index.ts +6 -1
  243. package/src/transports/fetch/transport.test.ts +86 -2
  244. package/src/transports/fetch/transport.ts +38 -16
  245. package/src/transports/fetch/types.ts +7 -0
  246. package/src/transports/offline/ConnectivityService.ts +1 -1
  247. package/src/transports/offline/OfflineCache.ts +2 -2
  248. package/src/transports/offline/transport.ts +3 -4
  249. package/src/userPersistence/UserPersistence.ts +1 -2
  250. package/dist/cjs/utils/throttle.js +0 -42
  251. package/dist/cjs/utils/throttle.js.map +0 -1
  252. package/dist/esm/utils/throttle.js +0 -34
  253. package/dist/esm/utils/throttle.js.map +0 -1
  254. package/dist/types/utils/throttle.d.ts +0 -9
  255. package/src/utils/throttle.test.ts +0 -141
  256. package/src/utils/throttle.ts +0 -34
@@ -55,11 +55,11 @@ exports.FrameMonitoringInstrumentation = void 0;
55
55
  var react_native_1 = require("react-native");
56
56
  var faro_core_1 = require("@grafana/faro-core");
57
57
  /**
58
- * Default configuration values matching Flutter SDK's hardcoded values.
59
- * These are React Native-specific advanced options for customization.
58
+ * Default configuration values. Frozen frame threshold aligns with Android Vitals
59
+ * and OpenTelemetry Android slow rendering instrumentation (700ms).
60
60
  */
61
61
  var DEFAULT_TARGET_FPS = 60;
62
- var DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 100;
62
+ var DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 700;
63
63
  var DEFAULT_REFRESH_RATE_POLLING_INTERVAL = 30000;
64
64
  var DEFAULT_NORMALIZED_REFRESH_RATE = 60;
65
65
  /**
@@ -76,7 +76,7 @@ var DEFAULT_NORMALIZED_REFRESH_RATE = 60;
76
76
  * - Reports meaningful performance degradation that affects user experience
77
77
  *
78
78
  * ## Frozen Frame Detection
79
- * Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 100ms).
79
+ * Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 700ms).
80
80
  * These represent severe jank where the app appears unresponsive.
81
81
  *
82
82
  * Sends measurements via Faro API:
@@ -93,7 +93,7 @@ var DEFAULT_NORMALIZED_REFRESH_RATE = 60;
93
93
  * instrumentations: [
94
94
  * new FrameMonitoringInstrumentation({
95
95
  * targetFps: 60,
96
- * frozenFrameThresholdMs: 100,
96
+ * frozenFrameThresholdMs: 700,
97
97
  * refreshRatePollingInterval: 30000,
98
98
  * }),
99
99
  * ],
@@ -142,7 +142,7 @@ var FrameMonitoringInstrumentation = /** @class */ (function (_super) {
142
142
  }
143
143
  // Start native frame monitoring with configuration
144
144
  this.startNativeMonitoring(nativeModule);
145
- // Set up event listeners for real-time frame events (Android pattern)
145
+ // Set up Android refresh rate events (throttled on native side); frozen/slow frames use polling
146
146
  this.setupEventListeners(nativeModule);
147
147
  // Set up polling for periodic metrics collection (iOS pattern)
148
148
  this.startPolling();
@@ -171,30 +171,24 @@ var FrameMonitoringInstrumentation = /** @class */ (function (_super) {
171
171
  };
172
172
  FrameMonitoringInstrumentation.prototype.setupEventListeners = function (nativeModule) {
173
173
  var _this = this;
174
- // Android uses event-based approach for frozen frames only
175
- // Slow frames are retrieved via polling (like iOS) to avoid flooding
176
- if (react_native_1.Platform.OS === 'android') {
174
+ // Android: refresh rate only. Slow and frozen frames are polled (same as iOS) to avoid duplicate reports.
175
+ if (react_native_1.Platform.OS === 'android' && this.refreshRateVitalsEnabled) {
177
176
  try {
178
177
  this.eventEmitter = new react_native_1.NativeEventEmitter(nativeModule);
179
- // Listen for frozen frame events
180
- var frozenFrameSubscription = this.eventEmitter.addListener('onFrozenFrame', function (data) {
181
- // Support both formats: number (legacy) or object (with duration)
182
- if (typeof data === 'number') {
183
- _this.handleFrozenFrame(data, 0);
178
+ var refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', function () {
179
+ var args = [];
180
+ for (var _i = 0; _i < arguments.length; _i++) {
181
+ args[_i] = arguments[_i];
184
182
  }
185
- else {
186
- _this.handleFrozenFrame(data.count, data.durationMs || 0);
183
+ var refreshRate = args[0];
184
+ if (typeof refreshRate !== 'number') {
185
+ _this.logWarn('Ignoring onRefreshRate event without a numeric payload');
186
+ return;
187
187
  }
188
+ _this.handleRefreshRate(refreshRate);
188
189
  });
189
- this.eventSubscriptions.push(frozenFrameSubscription);
190
- // Listen for refresh rate events (only when refreshRateVitals is enabled in config)
191
- if (this.refreshRateVitalsEnabled) {
192
- var refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', function (refreshRate) {
193
- _this.handleRefreshRate(refreshRate);
194
- });
195
- this.eventSubscriptions.push(refreshRateSubscription);
196
- }
197
- this.logDebug('Set up Android frame event listeners');
190
+ this.eventSubscriptions.push(refreshRateSubscription);
191
+ this.logDebug('Set up Android refresh rate event listener');
198
192
  }
199
193
  catch (error) {
200
194
  this.logError('Failed to set up frame event listeners', error);
@@ -203,8 +197,7 @@ var FrameMonitoringInstrumentation = /** @class */ (function (_super) {
203
197
  };
204
198
  FrameMonitoringInstrumentation.prototype.startPolling = function () {
205
199
  var _this = this;
206
- // Both iOS and Android use polling approach for slow frames
207
- // This provides consistent behavior and avoids flooding with events
200
+ // Both iOS and Android use polling for slow and frozen frames (avoids duplicate reports on Android).
208
201
  this.pollingIntervalId = setInterval(function () {
209
202
  _this.pollFrameMetrics();
210
203
  }, this.options.refreshRatePollingInterval);
@@ -273,11 +266,7 @@ var FrameMonitoringInstrumentation = /** @class */ (function (_super) {
273
266
  }, { skipDedupe: true });
274
267
  };
275
268
  FrameMonitoringInstrumentation.prototype.handleFrozenFrame = function (count, durationMs) {
276
- // Only send frozen frame events if duration is greater than 0
277
- // This filters out any erroneous 0ms frozen frames
278
269
  if (durationMs > 0) {
279
- // 🔍 TEMP DEBUG LOG - Remove after analysis
280
- console.log("[Faro DEBUG ".concat(react_native_1.Platform.OS.toUpperCase(), "] \uD83E\uDDCA SENDING frozen frame: count=").concat(count, ", duration=").concat(durationMs, "ms"));
281
270
  this.api.pushMeasurement({
282
271
  type: 'app_frozen_frame',
283
272
  values: {
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeEventEmitter, NativeModules, Platform } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { ReactNativeConfig } from '../../config/types';\n\nimport type { FrameMetrics, FrameMonitoringOptions } from './types';\n\n/**\n * Default configuration values matching Flutter SDK's hardcoded values.\n * These are React Native-specific advanced options for customization.\n */\nconst DEFAULT_TARGET_FPS = 60;\nconst DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 100;\nconst DEFAULT_REFRESH_RATE_POLLING_INTERVAL = 30000;\nconst DEFAULT_NORMALIZED_REFRESH_RATE = 60;\n\n/**\n * Frame Monitoring Instrumentation for React Native.\n *\n * Monitors frame rendering performance and detects slow/frozen frames.\n * Uses native CADisplayLink (iOS) and Choreographer (Android) for accurate metrics.\n *\n * ## Slow Frame Detection\n * Slow frames are detected using **event-based grouping** to report user-perceptible jank:\n * - Tracks consecutive frames below `targetFps` as a single \"slow frame event\"\n * - Only counts events lasting ≥50ms (~3 frames at 60fps) to filter noise\n * - This prevents reporting normal microsecond variations as slow frames\n * - Reports meaningful performance degradation that affects user experience\n *\n * ## Frozen Frame Detection\n * Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 100ms).\n * These represent severe jank where the app appears unresponsive.\n *\n * Sends measurements via Faro API:\n * - `app_refresh_rate`: Current refresh rate in FPS\n * - `app_frames_rate`: Number of slow frame events (not individual frames)\n * - `app_frozen_frame`: Number of frozen frames (exceeding frozenFrameThresholdMs)\n *\n * @example\n * ```typescript\n * import { initializeFaro, FrameMonitoringInstrumentation } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * instrumentations: [\n * new FrameMonitoringInstrumentation({\n * targetFps: 60,\n * frozenFrameThresholdMs: 100,\n * refreshRatePollingInterval: 30000,\n * }),\n * ],\n * });\n * ```\n */\nexport class FrameMonitoringInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-frame-monitoring';\n readonly version = VERSION;\n\n private readonly options: Required<FrameMonitoringOptions>;\n private pollingIntervalId: ReturnType<typeof setInterval> | null = null;\n private eventEmitter: NativeEventEmitter | null = null;\n private eventSubscriptions: Array<{ remove: () => void }> = [];\n\n constructor(options: FrameMonitoringOptions = {}) {\n super();\n this.options = {\n targetFps: options.targetFps ?? DEFAULT_TARGET_FPS,\n frozenFrameThresholdMs: options.frozenFrameThresholdMs ?? DEFAULT_FROZEN_FRAME_THRESHOLD_MS,\n refreshRatePollingInterval: options.refreshRatePollingInterval ?? DEFAULT_REFRESH_RATE_POLLING_INTERVAL,\n normalizedRefreshRate: options.normalizedRefreshRate ?? DEFAULT_NORMALIZED_REFRESH_RATE,\n };\n }\n\n /**\n * Check if refresh rate vitals are enabled in Faro config.\n * Reads from faro.config.refreshRateVitals (single source of truth).\n */\n private get refreshRateVitalsEnabled(): boolean {\n // Access config via this.config which is set by BaseInstrumentation after initialization\n const config = this.config as ReactNativeConfig | undefined;\n return config?.refreshRateVitals ?? false;\n }\n\n initialize(): void {\n this.logDebug('Initializing frame monitoring instrumentation');\n\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n this.logWarn('Native module not available for frame monitoring');\n return;\n }\n\n // Start native frame monitoring with configuration\n this.startNativeMonitoring(nativeModule);\n\n // Set up event listeners for real-time frame events (Android pattern)\n this.setupEventListeners(nativeModule);\n\n // Set up polling for periodic metrics collection (iOS pattern)\n this.startPolling();\n }\n\n private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {\n const { FaroReactNativeModule } = NativeModules;\n\n if (!FaroReactNativeModule) {\n return null;\n }\n\n return FaroReactNativeModule;\n }\n\n private startNativeMonitoring(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n try {\n if (typeof nativeModule.startFrameMonitoring === 'function') {\n nativeModule.startFrameMonitoring({\n targetFps: this.options.targetFps,\n frozenFrameThresholdMs: this.options.frozenFrameThresholdMs,\n normalizedRefreshRate: this.options.normalizedRefreshRate,\n });\n this.logDebug('Started native frame monitoring');\n }\n } catch (error) {\n this.logError('Failed to start native frame monitoring', error);\n }\n }\n\n private setupEventListeners(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n // Android uses event-based approach for frozen frames only\n // Slow frames are retrieved via polling (like iOS) to avoid flooding\n if (Platform.OS === 'android') {\n try {\n this.eventEmitter = new NativeEventEmitter(nativeModule);\n\n // Listen for frozen frame events\n const frozenFrameSubscription = this.eventEmitter.addListener(\n 'onFrozenFrame',\n (data: number | { count: number; durationMs: number }) => {\n // Support both formats: number (legacy) or object (with duration)\n if (typeof data === 'number') {\n this.handleFrozenFrame(data, 0);\n } else {\n this.handleFrozenFrame(data.count, data.durationMs || 0);\n }\n }\n );\n this.eventSubscriptions.push(frozenFrameSubscription);\n\n // Listen for refresh rate events (only when refreshRateVitals is enabled in config)\n if (this.refreshRateVitalsEnabled) {\n const refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', (refreshRate: number) => {\n this.handleRefreshRate(refreshRate);\n });\n this.eventSubscriptions.push(refreshRateSubscription);\n }\n\n this.logDebug('Set up Android frame event listeners');\n } catch (error) {\n this.logError('Failed to set up frame event listeners', error);\n }\n }\n }\n\n private startPolling(): void {\n // Both iOS and Android use polling approach for slow frames\n // This provides consistent behavior and avoids flooding with events\n this.pollingIntervalId = setInterval(() => {\n this.pollFrameMetrics();\n }, this.options.refreshRatePollingInterval);\n }\n\n private async pollFrameMetrics(): Promise<void> {\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n return;\n }\n\n try {\n // Get refresh rate (only send when refreshRateVitals is enabled in config)\n if (this.refreshRateVitalsEnabled) {\n if (typeof nativeModule.getRefreshRate === 'function') {\n const refreshRate = await nativeModule.getRefreshRate();\n if (refreshRate !== null && refreshRate > 0) {\n this.handleRefreshRate(refreshRate);\n }\n }\n }\n\n // Get full frame metrics if available\n if (typeof nativeModule.getFrameMetrics === 'function') {\n const metrics = (await nativeModule.getFrameMetrics()) as FrameMetrics | null;\n if (metrics) {\n if (this.refreshRateVitalsEnabled && metrics.refreshRate > 0) {\n this.handleRefreshRate(metrics.refreshRate);\n }\n if (metrics.slowFrames > 0) {\n this.handleSlowFrames(metrics.slowFrames);\n }\n if (metrics.frozenFrames > 0) {\n this.handleFrozenFrame(metrics.frozenFrames, metrics.frozenDurationMs);\n }\n }\n }\n } catch (error) {\n this.logError('Failed to poll frame metrics', error);\n }\n }\n\n private handleRefreshRate(refreshRate: number): void {\n this.api.pushMeasurement(\n {\n type: 'app_refresh_rate',\n values: { refresh_rate: refreshRate },\n },\n { skipDedupe: true }\n );\n }\n\n private handleSlowFrames(count: number): void {\n // Note: Despite the name \"slow_frames\", this count represents slow frame EVENTS, not individual frames.\n // Each event is a period of consecutive slow frames lasting ≥50ms (~3 frames at 60fps).\n this.api.pushMeasurement(\n {\n type: 'app_frames_rate',\n values: { slow_frames: count },\n },\n { skipDedupe: true }\n );\n }\n\n private handleFrozenFrame(count: number, durationMs: number): void {\n // Only send frozen frame events if duration is greater than 0\n // This filters out any erroneous 0ms frozen frames\n if (durationMs > 0) {\n // 🔍 TEMP DEBUG LOG - Remove after analysis\n console.log(\n `[Faro DEBUG ${Platform.OS.toUpperCase()}] 🧊 SENDING frozen frame: count=${count}, duration=${durationMs}ms`\n );\n\n this.api.pushMeasurement(\n {\n type: 'app_frozen_frame',\n values: {\n frozen_frames: count,\n frozen_duration: durationMs,\n },\n },\n { skipDedupe: true }\n );\n }\n }\n\n /**\n * Clean up resources when instrumentation is disabled.\n */\n unpatch(): void {\n // Stop polling\n if (this.pollingIntervalId !== null) {\n clearInterval(this.pollingIntervalId);\n this.pollingIntervalId = null;\n }\n\n // Remove event subscriptions\n for (const subscription of this.eventSubscriptions) {\n subscription.remove();\n }\n this.eventSubscriptions = [];\n this.eventEmitter = null;\n\n // Stop native monitoring\n const nativeModule = this.getNativeModule();\n if (nativeModule && typeof nativeModule.stopFrameMonitoring === 'function') {\n try {\n nativeModule.stopFrameMonitoring();\n } catch (error) {\n this.logError('Failed to stop native frame monitoring', error);\n }\n }\n\n this.logDebug('Frame monitoring instrumentation stopped');\n }\n}\n"]}
1
+ {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeEventEmitter, NativeModules, Platform } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { ReactNativeConfig } from '../../config/types';\n\nimport type { FrameMetrics, FrameMonitoringOptions } from './types';\n\n/**\n * Default configuration values. Frozen frame threshold aligns with Android Vitals\n * and OpenTelemetry Android slow rendering instrumentation (700ms).\n */\nconst DEFAULT_TARGET_FPS = 60;\nconst DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 700;\nconst DEFAULT_REFRESH_RATE_POLLING_INTERVAL = 30000;\nconst DEFAULT_NORMALIZED_REFRESH_RATE = 60;\n\n/**\n * Frame Monitoring Instrumentation for React Native.\n *\n * Monitors frame rendering performance and detects slow/frozen frames.\n * Uses native CADisplayLink (iOS) and Choreographer (Android) for accurate metrics.\n *\n * ## Slow Frame Detection\n * Slow frames are detected using **event-based grouping** to report user-perceptible jank:\n * - Tracks consecutive frames below `targetFps` as a single \"slow frame event\"\n * - Only counts events lasting ≥50ms (~3 frames at 60fps) to filter noise\n * - This prevents reporting normal microsecond variations as slow frames\n * - Reports meaningful performance degradation that affects user experience\n *\n * ## Frozen Frame Detection\n * Frozen frames are individual frames exceeding `frozenFrameThresholdMs` (default 700ms).\n * These represent severe jank where the app appears unresponsive.\n *\n * Sends measurements via Faro API:\n * - `app_refresh_rate`: Current refresh rate in FPS\n * - `app_frames_rate`: Number of slow frame events (not individual frames)\n * - `app_frozen_frame`: Number of frozen frames (exceeding frozenFrameThresholdMs)\n *\n * @example\n * ```typescript\n * import { initializeFaro, FrameMonitoringInstrumentation } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * instrumentations: [\n * new FrameMonitoringInstrumentation({\n * targetFps: 60,\n * frozenFrameThresholdMs: 700,\n * refreshRatePollingInterval: 30000,\n * }),\n * ],\n * });\n * ```\n */\nexport class FrameMonitoringInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-frame-monitoring';\n readonly version = VERSION;\n\n private readonly options: Required<FrameMonitoringOptions>;\n private pollingIntervalId: ReturnType<typeof setInterval> | null = null;\n private eventEmitter: NativeEventEmitter | null = null;\n private eventSubscriptions: Array<{ remove: () => void }> = [];\n\n constructor(options: FrameMonitoringOptions = {}) {\n super();\n this.options = {\n targetFps: options.targetFps ?? DEFAULT_TARGET_FPS,\n frozenFrameThresholdMs: options.frozenFrameThresholdMs ?? DEFAULT_FROZEN_FRAME_THRESHOLD_MS,\n refreshRatePollingInterval: options.refreshRatePollingInterval ?? DEFAULT_REFRESH_RATE_POLLING_INTERVAL,\n normalizedRefreshRate: options.normalizedRefreshRate ?? DEFAULT_NORMALIZED_REFRESH_RATE,\n };\n }\n\n /**\n * Check if refresh rate vitals are enabled in Faro config.\n * Reads from faro.config.refreshRateVitals (single source of truth).\n */\n private get refreshRateVitalsEnabled(): boolean {\n // Access config via this.config which is set by BaseInstrumentation after initialization\n const config = this.config as ReactNativeConfig | undefined;\n return config?.refreshRateVitals ?? false;\n }\n\n initialize(): void {\n this.logDebug('Initializing frame monitoring instrumentation');\n\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n this.logWarn('Native module not available for frame monitoring');\n return;\n }\n\n // Start native frame monitoring with configuration\n this.startNativeMonitoring(nativeModule);\n\n // Set up Android refresh rate events (throttled on native side); frozen/slow frames use polling\n this.setupEventListeners(nativeModule);\n\n // Set up polling for periodic metrics collection (iOS pattern)\n this.startPolling();\n }\n\n private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {\n const { FaroReactNativeModule } = NativeModules;\n\n if (!FaroReactNativeModule) {\n return null;\n }\n\n return FaroReactNativeModule;\n }\n\n private startNativeMonitoring(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n try {\n if (typeof nativeModule.startFrameMonitoring === 'function') {\n nativeModule.startFrameMonitoring({\n targetFps: this.options.targetFps,\n frozenFrameThresholdMs: this.options.frozenFrameThresholdMs,\n normalizedRefreshRate: this.options.normalizedRefreshRate,\n });\n this.logDebug('Started native frame monitoring');\n }\n } catch (error) {\n this.logError('Failed to start native frame monitoring', error);\n }\n }\n\n private setupEventListeners(nativeModule: typeof NativeModules.FaroReactNativeModule): void {\n // Android: refresh rate only. Slow and frozen frames are polled (same as iOS) to avoid duplicate reports.\n if (Platform.OS === 'android' && this.refreshRateVitalsEnabled) {\n try {\n this.eventEmitter = new NativeEventEmitter(nativeModule);\n\n const refreshRateSubscription = this.eventEmitter.addListener('onRefreshRate', (...args: readonly Object[]) => {\n const [refreshRate] = args;\n\n if (typeof refreshRate !== 'number') {\n this.logWarn('Ignoring onRefreshRate event without a numeric payload');\n return;\n }\n\n this.handleRefreshRate(refreshRate);\n });\n this.eventSubscriptions.push(refreshRateSubscription);\n\n this.logDebug('Set up Android refresh rate event listener');\n } catch (error) {\n this.logError('Failed to set up frame event listeners', error);\n }\n }\n }\n\n private startPolling(): void {\n // Both iOS and Android use polling for slow and frozen frames (avoids duplicate reports on Android).\n this.pollingIntervalId = setInterval(() => {\n this.pollFrameMetrics();\n }, this.options.refreshRatePollingInterval);\n }\n\n private async pollFrameMetrics(): Promise<void> {\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n return;\n }\n\n try {\n // Get refresh rate (only send when refreshRateVitals is enabled in config)\n if (this.refreshRateVitalsEnabled) {\n if (typeof nativeModule.getRefreshRate === 'function') {\n const refreshRate = await nativeModule.getRefreshRate();\n if (refreshRate !== null && refreshRate > 0) {\n this.handleRefreshRate(refreshRate);\n }\n }\n }\n\n // Get full frame metrics if available\n if (typeof nativeModule.getFrameMetrics === 'function') {\n const metrics = (await nativeModule.getFrameMetrics()) as FrameMetrics | null;\n if (metrics) {\n if (this.refreshRateVitalsEnabled && metrics.refreshRate > 0) {\n this.handleRefreshRate(metrics.refreshRate);\n }\n if (metrics.slowFrames > 0) {\n this.handleSlowFrames(metrics.slowFrames);\n }\n if (metrics.frozenFrames > 0) {\n this.handleFrozenFrame(metrics.frozenFrames, metrics.frozenDurationMs);\n }\n }\n }\n } catch (error) {\n this.logError('Failed to poll frame metrics', error);\n }\n }\n\n private handleRefreshRate(refreshRate: number): void {\n this.api.pushMeasurement(\n {\n type: 'app_refresh_rate',\n values: { refresh_rate: refreshRate },\n },\n { skipDedupe: true }\n );\n }\n\n private handleSlowFrames(count: number): void {\n // Note: Despite the name \"slow_frames\", this count represents slow frame EVENTS, not individual frames.\n // Each event is a period of consecutive slow frames lasting ≥50ms (~3 frames at 60fps).\n this.api.pushMeasurement(\n {\n type: 'app_frames_rate',\n values: { slow_frames: count },\n },\n { skipDedupe: true }\n );\n }\n\n private handleFrozenFrame(count: number, durationMs: number): void {\n if (durationMs > 0) {\n this.api.pushMeasurement(\n {\n type: 'app_frozen_frame',\n values: {\n frozen_frames: count,\n frozen_duration: durationMs,\n },\n },\n { skipDedupe: true }\n );\n }\n }\n\n /**\n * Clean up resources when instrumentation is disabled.\n */\n unpatch(): void {\n // Stop polling\n if (this.pollingIntervalId !== null) {\n clearInterval(this.pollingIntervalId);\n this.pollingIntervalId = null;\n }\n\n // Remove event subscriptions\n for (const subscription of this.eventSubscriptions) {\n subscription.remove();\n }\n this.eventSubscriptions = [];\n this.eventEmitter = null;\n\n // Stop native monitoring\n const nativeModule = this.getNativeModule();\n if (nativeModule && typeof nativeModule.stopFrameMonitoring === 'function') {\n try {\n nativeModule.stopFrameMonitoring();\n } catch (error) {\n this.logError('Failed to stop native frame monitoring', error);\n }\n }\n\n this.logDebug('Frame monitoring instrumentation stopped');\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration options for Frame Monitoring instrumentation.\n *\n * These are React Native-specific advanced options. The Flutter SDK uses fixed values\n * for these parameters (not configurable). Defaults here match Flutter's hardcoded values\n * for cross-platform consistency.\n *\n * Note: To enable frame monitoring, set `refreshRateVitals: true` in ReactNativeConfig.\n */\nexport interface FrameMonitoringOptions {\n /**\n * Target frames per second for slow frame detection.\n * Frames rendering below this threshold are considered \"slow\".\n * Default: 60 (Flutter SDK hardcoded value)\n */\n targetFps?: number;\n\n /**\n * Threshold in milliseconds for detecting frozen frames.\n * Frames taking longer than this are considered \"frozen\".\n * Default: 100ms (Flutter SDK uses 100_000_000 nanoseconds)\n */\n frozenFrameThresholdMs?: number;\n\n /**\n * Interval in milliseconds for collecting and sending frame metrics.\n * Default: 30000 (30 seconds, aligned with fetchVitalsInterval)\n */\n refreshRatePollingInterval?: number;\n\n /**\n * Normalized refresh rate for ProMotion displays (120Hz, etc.).\n * Used to normalize frame rates for high-refresh-rate displays to a standard baseline.\n * Default: 60 (Flutter SDK's backendSupportedFrameRate)\n */\n normalizedRefreshRate?: number;\n}\n\n/**\n * Frame metrics collected by the instrumentation.\n */\nexport interface FrameMetrics {\n /**\n * Current refresh rate in FPS.\n */\n refreshRate: number;\n\n /**\n * Number of slow frames since last measurement.\n */\n slowFrames: number;\n\n /**\n * Number of frozen frames since last measurement.\n */\n frozenFrames: number;\n\n /**\n * Total duration of frozen frames in milliseconds.\n */\n frozenDurationMs: number;\n}\n\n/**\n * Native module methods for frame monitoring.\n */\nexport interface FrameMonitoringNativeModule {\n /**\n * Start frame monitoring.\n */\n startFrameMonitoring(config: {\n targetFps: number;\n frozenFrameThresholdMs: number;\n normalizedRefreshRate: number;\n }): void;\n\n /**\n * Stop frame monitoring.\n */\n stopFrameMonitoring(): void;\n\n /**\n * Get current frame metrics.\n */\n getFrameMetrics(): Promise<FrameMetrics | null>;\n\n /**\n * Get current refresh rate.\n */\n getRefreshRate(): Promise<number | null>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration options for Frame Monitoring instrumentation.\n *\n * Frozen frame threshold (700ms).\n *\n * Note: To enable frame monitoring, set `refreshRateVitals: true` in ReactNativeConfig.\n */\nexport interface FrameMonitoringOptions {\n /**\n * Target frames per second for slow frame detection.\n * Frames rendering below this threshold are considered \"slow\".\n * Default: 60\n */\n targetFps?: number;\n\n /**\n * Threshold in milliseconds for detecting frozen frames.\n * Frames taking longer than this are considered \"frozen\".\n * Default: 700ms (Android Vitals and OpenTelemetry Android slow rendering instrumentation)\n */\n frozenFrameThresholdMs?: number;\n\n /**\n * Interval in milliseconds for collecting and sending frame metrics.\n * Default: 30000 (30 seconds, aligned with fetchVitalsInterval)\n */\n refreshRatePollingInterval?: number;\n\n /**\n * Normalized refresh rate for ProMotion displays (120Hz, etc.).\n * Used to normalize frame rates for high-refresh-rate displays to a standard baseline.\n * Default: 60\n */\n normalizedRefreshRate?: number;\n}\n\n/**\n * Frame metrics collected by the instrumentation.\n */\nexport interface FrameMetrics {\n /**\n * Current refresh rate in FPS.\n */\n refreshRate: number;\n\n /**\n * Number of slow frames since last measurement.\n */\n slowFrames: number;\n\n /**\n * Number of frozen frames since last measurement.\n */\n frozenFrames: number;\n\n /**\n * Total duration of frozen frames in milliseconds.\n */\n frozenDurationMs: number;\n}\n\n/**\n * Native module methods for frame monitoring.\n */\nexport interface FrameMonitoringNativeModule {\n /**\n * Start frame monitoring.\n */\n startFrameMonitoring(config: {\n targetFps: number;\n frozenFrameThresholdMs: number;\n normalizedRefreshRate: number;\n }): void;\n\n /**\n * Stop frame monitoring.\n */\n stopFrameMonitoring(): void;\n\n /**\n * Get current frame metrics.\n */\n getFrameMetrics(): Promise<FrameMetrics | null>;\n\n /**\n * Get current refresh rate.\n */\n getRefreshRate(): Promise<number | null>;\n}\n"]}
@@ -26,8 +26,6 @@ var FaroReactNativeModule = react_native_1.NativeModules.FaroReactNativeModule;
26
26
  * - iOS: task_info() for memory, host_statistics() for CPU
27
27
  * - Android: /proc/[pid]/status for memory, /proc/[pid]/stat for CPU
28
28
  *
29
- * Implementation ported from Faro Flutter SDK with feature parity.
30
- *
31
29
  * **Key Features**:
32
30
  * - ✅ NO manual setup required - OS tracks metrics automatically!
33
31
  * - ✅ Periodic collection (default: every 30 seconds)
@@ -141,15 +139,13 @@ var PerformanceInstrumentation = /** @class */ (function (_super) {
141
139
  }
142
140
  var cpuUsage = FaroReactNativeModule.getCpuUsage();
143
141
  this.logDebug("CPU: raw value = ".concat(cpuUsage));
144
- // Validate CPU usage (Flutter SDK filters 0-100 range, but allows >100)
145
- // Skip null, negative, or exactly 0 (baseline reading)
142
+ // Validate CPU usage. Skip null, negative, or exactly 0 (baseline reading).
146
143
  if (cpuUsage == null || cpuUsage <= 0) {
147
144
  this.logDebug('CPU: skipping - value is null or <= 0');
148
145
  return;
149
146
  }
150
147
  this.logDebug("CPU: pushing measurement with cpu_usage = ".concat(cpuUsage));
151
- // Flutter SDK also filters values >= 100, but we allow them as they can be valid
152
- // in multi-core scenarios where one core is maxed out
148
+ // Values >= 100 can be valid in multi-core scenarios where one core is maxed out.
153
149
  this.api.pushMeasurement({
154
150
  type: 'app_cpu_usage',
155
151
  values: { cpu_usage: cpuUsage },
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/performance/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { PerformanceInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app performance metrics (CPU and Memory usage)\n *\n * Collects periodic performance metrics using native OS APIs:\n * - iOS: task_info() for memory, host_statistics() for CPU\n * - Android: /proc/[pid]/status for memory, /proc/[pid]/stat for CPU\n *\n * Implementation ported from Faro Flutter SDK with feature parity.\n *\n * **Key Features**:\n * - ✅ NO manual setup required - OS tracks metrics automatically!\n * - ✅ Periodic collection (default: every 30 seconds)\n * - ✅ Configurable per-metric enable/disable\n * - ✅ Differential CPU calculation (accurate usage percentages)\n * - ✅ Memory usage in KB (Resident Set Size)\n *\n * **Metrics Captured**:\n * - `mem_usage`: Current memory usage in KB (RSS - physical memory)\n * - `cpu_usage`: Current CPU usage percentage (0-100+)\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 21+ for CPU (Android 5.0 Lollipop, ~99% of devices)\n * - Android any version for Memory\n *\n * @example\n * ```tsx\n * import { initializeFaro, getRNInstrumentations } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://your-collector.com',\n * instrumentations: [\n * ...getRNInstrumentations({\n * memoryUsageVitals: true, // default: true\n * cpuUsageVitals: true, // default: true\n * fetchVitalsInterval: 30000, // default: 30s\n * }),\n * ],\n * });\n * ```\n */\nexport class PerformanceInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-performance';\n readonly version = VERSION;\n\n private options: Required<PerformanceInstrumentationOptions>;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: PerformanceInstrumentationOptions = {}) {\n super();\n this.options = {\n memoryUsageVitals: options.memoryUsageVitals ?? true,\n cpuUsageVitals: options.cpuUsageVitals ?? true,\n fetchVitalsInterval: options.fetchVitalsInterval ?? 30000,\n };\n }\n\n initialize(): void {\n // Only start if at least one metric is enabled\n if (!this.options.memoryUsageVitals && !this.options.cpuUsageVitals) {\n this.logInfo('Performance monitoring disabled - no metrics enabled');\n return;\n }\n\n // Check native module availability\n if (!FaroReactNativeModule) {\n this.logWarn(\n 'Native module not available. Performance instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n // Start periodic collection\n this.startPeriodicCollection();\n }\n\n private startPeriodicCollection(): void {\n // Collect immediately on initialization\n this.collectMetrics();\n\n // Then collect periodically\n this.intervalId = setInterval(() => {\n this.collectMetrics();\n }, this.options.fetchVitalsInterval);\n\n this.logInfo(\n `Performance monitoring started - collecting every ${this.options.fetchVitalsInterval}ms ` +\n `(memory: ${this.options.memoryUsageVitals}, cpu: ${this.options.cpuUsageVitals})`\n );\n }\n\n private collectMetrics(): void {\n // Collect memory if enabled\n if (this.options.memoryUsageVitals) {\n this.collectMemoryUsage();\n }\n\n // Collect CPU if enabled\n if (this.options.cpuUsageVitals) {\n this.collectCpuUsage();\n }\n }\n\n private collectMemoryUsage(): void {\n try {\n if (!FaroReactNativeModule?.getMemoryUsage) {\n this.logDebug('Memory: getMemoryUsage not available on native module');\n return;\n }\n\n const memoryUsage = FaroReactNativeModule.getMemoryUsage();\n if (memoryUsage == null || memoryUsage <= 0) {\n this.logDebug('Memory: skipping - value is null or <= 0');\n return;\n }\n\n this.logDebug(`Memory: pushing measurement with mem_usage = ${memoryUsage}`);\n this.api.pushMeasurement({\n type: 'app_memory',\n values: { mem_usage: memoryUsage },\n });\n } catch (error) {\n this.logError('Failed to collect memory usage', error);\n }\n }\n\n private collectCpuUsage(): void {\n try {\n if (!FaroReactNativeModule?.getCpuUsage) {\n this.logDebug('CPU: getCpuUsage not available on native module');\n return;\n }\n\n const cpuUsage = FaroReactNativeModule.getCpuUsage();\n this.logDebug(`CPU: raw value = ${cpuUsage}`);\n\n // Validate CPU usage (Flutter SDK filters 0-100 range, but allows >100)\n // Skip null, negative, or exactly 0 (baseline reading)\n if (cpuUsage == null || cpuUsage <= 0) {\n this.logDebug('CPU: skipping - value is null or <= 0');\n return;\n }\n\n this.logDebug(`CPU: pushing measurement with cpu_usage = ${cpuUsage}`);\n // Flutter SDK also filters values >= 100, but we allow them as they can be valid\n // in multi-core scenarios where one core is maxed out\n this.api.pushMeasurement({\n type: 'app_cpu_usage',\n values: { cpu_usage: cpuUsage },\n });\n } catch (error) {\n this.logError('Failed to collect CPU usage', error);\n }\n }\n\n unpatch(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n this.logInfo('Performance monitoring stopped');\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/performance/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { PerformanceInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app performance metrics (CPU and Memory usage)\n *\n * Collects periodic performance metrics using native OS APIs:\n * - iOS: task_info() for memory, host_statistics() for CPU\n * - Android: /proc/[pid]/status for memory, /proc/[pid]/stat for CPU\n *\n * **Key Features**:\n * - ✅ NO manual setup required - OS tracks metrics automatically!\n * - ✅ Periodic collection (default: every 30 seconds)\n * - ✅ Configurable per-metric enable/disable\n * - ✅ Differential CPU calculation (accurate usage percentages)\n * - ✅ Memory usage in KB (Resident Set Size)\n *\n * **Metrics Captured**:\n * - `mem_usage`: Current memory usage in KB (RSS - physical memory)\n * - `cpu_usage`: Current CPU usage percentage (0-100+)\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 21+ for CPU (Android 5.0 Lollipop, ~99% of devices)\n * - Android any version for Memory\n *\n * @example\n * ```tsx\n * import { initializeFaro, getRNInstrumentations } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://your-collector.com',\n * instrumentations: [\n * ...getRNInstrumentations({\n * memoryUsageVitals: true, // default: true\n * cpuUsageVitals: true, // default: true\n * fetchVitalsInterval: 30000, // default: 30s\n * }),\n * ],\n * });\n * ```\n */\nexport class PerformanceInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-performance';\n readonly version = VERSION;\n\n private options: Required<PerformanceInstrumentationOptions>;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: PerformanceInstrumentationOptions = {}) {\n super();\n this.options = {\n memoryUsageVitals: options.memoryUsageVitals ?? true,\n cpuUsageVitals: options.cpuUsageVitals ?? true,\n fetchVitalsInterval: options.fetchVitalsInterval ?? 30000,\n };\n }\n\n initialize(): void {\n // Only start if at least one metric is enabled\n if (!this.options.memoryUsageVitals && !this.options.cpuUsageVitals) {\n this.logInfo('Performance monitoring disabled - no metrics enabled');\n return;\n }\n\n // Check native module availability\n if (!FaroReactNativeModule) {\n this.logWarn(\n 'Native module not available. Performance instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n // Start periodic collection\n this.startPeriodicCollection();\n }\n\n private startPeriodicCollection(): void {\n // Collect immediately on initialization\n this.collectMetrics();\n\n // Then collect periodically\n this.intervalId = setInterval(() => {\n this.collectMetrics();\n }, this.options.fetchVitalsInterval);\n\n this.logInfo(\n `Performance monitoring started - collecting every ${this.options.fetchVitalsInterval}ms ` +\n `(memory: ${this.options.memoryUsageVitals}, cpu: ${this.options.cpuUsageVitals})`\n );\n }\n\n private collectMetrics(): void {\n // Collect memory if enabled\n if (this.options.memoryUsageVitals) {\n this.collectMemoryUsage();\n }\n\n // Collect CPU if enabled\n if (this.options.cpuUsageVitals) {\n this.collectCpuUsage();\n }\n }\n\n private collectMemoryUsage(): void {\n try {\n if (!FaroReactNativeModule?.getMemoryUsage) {\n this.logDebug('Memory: getMemoryUsage not available on native module');\n return;\n }\n\n const memoryUsage = FaroReactNativeModule.getMemoryUsage();\n if (memoryUsage == null || memoryUsage <= 0) {\n this.logDebug('Memory: skipping - value is null or <= 0');\n return;\n }\n\n this.logDebug(`Memory: pushing measurement with mem_usage = ${memoryUsage}`);\n this.api.pushMeasurement({\n type: 'app_memory',\n values: { mem_usage: memoryUsage },\n });\n } catch (error) {\n this.logError('Failed to collect memory usage', error);\n }\n }\n\n private collectCpuUsage(): void {\n try {\n if (!FaroReactNativeModule?.getCpuUsage) {\n this.logDebug('CPU: getCpuUsage not available on native module');\n return;\n }\n\n const cpuUsage = FaroReactNativeModule.getCpuUsage();\n this.logDebug(`CPU: raw value = ${cpuUsage}`);\n\n // Validate CPU usage. Skip null, negative, or exactly 0 (baseline reading).\n if (cpuUsage == null || cpuUsage <= 0) {\n this.logDebug('CPU: skipping - value is null or <= 0');\n return;\n }\n\n this.logDebug(`CPU: pushing measurement with cpu_usage = ${cpuUsage}`);\n // Values >= 100 can be valid in multi-core scenarios where one core is maxed out.\n this.api.pushMeasurement({\n type: 'app_cpu_usage',\n values: { cpu_usage: cpuUsage },\n });\n } catch (error) {\n this.logError('Failed to collect CPU usage', error);\n }\n }\n\n unpatch(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n this.logInfo('Performance monitoring stopped');\n }\n }\n}\n"]}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __rest = (this && this.__rest) || function (s, e) {
14
+ var t = {};
15
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
16
+ t[p] = s[p];
17
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
18
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
19
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
20
+ t[p[i]] = s[p[i]];
21
+ }
22
+ return t;
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.FaroSessionActivityBoundary = FaroSessionActivityBoundary;
26
+ var jsx_runtime_1 = require("react/jsx-runtime");
27
+ var react_1 = require("react");
28
+ var react_native_1 = require("react-native");
29
+ var directSessionActivity_1 = require("./directSessionActivity");
30
+ /**
31
+ * Records touch activity for the React Native subtree without emitting a user
32
+ * action or taking ownership of the responder.
33
+ */
34
+ function FaroSessionActivityBoundary(_a) {
35
+ var children = _a.children, onStartShouldSetResponderCapture = _a.onStartShouldSetResponderCapture, style = _a.style, viewProps = __rest(_a, ["children", "onStartShouldSetResponderCapture", "style"]);
36
+ var handleStartShouldSetResponderCapture = (0, react_1.useCallback)(function (event) {
37
+ var _a;
38
+ (0, directSessionActivity_1.notifySessionActivity)();
39
+ return (_a = onStartShouldSetResponderCapture === null || onStartShouldSetResponderCapture === void 0 ? void 0 : onStartShouldSetResponderCapture(event)) !== null && _a !== void 0 ? _a : false;
40
+ }, [onStartShouldSetResponderCapture]);
41
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, __assign({}, viewProps, { onStartShouldSetResponderCapture: handleStartShouldSetResponderCapture, style: [styles.container, style], children: children })));
42
+ }
43
+ var styles = react_native_1.StyleSheet.create({
44
+ container: {
45
+ flex: 1,
46
+ },
47
+ });
48
+ //# sourceMappingURL=FaroSessionActivityBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FaroSessionActivityBoundary.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/FaroSessionActivityBoundary.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAWA,kEAuBC;;AAlCD,+BAA4D;AAC5D,6CAA4F;AAE5F,iEAAgE;AAIhE;;;GAGG;AACH,SAAgB,2BAA2B,CAAC,EAKT;IAJjC,IAAA,QAAQ,cAAA,EACR,gCAAgC,sCAAA,EAChC,KAAK,WAAA,EACF,SAAS,cAJ8B,yDAK3C,CADa;IAEZ,IAAM,oCAAoC,GAAG,IAAA,mBAAW,EACtD,UAAC,KAA4B;;QAC3B,IAAA,6CAAqB,GAAE,CAAC;QACxB,OAAO,MAAA,gCAAgC,aAAhC,gCAAgC,uBAAhC,gCAAgC,CAAG,KAAK,CAAC,mCAAI,KAAK,CAAC;IAC5D,CAAC,EACD,CAAC,gCAAgC,CAAC,CACnC,CAAC;IAEF,OAAO,CACL,uBAAC,mBAAI,eACC,SAAS,IACb,gCAAgC,EAAE,oCAAoC,EACtE,KAAK,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,YAE/B,QAAQ,IACJ,CACR,CAAC;AACJ,CAAC;AAED,IAAM,MAAM,GAAG,yBAAU,CAAC,MAAM,CAAC;IAC/B,SAAS,EAAE;QACT,IAAI,EAAE,CAAC;KACR;CACF,CAAC,CAAC","sourcesContent":["import { type PropsWithChildren, useCallback } from 'react';\nimport { type GestureResponderEvent, StyleSheet, View, type ViewProps } from 'react-native';\n\nimport { notifySessionActivity } from './directSessionActivity';\n\nexport type FaroSessionActivityBoundaryProps = PropsWithChildren<ViewProps>;\n\n/**\n * Records touch activity for the React Native subtree without emitting a user\n * action or taking ownership of the responder.\n */\nexport function FaroSessionActivityBoundary({\n children,\n onStartShouldSetResponderCapture,\n style,\n ...viewProps\n}: FaroSessionActivityBoundaryProps) {\n const handleStartShouldSetResponderCapture = useCallback(\n (event: GestureResponderEvent): boolean => {\n notifySessionActivity();\n return onStartShouldSetResponderCapture?.(event) ?? false;\n },\n [onStartShouldSetResponderCapture]\n );\n\n return (\n <View\n {...viewProps}\n onStartShouldSetResponderCapture={handleStartShouldSetResponderCapture}\n style={[styles.container, style]}\n >\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n },\n});\n"]}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearDirectSessionActivityHandler = clearDirectSessionActivityHandler;
4
+ exports.registerDirectSessionActivityHandler = registerDirectSessionActivityHandler;
5
+ exports.notifySessionActivity = notifySessionActivity;
6
+ var faro_core_1 = require("@grafana/faro-core");
7
+ var directSessionActivityHandler;
8
+ /** Clears a handler left by a previous SDK initialization. */
9
+ function clearDirectSessionActivityHandler() {
10
+ directSessionActivityHandler = undefined;
11
+ }
12
+ /** Registers the active session manager as the direct-activity consumer. */
13
+ function registerDirectSessionActivityHandler(handler) {
14
+ directSessionActivityHandler = handler;
15
+ return function () {
16
+ if (directSessionActivityHandler === handler) {
17
+ directSessionActivityHandler = undefined;
18
+ }
19
+ };
20
+ }
21
+ /**
22
+ * Refreshes session inactivity without emitting telemetry.
23
+ *
24
+ * This is a no-op until session tracking has initialized. Use it for direct
25
+ * interactions that do not pass through `FaroSessionActivityBoundary`.
26
+ */
27
+ function notifySessionActivity() {
28
+ var _a, _b;
29
+ try {
30
+ directSessionActivityHandler === null || directSessionActivityHandler === void 0 ? void 0 : directSessionActivityHandler();
31
+ }
32
+ catch (error) {
33
+ // Session tracking must never interrupt the interaction being observed.
34
+ (_b = (_a = faro_core_1.faro.unpatchedConsole) === null || _a === void 0 ? void 0 : _a.warn) === null || _b === void 0 ? void 0 : _b.call(_a, 'Failed to record session activity:', error);
35
+ }
36
+ }
37
+ //# sourceMappingURL=directSessionActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directSessionActivity.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/directSessionActivity.ts"],"names":[],"mappings":";;AAOA,8EAEC;AAGD,oFAQC;AAQD,sDAOC;AAnCD,gDAA0C;AAI1C,IAAI,4BAAsE,CAAC;AAE3E,8DAA8D;AAC9D,SAAgB,iCAAiC;IAC/C,4BAA4B,GAAG,SAAS,CAAC;AAC3C,CAAC;AAED,4EAA4E;AAC5E,SAAgB,oCAAoC,CAAC,OAAqC;IACxF,4BAA4B,GAAG,OAAO,CAAC;IAEvC,OAAO;QACL,IAAI,4BAA4B,KAAK,OAAO,EAAE,CAAC;YAC7C,4BAA4B,GAAG,SAAS,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,qBAAqB;;IACnC,IAAI,CAAC;QACH,4BAA4B,aAA5B,4BAA4B,uBAA5B,4BAA4B,EAAI,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,MAAA,MAAA,gBAAI,CAAC,gBAAgB,0CAAE,IAAI,mDAAG,oCAAoC,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC","sourcesContent":["import { faro } from '@grafana/faro-core';\n\ntype DirectSessionActivityHandler = () => void;\n\nlet directSessionActivityHandler: DirectSessionActivityHandler | undefined;\n\n/** Clears a handler left by a previous SDK initialization. */\nexport function clearDirectSessionActivityHandler(): void {\n directSessionActivityHandler = undefined;\n}\n\n/** Registers the active session manager as the direct-activity consumer. */\nexport function registerDirectSessionActivityHandler(handler: DirectSessionActivityHandler): () => void {\n directSessionActivityHandler = handler;\n\n return () => {\n if (directSessionActivityHandler === handler) {\n directSessionActivityHandler = undefined;\n }\n };\n}\n\n/**\n * Refreshes session inactivity without emitting telemetry.\n *\n * This is a no-op until session tracking has initialized. Use it for direct\n * interactions that do not pass through `FaroSessionActivityBoundary`.\n */\nexport function notifySessionActivity(): void {\n try {\n directSessionActivityHandler?.();\n } catch (error) {\n // Session tracking must never interrupt the interaction being observed.\n faro.unpatchedConsole?.warn?.('Failed to record session activity:', error);\n }\n}\n"]}