@grafana/faro-react-native 1.3.0 → 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 (266) hide show
  1. package/README.md +176 -30
  2. package/android/build.gradle +20 -15
  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/anr/instrumentation.d.ts +1 -1
  160. package/dist/types/instrumentations/appState/index.d.ts +1 -1
  161. package/dist/types/instrumentations/console/index.d.ts +1 -1
  162. package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
  163. package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +1 -1
  164. package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
  165. package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +3 -3
  166. package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +3 -3
  167. package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
  168. package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +3 -3
  169. package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
  170. package/dist/types/instrumentations/http/index.d.ts +1 -1
  171. package/dist/types/instrumentations/performance/index.d.ts +1 -3
  172. package/dist/types/instrumentations/session/FaroSessionActivityBoundary.d.ts +8 -0
  173. package/dist/types/instrumentations/session/directSessionActivity.d.ts +13 -0
  174. package/dist/types/instrumentations/session/index.d.ts +21 -2
  175. package/dist/types/instrumentations/session/sessionActivity.d.ts +9 -0
  176. package/dist/types/instrumentations/session/sessionAttributes.d.ts +6 -3
  177. package/dist/types/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.d.ts +16 -3
  178. package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +9 -3
  179. package/dist/types/instrumentations/session/sessionManager/persistentSessionRecord.d.ts +4 -0
  180. package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +19 -7
  181. package/dist/types/instrumentations/session/sessionManager/types.d.ts +6 -0
  182. package/dist/types/instrumentations/session/sessionProcess.d.ts +17 -0
  183. package/dist/types/instrumentations/startup/index.d.ts +3 -7
  184. package/dist/types/instrumentations/userActions/index.d.ts +1 -1
  185. package/dist/types/instrumentations/view/index.d.ts +1 -1
  186. package/dist/types/instrumentations/xhr/index.d.ts +1 -1
  187. package/dist/types/navigation/utils.d.ts +1 -0
  188. package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
  189. package/dist/types/transports/console/transport.d.ts +1 -1
  190. package/dist/types/transports/fetch/index.d.ts +1 -1
  191. package/dist/types/transports/fetch/transport.d.ts +9 -2
  192. package/dist/types/transports/fetch/types.d.ts +5 -0
  193. package/dist/types/transports/offline/ConnectivityService.d.ts +1 -1
  194. package/dist/types/transports/offline/OfflineCache.d.ts +2 -2
  195. package/dist/types/transports/offline/transport.d.ts +4 -5
  196. package/dist/types/userPersistence/UserPersistence.d.ts +1 -2
  197. package/ios/FaroCrashReporter.swift +214 -21
  198. package/ios/FaroReactNative.swift +24 -0
  199. package/ios/FaroReactNativeModule.h +2 -1
  200. package/ios/FaroReactNativeModule.mm +121 -6
  201. package/ios/RefreshRateVitals.swift +34 -38
  202. package/package.json +8 -6
  203. package/src/config/getRNInstrumentations.ts +6 -8
  204. package/src/config/makeRNConfig.test.ts +6 -0
  205. package/src/config/sampling.ts +0 -3
  206. package/src/config/types.ts +5 -3
  207. package/src/dataCollection/DataCollectionPolicy.ts +1 -2
  208. package/src/generated/faroRNPackageMeta.ts +1 -1
  209. package/src/index.ts +8 -2
  210. package/src/instrumentations/anr/instrumentation.ts +8 -1
  211. package/src/instrumentations/appState/index.ts +10 -2
  212. package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
  213. package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
  214. package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
  215. package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
  216. package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
  217. package/src/instrumentations/crashReporting/types.ts +7 -0
  218. package/src/instrumentations/errors/index.ts +12 -4
  219. package/src/instrumentations/errors/instrumentation.test.ts +35 -0
  220. package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
  221. package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
  222. package/src/instrumentations/frameMonitoring/types.ts +4 -6
  223. package/src/instrumentations/performance/index.ts +2 -6
  224. package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
  225. package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
  226. package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
  227. package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
  228. package/src/instrumentations/session/directSessionActivity.ts +36 -0
  229. package/src/instrumentations/session/index.ts +269 -46
  230. package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
  231. package/src/instrumentations/session/sessionActivity.test.ts +68 -0
  232. package/src/instrumentations/session/sessionActivity.ts +58 -0
  233. package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
  234. package/src/instrumentations/session/sessionAttributes.ts +12 -6
  235. package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
  236. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
  237. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
  238. package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
  239. package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
  240. package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
  241. package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
  242. package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
  243. package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
  244. package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
  245. package/src/instrumentations/session/sessionManager/types.ts +8 -0
  246. package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
  247. package/src/instrumentations/session/sessionProcess.test.ts +136 -0
  248. package/src/instrumentations/session/sessionProcess.ts +114 -0
  249. package/src/instrumentations/session/startNewSession.test.ts +337 -0
  250. package/src/instrumentations/startup/index.ts +2 -6
  251. package/src/navigation/utils.ts +3 -1
  252. package/src/transports/fetch/index.ts +6 -1
  253. package/src/transports/fetch/transport.test.ts +86 -2
  254. package/src/transports/fetch/transport.ts +38 -16
  255. package/src/transports/fetch/types.ts +7 -0
  256. package/src/transports/offline/ConnectivityService.ts +1 -1
  257. package/src/transports/offline/OfflineCache.ts +2 -2
  258. package/src/transports/offline/transport.ts +3 -4
  259. package/src/userPersistence/UserPersistence.ts +1 -2
  260. package/dist/cjs/utils/throttle.js +0 -42
  261. package/dist/cjs/utils/throttle.js.map +0 -1
  262. package/dist/esm/utils/throttle.js +0 -34
  263. package/dist/esm/utils/throttle.js.map +0 -1
  264. package/dist/types/utils/throttle.d.ts +0 -9
  265. package/src/utils/throttle.test.ts +0 -141
  266. package/src/utils/throttle.ts +0 -34
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/frameMonitoring/instrumentation.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAMlE;;;GAGG;AACH,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,iCAAiC,GAAG,GAAG,CAAC;AAC9C,MAAM,qCAAqC,GAAG,KAAK,CAAC;AACpD,MAAM,+BAA+B,GAAG,EAAE,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,OAAO,8BAA+B,SAAQ,mBAAmB;IASrE,YAAY,UAAkC,EAAE;;QAC9C,KAAK,EAAE,CAAC;QATD,SAAI,GAAG,6DAA6D,CAAC;QACrE,YAAO,GAAG,OAAO,CAAC;QAGnB,sBAAiB,GAA0C,IAAI,CAAC;QAChE,iBAAY,GAA8B,IAAI,CAAC;QAC/C,uBAAkB,GAAkC,EAAE,CAAC;QAI7D,IAAI,CAAC,OAAO,GAAG;YACb,SAAS,EAAE,MAAA,OAAO,CAAC,SAAS,mCAAI,kBAAkB;YAClD,sBAAsB,EAAE,MAAA,OAAO,CAAC,sBAAsB,mCAAI,iCAAiC;YAC3F,0BAA0B,EAAE,MAAA,OAAO,CAAC,0BAA0B,mCAAI,qCAAqC;YACvG,qBAAqB,EAAE,MAAA,OAAO,CAAC,qBAAqB,mCAAI,+BAA+B;SACxF,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,IAAY,wBAAwB;;QAClC,yFAAyF;QACzF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAuC,CAAC;QAC5D,OAAO,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,iBAAiB,mCAAI,KAAK,CAAC;IAC5C,CAAC;IAED,UAAU;QACR,IAAI,CAAC,QAAQ,CAAC,+CAA+C,CAAC,CAAC;QAE/D,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC5C,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,mDAAmD;QACnD,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;QAEzC,sEAAsE;QACtE,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;QAEvC,+DAA+D;QAC/D,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAEO,eAAe;QACrB,MAAM,EAAE,qBAAqB,EAAE,GAAG,aAAa,CAAC;QAEhD,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAEO,qBAAqB,CAAC,YAAwD;QACpF,IAAI,CAAC;YACH,IAAI,OAAO,YAAY,CAAC,oBAAoB,KAAK,UAAU,EAAE,CAAC;gBAC5D,YAAY,CAAC,oBAAoB,CAAC;oBAChC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;oBACjC,sBAAsB,EAAE,IAAI,CAAC,OAAO,CAAC,sBAAsB;oBAC3D,qBAAqB,EAAE,IAAI,CAAC,OAAO,CAAC,qBAAqB;iBAC1D,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,iCAAiC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAEO,mBAAmB,CAAC,YAAwD;QAClF,2DAA2D;QAC3D,qEAAqE;QACrE,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,IAAI,CAAC,YAAY,GAAG,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAC;gBAEzD,iCAAiC;gBACjC,MAAM,uBAAuB,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAC3D,eAAe,EACf,CAAC,IAAoD,EAAE,EAAE;oBACvD,kEAAkE;oBAClE,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;wBAC7B,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;oBAClC,CAAC;yBAAM,CAAC;wBACN,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;oBAC3D,CAAC;gBACH,CAAC,CACF,CAAC;gBACF,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;gBAEtD,oFAAoF;gBACpF,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;oBAClC,MAAM,uBAAuB,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,WAAmB,EAAE,EAAE;wBACrG,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;oBACtC,CAAC,CAAC,CAAC;oBACH,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;gBACxD,CAAC;gBAED,IAAI,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAC;YACxD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,QAAQ,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,4DAA4D;QAC5D,oEAAoE;QACpE,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;YACxC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAEa,gBAAgB;;YAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,2EAA2E;gBAC3E,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;oBAClC,IAAI,OAAO,YAAY,CAAC,cAAc,KAAK,UAAU,EAAE,CAAC;wBACtD,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,cAAc,EAAE,CAAC;wBACxD,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;4BAC5C,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;wBACtC,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,sCAAsC;gBACtC,IAAI,OAAO,YAAY,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;oBACvD,MAAM,OAAO,GAAG,CAAC,MAAM,YAAY,CAAC,eAAe,EAAE,CAAwB,CAAC;oBAC9E,IAAI,OAAO,EAAE,CAAC;wBACZ,IAAI,IAAI,CAAC,wBAAwB,IAAI,OAAO,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;4BAC7D,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;wBAC9C,CAAC;wBACD,IAAI,OAAO,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;4BAC3B,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;wBAC5C,CAAC;wBACD,IAAI,OAAO,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;4BAC7B,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;wBACzE,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,QAAQ,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;KAAA;IAEO,iBAAiB,CAAC,WAAmB;QAC3C,IAAI,CAAC,GAAG,CAAC,eAAe,CACtB;YACE,IAAI,EAAE,kBAAkB;YACxB,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE;SACtC,EACD,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,KAAa;QACpC,wGAAwG;QACxG,wFAAwF;QACxF,IAAI,CAAC,GAAG,CAAC,eAAe,CACtB;YACE,IAAI,EAAE,iBAAiB;YACvB,MAAM,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;SAC/B,EACD,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAC;IACJ,CAAC;IAEO,iBAAiB,CAAC,KAAa,EAAE,UAAkB;QACzD,8DAA8D;QAC9D,mDAAmD;QACnD,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACnB,4CAA4C;YAC5C,OAAO,CAAC,GAAG,CACT,eAAe,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,oCAAoC,KAAK,cAAc,UAAU,IAAI,CAC9G,CAAC;YAEF,IAAI,CAAC,GAAG,CAAC,eAAe,CACtB;gBACE,IAAI,EAAE,kBAAkB;gBACxB,MAAM,EAAE;oBACN,aAAa,EAAE,KAAK;oBACpB,eAAe,EAAE,UAAU;iBAC5B;aACF,EACD,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,OAAO;QACL,eAAe;QACf,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;YACpC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACtC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,6BAA6B;QAC7B,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACnD,YAAY,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAEzB,yBAAyB;QACzB,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC5C,IAAI,YAAY,IAAI,OAAO,YAAY,CAAC,mBAAmB,KAAK,UAAU,EAAE,CAAC;YAC3E,IAAI,CAAC;gBACH,YAAY,CAAC,mBAAmB,EAAE,CAAC;YACrC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,QAAQ,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,0CAA0C,CAAC,CAAC;IAC5D,CAAC;CACF","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"]}
@@ -8,8 +8,6 @@ const { FaroReactNativeModule } = NativeModules;
8
8
  * - iOS: task_info() for memory, host_statistics() for CPU
