@grafana/faro-react-native 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +176 -30
  2. package/android/build.gradle +17 -12
  3. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashIds.kt +44 -0
  4. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +134 -38
  5. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashSessionStore.kt +433 -0
  6. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +11 -2
  7. package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +128 -17
  8. package/android/src/main/java/com/grafana/faro/reactnative/FaroSessionProcess.kt +88 -0
  9. package/android/src/main/java/com/grafana/faro/reactnative/FrameMonitor.kt +34 -42
  10. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterBelowApi30Test.kt +21 -0
  11. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterTest.kt +59 -0
  12. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashSessionStoreTest.kt +268 -0
  13. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashTraceCacheTest.kt +52 -0
  14. package/android/src/test/java/com/grafana/faro/reactnative/FaroSessionProcessTest.kt +127 -0
  15. package/dist/cjs/config/getRNInstrumentations.js +6 -8
  16. package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
  17. package/dist/cjs/config/sampling.js.map +1 -1
  18. package/dist/cjs/config/types.js.map +1 -1
  19. package/dist/cjs/dataCollection/DataCollectionPolicy.js +1 -2
  20. package/dist/cjs/dataCollection/DataCollectionPolicy.js.map +1 -1
  21. package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
  22. package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
  23. package/dist/cjs/index.js +8 -3
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/instrumentations/anr/instrumentation.js +10 -1
  26. package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
  27. package/dist/cjs/instrumentations/appState/index.js +8 -2
  28. package/dist/cjs/instrumentations/appState/index.js.map +1 -1
  29. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +19 -6
  30. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
  31. package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +331 -0
  32. package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
  33. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
  34. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
  35. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
  36. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
  37. package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
  38. package/dist/cjs/instrumentations/errors/index.js +8 -3
  39. package/dist/cjs/instrumentations/errors/index.js.map +1 -1
  40. package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js +20 -31
  41. package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
  42. package/dist/cjs/instrumentations/frameMonitoring/types.js.map +1 -1
  43. package/dist/cjs/instrumentations/performance/index.js +2 -6
  44. package/dist/cjs/instrumentations/performance/index.js.map +1 -1
  45. package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js +48 -0
  46. package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
  47. package/dist/cjs/instrumentations/session/directSessionActivity.js +37 -0
  48. package/dist/cjs/instrumentations/session/directSessionActivity.js.map +1 -0
  49. package/dist/cjs/instrumentations/session/index.js +214 -45
  50. package/dist/cjs/instrumentations/session/index.js.map +1 -1
  51. package/dist/cjs/instrumentations/session/sessionActivity.js +43 -0
  52. package/dist/cjs/instrumentations/session/sessionActivity.js.map +1 -0
  53. package/dist/cjs/instrumentations/session/sessionAttributes.js +8 -8
  54. package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
  55. package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +199 -24
  56. package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
  57. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -13
  58. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  59. package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js +72 -0
  60. package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
  61. package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js +1 -1
  62. package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
  63. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js +124 -96
  64. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  65. package/dist/cjs/instrumentations/session/sessionManager/types.js.map +1 -1
  66. package/dist/cjs/instrumentations/session/sessionProcess.js +96 -0
  67. package/dist/cjs/instrumentations/session/sessionProcess.js.map +1 -0
  68. package/dist/cjs/instrumentations/startup/index.js +2 -6
  69. package/dist/cjs/instrumentations/startup/index.js.map +1 -1
  70. package/dist/cjs/navigation/utils.js +3 -1
  71. package/dist/cjs/navigation/utils.js.map +1 -1
  72. package/dist/cjs/transports/fetch/index.js.map +1 -1
  73. package/dist/cjs/transports/fetch/transport.js +56 -25
  74. package/dist/cjs/transports/fetch/transport.js.map +1 -1
  75. package/dist/cjs/transports/fetch/types.js.map +1 -1
  76. package/dist/cjs/transports/offline/ConnectivityService.js +1 -1
  77. package/dist/cjs/transports/offline/ConnectivityService.js.map +1 -1
  78. package/dist/cjs/transports/offline/OfflineCache.js +2 -2
  79. package/dist/cjs/transports/offline/OfflineCache.js.map +1 -1
  80. package/dist/cjs/transports/offline/transport.js +3 -4
  81. package/dist/cjs/transports/offline/transport.js.map +1 -1
  82. package/dist/cjs/userPersistence/UserPersistence.js +1 -2
  83. package/dist/cjs/userPersistence/UserPersistence.js.map +1 -1
  84. package/dist/esm/config/getRNInstrumentations.js +6 -8
  85. package/dist/esm/config/getRNInstrumentations.js.map +1 -1
  86. package/dist/esm/config/sampling.js.map +1 -1
  87. package/dist/esm/config/types.js.map +1 -1
  88. package/dist/esm/dataCollection/DataCollectionPolicy.js +1 -2
  89. package/dist/esm/dataCollection/DataCollectionPolicy.js.map +1 -1
  90. package/dist/esm/generated/faroRNPackageMeta.js +1 -1
  91. package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
  92. package/dist/esm/index.js +4 -2
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/instrumentations/anr/instrumentation.js +6 -1
  95. package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
  96. package/dist/esm/instrumentations/appState/index.js +8 -2
  97. package/dist/esm/instrumentations/appState/index.js.map +1 -1
  98. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +15 -4
  99. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
  100. package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +230 -0
  101. package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
  102. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
  103. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
  104. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
  105. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
  106. package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
  107. package/dist/esm/instrumentations/errors/index.js +8 -3
  108. package/dist/esm/instrumentations/errors/index.js.map +1 -1
  109. package/dist/esm/instrumentations/frameMonitoring/instrumentation.js +17 -32
  110. package/dist/esm/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
  111. package/dist/esm/instrumentations/frameMonitoring/types.js.map +1 -1
  112. package/dist/esm/instrumentations/performance/index.js +2 -6
  113. package/dist/esm/instrumentations/performance/index.js.map +1 -1
  114. package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js +34 -0
  115. package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
  116. package/dist/esm/instrumentations/session/directSessionActivity.js +32 -0
  117. package/dist/esm/instrumentations/session/directSessionActivity.js.map +1 -0
  118. package/dist/esm/instrumentations/session/index.js +207 -46
  119. package/dist/esm/instrumentations/session/index.js.map +1 -1
  120. package/dist/esm/instrumentations/session/sessionActivity.js +38 -0
  121. package/dist/esm/instrumentations/session/sessionActivity.js.map +1 -0
  122. package/dist/esm/instrumentations/session/sessionAttributes.js +7 -7
  123. package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
  124. package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +190 -27
  125. package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
  126. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -14
  127. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  128. package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js +56 -0
  129. package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
  130. package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js +1 -1
  131. package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
  132. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js +120 -44
  133. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  134. package/dist/esm/instrumentations/session/sessionManager/types.js.map +1 -1
  135. package/dist/esm/instrumentations/session/sessionProcess.js +89 -0
  136. package/dist/esm/instrumentations/session/sessionProcess.js.map +1 -0
  137. package/dist/esm/instrumentations/startup/index.js +2 -6
  138. package/dist/esm/instrumentations/startup/index.js.map +1 -1
  139. package/dist/esm/navigation/utils.js +2 -1
  140. package/dist/esm/navigation/utils.js.map +1 -1
  141. package/dist/esm/transports/fetch/index.js.map +1 -1
  142. package/dist/esm/transports/fetch/transport.js +38 -14
  143. package/dist/esm/transports/fetch/transport.js.map +1 -1
  144. package/dist/esm/transports/fetch/types.js.map +1 -1
  145. package/dist/esm/transports/offline/ConnectivityService.js +1 -1
  146. package/dist/esm/transports/offline/ConnectivityService.js.map +1 -1
  147. package/dist/esm/transports/offline/OfflineCache.js +2 -2
  148. package/dist/esm/transports/offline/OfflineCache.js.map +1 -1
  149. package/dist/esm/transports/offline/transport.js +3 -4
  150. package/dist/esm/transports/offline/transport.js.map +1 -1
  151. package/dist/esm/userPersistence/UserPersistence.js +1 -2
  152. package/dist/esm/userPersistence/UserPersistence.js.map +1 -1
  153. package/dist/types/config/getRNInstrumentations.d.ts +1 -2
  154. package/dist/types/config/sampling.d.ts +0 -3
  155. package/dist/types/config/types.d.ts +5 -3
  156. package/dist/types/dataCollection/DataCollectionPolicy.d.ts +1 -2
  157. package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
  158. package/dist/types/index.d.ts +4 -1
  159. package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
  160. package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
  161. package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +2 -2
  162. package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +2 -2
  163. package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
  164. package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +2 -2
  165. package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
  166. package/dist/types/instrumentations/performance/index.d.ts +0 -2
  167. package/dist/types/instrumentations/session/FaroSessionActivityBoundary.d.ts +8 -0
  168. package/dist/types/instrumentations/session/directSessionActivity.d.ts +13 -0
  169. package/dist/types/instrumentations/session/index.d.ts +20 -1
  170. package/dist/types/instrumentations/session/sessionActivity.d.ts +9 -0
  171. package/dist/types/instrumentations/session/sessionAttributes.d.ts +6 -3
  172. package/dist/types/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.d.ts +16 -3
  173. package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +9 -3
  174. package/dist/types/instrumentations/session/sessionManager/persistentSessionRecord.d.ts +4 -0
  175. package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +19 -7
  176. package/dist/types/instrumentations/session/sessionManager/types.d.ts +6 -0
  177. package/dist/types/instrumentations/session/sessionProcess.d.ts +17 -0
  178. package/dist/types/instrumentations/startup/index.d.ts +2 -6
  179. package/dist/types/navigation/utils.d.ts +1 -0
  180. package/dist/types/transports/fetch/index.d.ts +1 -1
  181. package/dist/types/transports/fetch/transport.d.ts +8 -1
  182. package/dist/types/transports/fetch/types.d.ts +5 -0
  183. package/dist/types/transports/offline/ConnectivityService.d.ts +1 -1
  184. package/dist/types/transports/offline/OfflineCache.d.ts +2 -2
  185. package/dist/types/transports/offline/transport.d.ts +3 -4
  186. package/dist/types/userPersistence/UserPersistence.d.ts +1 -2
  187. package/ios/FaroCrashReporter.swift +214 -21
  188. package/ios/FaroReactNative.swift +24 -0
  189. package/ios/FaroReactNativeModule.h +2 -1
  190. package/ios/FaroReactNativeModule.mm +121 -6
  191. package/ios/RefreshRateVitals.swift +34 -38
  192. package/package.json +7 -5
  193. package/src/config/getRNInstrumentations.ts +6 -8
  194. package/src/config/makeRNConfig.test.ts +6 -0
  195. package/src/config/sampling.ts +0 -3
  196. package/src/config/types.ts +5 -3
  197. package/src/dataCollection/DataCollectionPolicy.ts +1 -2
  198. package/src/generated/faroRNPackageMeta.ts +1 -1
  199. package/src/index.ts +8 -2
  200. package/src/instrumentations/anr/instrumentation.ts +8 -1
  201. package/src/instrumentations/appState/index.ts +10 -2
  202. package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
  203. package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
  204. package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
  205. package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
  206. package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
  207. package/src/instrumentations/crashReporting/types.ts +7 -0
  208. package/src/instrumentations/errors/index.ts +12 -4
  209. package/src/instrumentations/errors/instrumentation.test.ts +35 -0
  210. package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
  211. package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
  212. package/src/instrumentations/frameMonitoring/types.ts +4 -6
  213. package/src/instrumentations/performance/index.ts +2 -6
  214. package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
  215. package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
  216. package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
  217. package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
  218. package/src/instrumentations/session/directSessionActivity.ts +36 -0
  219. package/src/instrumentations/session/index.ts +269 -46
  220. package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
  221. package/src/instrumentations/session/sessionActivity.test.ts +68 -0
  222. package/src/instrumentations/session/sessionActivity.ts +58 -0
  223. package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
  224. package/src/instrumentations/session/sessionAttributes.ts +12 -6
  225. package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
  226. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
  227. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
  228. package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
  229. package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
  230. package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
  231. package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
  232. package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
  233. package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
  234. package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
  235. package/src/instrumentations/session/sessionManager/types.ts +8 -0
  236. package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
  237. package/src/instrumentations/session/sessionProcess.test.ts +136 -0
  238. package/src/instrumentations/session/sessionProcess.ts +114 -0
  239. package/src/instrumentations/session/startNewSession.test.ts +337 -0
  240. package/src/instrumentations/startup/index.ts +2 -6
  241. package/src/navigation/utils.ts +3 -1
  242. package/src/transports/fetch/index.ts +6 -1
  243. package/src/transports/fetch/transport.test.ts +86 -2
  244. package/src/transports/fetch/transport.ts +38 -16
  245. package/src/transports/fetch/types.ts +7 -0
  246. package/src/transports/offline/ConnectivityService.ts +1 -1
  247. package/src/transports/offline/OfflineCache.ts +2 -2
  248. package/src/transports/offline/transport.ts +3 -4
  249. package/src/userPersistence/UserPersistence.ts +1 -2
  250. package/dist/cjs/utils/throttle.js +0 -42
  251. package/dist/cjs/utils/throttle.js.map +0 -1
  252. package/dist/esm/utils/throttle.js +0 -34
  253. package/dist/esm/utils/throttle.js.map +0 -1
  254. package/dist/types/utils/throttle.d.ts +0 -9
  255. package/src/utils/throttle.test.ts +0 -141
  256. package/src/utils/throttle.ts +0 -34
