@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistentSessionRecord.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/persistentSessionRecord.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FaroUserSession } from './types';\n\nexport const PERSISTED_SESSION_SCHEMA_VERSION = 1;\n\ntype PersistedSessionRecord = {\n schemaVersion: typeof PERSISTED_SESSION_SCHEMA_VERSION;\n currentSessionId: string;\n previousSessionId: string | null;\n startedAt: number;\n lastActivityAt: number;\n isSampled: boolean;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value != null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isValidTimestamp(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n\nexport function serializePersistentSession(session: FaroUserSession): string {\n const previousSession = session.sessionMeta?.attributes?.['previousSession'];\n const record: PersistedSessionRecord = {\n schemaVersion: PERSISTED_SESSION_SCHEMA_VERSION,\n currentSessionId: session.sessionId,\n previousSessionId: isNonEmptyString(previousSession) ? previousSession : null,\n startedAt: session.started,\n // Preserve the record invariant if the device clock moves backwards after\n // the session starts.\n lastActivityAt: Math.max(session.started, session.lastActivity),\n isSampled: session.isSampled,\n };\n\n return JSON.stringify(record);\n}\n\nexport function parsePersistentSession(serialized: string): FaroUserSession | null {\n let value: unknown;\n try {\n value = JSON.parse(serialized);\n } catch {\n return null;\n }\n\n if (\n !isRecord(value) ||\n value['schemaVersion'] !== PERSISTED_SESSION_SCHEMA_VERSION ||\n !isNonEmptyString(value['currentSessionId']) ||\n !(value['previousSessionId'] === null || isNonEmptyString(value['previousSessionId'])) ||\n !isValidTimestamp(value['startedAt']) ||\n !isValidTimestamp(value['lastActivityAt']) ||\n value['lastActivityAt'] < value['startedAt'] ||\n typeof value['isSampled'] !== 'boolean'\n ) {\n return null;\n }\n\n const previousSessionId = value['previousSessionId'];\n\n return {\n sessionId: value['currentSessionId'],\n started: value['startedAt'],\n lastActivity: value['lastActivityAt'],\n isSampled: value['isSampled'],\n sessionMeta: {\n id: value['currentSessionId'],\n attributes: {\n isSampled: value['isSampled'].toString(),\n ...(previousSessionId == null ? {} : { previousSession: previousSessionId }),\n },\n },\n };\n}\n"]}
@@ -8,7 +8,7 @@ exports.MAX_SESSION_PERSISTENCE_TIME = DEFAULT_SESSION_PERSISTENCE_MS;
8
8
  /** React Native session defaults */
9
9
  exports.defaultSessionTrackingConfig = {
10
10
  enabled: true,
11
- persistent: false,
11
+ persistent: true,
12
12
  maxSessionPersistenceTime: exports.MAX_SESSION_PERSISTENCE_TIME,
13
13
  };
14
14
  //# sourceMappingURL=sessionConstants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionConstants.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/sessionConstants.ts"],"names":[],"mappings":";;;AAEa,QAAA,WAAW,GAAG,0BAA0B,CAAC;AACzC,QAAA,oBAAoB,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW;AAEzD,IAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AACvD,QAAA,4BAA4B,GAAG,8BAA8B,CAAC;AAE3E,oCAAoC;AACvB,QAAA,4BAA4B,GAAqC;IAC5E,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,KAAK;IACjB,yBAAyB,EAAE,oCAA4B;CACxD,CAAC","sourcesContent":["import type { ReactNativeSessionTrackingConfig } from '../../../config/types';\n\nexport const STORAGE_KEY = 'com.grafana.faro.session';\nexport const STORAGE_UPDATE_DELAY = 1 * 1000; // 1 second\n\nconst DEFAULT_SESSION_PERSISTENCE_MS = 15 * 60 * 1000; // 15 minutes\nexport const MAX_SESSION_PERSISTENCE_TIME = DEFAULT_SESSION_PERSISTENCE_MS;\n\n/** React Native session defaults */\nexport const defaultSessionTrackingConfig: ReactNativeSessionTrackingConfig = {\n enabled: true,\n persistent: false,\n maxSessionPersistenceTime: MAX_SESSION_PERSISTENCE_TIME,\n};\n"]}
1
+ {"version":3,"file":"sessionConstants.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/sessionConstants.ts"],"names":[],"mappings":";;;AAEa,QAAA,WAAW,GAAG,0BAA0B,CAAC;AACzC,QAAA,oBAAoB,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW;AAEzD,IAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AACvD,QAAA,4BAA4B,GAAG,8BAA8B,CAAC;AAE3E,oCAAoC;AACvB,QAAA,4BAA4B,GAAqC;IAC5E,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,IAAI;IAChB,yBAAyB,EAAE,oCAA4B;CACxD,CAAC","sourcesContent":["import type { ReactNativeSessionTrackingConfig } from '../../../config/types';\n\nexport const STORAGE_KEY = 'com.grafana.faro.session';\nexport const STORAGE_UPDATE_DELAY = 1 * 1000; // 1 second\n\nconst DEFAULT_SESSION_PERSISTENCE_MS = 15 * 60 * 1000; // 15 minutes\nexport const MAX_SESSION_PERSISTENCE_TIME = DEFAULT_SESSION_PERSISTENCE_MS;\n\n/** React Native session defaults */\nexport const defaultSessionTrackingConfig: ReactNativeSessionTrackingConfig = {\n enabled: true,\n persistent: true,\n maxSessionPersistenceTime: MAX_SESSION_PERSISTENCE_TIME,\n};\n"]}
@@ -10,49 +10,16 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
- return new (P || (P = Promise))(function (resolve, reject) {
16
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
- step((generator = generator.apply(thisArg, _arguments || [])).next());
20
- });
21
- };
22
- var __generator = (this && this.__generator) || function (thisArg, body) {
23
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
- function verb(n) { return function (v) { return step([n, v]); }; }
26
- function step(op) {
27
- if (f) throw new TypeError("Generator is already executing.");
28
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
- if (y = 0, t) op = [op[0] & 2, t.value];
31
- switch (op[0]) {
32
- case 0: case 1: t = op; break;
33
- case 4: _.label++; return { value: op[1], done: false };
34
- case 5: _.label++; y = op[1]; op = [0]; continue;
35
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
- default:
37
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
- if (t[2]) _.ops.pop();
42
- _.trys.pop(); continue;
43
- }
44
- op = body.call(thisArg, _);
45
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
- }
48
- };
49
13
  Object.defineProperty(exports, "__esModule", { value: true });
50
14
  exports.createUserSessionObject = createUserSessionObject;
51
15
  exports.isUserSessionValid = isUserSessionValid;
16
+ exports.getUserSessionResetter = getUserSessionResetter;
52
17
  exports.getUserSessionUpdater = getUserSessionUpdater;
18
+ exports.toStorableSessionMeta = toStorableSessionMeta;
53
19
  exports.addSessionMetadataToNextSession = addSessionMetadataToNextSession;
54
20
  exports.getSessionMetaUpdateHandler = getSessionMetaUpdateHandler;
55
21
  var faro_core_1 = require("@grafana/faro-core");
22
+ var sessionActivity_1 = require("../sessionActivity");
56
23
  var sampling_1 = require("./sampling");
57
24
  var sessionConstants_1 = require("./sessionConstants");
58
25
  var DEFAULT_SESSION_EXPIRATION_MS = 4 * 60 * 60 * 1000; // 4 hours (not in faro-core)
@@ -85,47 +52,86 @@ function isUserSessionValid(session) {
85
52
  }
86
53
  var _a = getSessionTimeouts(), sessionExpirationTime = _a.sessionExpirationTime, inactivityTimeout = _a.inactivityTimeout;
87
54
  var now = (0, faro_core_1.dateNow)();
55
+ if (session.started > now || session.lastActivity > now) {
56
+ return false;
57
+ }
88
58
  var lifetimeValid = now - session.started < sessionExpirationTime;
89
59
  if (!lifetimeValid) {
90
60
  return false;
91
61
  }
62
+ // The session expires at the boundary, matching cold-start record cleanup.
92
63
  var inactivityPeriodValid = now - session.lastActivity < inactivityTimeout;
93
64
  return inactivityPeriodValid;
94
65
  }
95
- function getUserSessionUpdater(_a) {
66
+ function notifySessionChange(previousSession, newSession) {
67
+ var _a, _b, _c, _d, _e;
68
+ var newSessionMeta = newSession.sessionMeta;
69
+ if (newSessionMeta == null) {
70
+ return;
71
+ }
72
+ try {
73
+ (_b = (_a = faro_core_1.faro.config.sessionTracking) === null || _a === void 0 ? void 0 : _a.onSessionChange) === null || _b === void 0 ? void 0 : _b.call(_a, (_c = previousSession === null || previousSession === void 0 ? void 0 : previousSession.sessionMeta) !== null && _c !== void 0 ? _c : null, newSessionMeta);
74
+ }
75
+ catch (error) {
76
+ (_e = (_d = faro_core_1.faro.unpatchedConsole) === null || _d === void 0 ? void 0 : _d.warn) === null || _e === void 0 ? void 0 : _e.call(_d, 'The session change callback failed:', error);
77
+ }
78
+ }
79
+ function createNextUserSession(previousSession) {
80
+ var _a, _b;
81
+ var nextSession = createUserSessionObject({ isSampled: (0, sampling_1.isSampled)() });
82
+ if (previousSession != null && nextSession.sessionId === previousSession.sessionId) {
83
+ (_b = (_a = faro_core_1.faro.unpatchedConsole) === null || _a === void 0 ? void 0 : _a.warn) === null || _b === void 0 ? void 0 : _b.call(_a, 'The session ID generator returned the current session ID; using a generated fallback for the new session.');
84
+ nextSession.sessionId = (0, faro_core_1.genShortID)();
85
+ }
86
+ return addSessionMetadataToNextSession(nextSession, previousSession);
87
+ }
88
+ function startNextUserSession(previousSession, storeUserSession) {
89
+ var _a;
90
+ var nextSession = createNextUserSession(previousSession);
91
+ storeUserSession(nextSession);
92
+ // setSession synchronously emits session_start through the meta listener.
93
+ // The transport hook then resumes and attributes the triggering item to
94
+ // the newly stored session.
95
+ (_a = faro_core_1.faro.api) === null || _a === void 0 ? void 0 : _a.setSession(nextSession.sessionMeta);
96
+ notifySessionChange(previousSession, nextSession);
97
+ return nextSession;
98
+ }
99
+ function getUserSessionResetter(_a) {
96
100
  var fetchUserSession = _a.fetchUserSession, storeUserSession = _a.storeUserSession;
97
- return function updateSession() {
98
- return __awaiter(this, void 0, void 0, function () {
99
- var sessionFromStorage, newSession;
100
- var _a, _b, _c, _d;
101
- return __generator(this, function (_e) {
102
- switch (_e.label) {
103
- case 0:
104
- if (!fetchUserSession || !storeUserSession) {
105
- return [2 /*return*/];
106
- }
107
- return [4 /*yield*/, fetchUserSession()];
108
- case 1:
109
- sessionFromStorage = _e.sent();
110
- if (!isUserSessionValid(sessionFromStorage)) return [3 /*break*/, 3];
111
- return [4 /*yield*/, storeUserSession(__assign(__assign({}, sessionFromStorage), { lastActivity: (0, faro_core_1.dateNow)() }))];
112
- case 2:
113
- _e.sent();
114
- return [3 /*break*/, 5];
115
- case 3:
116
- newSession = addSessionMetadataToNextSession(createUserSessionObject({ isSampled: (0, sampling_1.isSampled)() }), sessionFromStorage);
117
- return [4 /*yield*/, storeUserSession(newSession)];
118
- case 4:
119
- _e.sent();
120
- (_a = faro_core_1.faro.api) === null || _a === void 0 ? void 0 : _a.setSession(newSession.sessionMeta);
121
- (_c = (_b = faro_core_1.faro.config.sessionTracking) === null || _b === void 0 ? void 0 : _b.onSessionChange) === null || _c === void 0 ? void 0 : _c.call(_b, (_d = sessionFromStorage === null || sessionFromStorage === void 0 ? void 0 : sessionFromStorage.sessionMeta) !== null && _d !== void 0 ? _d : null, newSession.sessionMeta);
122
- _e.label = 5;
123
- case 5: return [2 /*return*/];
101
+ return function resetSession() {
102
+ return startNextUserSession(fetchUserSession(), storeUserSession);
103
+ };
104
+ }
105
+ function getUserSessionUpdater(_a) {
106
+ var fetchUserSession = _a.fetchUserSession, recordUserSessionActivity = _a.recordUserSessionActivity, storeUserSession = _a.storeUserSession;
107
+ var persistActivity = recordUserSessionActivity !== null && recordUserSessionActivity !== void 0 ? recordUserSessionActivity : storeUserSession;
108
+ return function updateSession(activity, currentSession) {
109
+ var sessionFromStorage = currentSession === undefined ? fetchUserSession() : currentSession;
110
+ if (sessionFromStorage != null && isUserSessionValid(sessionFromStorage)) {
111
+ if (activity === sessionActivity_1.SessionActivityKind.Meaningful) {
112
+ var lastActivity = (0, faro_core_1.dateNow)();
113
+ if (lastActivity === sessionFromStorage.lastActivity) {
114
+ return sessionFromStorage;
124
115
  }
125
- });
126
- });
116
+ var refreshedSession = __assign(__assign({}, sessionFromStorage), { lastActivity: lastActivity });
117
+ persistActivity(refreshedSession);
118
+ return refreshedSession;
119
+ }
120
+ return sessionFromStorage;
121
+ }
122
+ return startNextUserSession(sessionFromStorage, storeUserSession);
127
123
  };
128
124
  }
125
+ /**
126
+ * Persistent storage serializes sessions with `stringifyExternalJson`, which
127
+ * drops keys whose value is `undefined`. A session meta carrying such a key
128
+ * never compares `deepEqual` to its own stored copy, so the sync handler would
129
+ * rewrite the session on every meta notification. Applying the same round-trip
130
+ * in memory keeps the two sides comparable, for attributes and overrides alike.
131
+ */
132
+ function toStorableSessionMeta(sessionMeta) {
133
+ return JSON.parse((0, faro_core_1.stringifyExternalJson)(sessionMeta));
134
+ }
129
135
  function addSessionMetadataToNextSession(newSession, previousSession) {
130
136
  var _a, _b, _c, _d, _e, _f, _g;
131
137
  var sessionWithMeta = __assign(__assign({}, newSession), { sessionMeta: {
@@ -137,43 +143,65 @@ function addSessionMetadataToNextSession(newSession, previousSession) {
137
143
  sessionWithMeta.sessionMeta.overrides = overrides;
138
144
  }
139
145
  var previousSessionId = previousSession === null || previousSession === void 0 ? void 0 : previousSession.sessionId;
140
- if (previousSessionId != null) {
146
+ if (previousSessionId != null && previousSessionId !== newSession.sessionId) {
141
147
  sessionWithMeta.sessionMeta.attributes['previousSession'] = previousSessionId;
142
148
  }
149
+ // Normalize to what storage can hold, so the meta this session propagates
150
+ // stays comparable to its own stored copy.
151
+ sessionWithMeta.sessionMeta = toStorableSessionMeta(sessionWithMeta.sessionMeta);
143
152
  return sessionWithMeta;
144
153
  }
154
+ /**
155
+ * Set only while a handler applies its own session update.
156
+ *
157
+ * `setSession()` notifies meta listeners synchronously, so a handler is
158
+ * re-entered by its own write. This flag rejects that re-entry, and bounds any
159
+ * future divergence between the in-memory and the stored session to a single
160
+ * redundant write instead of an unbounded loop.
161
+ *
162
+ * Module scoped rather than per handler: more than one handler can be
163
+ * registered over the same storage, and per-handler flags would let them
164
+ * re-trigger one another through the echo notifications of their own writes.
165
+ */
166
+ var isApplyingOwnUpdate = false;
145
167
  function getSessionMetaUpdateHandler(_a) {
146
168
  var fetchUserSession = _a.fetchUserSession, storeUserSession = _a.storeUserSession;
147
169
  return function syncSessionIfChangedExternally(meta) {
148
- return __awaiter(this, void 0, void 0, function () {
149
- var session, sessionFromSessionStorage, sessionId, sessionAttributes, sessionOverrides, storedSessionMeta, storedSessionMetaOverrides, hasSessionOverridesChanged, hasAttributesChanged, hasSessionIdChanged, userSession;
150
- return __generator(this, function (_a) {
151
- switch (_a.label) {
152
- case 0:
153
- session = meta.session;
154
- return [4 /*yield*/, fetchUserSession()];
155
- case 1:
156
- sessionFromSessionStorage = _a.sent();
157
- sessionId = session === null || session === void 0 ? void 0 : session.id;
158
- sessionAttributes = session === null || session === void 0 ? void 0 : session.attributes;
159
- sessionOverrides = session === null || session === void 0 ? void 0 : session.overrides;
160
- storedSessionMeta = sessionFromSessionStorage === null || sessionFromSessionStorage === void 0 ? void 0 : sessionFromSessionStorage.sessionMeta;
161
- storedSessionMetaOverrides = storedSessionMeta === null || storedSessionMeta === void 0 ? void 0 : storedSessionMeta.overrides;
162
- hasSessionOverridesChanged = !!sessionOverrides && !(0, faro_core_1.deepEqual)(sessionOverrides, storedSessionMetaOverrides);
163
- hasAttributesChanged = !!sessionAttributes && !(0, faro_core_1.deepEqual)(sessionAttributes, storedSessionMeta === null || storedSessionMeta === void 0 ? void 0 : storedSessionMeta.attributes);
164
- hasSessionIdChanged = !!session && sessionId !== (sessionFromSessionStorage === null || sessionFromSessionStorage === void 0 ? void 0 : sessionFromSessionStorage.sessionId);
165
- if (!(hasSessionIdChanged || hasAttributesChanged || hasSessionOverridesChanged)) return [3 /*break*/, 3];
166
- userSession = addSessionMetadataToNextSession(createUserSessionObject({ sessionId: sessionId, isSampled: (0, sampling_1.isSampled)() }), sessionFromSessionStorage);
167
- return [4 /*yield*/, storeUserSession(userSession)];
168
- case 2:
169
- _a.sent();
170
- sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionMetaOverrides);
171
- faro_core_1.faro.api.setSession(userSession.sessionMeta);
172
- _a.label = 3;
173
- case 3: return [2 /*return*/];
174
- }
175
- });
176
- });
170
+ var _a, _b, _c;
171
+ // Checked before work begins so the synchronous re-entry triggered by
172
+ // `setSession()` below is rejected while the flag is still set.
173
+ if (isApplyingOwnUpdate) {
174
+ return;
175
+ }
176
+ // Compare in storable form, so keys that storage cannot hold do not read
177
+ // as a perpetual external change.
178
+ var session = meta.session && toStorableSessionMeta(meta.session);
179
+ var sessionFromSessionStorage = fetchUserSession();
180
+ var sessionId = session === null || session === void 0 ? void 0 : session.id;
181
+ var sessionAttributes = session === null || session === void 0 ? void 0 : session.attributes;
182
+ var sessionOverrides = session === null || session === void 0 ? void 0 : session.overrides;
183
+ var storedSessionMeta = sessionFromSessionStorage === null || sessionFromSessionStorage === void 0 ? void 0 : sessionFromSessionStorage.sessionMeta;
184
+ var storedSessionMetaOverrides = storedSessionMeta === null || storedSessionMeta === void 0 ? void 0 : storedSessionMeta.overrides;
185
+ var hasSessionOverridesChanged = sessionOverrides != null && !(0, faro_core_1.deepEqual)(sessionOverrides, storedSessionMetaOverrides);
186
+ var hasSessionMetaChanged = session != null &&
187
+ (!(0, faro_core_1.deepEqual)(sessionAttributes, storedSessionMeta === null || storedSessionMeta === void 0 ? void 0 : storedSessionMeta.attributes) ||
188
+ !(0, faro_core_1.deepEqual)(sessionOverrides, storedSessionMetaOverrides));
189
+ var hasSessionIdChanged = !!session && sessionId !== (sessionFromSessionStorage === null || sessionFromSessionStorage === void 0 ? void 0 : sessionFromSessionStorage.sessionId);
190
+ if (hasSessionIdChanged || hasSessionMetaChanged) {
191
+ var storedPreviousSessionId = (_a = storedSessionMeta === null || storedSessionMeta === void 0 ? void 0 : storedSessionMeta.attributes) === null || _a === void 0 ? void 0 : _a['previousSession'];
192
+ var effectiveOverrides = sessionOverrides !== null && sessionOverrides !== void 0 ? sessionOverrides : storedSessionMetaOverrides;
193
+ var userSession = sessionFromSessionStorage != null && !hasSessionIdChanged
194
+ ? __assign(__assign({}, sessionFromSessionStorage), { sessionMeta: toStorableSessionMeta(__assign({ id: sessionFromSessionStorage.sessionId, attributes: __assign(__assign(__assign(__assign({}, (_c = (_b = faro_core_1.faro.config.sessionTracking) === null || _b === void 0 ? void 0 : _b.session) === null || _c === void 0 ? void 0 : _c.attributes), (sessionAttributes !== null && sessionAttributes !== void 0 ? sessionAttributes : storedSessionMeta === null || storedSessionMeta === void 0 ? void 0 : storedSessionMeta.attributes)), (storedPreviousSessionId == null ? {} : { previousSession: storedPreviousSessionId })), { isSampled: sessionFromSessionStorage.isSampled.toString() }) }, ((0, faro_core_1.isEmpty)(effectiveOverrides) ? {} : { overrides: effectiveOverrides }))) }) : addSessionMetadataToNextSession(createUserSessionObject({ sessionId: sessionId, isSampled: (0, sampling_1.isSampled)() }), sessionFromSessionStorage);
195
+ storeUserSession(userSession);
196
+ sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionMetaOverrides);
197
+ isApplyingOwnUpdate = true;
198
+ try {
199
+ faro_core_1.faro.api.setSession(userSession.sessionMeta);
200
+ }
201
+ finally {
202
+ isApplyingOwnUpdate = false;
203
+ }
204
+ }
177
205
  };
178
206
  }
179
207
  function sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionOverrides) {
@@ -1 +1 @@
1
- {"version":3,"file":"sessionManagerUtils.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/sessionManagerUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import { dateNow, deepEqual, EVENT_OVERRIDES_SERVICE_NAME, faro, genShortID, isEmpty } from '@grafana/faro-core';\nimport type { Meta, MetaOverrides } from '@grafana/faro-core';\n\nimport { isSampled } from './sampling';\nimport { MAX_SESSION_PERSISTENCE_TIME } from './sessionConstants';\nimport type { FaroUserSession } from './types';\n\nconst DEFAULT_SESSION_EXPIRATION_MS = 4 * 60 * 60 * 1000; // 4 hours (not in faro-core)\n\nfunction getSessionTimeouts(): {\n sessionExpirationTime: number;\n inactivityTimeout: number;\n} {\n const inactivityTimeout = faro.config?.sessionTracking?.maxSessionPersistenceTime ?? MAX_SESSION_PERSISTENCE_TIME;\n return {\n sessionExpirationTime: DEFAULT_SESSION_EXPIRATION_MS,\n inactivityTimeout,\n };\n}\n\ntype CreateUserSessionObjectParams = {\n sessionId?: string;\n started?: number;\n lastActivity?: number;\n isSampled?: boolean;\n};\n\nexport function createUserSessionObject({\n sessionId,\n started,\n lastActivity,\n isSampled: sampledValue = true,\n}: CreateUserSessionObjectParams = {}): FaroUserSession {\n const now = dateNow();\n\n const generateSessionId = faro.config?.sessionTracking?.generateSessionId;\n\n if (sessionId == null) {\n sessionId = typeof generateSessionId === 'function' ? generateSessionId() : genShortID();\n }\n\n return {\n sessionId,\n lastActivity: lastActivity ?? now,\n started: started ?? now,\n isSampled: sampledValue,\n };\n}\n\nexport function isUserSessionValid(session: FaroUserSession | null): boolean {\n if (session == null) {\n return false;\n }\n\n const { sessionExpirationTime, inactivityTimeout } = getSessionTimeouts();\n const now = dateNow();\n const lifetimeValid = now - session.started < sessionExpirationTime;\n\n if (!lifetimeValid) {\n return false;\n }\n\n const inactivityPeriodValid = now - session.lastActivity < inactivityTimeout;\n return inactivityPeriodValid;\n}\n\ntype GetUserSessionUpdaterParams = {\n storeUserSession: (session: FaroUserSession) => void | Promise<void>;\n fetchUserSession: () => FaroUserSession | null | Promise<FaroUserSession | null>;\n};\n\nexport function getUserSessionUpdater({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionUpdaterParams): () => Promise<void> {\n return async function updateSession(): Promise<void> {\n if (!fetchUserSession || !storeUserSession) {\n return;\n }\n\n const sessionFromStorage = await fetchUserSession();\n\n if (isUserSessionValid(sessionFromStorage)) {\n await storeUserSession({ ...sessionFromStorage!, lastActivity: dateNow() });\n } else {\n let newSession = addSessionMetadataToNextSession(\n createUserSessionObject({ isSampled: isSampled() }),\n sessionFromStorage\n );\n\n await storeUserSession(newSession);\n\n faro.api?.setSession(newSession.sessionMeta);\n faro.config.sessionTracking?.onSessionChange?.(sessionFromStorage?.sessionMeta ?? null, newSession.sessionMeta!);\n }\n };\n}\n\nexport function addSessionMetadataToNextSession(newSession: FaroUserSession, previousSession: FaroUserSession | null) {\n const sessionWithMeta: Required<FaroUserSession> = {\n ...newSession,\n sessionMeta: {\n id: newSession.sessionId,\n attributes: {\n ...faro.config.sessionTracking?.session?.attributes,\n ...(faro.metas.value.session?.attributes ?? {}),\n isSampled: newSession.isSampled.toString(),\n },\n },\n };\n\n const overrides = faro.metas.value.session?.overrides ?? previousSession?.sessionMeta?.overrides;\n if (!isEmpty(overrides)) {\n sessionWithMeta.sessionMeta.overrides = overrides;\n }\n\n const previousSessionId = previousSession?.sessionId;\n if (previousSessionId != null) {\n sessionWithMeta.sessionMeta.attributes!['previousSession'] = previousSessionId;\n }\n\n return sessionWithMeta;\n}\n\ntype GetUserSessionMetaUpdateHandlerParams = {\n storeUserSession: (session: FaroUserSession) => void | Promise<void>;\n fetchUserSession: () => FaroUserSession | null | Promise<FaroUserSession | null>;\n};\n\nexport function getSessionMetaUpdateHandler({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionMetaUpdateHandlerParams) {\n return async function syncSessionIfChangedExternally(meta: Meta) {\n const session = meta.session;\n const sessionFromSessionStorage = await fetchUserSession();\n\n let sessionId = session?.id;\n const sessionAttributes = session?.attributes;\n const sessionOverrides = session?.overrides;\n\n const storedSessionMeta = sessionFromSessionStorage?.sessionMeta;\n const storedSessionMetaOverrides = storedSessionMeta?.overrides;\n\n const hasSessionOverridesChanged = !!sessionOverrides && !deepEqual(sessionOverrides, storedSessionMetaOverrides);\n const hasAttributesChanged = !!sessionAttributes && !deepEqual(sessionAttributes, storedSessionMeta?.attributes);\n const hasSessionIdChanged = !!session && sessionId !== sessionFromSessionStorage?.sessionId;\n\n if (hasSessionIdChanged || hasAttributesChanged || hasSessionOverridesChanged) {\n const userSession = addSessionMetadataToNextSession(\n createUserSessionObject({ sessionId, isSampled: isSampled() }),\n sessionFromSessionStorage\n );\n\n await storeUserSession(userSession);\n sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionMetaOverrides);\n faro.api.setSession(userSession.sessionMeta);\n }\n };\n}\n\nfunction sendOverrideEvent(\n hasSessionOverridesChanged: boolean,\n sessionOverrides: MetaOverrides = {},\n storedSessionOverrides: MetaOverrides = {}\n) {\n if (!hasSessionOverridesChanged) {\n return;\n }\n\n const serviceName = sessionOverrides.serviceName;\n const previousServiceName = storedSessionOverrides.serviceName ?? faro.metas.value.app?.name ?? '';\n\n if (serviceName && serviceName !== previousServiceName) {\n faro.api.pushEvent(EVENT_OVERRIDES_SERVICE_NAME, {\n serviceName,\n previousServiceName,\n });\n }\n}\n"]}
1
+ {"version":3,"file":"sessionManagerUtils.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/sessionManagerUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n dateNow,\n deepEqual,\n EVENT_OVERRIDES_SERVICE_NAME,\n faro,\n genShortID,\n isEmpty,\n stringifyExternalJson,\n} from '@grafana/faro-core';\nimport type { Meta, MetaOverrides, MetaSession } from '@grafana/faro-core';\n\nimport { SessionActivityKind } from '../sessionActivity';\n\nimport { isSampled } from './sampling';\nimport { MAX_SESSION_PERSISTENCE_TIME } from './sessionConstants';\nimport type { FaroUserSession } from './types';\n\nconst DEFAULT_SESSION_EXPIRATION_MS = 4 * 60 * 60 * 1000; // 4 hours (not in faro-core)\n\nfunction getSessionTimeouts(): {\n sessionExpirationTime: number;\n inactivityTimeout: number;\n} {\n const inactivityTimeout = faro.config?.sessionTracking?.maxSessionPersistenceTime ?? MAX_SESSION_PERSISTENCE_TIME;\n return {\n sessionExpirationTime: DEFAULT_SESSION_EXPIRATION_MS,\n inactivityTimeout,\n };\n}\n\ntype CreateUserSessionObjectParams = {\n sessionId?: string;\n started?: number;\n lastActivity?: number;\n isSampled?: boolean;\n};\n\nexport function createUserSessionObject({\n sessionId,\n started,\n lastActivity,\n isSampled: sampledValue = true,\n}: CreateUserSessionObjectParams = {}): FaroUserSession {\n const now = dateNow();\n\n const generateSessionId = faro.config?.sessionTracking?.generateSessionId;\n\n if (sessionId == null) {\n sessionId = typeof generateSessionId === 'function' ? generateSessionId() : genShortID();\n }\n\n return {\n sessionId,\n lastActivity: lastActivity ?? now,\n started: started ?? now,\n isSampled: sampledValue,\n };\n}\n\nexport function isUserSessionValid(session: FaroUserSession | null): boolean {\n if (session == null) {\n return false;\n }\n\n const { sessionExpirationTime, inactivityTimeout } = getSessionTimeouts();\n const now = dateNow();\n if (session.started > now || session.lastActivity > now) {\n return false;\n }\n\n const lifetimeValid = now - session.started < sessionExpirationTime;\n\n if (!lifetimeValid) {\n return false;\n }\n\n // The session expires at the boundary, matching cold-start record cleanup.\n const inactivityPeriodValid = now - session.lastActivity < inactivityTimeout;\n return inactivityPeriodValid;\n}\n\ntype GetUserSessionUpdaterParams = {\n storeUserSession: (session: FaroUserSession) => void;\n fetchUserSession: () => FaroUserSession | null;\n recordUserSessionActivity?: (session: FaroUserSession) => void;\n};\n\ntype GetUserSessionResetterParams = Pick<GetUserSessionUpdaterParams, 'fetchUserSession' | 'storeUserSession'>;\n\nfunction notifySessionChange(previousSession: FaroUserSession | null, newSession: FaroUserSession): void {\n const newSessionMeta = newSession.sessionMeta;\n if (newSessionMeta == null) {\n return;\n }\n\n try {\n faro.config.sessionTracking?.onSessionChange?.(previousSession?.sessionMeta ?? null, newSessionMeta);\n } catch (error) {\n faro.unpatchedConsole?.warn?.('The session change callback failed:', error);\n }\n}\n\nfunction createNextUserSession(previousSession: FaroUserSession | null): FaroUserSession {\n const nextSession = createUserSessionObject({ isSampled: isSampled() });\n\n if (previousSession != null && nextSession.sessionId === previousSession.sessionId) {\n faro.unpatchedConsole?.warn?.(\n 'The session ID generator returned the current session ID; using a generated fallback for the new session.'\n );\n nextSession.sessionId = genShortID();\n }\n\n return addSessionMetadataToNextSession(nextSession, previousSession);\n}\n\nfunction startNextUserSession(\n previousSession: FaroUserSession | null,\n storeUserSession: (session: FaroUserSession) => void\n): FaroUserSession {\n const nextSession = createNextUserSession(previousSession);\n\n storeUserSession(nextSession);\n // setSession synchronously emits session_start through the meta listener.\n // The transport hook then resumes and attributes the triggering item to\n // the newly stored session.\n faro.api?.setSession(nextSession.sessionMeta);\n notifySessionChange(previousSession, nextSession);\n\n return nextSession;\n}\n\nexport function getUserSessionResetter({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionResetterParams): () => FaroUserSession {\n return function resetSession(): FaroUserSession {\n return startNextUserSession(fetchUserSession(), storeUserSession);\n };\n}\n\nexport function getUserSessionUpdater({\n fetchUserSession,\n recordUserSessionActivity,\n storeUserSession,\n}: GetUserSessionUpdaterParams): (\n activity: SessionActivityKind,\n currentSession?: FaroUserSession | null\n) => FaroUserSession {\n const persistActivity = recordUserSessionActivity ?? storeUserSession;\n\n return function updateSession(\n activity: SessionActivityKind,\n currentSession?: FaroUserSession | null\n ): FaroUserSession {\n const sessionFromStorage = currentSession === undefined ? fetchUserSession() : currentSession;\n\n if (sessionFromStorage != null && isUserSessionValid(sessionFromStorage)) {\n if (activity === SessionActivityKind.Meaningful) {\n const lastActivity = dateNow();\n if (lastActivity === sessionFromStorage.lastActivity) {\n return sessionFromStorage;\n }\n\n const refreshedSession = { ...sessionFromStorage, lastActivity };\n persistActivity(refreshedSession);\n return refreshedSession;\n }\n\n return sessionFromStorage;\n }\n\n return startNextUserSession(sessionFromStorage, storeUserSession);\n };\n}\n\n/**\n * Persistent storage serializes sessions with `stringifyExternalJson`, which\n * drops keys whose value is `undefined`. A session meta carrying such a key\n * never compares `deepEqual` to its own stored copy, so the sync handler would\n * rewrite the session on every meta notification. Applying the same round-trip\n * in memory keeps the two sides comparable, for attributes and overrides alike.\n */\nexport function toStorableSessionMeta(sessionMeta: MetaSession): MetaSession {\n return JSON.parse(stringifyExternalJson(sessionMeta)) as MetaSession;\n}\n\nexport function addSessionMetadataToNextSession(newSession: FaroUserSession, previousSession: FaroUserSession | null) {\n const sessionWithMeta: Required<FaroUserSession> = {\n ...newSession,\n sessionMeta: {\n id: newSession.sessionId,\n attributes: {\n ...faro.config.sessionTracking?.session?.attributes,\n ...(faro.metas.value.session?.attributes ?? {}),\n isSampled: newSession.isSampled.toString(),\n },\n },\n };\n\n const overrides = faro.metas.value.session?.overrides ?? previousSession?.sessionMeta?.overrides;\n if (!isEmpty(overrides)) {\n sessionWithMeta.sessionMeta.overrides = overrides;\n }\n\n const previousSessionId = previousSession?.sessionId;\n if (previousSessionId != null && previousSessionId !== newSession.sessionId) {\n sessionWithMeta.sessionMeta.attributes!['previousSession'] = previousSessionId;\n }\n\n // Normalize to what storage can hold, so the meta this session propagates\n // stays comparable to its own stored copy.\n sessionWithMeta.sessionMeta = toStorableSessionMeta(sessionWithMeta.sessionMeta);\n\n return sessionWithMeta;\n}\n\ntype GetUserSessionMetaUpdateHandlerParams = {\n storeUserSession: (session: FaroUserSession) => void;\n fetchUserSession: () => FaroUserSession | null;\n};\n\n/**\n * Set only while a handler applies its own session update.\n *\n * `setSession()` notifies meta listeners synchronously, so a handler is\n * re-entered by its own write. This flag rejects that re-entry, and bounds any\n * future divergence between the in-memory and the stored session to a single\n * redundant write instead of an unbounded loop.\n *\n * Module scoped rather than per handler: more than one handler can be\n * registered over the same storage, and per-handler flags would let them\n * re-trigger one another through the echo notifications of their own writes.\n */\nlet isApplyingOwnUpdate = false;\n\nexport function getSessionMetaUpdateHandler({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionMetaUpdateHandlerParams) {\n return function syncSessionIfChangedExternally(meta: Meta): void {\n // Checked before work begins so the synchronous re-entry triggered by\n // `setSession()` below is rejected while the flag is still set.\n if (isApplyingOwnUpdate) {\n return;\n }\n\n // Compare in storable form, so keys that storage cannot hold do not read\n // as a perpetual external change.\n const session = meta.session && toStorableSessionMeta(meta.session);\n const sessionFromSessionStorage = fetchUserSession();\n\n let sessionId = session?.id;\n const sessionAttributes = session?.attributes;\n const sessionOverrides = session?.overrides;\n\n const storedSessionMeta = sessionFromSessionStorage?.sessionMeta;\n const storedSessionMetaOverrides = storedSessionMeta?.overrides;\n\n const hasSessionOverridesChanged =\n sessionOverrides != null && !deepEqual(sessionOverrides, storedSessionMetaOverrides);\n const hasSessionMetaChanged =\n session != null &&\n (!deepEqual(sessionAttributes, storedSessionMeta?.attributes) ||\n !deepEqual(sessionOverrides, storedSessionMetaOverrides));\n const hasSessionIdChanged = !!session && sessionId !== sessionFromSessionStorage?.sessionId;\n\n if (hasSessionIdChanged || hasSessionMetaChanged) {\n const storedPreviousSessionId = storedSessionMeta?.attributes?.['previousSession'];\n const effectiveOverrides = sessionOverrides ?? storedSessionMetaOverrides;\n const userSession =\n sessionFromSessionStorage != null && !hasSessionIdChanged\n ? {\n ...sessionFromSessionStorage,\n sessionMeta: toStorableSessionMeta({\n id: sessionFromSessionStorage.sessionId,\n attributes: {\n ...faro.config.sessionTracking?.session?.attributes,\n ...(sessionAttributes ?? storedSessionMeta?.attributes),\n ...(storedPreviousSessionId == null ? {} : { previousSession: storedPreviousSessionId }),\n isSampled: sessionFromSessionStorage.isSampled.toString(),\n },\n ...(isEmpty(effectiveOverrides) ? {} : { overrides: effectiveOverrides }),\n }),\n }\n : addSessionMetadataToNextSession(\n createUserSessionObject({ sessionId, isSampled: isSampled() }),\n sessionFromSessionStorage\n );\n\n storeUserSession(userSession);\n sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionMetaOverrides);\n\n isApplyingOwnUpdate = true;\n try {\n faro.api.setSession(userSession.sessionMeta);\n } finally {\n isApplyingOwnUpdate = false;\n }\n }\n };\n}\n\nfunction sendOverrideEvent(\n hasSessionOverridesChanged: boolean,\n sessionOverrides: MetaOverrides = {},\n storedSessionOverrides: MetaOverrides = {}\n) {\n if (!hasSessionOverridesChanged) {\n return;\n }\n\n const serviceName = sessionOverrides.serviceName;\n const previousServiceName = storedSessionOverrides.serviceName ?? faro.metas.value.app?.name ?? '';\n\n if (serviceName && serviceName !== previousServiceName) {\n faro.api.pushEvent(EVENT_OVERRIDES_SERVICE_NAME, {\n serviceName,\n previousServiceName,\n });\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { MetaSession } from '@grafana/faro-core';\n\nimport type { MmkvPersistentSessionsManager } from './MmkvPersistentSessionsManager';\nimport type { VolatileSessionsManager } from './VolatileSessionManager';\n\nexport interface FaroUserSession {\n sessionId: string;\n lastActivity: number;\n started: number;\n isSampled: boolean;\n sessionMeta?: MetaSession;\n}\n\nexport type SessionManager = typeof VolatileSessionsManager | typeof MmkvPersistentSessionsManager;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { MetaSession } from '@grafana/faro-core';\n\nimport type { SessionActivityKind } from '../sessionActivity';\n\nimport type { MmkvPersistentSessionsManager } from './MmkvPersistentSessionsManager';\nimport type { VolatileSessionsManager } from './VolatileSessionManager';\n\nexport interface FaroUserSession {\n sessionId: string;\n lastActivity: number;\n started: number;\n isSampled: boolean;\n sessionMeta?: MetaSession;\n}\n\nexport type SessionManager = typeof VolatileSessionsManager | typeof MmkvPersistentSessionsManager;\n\nexport interface SessionManagerInstance {\n checkSession(activity: SessionActivityKind, currentSession?: FaroUserSession | null): FaroUserSession;\n resetSession(): FaroUserSession;\n unpatch(): void;\n}\n"]}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAIN_PROCESS_SESSION_STORAGE_ID = void 0;
4
+ exports.getSessionProcessInfo = getSessionProcessInfo;
5
+ exports.claimSessionPersistenceStorageId = claimSessionPersistenceStorageId;
6
+ exports.releaseSessionPersistenceOwnership = releaseSessionPersistenceOwnership;
7
+ exports.resetSessionProcessForTests = resetSessionProcessForTests;
8
+ var react_native_1 = require("react-native");
9
+ exports.MAIN_PROCESS_SESSION_STORAGE_ID = 'grafana-faro-react-native-session';
10
+ var cachedProcessInfo;
11
+ var cachedPersistenceOwnership;
12
+ function getNativeModule() {
13
+ return react_native_1.NativeModules['FaroReactNativeModule'];
14
+ }
15
+ /** Stable native process identity used to isolate session chains. */
16
+ function getSessionProcessInfo() {
17
+ if (cachedProcessInfo !== undefined) {
18
+ return cachedProcessInfo;
19
+ }
20
+ var nativeModule = getNativeModule();
21
+ if (typeof (nativeModule === null || nativeModule === void 0 ? void 0 : nativeModule.getSessionProcessIdentifier) !== 'function' ||
22
+ typeof nativeModule.isMainSessionProcess !== 'function') {
23
+ cachedProcessInfo = null;
24
+ return cachedProcessInfo;
25
+ }
26
+ try {
27
+ var identifier = nativeModule.getSessionProcessIdentifier();
28
+ var isMain = nativeModule.isMainSessionProcess();
29
+ if (typeof identifier !== 'string' || identifier.trim().length === 0 || typeof isMain !== 'boolean') {
30
+ cachedProcessInfo = null;
31
+ return cachedProcessInfo;
32
+ }
33
+ cachedProcessInfo = { identifier: identifier.trim(), isMain: isMain };
34
+ }
35
+ catch (_a) {
36
+ cachedProcessInfo = null;
37
+ }
38
+ return cachedProcessInfo;
39
+ }
40
+ /**
41
+ * Returns the MMKV instance ID owned by this runtime, or null when exclusive
42
+ * ownership cannot be established. The main process retains the legacy ID so
43
+ * upgrades keep their existing previous-session link.
44
+ */
45
+ function claimSessionPersistenceStorageId() {
46
+ var processInfo = getSessionProcessInfo();
47
+ if (processInfo == null) {
48
+ return null;
49
+ }
50
+ if (cachedPersistenceOwnership === undefined) {
51
+ var nativeModule = getNativeModule();
52
+ if (typeof (nativeModule === null || nativeModule === void 0 ? void 0 : nativeModule.claimSessionPersistence) !== 'function') {
53
+ cachedPersistenceOwnership = false;
54
+ }
55
+ else {
56
+ try {
57
+ cachedPersistenceOwnership = nativeModule.claimSessionPersistence() === true;
58
+ }
59
+ catch (_a) {
60
+ cachedPersistenceOwnership = false;
61
+ }
62
+ }
63
+ }
64
+ if (!cachedPersistenceOwnership) {
65
+ return null;
66
+ }
67
+ return processInfo.isMain
68
+ ? exports.MAIN_PROCESS_SESSION_STORAGE_ID
69
+ : "".concat(exports.MAIN_PROCESS_SESSION_STORAGE_ID, ".").concat(encodeURIComponent(processInfo.identifier));
70
+ }
71
+ /** Releases storage ownership after MMKV initialization fails. */
72
+ function releaseSessionPersistenceOwnership() {
73
+ if (cachedPersistenceOwnership !== true) {
74
+ return false;
75
+ }
76
+ var nativeModule = getNativeModule();
77
+ if (typeof (nativeModule === null || nativeModule === void 0 ? void 0 : nativeModule.releaseSessionPersistence) !== 'function') {
78
+ return false;
79
+ }
80
+ try {
81
+ var released = nativeModule.releaseSessionPersistence() === true;
82
+ if (released) {
83
+ cachedPersistenceOwnership = undefined;
84
+ }
85
+ return released;
86
+ }
87
+ catch (_a) {
88
+ return false;
89
+ }
90
+ }
91
+ /** @internal */
92
+ function resetSessionProcessForTests() {
93
+ cachedProcessInfo = undefined;
94
+ cachedPersistenceOwnership = undefined;
95
+ }
96
+ //# sourceMappingURL=sessionProcess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionProcess.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/sessionProcess.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nexport const MAIN_PROCESS_SESSION_STORAGE_ID = 'grafana-faro-react-native-session';\n\ninterface SessionProcessNativeModule {\n claimSessionPersistence?: () => unknown;\n getSessionProcessIdentifier?: () => unknown;\n isMainSessionProcess?: () => unknown;\n releaseSessionPersistence?: () => unknown;\n}\n\nexport interface SessionProcessInfo {\n identifier: string;\n isMain: boolean;\n}\n\nlet cachedProcessInfo: SessionProcessInfo | null | undefined;\nlet cachedPersistenceOwnership: boolean | undefined;\n\nfunction getNativeModule(): SessionProcessNativeModule | undefined {\n return NativeModules['FaroReactNativeModule'] as SessionProcessNativeModule | undefined;\n}\n\n/** Stable native process identity used to isolate session chains. */\nexport function getSessionProcessInfo(): SessionProcessInfo | null {\n if (cachedProcessInfo !== undefined) {\n return cachedProcessInfo;\n }\n\n const nativeModule = getNativeModule();\n if (\n typeof nativeModule?.getSessionProcessIdentifier !== 'function' ||\n typeof nativeModule.isMainSessionProcess !== 'function'\n ) {\n cachedProcessInfo = null;\n return cachedProcessInfo;\n }\n\n try {\n const identifier = nativeModule.getSessionProcessIdentifier();\n const isMain = nativeModule.isMainSessionProcess();\n if (typeof identifier !== 'string' || identifier.trim().length === 0 || typeof isMain !== 'boolean') {\n cachedProcessInfo = null;\n return cachedProcessInfo;\n }\n\n cachedProcessInfo = { identifier: identifier.trim(), isMain };\n } catch {\n cachedProcessInfo = null;\n }\n\n return cachedProcessInfo;\n}\n\n/**\n * Returns the MMKV instance ID owned by this runtime, or null when exclusive\n * ownership cannot be established. The main process retains the legacy ID so\n * upgrades keep their existing previous-session link.\n */\nexport function claimSessionPersistenceStorageId(): string | null {\n const processInfo = getSessionProcessInfo();\n if (processInfo == null) {\n return null;\n }\n\n if (cachedPersistenceOwnership === undefined) {\n const nativeModule = getNativeModule();\n if (typeof nativeModule?.claimSessionPersistence !== 'function') {\n cachedPersistenceOwnership = false;\n } else {\n try {\n cachedPersistenceOwnership = nativeModule.claimSessionPersistence() === true;\n } catch {\n cachedPersistenceOwnership = false;\n }\n }\n }\n\n if (!cachedPersistenceOwnership) {\n return null;\n }\n\n return processInfo.isMain\n ? MAIN_PROCESS_SESSION_STORAGE_ID\n : `${MAIN_PROCESS_SESSION_STORAGE_ID}.${encodeURIComponent(processInfo.identifier)}`;\n}\n\n/** Releases storage ownership after MMKV initialization fails. */\nexport function releaseSessionPersistenceOwnership(): boolean {\n if (cachedPersistenceOwnership !== true) {\n return false;\n }\n\n const nativeModule = getNativeModule();\n if (typeof nativeModule?.releaseSessionPersistence !== 'function') {\n return false;\n }\n\n try {\n const released = nativeModule.releaseSessionPersistence() === true;\n if (released) {\n cachedPersistenceOwnership = undefined;\n }\n return released;\n } catch {\n return false;\n }\n}\n\n/** @internal */\nexport function resetSessionProcessForTests(): void {\n cachedProcessInfo = undefined;\n cachedPersistenceOwnership = undefined;\n}\n"]}
@@ -26,15 +26,12 @@ var FaroReactNativeModule = react_native_1.NativeModules.FaroReactNativeModule;
26
26
  * - iOS: sysctl() to query kernel for process start time
27
27
  * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)
28
28
  *
29
- * Implementation aligned with Faro Flutter SDK:
30
- * https://github.com/grafana/faro-flutter-sdk
31
- *
32
29
  * **Key Features**:
33
30
  * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!
34
31
  * - ✅ Cold start: appStartDuration from native, coldStart: 1
35
32
  * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0
36
33
  *
37
- * **Metrics Captured** (matches Flutter SDK format):
34
+ * **Metrics Captured**:
38
35
  * - Cold start: `appStartDuration`, `coldStart: 1`
39
36
  * - Warm start: `appStartDuration`, `coldStart: 0`
40
37
  *
@@ -74,7 +71,7 @@ var StartupInstrumentation = /** @class */ (function (_super) {
74
71
  };
75
72
  /**
76
73
  * Captures cold start duration from native (process start to Faro init).
77
- * Matches Flutter SDK: appStartDuration + coldStart: 1
74
+ * Sends appStartDuration with coldStart: 1.
78
75
  */
79
76
  StartupInstrumentation.prototype.captureColdStartMetrics = function () {
80
77
  var _this = this;
@@ -105,7 +102,6 @@ var StartupInstrumentation = /** @class */ (function (_super) {
105
102
  };
106
103
  /**
107
104
  * Tracks warm start: when app resumes from background, measures time to first frame.
108
- * Matches Flutter SDK: setWarmStart on resume, getWarmStart after postFrameCallback.
109
105
  */
110
106
  StartupInstrumentation.prototype.setupWarmStartTracking = function () {
111
107
  var _this = this;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/startup/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AppState, NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { StartupInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app startup time for both cold and warm starts.\n *\n * Uses native OS APIs for cold start and AppState for warm start:\n * - iOS: sysctl() to query kernel for process start time\n * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)\n *\n * Implementation aligned with Faro Flutter SDK:\n * https://github.com/grafana/faro-flutter-sdk\n *\n * **Key Features**:\n * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!\n * - ✅ Cold start: appStartDuration from native, coldStart: 1\n * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0\n *\n * **Metrics Captured** (matches Flutter SDK format):\n * - Cold start: `appStartDuration`, `coldStart: 1`\n * - Warm start: `appStartDuration`, `coldStart: 0`\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 24+ (Android 7.0 Nougat, ~99% of devices as of 2025)\n *\n * @example\n * ```tsx\n * import { initializeFaro } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * app: { name: 'my-app', version: '1.0.0' },\n * url: 'https://your-collector.com',\n * });\n * ```\n * StartupInstrumentation is included by default via makeRNConfig.\n */\nexport class StartupInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-startup';\n readonly version = VERSION;\n\n /** 0 = came from background (warm start eligible), null = never backgrounded */\n private warmStartTimestamp: number | null = null;\n private appStateSubscription: ReturnType<typeof AppState.addEventListener> | undefined;\n\n constructor(private options: StartupInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n if (this.options.enabled === false) {\n return;\n }\n\n this.captureColdStartMetrics();\n this.setupWarmStartTracking();\n }\n\n /**\n * Captures cold start duration from native (process start to Faro init).\n * Matches Flutter SDK: appStartDuration + coldStart: 1\n */\n private captureColdStartMetrics(): void {\n try {\n if (!FaroReactNativeModule?.getAppStartDuration) {\n this.logWarn(\n 'Native module not available. Startup instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n const appStartDuration = FaroReactNativeModule.getAppStartDuration();\n\n if (appStartDuration === 0) {\n this.logWarn(\n 'App startup duration is 0. This may indicate unsupported Android version (< API 24) ' +\n 'or an issue with the native module.'\n );\n return;\n }\n\n const values: Record<string, number> = {\n appStartDuration,\n coldStart: 1,\n };\n\n setTimeout(() => {\n this.api.pushMeasurement({ type: 'app_startup', values }, { skipDedupe: true });\n }, 100);\n\n this.logInfo(`Cold start metrics captured: ${appStartDuration}ms`);\n } catch (error) {\n this.logError('Failed to capture cold start metrics', error);\n }\n }\n\n /**\n * Tracks warm start: when app resumes from background, measures time to first frame.\n * Matches Flutter SDK: setWarmStart on resume, getWarmStart after postFrameCallback.\n */\n private setupWarmStartTracking(): void {\n this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {\n if (nextAppState === 'background' || nextAppState === 'inactive') {\n this.warmStartTimestamp = 0; // Sentinel: next 'active' is a warm start\n } else if (nextAppState === 'active' && this.warmStartTimestamp === 0) {\n // Came from background: measure time from resume to first frame\n const resumeTimestamp = Date.now();\n this.warmStartTimestamp = null;\n\n requestAnimationFrame(() => {\n const appStartDuration = Date.now() - resumeTimestamp;\n if (appStartDuration > 0) {\n this.api.pushMeasurement(\n {\n type: 'app_startup',\n values: { appStartDuration, coldStart: 0 },\n },\n { skipDedupe: true }\n );\n this.logInfo(`Warm start metrics captured: ${appStartDuration}ms`);\n }\n });\n }\n });\n }\n\n unpatch(): void {\n if (this.appStateSubscription) {\n this.appStateSubscription.remove();\n this.appStateSubscription = undefined;\n this.warmStartTimestamp = null;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/startup/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AppState, NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { StartupInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app startup time for both cold and warm starts.\n *\n * Uses native OS APIs for cold start and AppState for warm start:\n * - iOS: sysctl() to query kernel for process start time\n * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)\n *\n * **Key Features**:\n * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!\n * - ✅ Cold start: appStartDuration from native, coldStart: 1\n * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0\n *\n * **Metrics Captured**:\n * - Cold start: `appStartDuration`, `coldStart: 1`\n * - Warm start: `appStartDuration`, `coldStart: 0`\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 24+ (Android 7.0 Nougat, ~99% of devices as of 2025)\n *\n * @example\n * ```tsx\n * import { initializeFaro } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * app: { name: 'my-app', version: '1.0.0' },\n * url: 'https://your-collector.com',\n * });\n * ```\n * StartupInstrumentation is included by default via makeRNConfig.\n */\nexport class StartupInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-startup';\n readonly version = VERSION;\n\n /** 0 = came from background (warm start eligible), null = never backgrounded */\n private warmStartTimestamp: number | null = null;\n private appStateSubscription: ReturnType<typeof AppState.addEventListener> | undefined;\n\n constructor(private options: StartupInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n if (this.options.enabled === false) {\n return;\n }\n\n this.captureColdStartMetrics();\n this.setupWarmStartTracking();\n }\n\n /**\n * Captures cold start duration from native (process start to Faro init).\n * Sends appStartDuration with coldStart: 1.\n */\n private captureColdStartMetrics(): void {\n try {\n if (!FaroReactNativeModule?.getAppStartDuration) {\n this.logWarn(\n 'Native module not available. Startup instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n const appStartDuration = FaroReactNativeModule.getAppStartDuration();\n\n if (appStartDuration === 0) {\n this.logWarn(\n 'App startup duration is 0. This may indicate unsupported Android version (< API 24) ' +\n 'or an issue with the native module.'\n );\n return;\n }\n\n const values: Record<string, number> = {\n appStartDuration,\n coldStart: 1,\n };\n\n setTimeout(() => {\n this.api.pushMeasurement({ type: 'app_startup', values }, { skipDedupe: true });\n }, 100);\n\n this.logInfo(`Cold start metrics captured: ${appStartDuration}ms`);\n } catch (error) {\n this.logError('Failed to capture cold start metrics', error);\n }\n }\n\n /**\n * Tracks warm start: when app resumes from background, measures time to first frame.\n */\n private setupWarmStartTracking(): void {\n this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {\n if (nextAppState === 'background' || nextAppState === 'inactive') {\n this.warmStartTimestamp = 0; // Sentinel: next 'active' is a warm start\n } else if (nextAppState === 'active' && this.warmStartTimestamp === 0) {\n // Came from background: measure time from resume to first frame\n const resumeTimestamp = Date.now();\n this.warmStartTimestamp = null;\n\n requestAnimationFrame(() => {\n const appStartDuration = Date.now() - resumeTimestamp;\n if (appStartDuration > 0) {\n this.api.pushMeasurement(\n {\n type: 'app_startup',\n values: { appStartDuration, coldStart: 0 },\n },\n { skipDedupe: true }\n );\n this.logInfo(`Warm start metrics captured: ${appStartDuration}ms`);\n }\n });\n }\n });\n }\n\n unpatch(): void {\n if (this.appStateSubscription) {\n this.appStateSubscription.remove();\n this.appStateSubscription = undefined;\n this.warmStartTimestamp = null;\n }\n }\n}\n"]}
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVENT_NAVIGATION = void 0;
3
4
  exports.getCurrentRoute = getCurrentRoute;
4
5
  exports.getRouteName = getRouteName;
5
6
  exports.onNavigationStateChange = onNavigationStateChange;
6
7
  exports.createNavigationStateChangeHandler = createNavigationStateChangeHandler;
7
8
  var faro_core_1 = require("@grafana/faro-core");
8
9
  var screen_1 = require("../metas/screen");
10
+ exports.EVENT_NAVIGATION = 'navigation';
9
11
  /**
10
12
  * Gets the currently active route from navigation state
11
13
  */
@@ -49,7 +51,7 @@ function onNavigationStateChange(state) {
49
51
  (_a = faro_core_1.faro.api) === null || _a === void 0 ? void 0 : _a.setView({ name: screenName });
50
52
  // Optionally push additional attributes if route has params
51
53
  if (currentRoute === null || currentRoute === void 0 ? void 0 : currentRoute.params) {
52
- (_b = faro_core_1.faro.api) === null || _b === void 0 ? void 0 : _b.pushEvent('navigation', {
54
+ (_b = faro_core_1.faro.api) === null || _b === void 0 ? void 0 : _b.pushEvent(exports.EVENT_NAVIGATION, {
53
55
  screen: screenName,
54
56
  params: JSON.stringify(currentRoute.params),
55
57
  });
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/navigation/utils.ts"],"names":[],"mappings":";;AASA,0CAoBC;AAKD,oCAEC;AAMD,0DAuBC;AAmBD,gFAEC;AApFD,gDAA0C;AAE1C,0CAAmD;AAEnD;;GAEG;AACH,SAAgB,eAAe,CAC7B,KAAkE;;IAElE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,MAAA,KAAK,CAAC,KAAK,mCAAI,CAAC,CAAC,CAAC;IAE7C,mEAAmE;IACnE,IAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,KAAK,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,KAAK,CAAC,KAAwB,CAAC,CAAC;IACzD,CAAC;IAED,mDAAmD;IACnD,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1E,OAAO,KAAsB,CAAC;IAChC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,KAAgC;IAC3D,OAAO,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,SAAgB,uBAAuB,CAAC,KAAkC;;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IAED,IAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE9C,IAAI,UAAU,EAAE,CAAC;QACf,yBAAyB;QACzB,IAAA,yBAAgB,EAAC,UAAU,CAAC,CAAC;QAE7B,6DAA6D;QAC7D,MAAA,gBAAI,CAAC,GAAG,0CAAE,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAExC,4DAA4D;QAC5D,IAAI,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,MAAM,EAAE,CAAC;YACzB,MAAA,gBAAI,CAAC,GAAG,0CAAE,SAAS,CAAC,YAAY,EAAE;gBAChC,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,kCAAkC;IAChD,OAAO,uBAAuB,CAAC;AACjC,CAAC","sourcesContent":["import type { NavigationState, PartialState, Route } from '@react-navigation/native';\n\nimport { faro } from '@grafana/faro-core';\n\nimport { setCurrentScreen } from '../metas/screen';\n\n/**\n * Gets the currently active route from navigation state\n */\nexport function getCurrentRoute(\n state: NavigationState | PartialState<NavigationState> | undefined\n): Route<string> | undefined {\n if (!state || !state.routes || state.routes.length === 0) {\n return undefined;\n }\n\n const route = state.routes[state.index ?? 0];\n\n // If this route has nested state, recursively get the active route\n if (route?.state) {\n return getCurrentRoute(route.state as NavigationState);\n }\n\n // Only return if route has all required properties\n if (route && 'key' in route && 'name' in route && route.key && route.name) {\n return route as Route<string>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts the route name from a route object\n */\nexport function getRouteName(route: Route<string> | undefined): string | undefined {\n return route?.name;\n}\n\n/**\n * Handles navigation state changes and updates Faro with the new screen\n * @param state - The current navigation state\n */\nexport function onNavigationStateChange(state: NavigationState | undefined): void {\n if (!state) {\n return;\n }\n\n const currentRoute = getCurrentRoute(state);\n const screenName = getRouteName(currentRoute);\n\n if (screenName) {\n // Update the screen meta\n setCurrentScreen(screenName);\n\n // Update the view meta which will trigger VIEW_CHANGED event\n faro.api?.setView({ name: screenName });\n\n // Optionally push additional attributes if route has params\n if (currentRoute?.params) {\n faro.api?.pushEvent('navigation', {\n screen: screenName,\n params: JSON.stringify(currentRoute.params),\n });\n }\n }\n}\n\n/**\n * Creates a navigation state change handler\n * Use this with NavigationContainer's onStateChange prop\n *\n * @example\n * ```\n * import { NavigationContainer } from '@react-navigation/native';\n * import { createNavigationStateChangeHandler } from '@grafana/faro-react-native';\n *\n * const onStateChange = createNavigationStateChangeHandler();\n *\n * // In your component:\n * <NavigationContainer onStateChange={onStateChange}>\n * // your navigation\n * </NavigationContainer>\n * ```\n */\nexport function createNavigationStateChangeHandler(): (state: NavigationState | undefined) => void {\n return onNavigationStateChange;\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/navigation/utils.ts"],"names":[],"mappings":";;;AAWA,0CAoBC;AAKD,oCAEC;AAMD,0DAuBC;AAmBD,gFAEC;AAtFD,gDAA0C;AAE1C,0CAAmD;AAEtC,QAAA,gBAAgB,GAAG,YAAY,CAAC;AAE7C;;GAEG;AACH,SAAgB,eAAe,CAC7B,KAAkE;;IAElE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,MAAA,KAAK,CAAC,KAAK,mCAAI,CAAC,CAAC,CAAC;IAE7C,mEAAmE;IACnE,IAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,KAAK,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,KAAK,CAAC,KAAwB,CAAC,CAAC;IACzD,CAAC;IAED,mDAAmD;IACnD,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1E,OAAO,KAAsB,CAAC;IAChC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,KAAgC;IAC3D,OAAO,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,SAAgB,uBAAuB,CAAC,KAAkC;;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IAED,IAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE9C,IAAI,UAAU,EAAE,CAAC;QACf,yBAAyB;QACzB,IAAA,yBAAgB,EAAC,UAAU,CAAC,CAAC;QAE7B,6DAA6D;QAC7D,MAAA,gBAAI,CAAC,GAAG,0CAAE,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAExC,4DAA4D;QAC5D,IAAI,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,MAAM,EAAE,CAAC;YACzB,MAAA,gBAAI,CAAC,GAAG,0CAAE,SAAS,CAAC,wBAAgB,EAAE;gBACpC,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,kCAAkC;IAChD,OAAO,uBAAuB,CAAC;AACjC,CAAC","sourcesContent":["import type { NavigationState, PartialState, Route } from '@react-navigation/native';\n\nimport { faro } from '@grafana/faro-core';\n\nimport { setCurrentScreen } from '../metas/screen';\n\nexport const EVENT_NAVIGATION = 'navigation';\n\n/**\n * Gets the currently active route from navigation state\n */\nexport function getCurrentRoute(\n state: NavigationState | PartialState<NavigationState> | undefined\n): Route<string> | undefined {\n if (!state || !state.routes || state.routes.length === 0) {\n return undefined;\n }\n\n const route = state.routes[state.index ?? 0];\n\n // If this route has nested state, recursively get the active route\n if (route?.state) {\n return getCurrentRoute(route.state as NavigationState);\n }\n\n // Only return if route has all required properties\n if (route && 'key' in route && 'name' in route && route.key && route.name) {\n return route as Route<string>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts the route name from a route object\n */\nexport function getRouteName(route: Route<string> | undefined): string | undefined {\n return route?.name;\n}\n\n/**\n * Handles navigation state changes and updates Faro with the new screen\n * @param state - The current navigation state\n */\nexport function onNavigationStateChange(state: NavigationState | undefined): void {\n if (!state) {\n return;\n }\n\n const currentRoute = getCurrentRoute(state);\n const screenName = getRouteName(currentRoute);\n\n if (screenName) {\n // Update the screen meta\n setCurrentScreen(screenName);\n\n // Update the view meta which will trigger VIEW_CHANGED event\n faro.api?.setView({ name: screenName });\n\n // Optionally push additional attributes if route has params\n if (currentRoute?.params) {\n faro.api?.pushEvent(EVENT_NAVIGATION, {\n screen: screenName,\n params: JSON.stringify(currentRoute.params),\n });\n }\n }\n}\n\n/**\n * Creates a navigation state change handler\n * Use this with NavigationContainer's onStateChange prop\n *\n * @example\n * ```\n * import { NavigationContainer } from '@react-navigation/native';\n * import { createNavigationStateChangeHandler } from '@grafana/faro-react-native';\n *\n * const onStateChange = createNavigationStateChangeHandler();\n *\n * // In your component:\n * <NavigationContainer onStateChange={onStateChange}>\n * // your navigation\n * </NavigationContainer>\n * ```\n */\nexport function createNavigationStateChangeHandler(): (state: NavigationState | undefined) => void {\n return onNavigationStateChange;\n}\n"]}