@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
package/README.md CHANGED
@@ -62,9 +62,7 @@ initializeFaro({
62
62
  // Session - optional
63
63
  sessionTracking: {
64
64
  enabled: true,
65
- persistent: false,
66
- inactivityTimeout: 15 * 60 * 1000,
67
- sessionExpirationTime: 4 * 60 * 60 * 1000,
65
+ persistent: true,
68
66
  maxSessionPersistenceTime: 15 * 60 * 1000,
69
67
  // Optional: sampling: new SamplingRate(0.1) or new SamplingFunction((ctx) => ...)
70
68
  // Omit sampling to record all sessions (default).
@@ -178,6 +176,12 @@ This package’s **`android/build.gradle`** registers **`faroUploadComposedSourc
178
176
  - **Crash Reporting Instrumentation** - Captures native crashes — `enableCrashReporting` (default: false)
179
177
  - **Tracing Instrumentation** - OpenTelemetry distributed tracing (requires `@grafana/faro-react-native-tracing`) — `enableTracing` (default: false)
180
178
 
179
+ On iOS and Android 11 or newer, recovered native crashes are reported on the next launch with the session ID
180
+ and timestamp from the process that crashed. Failed deliveries remain pending for a later launch; Android
181
+ retries reports for up to seven days, while PLCrashReporter keeps one pending iOS report. The newly started
182
+ session continues to identify live telemetry. If the original session context is unavailable, the recovered
183
+ crash is skipped rather than assigned to the newly started session.
184
+
181
185
  ### React Integration
182
186
 
183
187
  - **Error Boundary** - Catch and report React component errors with `FaroErrorBoundary`
@@ -777,7 +781,64 @@ initializeFaro({
777
781
 
778
782
  ### Session Configuration
779
783
 
780
- The SDK supports both persistent and volatile session tracking with configurable expiration and inactivity timeouts:
784
+ Session persistence is enabled by default. The SDK does not bundle storage, so
785
+ apps using the default configuration must install the optional
786
+ `react-native-mmkv` peer dependency:
787
+
788
+ ```bash
789
+ yarn add react-native-mmkv
790
+ ```
791
+
792
+ Rebuild the native projects after installing or upgrading the SDK. A JavaScript-only
793
+ or OTA update cannot add the native process-coordination methods; when they are
794
+ unavailable, Faro uses an in-memory session rather than risking concurrent writes.
795
+ Apps using `persistent: false` use in-memory sessions and do not need MMKV.
796
+
797
+ Use `maxSessionPersistenceTime` to control the inactivity and cold-start linking window. A session's
798
+ maximum lifetime is fixed at four hours.
799
+
800
+ The inactivity window is refreshed only by meaningful activity:
801
+
802
+ | Activity source | Activity category |
803
+ | ------------------------------------------------------------------------- | ----------------- |
804
+ | Navigation and view changes | Meaningful |
805
+ | Return from `background` to `active` | Meaningful |
806
+ | Telemetry linked to a tracked user action | Meaningful |
807
+ | React Native touch starts inside `FaroSessionActivityBoundary` | Meaningful |
808
+ | Explicit `notifySessionActivity()` calls | Meaningful |
809
+ | Other app lifecycle events | Passive |
810
+ | Unmarked events, logs, errors, measurements, and traces | Passive |
811
+ | Unmarked HTTP and XHR requests | Passive |
812
+ | Unmarked session, startup, ANR, frame, console, and performance telemetry | Passive |
813
+
814
+ Passive telemetry still checks whether the session has expired, but it does not
815
+ keep the session alive. To refresh inactivity for ordinary touches without
816
+ emitting user-action telemetry, wrap the application once with
817
+ `FaroSessionActivityBoundary`:
818
+
819
+ ```tsx
820
+ import { FaroSessionActivityBoundary } from '@grafana/faro-react-native';
821
+
822
+ export function App() {
823
+ return (
824
+ <FaroSessionActivityBoundary>
825
+ <AppNavigator />
826
+ </FaroSessionActivityBoundary>
827
+ );
828
+ }
829
+ ```
830
+
831
+ The boundary observes touch-start responder negotiation from descendants
832
+ without becoming the responder. This covers standard React Native presses,
833
+ scrolling, dragging, and touches that focus inputs on Android and iOS.
834
+ Continued keyboard input after focus is not another touch. Interactions
835
+ rendered outside the boundary's React Native responder tree, or native and
836
+ accessibility interactions that do not produce a React Native touch event, are
837
+ also not observed. Call `notifySessionActivity()` from those paths. The
838
+ boundary renders a `flex: 1` view for root usage; pass `style` to override its
839
+ layout when wrapping a subtree. Use `withFaroUserAction` or `trackUserAction`
840
+ only when the interaction should also emit user-action telemetry and correlate
841
+ related work.
781
842
 
782
843
  ```tsx
783
844
  import { initializeFaro, SamplingFunction, SamplingRate } from '@grafana/faro-react-native';
@@ -790,10 +851,8 @@ initializeFaro({
790
851
  },
791
852
  sessionTracking: {
792
853
  enabled: true, // default: true
793
- persistent: true, // default: false (volatile)
794
- // Configurable timeouts (all in ms):
795
- inactivityTimeout: 15 * 60 * 1000, // default: 15 min
796
- sessionExpirationTime: 4 * 60 * 60 * 1000, // default: 4 h
854
+ persistent: true, // default: true
855
+ // Inactivity and cold-start linking window (ms):
797
856
  maxSessionPersistenceTime: 15 * 60 * 1000, // default: 15 min
798
857
 
799
858
  // Optional: session sampling (omit = all sessions recorded)
@@ -822,37 +881,78 @@ initializeFaro({
822
881
 
823
882
  **Session Types:**
824
883
 
825
- - **Persistent Sessions** (`persistent: true`): Stored in AsyncStorage and survive app restarts. Sessions expire after `sessionExpirationTime` (default 4 h) or `inactivityTimeout` (default 15 min).
884
+ - **Persistent Sessions** (`persistent: true`, default): A versioned record is stored in MMKV. Every process start creates a new session and links the previous ID when the record is valid. The live session is never resumed across a process start.
885
+
886
+ - **Volatile Sessions** (`persistent: false`): Stored in memory only. Each app launch creates a new session.
887
+
888
+ The persisted record contains only the current and previous session IDs, start and last-activity timestamps, the sampling decision, and a schema version. Session attributes, device state, and overrides remain in memory. Records older than `maxSessionPersistenceTime` are discarded. Unversioned records from earlier SDK versions, corrupt records, and unsupported schema versions are removed and start an unlinked session.
889
+
890
+ Persistent storage is isolated by native process:
891
+
892
+ - On Android, the main process keeps the existing MMKV storage ID. A process declared with `android:process` uses a separate record based on its full process name.
893
+ - On iOS, the host app keeps the existing MMKV storage ID. Each extension uses a separate record based on its `Bundle.main.bundleIdentifier`. Each target that initializes Faro must include the Faro native module and MMKV. Extension persistence requires `react-native-mmkv` v3 or newer so Faro can enable MMKV's multi-process mode; older versions fall back to memory in extensions.
894
+ - Each process maintains its own previous-session link. The `process_name` session attribute identifies which process or extension produced the telemetry.
895
+
896
+ Only one React Native runtime at a time may write a native process's record. An overlapping runtime that has already fallen back remains in memory for its lifetime. After the owning runtime is invalidated, a newly created replacement runtime can claim persistence. The SDK also falls back to memory when native process identity is unavailable, MMKV cannot be initialized safely, or exclusive ownership cannot be established, and logs a warning rather than risking concurrent writes.
826
897
 
827
- - **Volatile Sessions** (`persistent: false`, default): Stored in memory only. Each app launch creates a new session.
898
+ A single session shared across Android processes or between an iOS host app and extension is not supported. Configuring an App Group does not merge Faro session chains between a host app and its extensions because their record IDs remain distinct. If multiple host apps share the same App Group, they share Faro's fixed host record ID; only one of those apps may use `persistent: true`, and the others must use `persistent: false`. Shared sessions would require a multi-process-safe store and native writer coordination. Use `persistent: false` for runtimes where an independent persisted chain is not wanted.
828
899
 
829
900
  **Sampling:** Set `sessionTracking.sampling` to a `SamplingRate` (fixed 0–1) or `SamplingFunction` (dynamic, receives `context.meta`). Omit `sampling` to record all sessions. The decision is made once per session.
830
901
 
831
- **Defaults:** `persistent=false`, `inactivityTimeout=15min`, `sessionExpirationTime=4h`, `maxSessionPersistenceTime=15min`
902
+ **Defaults:** `persistent=true`, `maxSessionPersistenceTime=15min`. The maximum session lifetime is four hours.
832
903
 
833
904
  **Session events:**
834
905
 
835
- The SDK emits `session_start` when a new session is created (including when session metadata changes to a new session id). Resuming a valid persisted session does not emit additional lifecycle events.
906
+ The SDK emits `session_start` when a new session is created, including each cold start and when session metadata changes to a new session ID.
907
+
908
+ **Starting a new linked session:**
909
+
910
+ Use `startNewSession()` after changing or clearing the current user at an
911
+ application-defined boundary. It immediately creates a new session, links the
912
+ previous session ID, restarts the lifetime, inactivity, and sampling windows,
913
+ and creates the normal `session_start` event. As with other telemetry, the event
914
+ is not exported when the new session is sampled out. Persistent sessions attempt
915
+ the synchronous MMKV write before the call returns; write failures are logged
916
+ and the new session remains active in memory.
917
+
918
+ ```tsx
919
+ import { startNewSession } from '@grafana/faro-react-native';
920
+
921
+ function logout() {
922
+ faro.api.resetUser();
923
+ startNewSession();
924
+ }
925
+
926
+ function switchAccount(nextUserId: string) {
927
+ faro.api.setUser({ id: nextUserId });
928
+ startNewSession();
929
+ }
930
+ ```
931
+
932
+ Any active user action is ended before the new session starts so its buffered telemetry
933
+ stays with the previous session. Calls made before Faro initializes, after
934
+ teardown, or while session tracking is disabled have no effect.
836
935
 
837
936
  ### Default Session Attributes
838
937
 
839
- Every telemetry event automatically includes default session attributes with device and SDK information. These attributes match the [Grafana Faro Flutter SDK](https://github.com/grafana/faro-flutter-sdk) format for cross-platform compatibility.
938
+ Every telemetry event automatically includes default session attributes with device and SDK information.
840
939
 
841
940
  **Automatically Collected Attributes:**
842
941
 
843
- | Attribute | Description | iOS Example | Android Example |
844
- | ---------------------- | -------------------- | --------------- | --------------------- |
845
- | `faro_sdk_version` | SDK version | `2.0.2` | `2.0.2` |
846
- | `react_native_version` | React Native version | `0.75.1` | `0.75.1` |
847
- | `device_os` | Operating system | `iOS` | `Android` |
848
- | `device_os_version` | OS version | `17.0` | `15` |
849
- | `device_os_detail` | Detailed OS info | `iOS 17.0` | `Android 15 (SDK 35)` |
850
- | `device_manufacturer` | Manufacturer | `apple` | `samsung` |
851
- | `device_model` | Raw model identifier | `iPhone16,1` | `SM-A155F` |
852
- | `device_model_name` | Human-readable model | `iPhone 15 Pro` | `SM-A155F`\* |
853
- | `device_brand` | Device brand | `iPhone` | `samsung` |
854
- | `device_is_physical` | Physical or emulator | `true` | `true` |
855
- | `device_id` | Unique device ID | `uuid` | `uuid` |
942
+ | Attribute | Description | iOS Example | Android Example |
943
+ | ---------------------- | -------------------- | ---------------- | --------------------- |
944
+ | `faro_sdk_version` | SDK version | `2.0.2` | `2.0.2` |
945
+ | `react_native_version` | React Native version | `0.75.1` | `0.75.1` |
946
+ | `process_name` | Process identity | `com.acme.share` | `com.acme.app:sync` |
947
+ | `device_os` | Operating system | `iOS` | `Android` |
948
+ | `device_os_version` | OS version | `17.0` | `15` |
949
+ | `device_os_detail` | Detailed OS info | `iOS 17.0` | `Android 15 (SDK 35)` |
950
+ | `device_manufacturer` | Manufacturer | `apple` | `samsung` |
951
+ | `device_model` | Raw model identifier | `iPhone16,1` | `SM-A155F` |
952
+ | `device_model_name` | Human-readable model | `iPhone 15 Pro` | `SM-A155F`\* |
953
+ | `device_brand` | Device brand | `iPhone` | `samsung` |
954
+ | `device_is_physical` | Physical or emulator | `true` | `true` |
955
+ | `device_id` | Unique device ID | `uuid` | `uuid` |
856
956
 
857
957
  \*Android does not provide a mapping from model codes to marketing names, so `device_model_name` equals `device_model`.
858
958
 
@@ -1067,6 +1167,43 @@ The SDK automatically tracks app startup time from process start to Faro initial
1067
1167
  | avg
1068
1168
  ```
1069
1169
 
1170
+ #### Frame Monitoring (Refresh Rate, Slow & Frozen Frames)
1171
+
1172
+ Enable with `refreshRateVitals: true`. Uses native frame callbacks
1173
+ (`CADisplayLink` on iOS, `Choreographer` on Android).
1174
+
1175
+ **Defaults** (override via `frameMonitoringOptions`):
1176
+
1177
+ - **Frozen frame threshold**: 700ms (aligned with Android Vitals)
1178
+ - **Slow frame target**: 60 FPS (event-based grouping; events ≥50ms count)
1179
+ - **Poll interval**: 30s (`refreshRatePollingInterval`)
1180
+
1181
+ Slow and frozen frames are **polled** on both platforms (no duplicate
1182
+ Android event stream). Refresh rate may also emit on Android between polls
1183
+ when `refreshRateVitals` is enabled.
1184
+
1185
+ **Metrics:**
1186
+
1187
+ | Type | Values | Notes |
1188
+ | ------------------ | ---------------------------------- | ----------------------------------------------------- |
1189
+ | `app_refresh_rate` | `refresh_rate` | Current FPS |
1190
+ | `app_frames_rate` | `slow_frames` | Count of slow frame **events**, not individual frames |
1191
+ | `app_frozen_frame` | `frozen_frames`, `frozen_duration` | Frames above threshold; duration in ms |
1192
+
1193
+ **Configuration example:**
1194
+
1195
+ ```tsx
1196
+ initializeFaro({
1197
+ url: 'https://your-faro-collector-url',
1198
+ app: { name: 'my-app', version: '1.0.0' },
1199
+ refreshRateVitals: true,
1200
+ frameMonitoringOptions: {
1201
+ frozenFrameThresholdMs: 700,
1202
+ refreshRatePollingInterval: 30000,
1203
+ },
1204
+ });
1205
+ ```
1206
+
1070
1207
  #### Performance Best Practices
1071
1208
 
1072
1209
  **For Production:**
@@ -1163,13 +1300,13 @@ The SDK collects the following device information synchronously:
1163
1300
 
1164
1301
  ## Device Information
1165
1302
 
1166
- The SDK automatically collects device information and sends it as **session attributes** with every telemetry event. This matches the Faro Flutter SDK convention and provides comprehensive device context for mobile observability.
1303
+ The SDK automatically collects device information and sends it as **session attributes** with every telemetry event.
1167
1304
 
1168
1305
  ### Session Attributes
1169
1306
 
1170
- All device information is sent as session attributes (not browser meta) to match Flutter SDK:
1307
+ All device information is sent as session attributes (not browser meta):
1171
1308
 
1172
- **Core Attributes (matching Flutter SDK):**
1309
+ **Core Attributes:**
1173
1310
 
1174
1311
  - `faro_sdk_version` - SDK version (e.g., "1.0.0")
1175
1312
  - `react_native_version` - React Native version (e.g., "0.75.1")
@@ -1244,7 +1381,7 @@ These attributes are automatically collected during Faro initialization and incl
1244
1381
  - Session attributes are included with every telemetry event
1245
1382
  - All fields are optional and gracefully handle permission errors
1246
1383
  - The React Native SDK sends an empty `page` meta field to override faro-core's default web-specific page meta
1247
- - Screen tracking is handled via `view` meta instead of `page` meta (matching Flutter SDK)
1384
+ - Screen tracking is handled via `view` meta instead of `page` meta
1248
1385
  - Battery, carrier, and low power mode info may not be available on all devices/OS versions
1249
1386
 
1250
1387
  ## TypeScript
@@ -1270,12 +1407,20 @@ See the [demo](../../demo) directory for a complete example application.
1270
1407
  - `faro.api.pushMeasurement(measurement: Measurement)` - Track performance
1271
1408
  - `faro.api.setUser(user: User)` - Identify users
1272
1409
  - `faro.api.resetUser()` - Clear user identification
1410
+ - `startNewSession()` - Top-level API that starts a new session linked to the current session
1273
1411
 
1274
1412
  ### User Actions API
1275
1413
 
1276
1414
  - `withFaroUserAction<P>(Component, defaultActionName)` - HOC for tracking component interactions
1277
1415
  - `trackUserAction(actionName, context?)` - Manual user action tracking
1278
1416
 
1417
+ ### Session Activity API
1418
+
1419
+ - `FaroSessionActivityBoundary` - Refresh session inactivity for touch starts
1420
+ inside a React Native responder subtree without emitting user-action telemetry
1421
+ - `notifySessionActivity()` - Refresh session inactivity for supported
1422
+ interactions outside the boundary
1423
+
1279
1424
  ### Error Boundary API
1280
1425
 
1281
1426
  - `FaroErrorBoundary` - React component for catching and reporting component errors
@@ -1369,6 +1514,7 @@ console.debug('New event', {
1369
1514
  ### React Components
1370
1515
 
1371
1516
  - `FaroErrorBoundary` - Error boundary component for catching React errors
1517
+ - `FaroSessionActivityBoundary` - Touch activity boundary for session inactivity
1372
1518
  - `withFaroErrorBoundary` - HOC for wrapping components with error boundary
1373
1519
 
1374
1520
  ## Future Enhancements
@@ -1,4 +1,12 @@
1
1
  buildscript {
2
+ // Follow the consumer app's Kotlin version. Pinning our own would ship a
3
+ // stdlib the app's compiler cannot read: Kotlin 1.9.x rejects 2.2 metadata,
4
+ // and our peer range still admits react-native >=0.70.0. The fallback only
5
+ // applies to apps whose root buildscript sets no kotlinVersion.
6
+ ext.faroKotlinVersion = rootProject.ext.has('kotlinVersion')
7
+ ? rootProject.ext.get('kotlinVersion')
8
+ : '1.9.25'
9
+
2
10
  repositories {
3
11
  google()
4
12
  mavenCentral()
@@ -6,8 +14,8 @@ buildscript {
6
14
 
7
15
  dependencies {
8
16
  classpath 'com.android.tools.build:gradle:7.4.2'
9
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
10
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
17
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$faroKotlinVersion"
18
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.10.0'
11
19
  }
12
20
  }
13
21
 
@@ -17,20 +25,17 @@ apply plugin: 'com.google.protobuf'
17
25
 
18
26
  android {
19
27
  namespace 'com.grafana.faro.reactnative'
20
- compileSdkVersion 33
28
+ compileSdkVersion 34
21
29
 
22
30
  defaultConfig {
23
31
  minSdkVersion 21
24
- targetSdkVersion 33
32
+ targetSdkVersion 34
25
33
  consumerProguardFiles 'consumer-proguard-rules.pro'
26
34
  }
27
35
 
28
36
  sourceSets {
29
37
  main {
30
38
  java.srcDirs = ['src/main/java']
31
- proto {
32
- srcDir 'src/main/proto'
33
- }
34
39
  }
35
40
  }
36
41
 
@@ -45,7 +50,7 @@ android {
45
50
 
46
51
  protobuf {
47
52
  protoc {
48
- artifact = 'com.google.protobuf:protoc:3.25.1'
53
+ artifact = 'com.google.protobuf:protoc:3.25.9'
49
54
  }
50
55
  generateProtoTasks {
51
56
  all().each { task ->
@@ -66,12 +71,12 @@ repositories {
66
71
  dependencies {
67
72
  //noinspection GradleDynamicVersion
68
73
  implementation 'com.facebook.react:react-native:+'
69
- implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
70
- implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
74
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$faroKotlinVersion"
75
+ implementation 'com.google.protobuf:protobuf-javalite:3.25.9'
71
76
 
72
77
  testImplementation 'junit:junit:4.13.2'
73
- testImplementation 'org.robolectric:robolectric:4.11.1'
74
- testImplementation 'androidx.test:core:1.5.0'
78
+ testImplementation 'org.robolectric:robolectric:4.17'
79
+ testImplementation 'androidx.test:core:1.7.0'
75
80
  }
76
81
 
77
82
  /* ---------------------------------------------------------------------------
@@ -85,7 +90,7 @@ dependencies {
85
90
  * 2. Wrap metro.config.js with `withFaroConfig(...)`
86
91
  * 3. Export FARO_SOURCEMAP_* before running `yarn android --mode=release`.
87
92
  * Bundle id is read from `app/build/faro/bundle-id-release.txt` when the
88
- * `com.grafana.faro` Gradle plugin is applied (same id Metro uses).
93
+ * `com.grafana.faro.android-symbols` Gradle plugin is applied (same id Metro uses).
89
94
  *
90
95
  * On any release-style command (assembleRelease, bundleRelease, installRelease,
91
96
  * `yarn android --mode=release`), release entry tasks depend on
@@ -99,7 +104,7 @@ dependencies {
99
104
  * so secrets do not appear in `ps` or Gradle `--info` command lines.
100
105
  *
101
106
  * Required at build time:
102
- * Bundle id — `app/build/faro/bundle-id-release.txt` from `com.grafana.faro`
107
+ * Bundle id — `app/build/faro/bundle-id-release.txt` from `com.grafana.faro.android-symbols`
103
108
  * (`faroWriteBundleIdRelease`). Same id Metro uses. Forwarded as --bundle-id.
104
109
  *
105
110
  * Required env (Gradle forwards to the Node child process):
@@ -217,7 +222,7 @@ gradle.projectsEvaluated {
217
222
  def resolvedBundleId = resolveFaroBundleId()
218
223
  def missing = []
219
224
  if (resolvedBundleId.isEmpty()) {
220
- missing << "bundle id file ${bundleIdFile} (apply com.grafana.faro and run faroWriteBundleIdRelease)"
225
+ missing << "bundle id file ${bundleIdFile} (apply com.grafana.faro.android-symbols and run faroWriteBundleIdRelease)"
221
226
  } else if (!isValidAndroidBundleId(resolvedBundleId)) {
222
227
  logger.error("[Faro] Skipping composed source map upload — invalid bundle id \"${resolvedBundleId}\" in ${bundleIdFile} (expected applicationId@versionCode@versionName).")
223
228
  return false
@@ -0,0 +1,44 @@
1
+ package com.grafana.faro.reactnative
2
+
3
+ import java.security.MessageDigest
4
+
5
+ internal object FaroCrashIds {
6
+ private const val SCHEMA_VERSION = 1
7
+
8
+ fun sessionContextId(
9
+ sessionId: String,
10
+ activatedAtMs: Long,
11
+ pid: Int,
12
+ processName: String,
13
+ ): String {
14
+ return digest("context", sessionId, activatedAtMs, pid, processName)
15
+ }
16
+
17
+ fun reportId(
18
+ packageName: String,
19
+ timestampMs: Long,
20
+ pid: Int,
21
+ processName: String,
22
+ ): String {
23
+ return digest("report", packageName, timestampMs, pid, processName)
24
+ }
25
+
26
+ private fun digest(kind: String, vararg parts: Any): String {
27
+ val input = buildString {
28
+ append(SCHEMA_VERSION)
29
+ append('|')
30
+ append(kind)
31
+ for (part in parts) {
32
+ val value = part.toString()
33
+ append('|')
34
+ append(value.length)
35
+ append(':')
36
+ append(value)
37
+ }
38
+ }
39
+ val hash = MessageDigest.getInstance("SHA-256")
40
+ .digest(input.toByteArray(Charsets.UTF_8))
41
+ .joinToString("") { byte -> "%02x".format(byte.toInt() and 0xff) }
42
+ return "v$SCHEMA_VERSION:$hash"
43
+ }
44
+ }