@@ -0,0 +1,268 @@
1
+ package com.grafana.faro.reactnative
2
+
3
+ import android.content.Context
4
+ import androidx.test.core.app.ApplicationProvider
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Assert.assertFalse
7
+ import org.junit.Assert.assertNull
8
+ import org.junit.Assert.assertTrue
9
+ import org.junit.Before
10
+ import org.junit.Test
11
+ import org.junit.runner.RunWith
12
+ import org.robolectric.RobolectricTestRunner
13
+ import org.robolectric.annotation.Config
14
+
15
+ @RunWith(RobolectricTestRunner::class)
16
+ @Config(manifest = Config.NONE, sdk = [30])
17
+ class FaroCrashSessionStoreTest {
18
+
19
+ private lateinit var context: Context
20
+
21
+ @Before
22
+ fun setUp() {
23
+ context = ApplicationProvider.getApplicationContext()
24
+ context.getSharedPreferences(
25
+ "com.grafana.faro.crash_session_store",
26
+ Context.MODE_PRIVATE,
27
+ ).edit().clear().commit()
28
+ }
29
+
30
+ @Test
31
+ fun recordSessionContext_preservesFirstActivationForSessionUpdates() {
32
+ recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L)
33
+ recordSession("session-a", activatedAtMs = 2_000L, nowMs = 2_000L)
34
+
35
+ val contexts = FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 2_000L)
36
+
37
+ assertEquals(1, contexts.size)
38
+ assertEquals(1_000L, contexts.single().activatedAtMs)
39
+ assertEquals(2_000L, contexts.single().updatedAtMs)
40
+ }
41
+
42
+ @Test
43
+ fun findMatchingContext_usesLatestSessionBeforeCrashInSameProcess() {
44
+ recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L)
45
+ recordSession("session-b", activatedAtMs = 3_000L, nowMs = 3_000L)
46
+
47
+ val sessionA = FaroCrashSessionStore.findMatchingContext(
48
+ context,
49
+ crashTimestampMs = 2_000L,
50
+ pid = 101,
51
+ processName = "com.example.app",
52
+ nowMs = 4_000L,
53
+ )
54
+ val sessionB = FaroCrashSessionStore.findMatchingContext(
55
+ context,
56
+ crashTimestampMs = 4_000L,
57
+ pid = 101,
58
+ processName = "com.example.app",
59
+ nowMs = 4_000L,
60
+ )
61
+
62
+ assertEquals("session-a", sessionA?.sessionId)
63
+ assertEquals("session-b", sessionB?.sessionId)
64
+ }
65
+
66
+ @Test
67
+ fun findMatchingContext_doesNotCrossProcessBoundaries() {
68
+ recordSession(
69
+ sessionId = "session-a",
70
+ activatedAtMs = 1_000L,
71
+ nowMs = 1_000L,
72
+ pid = 101,
73
+ )
74
+ recordSession(
75
+ sessionId = "session-b",
76
+ activatedAtMs = 2_000L,
77
+ nowMs = 2_000L,
78
+ pid = 202,
79
+ )
80
+
81
+ val match = FaroCrashSessionStore.findMatchingContext(
82
+ context,
83
+ crashTimestampMs = 3_000L,
84
+ pid = 101,
85
+ processName = "com.example.app",
86
+ nowMs = 3_000L,
87
+ )
88
+
89
+ assertEquals("session-a", match?.sessionId)
90
+ }
91
+
92
+ @Test
93
+ fun acknowledgeReports_removesOnlyItsOwnSessionContext() {
94
+ recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L, pid = 101)
95
+ recordSession("session-b", activatedAtMs = 2_000L, nowMs = 2_000L, pid = 202)
96
+ val contexts = FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 3_000L)
97
+ val sessionA = contexts.first { it.sessionId == "session-a" }
98
+ val sessionB = contexts.first { it.sessionId == "session-b" }
99
+
100
+ FaroCrashSessionStore.rememberPendingReport(
101
+ context,
102
+ reportId = "report-a",
103
+ crashTimestampMs = 1_500L,
104
+ sessionContext = sessionA,
105
+ usesPendingTrace = true,
106
+ nowMs = 3_000L,
107
+ )
108
+ FaroCrashSessionStore.rememberPendingReport(
109
+ context,
110
+ reportId = "report-b",
111
+ crashTimestampMs = 2_500L,
112
+ sessionContext = sessionB,
113
+ usesPendingTrace = false,
114
+ nowMs = 3_000L,
115
+ )
116
+
117
+ val result = FaroCrashSessionStore.acknowledgeReports(
118
+ context,
119
+ reportIds = listOf("report-a"),
120
+ nowMs = 4_000L,
121
+ )
122
+
123
+ val remaining = FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 4_000L)
124
+ assertTrue(result.clearPendingTrace)
125
+ assertTrue(FaroCrashSessionStore.isReportAcknowledged(context, "report-a", nowMs = 4_000L))
126
+ assertFalse(FaroCrashSessionStore.isReportAcknowledged(context, "report-b", nowMs = 4_000L))
127
+ assertEquals(listOf("session-b"), remaining.map { it.sessionId })
128
+ assertEquals(
129
+ "session-b",
130
+ FaroCrashSessionStore.contextForPendingReport(context, "report-b", nowMs = 4_000L)?.sessionId,
131
+ )
132
+ }
133
+
134
+ @Test
135
+ fun acknowledgeReports_keepsContextReferencedByAnotherPendingReport() {
136
+ recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L)
137
+ val sessionA = FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 2_000L).single()
138
+ FaroCrashSessionStore.rememberPendingReport(
139
+ context,
140
+ "report-a",
141
+ 1_500L,
142
+ sessionA,
143
+ usesPendingTrace = false,
144
+ nowMs = 2_000L,
145
+ )
146
+ FaroCrashSessionStore.rememberPendingReport(
147
+ context,
148
+ "report-b",
149
+ 1_600L,
150
+ sessionA,
151
+ usesPendingTrace = false,
152
+ nowMs = 2_000L,
153
+ )
154
+
155
+ FaroCrashSessionStore.acknowledgeReports(context, listOf("report-a"), nowMs = 3_000L)
156
+
157
+ assertEquals(
158
+ "session-a",
159
+ FaroCrashSessionStore.contextForPendingReport(context, "report-b", nowMs = 3_000L)?.sessionId,
160
+ )
161
+ }
162
+
163
+ @Test
164
+ fun acknowledgeReports_clearsSharedTraceOnlyAfterLastReferencingReport() {
165
+ recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L)
166
+ val sessionA = FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 2_000L).single()
167
+ FaroCrashSessionStore.rememberPendingReport(
168
+ context,
169
+ "report-a",
170
+ 1_500L,
171
+ sessionA,
172
+ usesPendingTrace = true,
173
+ nowMs = 2_000L,
174
+ )
175
+ FaroCrashSessionStore.rememberPendingReport(
176
+ context,
177
+ "report-b",
178
+ 1_600L,
179
+ sessionA,
180
+ usesPendingTrace = true,
181
+ nowMs = 2_000L,
182
+ )
183
+
184
+ val first = FaroCrashSessionStore.acknowledgeReports(context, listOf("report-a"), nowMs = 3_000L)
185
+ val second = FaroCrashSessionStore.acknowledgeReports(context, listOf("report-b"), nowMs = 4_000L)
186
+
187
+ assertTrue(first.persisted)
188
+ assertFalse(first.clearPendingTrace)
189
+ assertTrue(second.persisted)
190
+ assertTrue(second.clearPendingTrace)
191
+ }
192
+
193
+ @Test
194
+ fun acknowledgeReports_canTombstoneAnIgnoredReportWithoutPendingContext() {
195
+ val result = FaroCrashSessionStore.acknowledgeReports(
196
+ context,
197
+ reportIds = listOf("ignored-report"),
198
+ nowMs = 1_000L,
199
+ )
200
+
201
+ assertTrue(result.persisted)
202
+ assertFalse(result.clearPendingTrace)
203
+ assertTrue(FaroCrashSessionStore.isReportAcknowledged(context, "ignored-report", nowMs = 1_000L))
204
+ }
205
+
206
+ @Test
207
+ fun sessionContextExpiresOnlyAfterSevenDayBoundary() {
208
+ val sevenDays = FaroCrashSessionStore.MAX_CONTEXT_AGE_MS
209
+ recordSession("at-boundary", activatedAtMs = 1L, nowMs = 1_000L)
210
+
211
+ assertEquals(
212
+ 1,
213
+ FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 1_000L + sevenDays).size,
214
+ )
215
+ assertTrue(
216
+ FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 1_001L + sevenDays).isEmpty(),
217
+ )
218
+ }
219
+
220
+ @Test
221
+ fun malformedStorageDoesNotPreventNewContextFromBeingRecorded() {
222
+ context.getSharedPreferences(
223
+ "com.grafana.faro.crash_session_store",
224
+ Context.MODE_PRIVATE,
225
+ ).edit().putString("session_contexts", "not-json").commit()
226
+
227
+ assertTrue(recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L))
228
+ assertEquals(
229
+ "session-a",
230
+ FaroCrashSessionStore.sessionContextsForTest(context, nowMs = 1_000L).single().sessionId,
231
+ )
232
+ }
233
+
234
+ @Test
235
+ fun findMatchingContext_returnsNullWithoutUsableProcessIdentity() {
236
+ recordSession("session-a", activatedAtMs = 1_000L, nowMs = 1_000L)
237
+
238
+ val match = FaroCrashSessionStore.findMatchingContext(
239
+ context,
240
+ crashTimestampMs = 2_000L,
241
+ pid = 0,
242
+ processName = "",
243
+ nowMs = 2_000L,
244
+ )
245
+
246
+ assertNull(match)
247
+ }
248
+
249
+ private fun recordSession(
250
+ sessionId: String,
251
+ activatedAtMs: Long,
252
+ nowMs: Long,
253
+ pid: Int = 101,
254
+ ): Boolean {
255
+ return FaroCrashSessionStore.recordSessionContext(
256
+ context = context,
257
+ sessionId = sessionId,
258
+ activatedAtMs = activatedAtMs,
259
+ isSampled = true,
260
+ appVersion = "1.0.0",
261
+ appRelease = "42",
262
+ appBundleId = "com.example.app@42@1.0.0",
263
+ nowMs = nowMs,
264
+ pid = pid,
265
+ processName = "com.example.app",
266
+ )
267
+ }
268
+ }
@@ -0,0 +1,52 @@
1
+ package com.grafana.faro.reactnative
2
+
3
+ import android.content.Context
4
+ import androidx.test.core.app.ApplicationProvider
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Assert.assertNotNull
7
+ import org.junit.Assert.assertNull
8
+ import org.junit.Before
9
+ import org.junit.Test
10
+ import org.junit.runner.RunWith
11
+ import org.robolectric.RobolectricTestRunner
12
+ import org.robolectric.annotation.Config
13
+
14
+ @RunWith(RobolectricTestRunner::class)
15
+ @Config(manifest = Config.NONE, sdk = [30])
16
+ class FaroCrashTraceCacheTest {
17
+
18
+ private lateinit var context: Context
19
+
20
+ @Before
21
+ fun setUp() {
22
+ context = ApplicationProvider.getApplicationContext()
23
+ context.getSharedPreferences(
24
+ "com.grafana.faro.crash_trace_cache",
25
+ Context.MODE_PRIVATE,
26
+ ).edit().clear().commit()
27
+ }
28
+
29
+ @Test
30
+ fun timestampMismatchDoesNotClearTraceForLaterMatchingReport() {
31
+ FaroCrashTraceCache.savePendingCrashTrace(context, "java.lang.RuntimeException", 20_000L)
32
+ val pending = FaroCrashTraceCache.peekPendingCrashTrace(context, 20_001L)
33
+
34
+ assertEquals("", FaroCrashTraceCache.traceForExitTimestamp(context, pending, 1_000L))
35
+ assertNotNull(FaroCrashTraceCache.peekPendingCrashTrace(context, 20_001L))
36
+ assertEquals(
37
+ "java.lang.RuntimeException",
38
+ FaroCrashTraceCache.traceForExitTimestamp(context, pending, 20_000L),
39
+ )
40
+ }
41
+
42
+ @Test
43
+ fun traceExpiresAfterReplayWindow() {
44
+ val timestamp = 20_000L
45
+ val replayWindow = FaroCrashSessionStore.MAX_CONTEXT_AGE_MS
46
+ FaroCrashTraceCache.savePendingCrashTrace(context, "java.lang.RuntimeException", timestamp)
47
+
48
+ assertNotNull(FaroCrashTraceCache.peekPendingCrashTrace(context, timestamp + replayWindow))
49
+ assertNull(FaroCrashTraceCache.peekPendingCrashTrace(context, timestamp + replayWindow + 1L))
50
+ assertNull(FaroCrashTraceCache.peekPendingCrashTrace(context, timestamp + replayWindow + 1L))
51
+ }
52
+ }
@@ -0,0 +1,127 @@
1
+ package com.grafana.faro.reactnative
2
+
3
+ import org.junit.After
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertFalse
6
+ import org.junit.Assert.assertNull
7
+ import org.junit.Assert.assertTrue
8
+ import org.junit.Before
9
+ import org.junit.Test
10
+
11
+ class FaroSessionProcessTest {
12
+
13
+ @Before
14
+ fun setUp() {
15
+ FaroSessionProcess.resetPersistenceClaimForTest()
16
+ }
17
+
18
+ @After
19
+ fun tearDown() {
20
+ FaroSessionProcess.resetPersistenceClaimForTest()
21
+ }
22
+
23
+ @Test
24
+ fun normalizeIdentifier_rejectsMissingProcessNames() {
25
+ assertNull(FaroSessionProcess.normalizeIdentifier(null))
26
+ assertNull(FaroSessionProcess.normalizeIdentifier(" "))
27
+ }
28
+
29
+ @Test
30
+ fun normalizeIdentifier_trimsKnownProcessName() {
31
+ assertEquals(
32
+ "com.example.app:worker",
33
+ FaroSessionProcess.normalizeIdentifier(" com.example.app:worker "),
34
+ )
35
+ }
36
+
37
+ @Test
38
+ fun identifier_usesThePlatformApiOnAndroidPAndNewer() {
39
+ assertEquals(
40
+ "com.example.app:worker",
41
+ FaroSessionProcess.identifier(
42
+ sdkInt = 28,
43
+ currentProcessName = { "com.example.app:worker" },
44
+ legacyProcessName = { error("legacy lookup should not run") },
45
+ ),
46
+ )
47
+ }
48
+
49
+ @Test
50
+ fun identifier_readsTheNullTerminatedCmdlineBeforeAndroidP() {
51
+ assertEquals(
52
+ "com.example.app:worker",
53
+ FaroSessionProcess.identifier(
54
+ sdkInt = 27,
55
+ currentProcessName = { error("platform lookup should not run") },
56
+ legacyProcessName = { "com.example.app:worker\u0000ignored" },
57
+ ),
58
+ )
59
+ }
60
+
61
+ @Test
62
+ fun identifier_failsClosedWhenTheProcessNameCannotBeRead() {
63
+ assertNull(
64
+ FaroSessionProcess.identifier(
65
+ sdkInt = 27,
66
+ currentProcessName = { error("platform lookup should not run") },
67
+ legacyProcessName = { throw IllegalStateException("unavailable") },
68
+ ),
69
+ )
70
+ }
71
+
72
+ @Test
73
+ fun isMainProcess_comparesCurrentProcessWithDeclaredMainProcess() {
74
+ assertEquals(
75
+ true,
76
+ FaroSessionProcess.isMainProcess(
77
+ currentName = "com.example.app",
78
+ declaredMainName = "com.example.app",
79
+ packageName = "com.example.app",
80
+ ),
81
+ )
82
+ assertEquals(
83
+ false,
84
+ FaroSessionProcess.isMainProcess(
85
+ currentName = "com.example.app:worker",
86
+ declaredMainName = "com.example.app",
87
+ packageName = "com.example.app",
88
+ ),
89
+ )
90
+ }
91
+
92
+ @Test
93
+ fun isMainProcess_usesPackageNameWhenMainProcessIsNotDeclared() {
94
+ assertEquals(
95
+ true,
96
+ FaroSessionProcess.isMainProcess(
97
+ currentName = "com.example.app",
98
+ declaredMainName = " ",
99
+ packageName = "com.example.app",
100
+ ),
101
+ )
102
+ }
103
+
104
+ @Test
105
+ fun isMainProcess_failsClosedWithoutCurrentIdentity() {
106
+ assertNull(
107
+ FaroSessionProcess.isMainProcess(
108
+ currentName = null,
109
+ declaredMainName = "com.example.app",
110
+ packageName = "com.example.app",
111
+ ),
112
+ )
113
+ }
114
+
115
+ @Test
116
+ fun claimPersistence_allowsOnlyOneOwnerAtATime() {
117
+ val firstOwner = Any()
118
+ val secondOwner = Any()
119
+
120
+ assertTrue(FaroSessionProcess.claimPersistence(firstOwner))
121
+ assertTrue(FaroSessionProcess.claimPersistence(firstOwner))
122
+ assertFalse(FaroSessionProcess.claimPersistence(secondOwner))
123
+ assertFalse(FaroSessionProcess.releasePersistence(secondOwner))
124
+ assertTrue(FaroSessionProcess.releasePersistence(firstOwner))
125
+ assertTrue(FaroSessionProcess.claimPersistence(secondOwner))
126
+ }
127
+ }
@@ -22,8 +22,7 @@ function toRegExpArray(patterns) {
22
22
  /**
23
23
  * Returns the default set of instrumentations for React Native.
24
24
  *
25
- * Reads all options from the Faro config (single source of truth).
26
- * Property names are aligned with Flutter SDK FaroConfig:
25
+ * Reads all options from the Faro config (single source of truth):
27
26
  * - cpuUsageVitals, memoryUsageVitals, anrTracking, refreshRateVitals
28
27
  * - enableCrashReporting, enableErrorReporting
29
28
  * - fetchVitalsInterval, ignoreUrls
@@ -44,7 +43,6 @@ function toRegExpArray(patterns) {
44
43
  */
45
44
  function getRNInstrumentations(config) {
46
45
  if (config === void 0) { config = {}; }
47
- // Aligned with Flutter SDK FaroConfig defaults
48
46
  var
49
47
  // Error & crash tracking
50
48
  _a = config.enableErrorReporting,
@@ -67,21 +65,21 @@ function getRNInstrumentations(config) {
67
65
  // Tracing
68
66
  enableTracing = _p === void 0 ? false : _p, _q = config.tracingOptions, tracingOptions = _q === void 0 ? {} : _q;
69
67
  var instrumentations = [];
70
- // Error reporting (Flutter: enableFlutterErrorReporting)
68
+ // Error reporting
71
69
  if (enableErrorReporting) {
72
70
  instrumentations.push(new errors_1.ErrorsInstrumentation({
73
71
  releaseBundleFilename: config.releaseBundleFilename,
74
72
  }));
75
73
  }
76
- // Console capture - not in Flutter SDK, RN-specific
74
+ // Console capture
77
75
  if (enableConsoleCapture) {
78
76
  instrumentations.push(new console_1.ConsoleInstrumentation());
79
77
  }
80
- // Sessions - always enabled in Flutter, same here
78
+ // Sessions
81
79
  instrumentations.push(new session_1.SessionInstrumentation());
82
- // Views - always enabled in Flutter, same here
80
+ // Views
83
81
  instrumentations.push(new view_1.ViewInstrumentation());
84
- // App state - always enabled in Flutter, same here
82
+ // App state
85
83
  instrumentations.push(new appState_1.AppStateInstrumentation());
86
84
  // User actions - enabled by default, opt-out via enableUserActions
87
85
  if (enableUserActions) {
@@ -1 +1 @@
1
- {"version":3,"file":"getRNInstrumentations.js","sourceRoot":"","sources":["../../../src/config/getRNInstrumentations.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\n\nimport type { Instrumentation, Patterns } from '@grafana/faro-core';\n\nimport { ANRInstrumentation } from '../instrumentations/anr';\nimport { AppStateInstrumentation } from '../instrumentations/appState';\nimport { ConsoleInstrumentation } from '../instrumentations/console';\nimport { CrashReportingInstrumentation } from '../instrumentations/crashReporting';\nimport { ErrorsInstrumentation } from '../instrumentations/errors';\nimport { FrameMonitoringInstrumentation } from '../instrumentations/frameMonitoring';\nimport { HttpInstrumentation } from '../instrumentations/http';\nimport { PerformanceInstrumentation } from '../instrumentations/performance';\nimport { SessionInstrumentation } from '../instrumentations/session';\nimport { StartupInstrumentation } from '../instrumentations/startup';\nimport { UserActionInstrumentation } from '../instrumentations/userActions';\nimport { ViewInstrumentation } from '../instrumentations/view';\nimport { XHRInstrumentation } from '../instrumentations/xhr';\n\nimport type { ReactNativeConfig } from './types';\n\n/** Convert Patterns (string | RegExp)[] to RegExp[] for instrumentations that require RegExp[]. */\nfunction toRegExpArray(patterns: Patterns): RegExp[] {\n return patterns.map((p) => (typeof p === 'string' ? new RegExp(p.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')) : p));\n}\n\n/**\n * Returns the default set of instrumentations for React Native.\n *\n * Reads all options from the Faro config (single source of truth).\n * Property names are aligned with Flutter SDK FaroConfig:\n * - cpuUsageVitals, memoryUsageVitals, anrTracking, refreshRateVitals\n * - enableCrashReporting, enableErrorReporting\n * - fetchVitalsInterval, ignoreUrls\n *\n * @example\n * ```ts\n * const config: ReactNativeConfig = {\n * app: { name: 'my-app', version: '1.0.0' },\n * cpuUsageVitals: true,\n * memoryUsageVitals: true,\n * anrTracking: true,\n * refreshRateVitals: true,\n * enableCrashReporting: true,\n * fetchVitalsInterval: 30000,\n * instrumentations: getRNInstrumentations(config),\n * };\n * ```\n */\nexport function getRNInstrumentations(config: Partial<ReactNativeConfig> = {}): Instrumentation[] {\n // Aligned with Flutter SDK FaroConfig defaults\n const {\n // Error & crash tracking\n enableErrorReporting = true,\n enableCrashReporting = false,\n anrTracking = false,\n anrOptions = {},\n\n // Performance vitals\n cpuUsageVitals = true,\n memoryUsageVitals = true,\n refreshRateVitals = false,\n fetchVitalsInterval = 30000,\n frameMonitoringOptions = {},\n\n // Network\n ignoreUrls = [],\n enableHttpInstrumentation = {},\n\n // Console and user actions\n enableConsoleCapture = true,\n enableUserActions = true,\n\n // Tracing\n enableTracing = false,\n tracingOptions = {},\n } = config;\n\n const instrumentations: Instrumentation[] = [];\n\n // Error reporting (Flutter: enableFlutterErrorReporting)\n if (enableErrorReporting) {\n instrumentations.push(\n new ErrorsInstrumentation({\n releaseBundleFilename: config.releaseBundleFilename,\n })\n );\n }\n\n // Console capture - not in Flutter SDK, RN-specific\n if (enableConsoleCapture) {\n instrumentations.push(new ConsoleInstrumentation());\n }\n\n // Sessions - always enabled in Flutter, same here\n instrumentations.push(new SessionInstrumentation());\n\n // Views - always enabled in Flutter, same here\n instrumentations.push(new ViewInstrumentation());\n\n // App state - always enabled in Flutter, same here\n instrumentations.push(new AppStateInstrumentation());\n\n // User actions - enabled by default, opt-out via enableUserActions\n if (enableUserActions) {\n instrumentations.push(new UserActionInstrumentation());\n }\n\n // HTTP/XHR tracking: when tracing is enabled, TracingInstrumentation patches fetch and XHR.\n // When tracing is disabled, add HttpInstrumentation and/or XHRInstrumentation per enableHttpInstrumentation.\n if (!enableTracing) {\n const { fetch: enableFetch = true, xhr: enableXhr = true } = enableHttpInstrumentation;\n const ignoredUrlsRegExp = toRegExpArray(ignoreUrls);\n if (enableFetch) {\n instrumentations.push(new HttpInstrumentation({ ignoredUrls: ignoredUrlsRegExp }));\n }\n if (enableXhr) {\n instrumentations.push(new XHRInstrumentation({ ignoredUrls: ignoredUrlsRegExp }));\n }\n }\n\n // Performance vitals (CPU/memory) - only add if at least one metric is enabled\n if (cpuUsageVitals || memoryUsageVitals) {\n const perfInstrumentation = new PerformanceInstrumentation({\n memoryUsageVitals,\n cpuUsageVitals,\n fetchVitalsInterval,\n });\n instrumentations.push(perfInstrumentation);\n }\n\n // Startup tracking - always enabled\n instrumentations.push(new StartupInstrumentation());\n\n // Frame monitoring: enabled when refreshRateVitals is true\n // The instrumentation reads refreshRateVitals from faro.config (single source of truth)\n if (refreshRateVitals) {\n instrumentations.push(new FrameMonitoringInstrumentation(frameMonitoringOptions));\n }\n\n // ANR detection (Android only)\n if (anrTracking && Platform.OS === 'android') {\n instrumentations.push(new ANRInstrumentation(anrOptions));\n }\n\n // Crash reporting\n if (enableCrashReporting) {\n instrumentations.push(\n new CrashReportingInstrumentation({\n releaseBundleFilename: config.releaseBundleFilename,\n })\n );\n }\n\n if (enableTracing) {\n try {\n const { TracingInstrumentation } = require('@grafana/faro-react-native-tracing');\n instrumentations.push(new TracingInstrumentation(tracingOptions));\n } catch {\n console.warn(\n '[Faro] enableTracing is true but @grafana/faro-react-native-tracing is not installed. ' +\n 'Add it to use tracing: yarn add @grafana/faro-react-native-tracing'\n );\n }\n }\n\n return instrumentations;\n}\n"]}
1
+ {"version":3,"file":"getRNInstrumentations.js","sourceRoot":"","sources":["../../../src/config/getRNInstrumentations.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from 'react-native';\n\nimport type { Instrumentation, Patterns } from '@grafana/faro-core';\n\nimport { ANRInstrumentation } from '../instrumentations/anr';\nimport { AppStateInstrumentation } from '../instrumentations/appState';\nimport { ConsoleInstrumentation } from '../instrumentations/console';\nimport { CrashReportingInstrumentation } from '../instrumentations/crashReporting';\nimport { ErrorsInstrumentation } from '../instrumentations/errors';\nimport { FrameMonitoringInstrumentation } from '../instrumentations/frameMonitoring';\nimport { HttpInstrumentation } from '../instrumentations/http';\nimport { PerformanceInstrumentation } from '../instrumentations/performance';\nimport { SessionInstrumentation } from '../instrumentations/session';\nimport { StartupInstrumentation } from '../instrumentations/startup';\nimport { UserActionInstrumentation } from '../instrumentations/userActions';\nimport { ViewInstrumentation } from '../instrumentations/view';\nimport { XHRInstrumentation } from '../instrumentations/xhr';\n\nimport type { ReactNativeConfig } from './types';\n\n/** Convert Patterns (string | RegExp)[] to RegExp[] for instrumentations that require RegExp[]. */\nfunction toRegExpArray(patterns: Patterns): RegExp[] {\n return patterns.map((p) => (typeof p === 'string' ? new RegExp(p.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')) : p));\n}\n\n/**\n * Returns the default set of instrumentations for React Native.\n *\n * Reads all options from the Faro config (single source of truth):\n * - cpuUsageVitals, memoryUsageVitals, anrTracking, refreshRateVitals\n * - enableCrashReporting, enableErrorReporting\n * - fetchVitalsInterval, ignoreUrls\n *\n * @example\n * ```ts\n * const config: ReactNativeConfig = {\n * app: { name: 'my-app', version: '1.0.0' },\n * cpuUsageVitals: true,\n * memoryUsageVitals: true,\n * anrTracking: true,\n * refreshRateVitals: true,\n * enableCrashReporting: true,\n * fetchVitalsInterval: 30000,\n * instrumentations: getRNInstrumentations(config),\n * };\n * ```\n */\nexport function getRNInstrumentations(config: Partial<ReactNativeConfig> = {}): Instrumentation[] {\n const {\n // Error & crash tracking\n enableErrorReporting = true,\n enableCrashReporting = false,\n anrTracking = false,\n anrOptions = {},\n\n // Performance vitals\n cpuUsageVitals = true,\n memoryUsageVitals = true,\n refreshRateVitals = false,\n fetchVitalsInterval = 30000,\n frameMonitoringOptions = {},\n\n // Network\n ignoreUrls = [],\n enableHttpInstrumentation = {},\n\n // Console and user actions\n enableConsoleCapture = true,\n enableUserActions = true,\n\n // Tracing\n enableTracing = false,\n tracingOptions = {},\n } = config;\n\n const instrumentations: Instrumentation[] = [];\n\n // Error reporting\n if (enableErrorReporting) {\n instrumentations.push(\n new ErrorsInstrumentation({\n releaseBundleFilename: config.releaseBundleFilename,\n })\n );\n }\n\n // Console capture\n if (enableConsoleCapture) {\n instrumentations.push(new ConsoleInstrumentation());\n }\n\n // Sessions\n instrumentations.push(new SessionInstrumentation());\n\n // Views\n instrumentations.push(new ViewInstrumentation());\n\n // App state\n instrumentations.push(new AppStateInstrumentation());\n\n // User actions - enabled by default, opt-out via enableUserActions\n if (enableUserActions) {\n instrumentations.push(new UserActionInstrumentation());\n }\n\n // HTTP/XHR tracking: when tracing is enabled, TracingInstrumentation patches fetch and XHR.\n // When tracing is disabled, add HttpInstrumentation and/or XHRInstrumentation per enableHttpInstrumentation.\n if (!enableTracing) {\n const { fetch: enableFetch = true, xhr: enableXhr = true } = enableHttpInstrumentation;\n const ignoredUrlsRegExp = toRegExpArray(ignoreUrls);\n if (enableFetch) {\n instrumentations.push(new HttpInstrumentation({ ignoredUrls: ignoredUrlsRegExp }));\n }\n if (enableXhr) {\n instrumentations.push(new XHRInstrumentation({ ignoredUrls: ignoredUrlsRegExp }));\n }\n }\n\n // Performance vitals (CPU/memory) - only add if at least one metric is enabled\n if (cpuUsageVitals || memoryUsageVitals) {\n const perfInstrumentation = new PerformanceInstrumentation({\n memoryUsageVitals,\n cpuUsageVitals,\n fetchVitalsInterval,\n });\n instrumentations.push(perfInstrumentation);\n }\n\n // Startup tracking - always enabled\n instrumentations.push(new StartupInstrumentation());\n\n // Frame monitoring: enabled when refreshRateVitals is true\n // The instrumentation reads refreshRateVitals from faro.config (single source of truth)\n if (refreshRateVitals) {\n instrumentations.push(new FrameMonitoringInstrumentation(frameMonitoringOptions));\n }\n\n // ANR detection (Android only)\n if (anrTracking && Platform.OS === 'android') {\n instrumentations.push(new ANRInstrumentation(anrOptions));\n }\n\n // Crash reporting\n if (enableCrashReporting) {\n instrumentations.push(\n new CrashReportingInstrumentation({\n releaseBundleFilename: config.releaseBundleFilename,\n })\n );\n }\n\n if (enableTracing) {\n try {\n const { TracingInstrumentation } = require('@grafana/faro-react-native-tracing');\n instrumentations.push(new TracingInstrumentation(tracingOptions));\n } catch {\n console.warn(\n '[Faro] enableTracing is true but @grafana/faro-react-native-tracing is not installed. ' +\n 'Add it to use tracing: yarn add @grafana/faro-react-native-tracing'\n );\n }\n }\n\n return instrumentations;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.js","sourceRoot":"","sources":["../../../src/config/sampling.ts"],"names":[],"mappings":";;;AA+EA;;;;;;;;;;;;GAYG;AACH;IACE;;;;OAIG;IACH,sBAA4B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAE5C,8BAAO,GAAP,UAAQ,QAAyB;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IACH,mBAAC;AAAD,CAAC,AAXD,IAWC;AAXY,oCAAY;AAazB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IACE;;;;;OAKG;IACH,0BAA6B,EAAwC;QAAxC,OAAE,GAAF,EAAE,CAAsC;IAAG,CAAC;IAEzE,kCAAO,GAAP,UAAQ,OAAwB;QAC9B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IACH,uBAAC;AAAD,CAAC,AAZD,IAYC;AAZY,4CAAgB","sourcesContent":["import type { Meta } from '@grafana/faro-core';\n\n/**\n * Context passed to sampling implementations.\n *\n * Provides access to the current metadata state at SDK initialization time,\n * allowing sampling decisions based on session attributes, user info, app\n * environment, and other metadata.\n *\n * This aligns with the Faro Flutter SDK's `SamplingContext` which provides\n * `meta` to the sampler.\n *\n * @example\n * ```ts\n * sampling: SamplingFunction((context) => {\n * // Sample all beta users\n * if (context.meta.user?.attributes?.['role'] === 'beta') {\n * return 1;\n * }\n * // Sample 10% of production sessions\n * if (context.meta.app?.environment === 'production') {\n * return 0.1;\n * }\n * return 1;\n * })\n * ```\n */\nexport interface SamplingContext {\n /** The current merged metadata state (session, user, app, sdk, view). */\n meta: Meta;\n}\n\n/**\n * Defines how sessions should be sampled.\n *\n * Implement this interface to provide custom sampling logic. Use one of the\n * built-in implementations:\n * - {@link SamplingRate} for a fixed sampling rate\n * - {@link SamplingFunction} for dynamic sampling based on context\n *\n * @example\n * ```ts\n * // Fixed 10% sampling\n * sessionTracking: {\n * sampling: new SamplingRate(0.1),\n * }\n *\n * // Dynamic sampling based on context\n * sessionTracking: {\n * sampling: new SamplingFunction((context) => {\n * if (context.meta.app?.environment === 'production') {\n * return 0.1;\n * }\n * return 1;\n * }),\n * }\n *\n * // Custom implementation\n * class BetaUserSampling implements Sampling {\n * resolve(context: SamplingContext): number {\n * return context.meta.user?.attributes?.['role'] === 'beta' ? 1 : 0.1;\n * }\n * }\n * sessionTracking: { sampling: new BetaUserSampling() }\n * ```\n */\nexport interface Sampling {\n /**\n * Resolves the sampling rate for the given context.\n *\n * @param context - The current metadata state\n * @returns A value between 0 and 1 (inclusive). Values are clamped to [0, 1].\n * - 1: Sample all sessions (100%)\n * - 0.5: Sample half of sessions (50%)\n * - 0: Sample no sessions (0%)\n */\n resolve(context: SamplingContext): number;\n}\n\n/**\n * Fixed sampling rate.\n *\n * Use this when you want a constant sampling probability regardless of\n * session context.\n *\n * @example\n * ```ts\n * sessionTracking: {\n * sampling: new SamplingRate(0.1), // 10% of sessions\n * }\n * ```\n */\nexport class SamplingRate implements Sampling {\n /**\n * Creates a fixed sampling rate.\n *\n * @param rate - The fixed sampling rate (0 to 1). Values are clamped to [0, 1].\n */\n constructor(public readonly rate: number) {}\n\n resolve(_context: SamplingContext): number {\n return Math.max(0, Math.min(1, this.rate));\n }\n}\n\n/**\n * Dynamic sampling based on context.\n *\n * Use this when you want to make sampling decisions based on session\n * metadata like user attributes, app environment, or other context.\n *\n * @example\n * ```ts\n * sessionTracking: {\n * sampling: new SamplingFunction((context) => {\n * // Sample all beta users\n * if (context.meta.user?.attributes?.['role'] === 'beta') {\n * return 1;\n * }\n * // Sample 10% of production sessions\n * if (context.meta.app?.environment === 'production') {\n * return 0.1;\n * }\n * return 1;\n * }),\n * }\n * ```\n */\nexport class SamplingFunction implements Sampling {\n /**\n * Creates a dynamic sampler with the given function.\n *\n * @param fn - The function that determines the rate based on context.\n * Receives a {@link SamplingContext} and should return a rate between 0 and 1.\n */\n constructor(private readonly fn: (context: SamplingContext) => number) {}\n\n resolve(context: SamplingContext): number {\n return Math.max(0, Math.min(1, this.fn(context)));\n }\n}\n"]}
1
+ {"version":3,"file":"sampling.js","sourceRoot":"","sources":["../../../src/config/sampling.ts"],"names":[],"mappings":";;;AA4EA;;;;;;;;;;;;GAYG;AACH;IACE;;;;OAIG;IACH,sBAA4B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAE5C,8BAAO,GAAP,UAAQ,QAAyB;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IACH,mBAAC;AAAD,CAAC,AAXD,IAWC;AAXY,oCAAY;AAazB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IACE;;;;;OAKG;IACH,0BAA6B,EAAwC;QAAxC,OAAE,GAAF,EAAE,CAAsC;IAAG,CAAC;IAEzE,kCAAO,GAAP,UAAQ,OAAwB;QAC9B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IACH,uBAAC;AAAD,CAAC,AAZD,IAYC;AAZY,4CAAgB","sourcesContent":["import type { Meta } from '@grafana/faro-core';\n\n/**\n * Context passed to sampling implementations.\n *\n * Provides access to the current metadata state at SDK initialization time,\n * allowing sampling decisions based on session attributes, user info, app\n * environment, and other metadata.\n *\n * @example\n * ```ts\n * sampling: SamplingFunction((context) => {\n * // Sample all beta users\n * if (context.meta.user?.attributes?.['role'] === 'beta') {\n * return 1;\n * }\n * // Sample 10% of production sessions\n * if (context.meta.app?.environment === 'production') {\n * return 0.1;\n * }\n * return 1;\n * })\n * ```\n */\nexport interface SamplingContext {\n /** The current merged metadata state (session, user, app, sdk, view). */\n meta: Meta;\n}\n\n/**\n * Defines how sessions should be sampled.\n *\n * Implement this interface to provide custom sampling logic. Use one of the\n * built-in implementations:\n * - {@link SamplingRate} for a fixed sampling rate\n * - {@link SamplingFunction} for dynamic sampling based on context\n *\n * @example\n * ```ts\n * // Fixed 10% sampling\n * sessionTracking: {\n * sampling: new SamplingRate(0.1),\n * }\n *\n * // Dynamic sampling based on context\n * sessionTracking: {\n * sampling: new SamplingFunction((context) => {\n * if (context.meta.app?.environment === 'production') {\n * return 0.1;\n * }\n * return 1;\n * }),\n * }\n *\n * // Custom implementation\n * class BetaUserSampling implements Sampling {\n * resolve(context: SamplingContext): number {\n * return context.meta.user?.attributes?.['role'] === 'beta' ? 1 : 0.1;\n * }\n * }\n * sessionTracking: { sampling: new BetaUserSampling() }\n * ```\n */\nexport interface Sampling {\n /**\n * Resolves the sampling rate for the given context.\n *\n * @param context - The current metadata state\n * @returns A value between 0 and 1 (inclusive). Values are clamped to [0, 1].\n * - 1: Sample all sessions (100%)\n * - 0.5: Sample half of sessions (50%)\n * - 0: Sample no sessions (0%)\n */\n resolve(context: SamplingContext): number;\n}\n\n/**\n * Fixed sampling rate.\n *\n * Use this when you want a constant sampling probability regardless of\n * session context.\n *\n * @example\n * ```ts\n * sessionTracking: {\n * sampling: new SamplingRate(0.1), // 10% of sessions\n * }\n * ```\n */\nexport class SamplingRate implements Sampling {\n /**\n * Creates a fixed sampling rate.\n *\n * @param rate - The fixed sampling rate (0 to 1). Values are clamped to [0, 1].\n */\n constructor(public readonly rate: number) {}\n\n resolve(_context: SamplingContext): number {\n return Math.max(0, Math.min(1, this.rate));\n }\n}\n\n/**\n * Dynamic sampling based on context.\n *\n * Use this when you want to make sampling decisions based on session\n * metadata like user attributes, app environment, or other context.\n *\n * @example\n * ```ts\n * sessionTracking: {\n * sampling: new SamplingFunction((context) => {\n * // Sample all beta users\n * if (context.meta.user?.attributes?.['role'] === 'beta') {\n * return 1;\n * }\n * // Sample 10% of production sessions\n * if (context.meta.app?.environment === 'production') {\n * return 0.1;\n * }\n * return 1;\n * }),\n * }\n * ```\n */\nexport class SamplingFunction implements Sampling {\n /**\n * Creates a dynamic sampler with the given function.\n *\n * @param fn - The function that determines the rate based on context.\n * Receives a {@link SamplingContext} and should return a rate between 0 and 1.\n */\n constructor(private readonly fn: (context: SamplingContext) => number) {}\n\n resolve(context: SamplingContext): number {\n return Math.max(0, Math.min(1, this.fn(context)));\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Config } from '@grafana/faro-core';\n\nimport type { ANRInstrumentationOptions } from '../instrumentations/anr';\nimport type { FrameMonitoringOptions } from '../instrumentations/frameMonitoring';\nimport type { PreloadedMobileMeta, SessionAttributes } from '../instrumentations/session/sessionAttributes';\n\nimport type { Sampling } from './sampling';\n\n/**\n * React Native session tracking config.\n * Extends Config['sessionTracking'] with sampling (Flutter-style) and RN-specific props.\n * Excludes samplingRate and sampler in favor of sampling.\n *\n * When `persistent` is true, sessions are stored with [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv)\n * (synchronous). Add `react-native-mmkv` to your app and rebuild native projects.\n */\nexport type ReactNativeSessionTrackingConfig = Omit<\n NonNullable<Config['sessionTracking']>,\n 'samplingRate' | 'sampler'\n> & {\n /**\n * Session sampling. Use {@link SamplingRate} for fixed rate or\n * {@link SamplingFunction} for dynamic sampling.\n */\n sampling?: Sampling;\n};\n\n/**\n * Flags for enabling optional built-in transports.\n * FetchTransport is always instantiated when url is provided.\n */\nexport interface EnableTransportsConfig {\n /** Enable OfflineTransport for caching when offline (default: false) */\n offline?: boolean;\n /** Enable ConsoleTransport to log telemetry to Metro console for debugging (default: false) */\n console?: boolean;\n}\n\n/** RN-specific config: overrides + new props. Omit faro-core props we replace with RN equivalents. */\ntype ReactNativeConfigOverrides = {\n /** Application metadata (required) */\n app: Config['app'];\n sessionTracking?: ReactNativeSessionTrackingConfig;\n url?: string;\n apiKey?: string;\n enableTransports?: EnableTransportsConfig;\n /**\n * Which HTTP instrumentations to enable. When tracing is disabled, these control\n * HttpInstrumentation (fetch) and XHRInstrumentation (xhr). Default: both true.\n */\n enableHttpInstrumentation?: {\n /** HttpInstrumentation - patches fetch API (default: true) */\n fetch?: boolean;\n /** XHRInstrumentation - patches XMLHttpRequest (default: true) */\n xhr?: boolean;\n };\n enableTracing?: boolean;\n /** Passed to TracingInstrumentation. See TracingInstrumentationOptions in @grafana/faro-react-native-tracing. */\n tracingOptions?: Record<string, unknown>;\n cpuUsageVitals?: boolean;\n memoryUsageVitals?: boolean;\n refreshRateVitals?: boolean;\n fetchVitalsInterval?: number;\n frameMonitoringOptions?: FrameMonitoringOptions;\n enableErrorReporting?: boolean;\n enableCrashReporting?: boolean;\n anrTracking?: boolean;\n anrOptions?: ANRInstrumentationOptions;\n enableConsoleCapture?: boolean;\n consoleCaptureOptions?: Config['consoleInstrumentation'];\n enableUserActions?: boolean;\n userActionsOptions?: Config['userActionsInstrumentation'];\n /**\n * Hermes / minified release stack lines omit a file path. This must match the Metro plugin\n * source map top-level `file` (default `bundle.js`, or your `sourceMapFile` option there).\n */\n releaseBundleFilename?: string;\n};\n\n/**\n * React Native config: Config (omit replaced props) & overrides.\n * Omit: sessionTracking, consoleInstrumentation, userActionsInstrumentation.\n * instrumentations and transports inherited from Config for extras (built-ins come from flags).\n */\nexport type ReactNativeConfig = Partial<\n Omit<Config, 'sessionTracking' | 'consoleInstrumentation' | 'userActionsInstrumentation'>\n> &\n ReactNativeConfigOverrides;\n\n/**\n * Internal full config returned by `makeRNConfig`.\n * Extends faro-core `Config` with RN-specific fields that instrumentations need at init time.\n */\nexport type ReactNativeFullConfig = Config & {\n preloadedMobileMeta?: PreloadedMobileMeta['meta'];\n preloadedSessionDeviceAttributes?: SessionAttributes;\n};\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Config } from '@grafana/faro-core';\n\nimport type { ANRInstrumentationOptions } from '../instrumentations/anr';\nimport type { FrameMonitoringOptions } from '../instrumentations/frameMonitoring';\nimport type { PreloadedMobileMeta, SessionAttributes } from '../instrumentations/session/sessionAttributes';\n\nimport type { Sampling } from './sampling';\n\n/**\n * React Native session tracking config.\n * Extends Config['sessionTracking'] with sampling and RN-specific props.\n * Excludes samplingRate and sampler in favor of sampling.\n *\n * Persistence defaults to true and stores a minimal, versioned record with\n * [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) (synchronous).\n * Add `react-native-mmkv` to your app and rebuild native projects, or set\n * `persistent` to false to use in-memory sessions.\n */\nexport type ReactNativeSessionTrackingConfig = Omit<\n NonNullable<Config['sessionTracking']>,\n 'samplingRate' | 'sampler'\n> & {\n /**\n * Session sampling. Use {@link SamplingRate} for fixed rate or\n * {@link SamplingFunction} for dynamic sampling.\n */\n sampling?: Sampling;\n};\n\n/**\n * Flags for enabling optional built-in transports.\n * FetchTransport is always instantiated when url is provided.\n */\nexport interface EnableTransportsConfig {\n /** Enable OfflineTransport for caching when offline (default: false) */\n offline?: boolean;\n /** Enable ConsoleTransport to log telemetry to Metro console for debugging (default: false) */\n console?: boolean;\n}\n\n/** RN-specific config: overrides + new props. Omit faro-core props we replace with RN equivalents. */\ntype ReactNativeConfigOverrides = {\n /** Application metadata (required) */\n app: Config['app'];\n sessionTracking?: ReactNativeSessionTrackingConfig;\n url?: string;\n apiKey?: string;\n enableTransports?: EnableTransportsConfig;\n /**\n * Which HTTP instrumentations to enable. When tracing is disabled, these control\n * HttpInstrumentation (fetch) and XHRInstrumentation (xhr). Default: both true.\n */\n enableHttpInstrumentation?: {\n /** HttpInstrumentation - patches fetch API (default: true) */\n fetch?: boolean;\n /** XHRInstrumentation - patches XMLHttpRequest (default: true) */\n xhr?: boolean;\n };\n enableTracing?: boolean;\n /** Passed to TracingInstrumentation. See TracingInstrumentationOptions in @grafana/faro-react-native-tracing. */\n tracingOptions?: Record<string, unknown>;\n cpuUsageVitals?: boolean;\n memoryUsageVitals?: boolean;\n refreshRateVitals?: boolean;\n fetchVitalsInterval?: number;\n frameMonitoringOptions?: FrameMonitoringOptions;\n enableErrorReporting?: boolean;\n enableCrashReporting?: boolean;\n anrTracking?: boolean;\n anrOptions?: ANRInstrumentationOptions;\n enableConsoleCapture?: boolean;\n consoleCaptureOptions?: Config['consoleInstrumentation'];\n enableUserActions?: boolean;\n userActionsOptions?: Config['userActionsInstrumentation'];\n /**\n * Hermes / minified release stack lines omit a file path. This must match the Metro plugin\n * source map top-level `file` (default `bundle.js`, or your `sourceMapFile` option there).\n */\n releaseBundleFilename?: string;\n};\n\n/**\n * React Native config: Config (omit replaced props) & overrides.\n * Omit: sessionTracking, consoleInstrumentation, userActionsInstrumentation.\n * instrumentations and transports inherited from Config for extras (built-ins come from flags).\n */\nexport type ReactNativeConfig = Partial<\n Omit<Config, 'sessionTracking' | 'consoleInstrumentation' | 'userActionsInstrumentation'>\n> &\n ReactNativeConfigOverrides;\n\n/**\n * Internal full config returned by `makeRNConfig`.\n * Extends faro-core `Config` with RN-specific fields that instrumentations need at init time.\n */\nexport type ReactNativeFullConfig = Config & {\n preloadedMobileMeta?: PreloadedMobileMeta['meta'];\n preloadedSessionDeviceAttributes?: SessionAttributes;\n};\n"]}
@@ -55,8 +55,7 @@ var DEFAULT_STORAGE_KEY = 'faro_enable_data_collection';
55
55
  * Controls whether telemetry data is collected and sent.
56
56
  * The setting is persisted to AsyncStorage so it survives app restarts.
57
57
  *
58
- * Implementation follows Flutter SDK's DataCollectionPolicy pattern:
59
- * - Uses AsyncStorage for persistence (matching Flutter's SharedPreferences)
58
+ * Uses AsyncStorage for persisting data collection policy state.
60
59
  * - Provides enable/disable methods
61
60
  * - Initializes from persisted value or default
62
61
  *
@@ -1 +1 @@
1
- {"version":3,"file":"DataCollectionPolicy.js","sourceRoot":"","sources":["../../../src/dataCollection/DataCollectionPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage';\n\nimport type { DataCollectionPolicy, DataCollectionPolicyOptions } from './types';\n\n/**\n * Default storage key for the data collection policy.\n */\nconst DEFAULT_STORAGE_KEY = 'faro_enable_data_collection';\n\n/**\n * DataCollectionPolicy implementation using AsyncStorage.\n *\n * Controls whether telemetry data is collected and sent.\n * The setting is persisted to AsyncStorage so it survives app restarts.\n *\n * Implementation follows Flutter SDK's DataCollectionPolicy pattern:\n * - Uses AsyncStorage for persistence (matching Flutter's SharedPreferences)\n * - Provides enable/disable methods\n * - Initializes from persisted value or default\n *\n * @example\n * ```typescript\n * import { createDataCollectionPolicy } from '@grafana/faro-react-native';\n *\n * // Create and initialize the policy\n * const policy = await createDataCollectionPolicy();\n *\n * // Check if enabled\n * if (policy.isEnabled) {\n * // Data collection is allowed\n * }\n *\n * // User opts out\n * await policy.disable();\n *\n * // User opts back in\n * await policy.enable();\n * ```\n */\nexport class AsyncStorageDataCollectionPolicy implements DataCollectionPolicy {\n private _isEnabled: boolean;\n private readonly storageKey: string;\n private readonly subscribers: Set<(isEnabled: boolean) => void> = new Set();\n\n private constructor(options: DataCollectionPolicyOptions = {}) {\n this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n this._isEnabled = options.defaultEnabled ?? true;\n }\n\n /**\n * Create and initialize a DataCollectionPolicy.\n *\n * This async factory method loads the persisted value from AsyncStorage.\n */\n static async create(options: DataCollectionPolicyOptions = {}): Promise<AsyncStorageDataCollectionPolicy> {\n const policy = new AsyncStorageDataCollectionPolicy(options);\n await policy.initialize();\n return policy;\n }\n\n get isEnabled(): boolean {\n return this._isEnabled;\n }\n\n async enable(): Promise<void> {\n this._isEnabled = true;\n await this.persistSetting();\n this.notifySubscribers();\n }\n\n async disable(): Promise<void> {\n this._isEnabled = false;\n await this.persistSetting();\n this.notifySubscribers();\n }\n\n subscribe(callback: (isEnabled: boolean) => void): () => void {\n this.subscribers.add(callback);\n // Immediately notify subscriber of current state\n callback(this._isEnabled);\n\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n const storedValue = await AsyncStorage.getItem(this.storageKey);\n if (storedValue !== null) {\n this._isEnabled = storedValue === 'true';\n }\n } catch {\n // If loading fails, use default value\n }\n }\n\n private async persistSetting(): Promise<void> {\n try {\n await AsyncStorage.setItem(this.storageKey, String(this._isEnabled));\n } catch {\n // If persisting fails, log but don't throw\n }\n }\n\n private notifySubscribers(): void {\n this.subscribers.forEach((subscriber) => {\n try {\n subscriber(this._isEnabled);\n } catch {\n // Ignore subscriber errors\n }\n });\n }\n}\n\n/**\n * Factory function to create a DataCollectionPolicy.\n *\n * @example\n * ```typescript\n * const policy = await createDataCollectionPolicy();\n * ```\n */\nexport async function createDataCollectionPolicy(\n options: DataCollectionPolicyOptions = {}\n): Promise<DataCollectionPolicy> {\n return AsyncStorageDataCollectionPolicy.create(options);\n}\n\n/**\n * Global data collection policy instance.\n * Must be initialized before use.\n */\nlet globalPolicy: DataCollectionPolicy | null = null;\n\n/**\n * Initialize the global data collection policy.\n *\n * This should be called early in app initialization, before initializeFaro().\n *\n * @example\n * ```typescript\n * import { initializeDataCollectionPolicy, getDataCollectionPolicy } from '@grafana/faro-react-native';\n *\n * // Early in app startup\n * await initializeDataCollectionPolicy();\n *\n * // Later, check if collection is enabled\n * const policy = getDataCollectionPolicy();\n * if (policy?.isEnabled) {\n * // Initialize Faro\n * }\n * ```\n */\nexport async function initializeDataCollectionPolicy(\n options: DataCollectionPolicyOptions = {}\n): Promise<DataCollectionPolicy> {\n globalPolicy = await createDataCollectionPolicy(options);\n return globalPolicy;\n}\n\n/**\n * Get the global data collection policy.\n *\n * Returns null if not initialized.\n */\nexport function getDataCollectionPolicy(): DataCollectionPolicy | null {\n return globalPolicy;\n}\n\n/**\n * Set a custom data collection policy.\n *\n * Useful for testing or custom implementations.\n */\nexport function setDataCollectionPolicy(policy: DataCollectionPolicy | null): void {\n globalPolicy = policy;\n}\n"]}
1
+ {"version":3,"file":"DataCollectionPolicy.js","sourceRoot":"","sources":["../../../src/dataCollection/DataCollectionPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage';\n\nimport type { DataCollectionPolicy, DataCollectionPolicyOptions } from './types';\n\n/**\n * Default storage key for the data collection policy.\n */\nconst DEFAULT_STORAGE_KEY = 'faro_enable_data_collection';\n\n/**\n * DataCollectionPolicy implementation using AsyncStorage.\n *\n * Controls whether telemetry data is collected and sent.\n * The setting is persisted to AsyncStorage so it survives app restarts.\n *\n * Uses AsyncStorage for persisting data collection policy state.\n * - Provides enable/disable methods\n * - Initializes from persisted value or default\n *\n * @example\n * ```typescript\n * import { createDataCollectionPolicy } from '@grafana/faro-react-native';\n *\n * // Create and initialize the policy\n * const policy = await createDataCollectionPolicy();\n *\n * // Check if enabled\n * if (policy.isEnabled) {\n * // Data collection is allowed\n * }\n *\n * // User opts out\n * await policy.disable();\n *\n * // User opts back in\n * await policy.enable();\n * ```\n */\nexport class AsyncStorageDataCollectionPolicy implements DataCollectionPolicy {\n private _isEnabled: boolean;\n private readonly storageKey: string;\n private readonly subscribers: Set<(isEnabled: boolean) => void> = new Set();\n\n private constructor(options: DataCollectionPolicyOptions = {}) {\n this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n this._isEnabled = options.defaultEnabled ?? true;\n }\n\n /**\n * Create and initialize a DataCollectionPolicy.\n *\n * This async factory method loads the persisted value from AsyncStorage.\n */\n static async create(options: DataCollectionPolicyOptions = {}): Promise<AsyncStorageDataCollectionPolicy> {\n const policy = new AsyncStorageDataCollectionPolicy(options);\n await policy.initialize();\n return policy;\n }\n\n get isEnabled(): boolean {\n return this._isEnabled;\n }\n\n async enable(): Promise<void> {\n this._isEnabled = true;\n await this.persistSetting();\n this.notifySubscribers();\n }\n\n async disable(): Promise<void> {\n this._isEnabled = false;\n await this.persistSetting();\n this.notifySubscribers();\n }\n\n subscribe(callback: (isEnabled: boolean) => void): () => void {\n this.subscribers.add(callback);\n // Immediately notify subscriber of current state\n callback(this._isEnabled);\n\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private async initialize(): Promise<void> {\n try {\n const storedValue = await AsyncStorage.getItem(this.storageKey);\n if (storedValue !== null) {\n this._isEnabled = storedValue === 'true';\n }\n } catch {\n // If loading fails, use default value\n }\n }\n\n private async persistSetting(): Promise<void> {\n try {\n await AsyncStorage.setItem(this.storageKey, String(this._isEnabled));\n } catch {\n // If persisting fails, log but don't throw\n }\n }\n\n private notifySubscribers(): void {\n this.subscribers.forEach((subscriber) => {\n try {\n subscriber(this._isEnabled);\n } catch {\n // Ignore subscriber errors\n }\n });\n }\n}\n\n/**\n * Factory function to create a DataCollectionPolicy.\n *\n * @example\n * ```typescript\n * const policy = await createDataCollectionPolicy();\n * ```\n */\nexport async function createDataCollectionPolicy(\n options: DataCollectionPolicyOptions = {}\n): Promise<DataCollectionPolicy> {\n return AsyncStorageDataCollectionPolicy.create(options);\n}\n\n/**\n * Global data collection policy instance.\n * Must be initialized before use.\n */\nlet globalPolicy: DataCollectionPolicy | null = null;\n\n/**\n * Initialize the global data collection policy.\n *\n * This should be called early in app initialization, before initializeFaro().\n *\n * @example\n * ```typescript\n * import { initializeDataCollectionPolicy, getDataCollectionPolicy } from '@grafana/faro-react-native';\n *\n * // Early in app startup\n * await initializeDataCollectionPolicy();\n *\n * // Later, check if collection is enabled\n * const policy = getDataCollectionPolicy();\n * if (policy?.isEnabled) {\n * // Initialize Faro\n * }\n * ```\n */\nexport async function initializeDataCollectionPolicy(\n options: DataCollectionPolicyOptions = {}\n): Promise<DataCollectionPolicy> {\n globalPolicy = await createDataCollectionPolicy(options);\n return globalPolicy;\n}\n\n/**\n * Get the global data collection policy.\n *\n * Returns null if not initialized.\n */\nexport function getDataCollectionPolicy(): DataCollectionPolicy | null {\n return globalPolicy;\n}\n\n/**\n * Set a custom data collection policy.\n *\n * Useful for testing or custom implementations.\n */\nexport function setDataCollectionPolicy(policy: DataCollectionPolicy | null): void {\n globalPolicy = policy;\n}\n"]}
@@ -5,5 +5,5 @@ exports.FARO_REACT_NATIVE_NPM_VERSION = exports.FARO_REACT_NATIVE_NPM_NAME = voi
5
5
  * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.
6
6
  */
7
7
  exports.FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native';
8
- exports.FARO_REACT_NATIVE_NPM_VERSION = '1.3.1';
8
+ exports.FARO_REACT_NATIVE_NPM_VERSION = '1.4.0';
9
9
  //# sourceMappingURL=faroRNPackageMeta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"faroRNPackageMeta.js","sourceRoot":"","sources":["../../../src/generated/faroRNPackageMeta.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,0BAA0B,GAAG,4BAAqC,CAAC;AACnE,QAAA,6BAA6B,GAAG,OAAgB,CAAC","sourcesContent":["/**\n * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.\n */\nexport const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;\nexport const FARO_REACT_NATIVE_NPM_VERSION = '1.3.1' as const;\n"]}
1
+ {"version":3,"file":"faroRNPackageMeta.js","sourceRoot":"","sources":["../../../src/generated/faroRNPackageMeta.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,0BAA0B,GAAG,4BAAqC,CAAC;AACnE,QAAA,6BAA6B,GAAG,OAAgB,CAAC","sourcesContent":["/**\n * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.\n */\nexport const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;\nexport const FARO_REACT_NATIVE_NPM_VERSION = '1.4.0' as const;\n"]}