9
9
  * - Android: /proc/[pid]/status for memory, /proc/[pid]/stat for CPU
10
10
  *
11
- * Implementation ported from Faro Flutter SDK with feature parity.
12
- *
13
11
  * **Key Features**:
14
12
  * - ✅ NO manual setup required - OS tracks metrics automatically!
15
13
  * - ✅ Periodic collection (default: every 30 seconds)
@@ -119,15 +117,13 @@ export class PerformanceInstrumentation extends BaseInstrumentation {
119
117
  }
120
118
  const cpuUsage = FaroReactNativeModule.getCpuUsage();
121
119
  this.logDebug(`CPU: raw value = ${cpuUsage}`);
122
- // Validate CPU usage (Flutter SDK filters 0-100 range, but allows >100)
123
- // Skip null, negative, or exactly 0 (baseline reading)
120
+ // Validate CPU usage. Skip null, negative, or exactly 0 (baseline reading).
124
121
  if (cpuUsage == null || cpuUsage <= 0) {
125
122
  this.logDebug('CPU: skipping - value is null or <= 0');
126
123
  return;
127
124
  }
128
125
  this.logDebug(`CPU: pushing measurement with cpu_usage = ${cpuUsage}`);
129
- // Flutter SDK also filters values >= 100, but we allow them as they can be valid
130
- // in multi-core scenarios where one core is maxed out
126
+ // Values >= 100 can be valid in multi-core scenarios where one core is maxed out.
131
127
  this.api.pushMeasurement({
132
128
  type: 'app_cpu_usage',
133
129
  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,34 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { useCallback } from 'react';
14
+ import { StyleSheet, View } from 'react-native';
15
+ import { notifySessionActivity } from './directSessionActivity';
16
+ /**
17
+ * Records touch activity for the React Native subtree without emitting a user
18
+ * action or taking ownership of the responder.
19
+ */
20
+ export function FaroSessionActivityBoundary(_a) {
21
+ var { children, onStartShouldSetResponderCapture, style } = _a, viewProps = __rest(_a, ["children", "onStartShouldSetResponderCapture", "style"]);
22
+ const handleStartShouldSetResponderCapture = useCallback((event) => {
23
+ var _a;
24
+ notifySessionActivity();
25
+ return (_a = onStartShouldSetResponderCapture === null || onStartShouldSetResponderCapture === void 0 ? void 0 : onStartShouldSetResponderCapture(event)) !== null && _a !== void 0 ? _a : false;
26
+ }, [onStartShouldSetResponderCapture]);
27
+ return (_jsx(View, Object.assign({}, viewProps, { onStartShouldSetResponderCapture: handleStartShouldSetResponderCapture, style: [styles.container, style], children: children })));
28
+ }
29
+ const styles = StyleSheet.create({
30
+ container: {
31
+ flex: 1,
32
+ },
33
+ });
34
+ //# sourceMappingURL=FaroSessionActivityBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FaroSessionActivityBoundary.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/FaroSessionActivityBoundary.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAA0B,WAAW,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAA8B,UAAU,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;AAE5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAIhE;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,EAKT;QALS,EAC1C,QAAQ,EACR,gCAAgC,EAChC,KAAK,OAE4B,EAD9B,SAAS,cAJ8B,yDAK3C,CADa;IAEZ,MAAM,oCAAoC,GAAG,WAAW,CACtD,CAAC,KAA4B,EAAW,EAAE;;QACxC,qBAAqB,EAAE,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,KAAC,IAAI,oBACC,SAAS,IACb,gCAAgC,EAAE,oCAAoC,EACtE,KAAK,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,YAE/B,QAAQ,IACJ,CACR,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,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,32 @@
1
+ import { faro } from '@grafana/faro-core';
2
+ let directSessionActivityHandler;
3
+ /** Clears a handler left by a previous SDK initialization. */
4
+ export function clearDirectSessionActivityHandler() {
5
+ directSessionActivityHandler = undefined;
6
+ }
7
+ /** Registers the active session manager as the direct-activity consumer. */
8
+ export function registerDirectSessionActivityHandler(handler) {
9
+ directSessionActivityHandler = handler;
10
+ return () => {
11
+ if (directSessionActivityHandler === handler) {
12
+ directSessionActivityHandler = undefined;
13
+ }
14
+ };
15
+ }
16
+ /**
17
+ * Refreshes session inactivity without emitting telemetry.
18
+ *
19
+ * This is a no-op until session tracking has initialized. Use it for direct
20
+ * interactions that do not pass through `FaroSessionActivityBoundary`.
21
+ */
22
+ export function notifySessionActivity() {
23
+ var _a, _b;
24
+ try {
25
+ directSessionActivityHandler === null || directSessionActivityHandler === void 0 ? void 0 : directSessionActivityHandler();
26
+ }
27
+ catch (error) {
28
+ // Session tracking must never interrupt the interaction being observed.
29
+ (_b = (_a = 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);
30
+ }
31
+ }
32
+ //# sourceMappingURL=directSessionActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directSessionActivity.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/directSessionActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAI1C,IAAI,4BAAsE,CAAC;AAE3E,8DAA8D;AAC9D,MAAM,UAAU,iCAAiC;IAC/C,4BAA4B,GAAG,SAAS,CAAC;AAC3C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,oCAAoC,CAAC,OAAqC;IACxF,4BAA4B,GAAG,OAAO,CAAC;IAEvC,OAAO,GAAG,EAAE;QACV,IAAI,4BAA4B,KAAK,OAAO,EAAE,CAAC;YAC7C,4BAA4B,GAAG,SAAS,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;;IACnC,IAAI,CAAC;QACH,4BAA4B,aAA5B,4BAA4B,uBAA5B,4BAA4B,EAAI,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,MAAA,MAAA,IAAI,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"]}
@@ -1,8 +1,69 @@
1
- import { BaseInstrumentation, dateNow, EVENT_SESSION_START, genShortID, VERSION } from '@grafana/faro-core';
1
+ import { BaseInstrumentation, dateNow, EVENT_SESSION_START, faro, VERSION } from '@grafana/faro-core';
2
+ import { clearDirectSessionActivityHandler, registerDirectSessionActivityHandler } from './directSessionActivity';
3
+ import { classifySessionActivity, isRecoveredCrashItem, SessionActivityKind } from './sessionActivity';
2
4
  import { minimalSessionDeviceAttributes } from './sessionAttributes';
3
5
  import { getSessionManagerByConfig, isSampled } from './sessionManager';
4
6
  import { MAX_SESSION_PERSISTENCE_TIME } from './sessionManager/sessionConstants';
5
7
  import { createUserSessionObject, isUserSessionValid } from './sessionManager/sessionManagerUtils';
8
+ const SESSION_INSTRUMENTATION_NAME = '@grafana/faro-react-native:instrumentation-session';
9
+ // Track the current session hook per transport so superseded hooks yield to a
10
+ // replacement. The shared fallback preserves sampling decisions captured on
11
+ // queued items after the owning instrumentation has detached.
12
+ const activeSessionBeforeSendHooks = new WeakMap();
13
+ const sessionSamplingFallbackHooks = new WeakMap();
14
+ function applySessionSampling(item, fallbackSamplingDecision) {
15
+ var _a, _b, _c, _d;
16
+ const attributes = (_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.attributes;
17
+ const samplingAttribute = (_b = attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) !== null && _b !== void 0 ? _b : fallbackSamplingDecision === null || fallbackSamplingDecision === void 0 ? void 0 : fallbackSamplingDecision.toString();
18
+ if (samplingAttribute === 'false') {
19
+ return null;
20
+ }
21
+ if (samplingAttribute !== 'true') {
22
+ return item;
23
+ }
24
+ const sampledItem = Object.assign(Object.assign({}, item), { meta: Object.assign(Object.assign({}, item.meta), { session: item.meta.session
25
+ ? Object.assign(Object.assign({}, item.meta.session), { attributes: item.meta.session.attributes ? Object.assign({}, item.meta.session.attributes) : undefined }) : undefined }) });
26
+ const sampledAttributes = (_c = sampledItem.meta.session) === null || _c === void 0 ? void 0 : _c.attributes;
27
+ sampledAttributes === null || sampledAttributes === void 0 ? true : delete sampledAttributes['isSampled'];
28
+ if (Object.keys(sampledAttributes !== null && sampledAttributes !== void 0 ? sampledAttributes : {}).length === 0) {
29
+ (_d = sampledItem.meta.session) === null || _d === void 0 ? true : delete _d.attributes;
30
+ }
31
+ return sampledItem;
32
+ }
33
+ function ensureSessionSamplingFallback(transports) {
34
+ if (sessionSamplingFallbackHooks.has(transports)) {
35
+ return;
36
+ }
37
+ const fallbackHook = (item) => activeSessionBeforeSendHooks.has(transports) ? item : applySessionSampling(item);
38
+ transports.addBeforeSendHooks(fallbackHook);
39
+ sessionSamplingFallbackHooks.set(transports, fallbackHook);
40
+ }
41
+ function removeInternalSamplingAttribute(session) {
42
+ var _a;
43
+ if (((_a = session === null || session === void 0 ? void 0 : session.attributes) === null || _a === void 0 ? void 0 : _a['isSampled']) == null) {
44
+ return session;
45
+ }
46
+ const attributes = Object.assign({}, session.attributes);
47
+ delete attributes['isSampled'];
48
+ return Object.assign(Object.assign({}, session), { attributes: Object.keys(attributes).length === 0 ? undefined : attributes });
49
+ }
50
+ /**
51
+ * Starts a new linked session at an application-defined boundary.
52
+ *
53
+ * Call this after changing or clearing the current user for logout, account
54
+ * changes, or another boundary that must not share a session. Calls made
55
+ * before Faro initializes, after teardown, or while session tracking is
56
+ * disabled have no effect.
57
+ */
58
+ export function startNewSession() {
59
+ var _a;
60
+ const instrumentation = (_a = faro === null || faro === void 0 ? void 0 : faro.instrumentations) === null || _a === void 0 ? void 0 : _a.instrumentations.find(({ name }) => name === SESSION_INSTRUMENTATION_NAME);
61
+ if (instrumentation != null &&
62
+ 'startNewSession' in instrumentation &&
63
+ typeof instrumentation.startNewSession === 'function') {
64
+ instrumentation.startNewSession();
65
+ }
66
+ }
6
67
  /**
7
68
  * Session instrumentation for React Native
8
69
  * Manages persistent or volatile sessions with expiration and inactivity tracking
@@ -10,8 +71,19 @@ import { createUserSessionObject, isUserSessionValid } from './sessionManager/se
10
71
  export class SessionInstrumentation extends BaseInstrumentation {
11
72
  constructor() {
12
73
  super(...arguments);
13
- this.name = '@grafana/faro-react-native:instrumentation-session';
74
+ this.name = SESSION_INSTRUMENTATION_NAME;
14
75
  this.version = VERSION;
76
+ this.isResettingSession = false;
77
+ this.sendSessionStartEvent = (meta) => {
78
+ var _a;
79
+ const session = meta.session;
80
+ if (session && session.id !== ((_a = this.notifiedSession) === null || _a === void 0 ? void 0 : _a.id)) {
81
+ this.notifiedSession = session;
82
+ // no need to add attributes and session id, they are included as part of meta
83
+ // automatically
84
+ this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });
85
+ }
86
+ };
15
87
  }
16
88
  getDefaultSessionDeviceAttributes() {
17
89
  const cfg = this.config;
@@ -20,24 +92,20 @@ export class SessionInstrumentation extends BaseInstrumentation {
20
92
  }
21
93
  return minimalSessionDeviceAttributes();
22
94
  }
23
- sendSessionStartEvent(meta) {
24
- var _a;
25
- const session = meta.session;
26
- if (session && session.id !== ((_a = this.notifiedSession) === null || _a === void 0 ? void 0 : _a.id)) {
27
- this.notifiedSession = session;
28
- // no need to add attributes and session id, they are included as part of meta
29
- // automatically
30
- this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });
31
- }
95
+ registerSessionMetaListener() {
96
+ this.metas.addListener(this.sendSessionStartEvent);
97
+ this.sessionMetaListenerMetas = this.metas;
32
98
  }
33
99
  createInitialSession(SessionManagerClass, sessionsConfig) {
34
- var _a, _b, _c, _d, _e, _f, _g;
100
+ var _a, _b, _c, _d, _e, _f;
35
101
  let storedUserSession = SessionManagerClass.fetchUserSession();
36
102
  const sessionsConfigTyped = sessionsConfig;
37
103
  const maxPersistenceMs = (_a = sessionsConfigTyped.maxSessionPersistenceTime) !== null && _a !== void 0 ? _a : MAX_SESSION_PERSISTENCE_TIME;
38
104
  if (sessionsConfig.persistent && storedUserSession) {
39
105
  const now = dateNow();
40
- const shouldClearPersistentSession = storedUserSession.lastActivity < now - maxPersistenceMs;
106
+ const shouldClearPersistentSession = storedUserSession.started > now ||
107
+ storedUserSession.lastActivity > now ||
108
+ storedUserSession.lastActivity <= now - maxPersistenceMs;
41
109
  if (shouldClearPersistentSession) {
42
110
  SessionManagerClass.removeUserSession();
43
111
  storedUserSession = null;
@@ -46,7 +114,7 @@ export class SessionInstrumentation extends BaseInstrumentation {
46
114
  const defaultAttributes = this.getDefaultSessionDeviceAttributes();
47
115
  let emitSessionStartOnInit;
48
116
  let initialSession;
49
- if (isUserSessionValid(storedUserSession)) {
117
+ if (!sessionsConfig.persistent && isUserSessionValid(storedUserSession)) {
50
118
  const sessionId = storedUserSession === null || storedUserSession === void 0 ? void 0 : storedUserSession.sessionId;
51
119
  initialSession = createUserSessionObject({
52
120
  sessionId,
@@ -63,71 +131,164 @@ export class SessionInstrumentation extends BaseInstrumentation {
63
131
  emitSessionStartOnInit = false;
64
132
  }
65
133
  else {
66
- const sessionId = (_e = (_d = sessionsConfig.session) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : genShortID();
67
134
  initialSession = createUserSessionObject({
68
- sessionId,
135
+ sessionId: (_d = sessionsConfig.session) === null || _d === void 0 ? void 0 : _d.id,
69
136
  isSampled: isSampled(),
70
137
  });
71
- const overrides = (_f = sessionsConfig.session) === null || _f === void 0 ? void 0 : _f.overrides;
72
- initialSession.sessionMeta = Object.assign({ id: sessionId, attributes: Object.assign(Object.assign({ isSampled: initialSession.isSampled.toString() }, (_g = sessionsConfig.session) === null || _g === void 0 ? void 0 : _g.attributes), defaultAttributes) }, (overrides ? { overrides } : {}));
138
+ const sessionId = initialSession.sessionId;
139
+ const previousSessionId = (storedUserSession === null || storedUserSession === void 0 ? void 0 : storedUserSession.sessionId) === sessionId ? undefined : storedUserSession === null || storedUserSession === void 0 ? void 0 : storedUserSession.sessionId;
140
+ const overrides = (_e = sessionsConfig.session) === null || _e === void 0 ? void 0 : _e.overrides;
141
+ initialSession.sessionMeta = Object.assign({ id: sessionId, attributes: Object.assign(Object.assign(Object.assign(Object.assign({}, (_f = sessionsConfig.session) === null || _f === void 0 ? void 0 : _f.attributes), defaultAttributes), { isSampled: initialSession.isSampled.toString() }), (previousSessionId == null ? {} : { previousSession: previousSessionId })) }, (overrides ? { overrides } : {}));
73
142
  emitSessionStartOnInit = true;
74
143
  }
75
144
  return { initialSession, emitSessionStartOnInit };
76
145
  }
77
146
  registerBeforeSendHook(SessionManagerClass) {
78
- var _a;
79
- const { updateSession } = new SessionManagerClass();
80
- (_a = this.transports) === null || _a === void 0 ? void 0 : _a.addBeforeSendHooks((item) => {
81
- var _a, _b, _c;
82
- updateSession();
83
- const attributes = (_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.attributes;
84
- // Only filter out items when session is explicitly NOT sampled (isSampled='false')
85
- // If isSampled='true', remove the attribute before sending (it's internal)
86
- // If no isSampled attribute, pass through the item unchanged
87
- if ((attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) === 'false') {
88
- // Session is not sampled - drop this item
89
- return null;
147
+ const registeredTransports = this.transports;
148
+ ensureSessionSamplingFallback(registeredTransports);
149
+ const beforeSendHook = (item) => {
150
+ var _a;
151
+ const activeBeforeSendHook = activeSessionBeforeSendHooks.get(registeredTransports);
152
+ if (activeBeforeSendHook != null && activeBeforeSendHook !== beforeSendHook) {
153
+ return item;
90
154
  }
91
- if ((attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) === 'true') {
92
- // Session is sampled - remove internal isSampled attribute before sending
93
- let newItem = JSON.parse(JSON.stringify(item));
94
- const newAttributes = (_b = newItem.meta.session) === null || _b === void 0 ? void 0 : _b.attributes;
95
- newAttributes === null || newAttributes === void 0 ? true : delete newAttributes['isSampled'];
96
- if (Object.keys(newAttributes !== null && newAttributes !== void 0 ? newAttributes : {}).length === 0) {
97
- (_c = newItem.meta.session) === null || _c === void 0 ? true : delete _c.attributes;
155
+ let nextItem = item;
156
+ let fallbackSamplingDecision;
157
+ const sessionManager = this.beforeSendHook === beforeSendHook ? this.sessionManagerInstance : undefined;
158
+ if (sessionManager != null) {
159
+ const storedSession = SessionManagerClass.fetchUserSession();
160
+ const recoveredCrash = isRecoveredCrashItem(item, storedSession === null || storedSession === void 0 ? void 0 : storedSession.sessionId);
161
+ // Ending an active action can send telemetry synchronously. Keep that
162
+ // telemetry on the current session until the explicit reset completes.
163
+ const checkedSession = recoveredCrash
164
+ ? null
165
+ : this.isResettingSession
166
+ ? storedSession
167
+ : sessionManager.checkSession(classifySessionActivity(item), storedSession);
168
+ if ((checkedSession === null || checkedSession === void 0 ? void 0 : checkedSession.sessionMeta) != null && ((_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.id) !== checkedSession.sessionId) {
169
+ nextItem = Object.assign(Object.assign({}, item), { meta: Object.assign(Object.assign({}, item.meta), { session: checkedSession.sessionMeta }) });
98
170
  }
99
- return newItem;
171
+ // New crash records carry the crash-time decision. Older records may not,
172
+ // so retain the previous behavior and use the live session's stable
173
+ // decision rather than implicitly sampling every recovered crash.
174
+ fallbackSamplingDecision = recoveredCrash ? storedSession === null || storedSession === void 0 ? void 0 : storedSession.isSampled : checkedSession === null || checkedSession === void 0 ? void 0 : checkedSession.isSampled;
100
175
  }
101
- // No isSampled attribute or other value - pass through unchanged
102
- return item;
103
- });
176
+ return applySessionSampling(nextItem, fallbackSamplingDecision);
177
+ };
178
+ this.beforeSendHook = beforeSendHook;
179
+ this.beforeSendHookApi = this.api;
180
+ this.beforeSendHookTransports = registeredTransports;
181
+ registeredTransports.addBeforeSendHooks(beforeSendHook);
182
+ activeSessionBeforeSendHooks.set(registeredTransports, beforeSendHook);
104
183
  }
105
184
  initialize() {
185
+ // BaseInstrumentation instances can be registered more than once. Detach
186
+ // every resource from the previous Faro instance before binding the next.
187
+ this.unpatch();
106
188
  const sessionTrackingConfig = this.config.sessionTracking;
189
+ // A new initialization owns direct-activity routing, including when
190
+ // session tracking is disabled.
191
+ clearDirectSessionActivityHandler();
107
192
  if (!(sessionTrackingConfig === null || sessionTrackingConfig === void 0 ? void 0 : sessionTrackingConfig.enabled)) {
108
- this.metas.addListener(this.sendSessionStartEvent.bind(this));
193
+ this.registerSessionMetaListener();
109
194
  return;
110
195
  }
111
196
  const SessionManagerClass = getSessionManagerByConfig(sessionTrackingConfig);
197
+ // Constructing a manager registers an AppState subscription and a metas
198
+ // listener, so exactly one instance is created and reused. It is also the
199
+ // instance `unpatch()` cleans up.
200
+ this.sessionManagerInstance = new SessionManagerClass(this.metas);
112
201
  this.registerBeforeSendHook(SessionManagerClass);
113
202
  const { initialSession, emitSessionStartOnInit } = this.createInitialSession(SessionManagerClass, sessionTrackingConfig);
114
203
  SessionManagerClass.storeUserSession(initialSession);
115
204
  const initialSessionMeta = initialSession.sessionMeta;
116
205
  this.notifiedSession = initialSessionMeta;
117
206
  this.api.setSession(initialSessionMeta);
118
- this.sessionManagerInstance = new SessionManagerClass();
119
207
  if (emitSessionStartOnInit) {
120
208
  this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });
121
209
  }
122
- this.metas.addListener(this.sendSessionStartEvent.bind(this));
210
+ this.unregisterDirectSessionActivity = registerDirectSessionActivityHandler(() => {
211
+ var _a;
212
+ (_a = this.sessionManagerInstance) === null || _a === void 0 ? void 0 : _a.checkSession(SessionActivityKind.Meaningful);
213
+ });
214
+ this.registerSessionMetaListener();
215
+ }
216
+ /** Starts a new linked session immediately. */
217
+ startNewSession() {
218
+ var _a, _b;
219
+ if (this.sessionManagerInstance == null || this.isResettingSession) {
220
+ return;
221
+ }
222
+ this.isResettingSession = true;
223
+ try {
224
+ try {
225
+ const activeAction = (_b = (_a = this.api).getActiveUserAction) === null || _b === void 0 ? void 0 : _b.call(_a);
226
+ activeAction === null || activeAction === void 0 ? void 0 : activeAction.end();
227
+ }
228
+ catch (error) {
229
+ this.logWarn('Failed to end the active user action before resetting the session:', error);
230
+ }
231
+ this.sessionManagerInstance.resetSession();
232
+ }
233
+ finally {
234
+ this.isResettingSession = false;
235
+ }
123
236
  }
124
237
  /**
125
238
  * Clean up session manager listeners
126
239
  */
127
240
  unpatch() {
128
- if (this.sessionManagerInstance && 'unpatch' in this.sessionManagerInstance) {
129
- this.sessionManagerInstance.unpatch();
241
+ var _a, _b;
242
+ (_a = this.unregisterDirectSessionActivity) === null || _a === void 0 ? void 0 : _a.call(this);
243
+ this.unregisterDirectSessionActivity = undefined;
244
+ const beforeSendHook = this.beforeSendHook;
245
+ const beforeSendHookApi = this.beforeSendHookApi;
246
+ const beforeSendHookTransports = this.beforeSendHookTransports;
247
+ const ownedActiveHook = beforeSendHook != null &&
248
+ beforeSendHookTransports != null &&
249
+ activeSessionBeforeSendHooks.get(beforeSendHookTransports) === beforeSendHook;
250
+ // Stop the manager's metas synchronization before removing the internal
251
+ // sampling marker. Keep the session hook active during that update so any
252
+ // surviving metas listener that emits telemetry is still attributed and
253
+ // filtered with the current session.
254
+ (_b = this.sessionManagerInstance) === null || _b === void 0 ? void 0 : _b.unpatch();
255
+ try {
256
+ if (ownedActiveHook && beforeSendHookApi) {
257
+ const session = beforeSendHookApi.getSession();
258
+ const sessionWithoutSamplingAttribute = removeInternalSamplingAttribute(session);
259
+ if (sessionWithoutSamplingAttribute !== session) {
260
+ this.isResettingSession = true;
261
+ try {
262
+ beforeSendHookApi.setSession(sessionWithoutSamplingAttribute);
263
+ }
264
+ finally {
265
+ this.isResettingSession = false;
266
+ }
267
+ }
268
+ }
269
+ }
270
+ finally {
271
+ this.beforeSendHook = undefined;
272
+ this.beforeSendHookApi = undefined;
273
+ this.beforeSendHookTransports = undefined;
274
+ if (beforeSendHook && beforeSendHookTransports) {
275
+ if (ownedActiveHook) {
276
+ activeSessionBeforeSendHooks.delete(beforeSendHookTransports);
277
+ }
278
+ beforeSendHookTransports.removeBeforeSendHooks(beforeSendHook);
279
+ }
280
+ this.sessionManagerInstance = undefined;
281
+ const sessionMetaListenerMetas = this.sessionMetaListenerMetas;
282
+ this.sessionMetaListenerMetas = undefined;
283
+ if (sessionMetaListenerMetas) {
284
+ sessionMetaListenerMetas.removeListener(this.sendSessionStartEvent);
285
+ }
286
+ this.notifiedSession = undefined;
287
+ this.isResettingSession = false;
130
288
  }
131
289
  }
290
+ destroy() {
291
+ this.unpatch();
292
+ }
132
293
  }
133
294
  //# sourceMappingURL=index.js.map