@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
@@ -7,15 +7,12 @@ import type { StartupInstrumentationOptions } from './types';
7
7
  * - iOS: sysctl() to query kernel for process start time
8
8
  * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)
9
9
  *
10
- * Implementation aligned with Faro Flutter SDK:
11
- * https://github.com/grafana/faro-flutter-sdk
12
- *
13
10
  * **Key Features**:
14
11
  * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!
15
12
  * - ✅ Cold start: appStartDuration from native, coldStart: 1
16
13
  * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0
17
14
  *
18
- * **Metrics Captured** (matches Flutter SDK format):
15
+ * **Metrics Captured**:
19
16
  * - Cold start: `appStartDuration`, `coldStart: 1`
20
17
  * - Warm start: `appStartDuration`, `coldStart: 0`
21
18
  *
@@ -37,7 +34,7 @@ import type { StartupInstrumentationOptions } from './types';
37
34
  export declare class StartupInstrumentation extends BaseInstrumentation {
38
35
  private options;
39
36
  readonly name = "@grafana/faro-react-native:instrumentation-startup";
40
- readonly version = "2.7.0";
37
+ readonly version = "2.8.2";
41
38
  /** 0 = came from background (warm start eligible), null = never backgrounded */
42
39
  private warmStartTimestamp;
43
40
  private appStateSubscription;
@@ -45,12 +42,11 @@ export declare class StartupInstrumentation extends BaseInstrumentation {
45
42
  initialize(): void;
46
43
  /**
47
44
  * Captures cold start duration from native (process start to Faro init).
48
- * Matches Flutter SDK: appStartDuration + coldStart: 1
45
+ * Sends appStartDuration with coldStart: 1.
49
46
  */
50
47
  private captureColdStartMetrics;
51
48
  /**
52
49
  * Tracks warm start: when app resumes from background, measures time to first frame.
53
- * Matches Flutter SDK: setWarmStart on resume, getWarmStart after postFrameCallback.
54
50
  */
55
51
  private setupWarmStartTracking;
56
52
  unpatch(): void;
@@ -24,7 +24,7 @@ import { BaseInstrumentation } from '@grafana/faro-core';
24
24
  */
25
25
  export declare class UserActionInstrumentation extends BaseInstrumentation {
26
26
  readonly name = "@grafana/faro-react-native:instrumentation-user-action";
27
- readonly version = "2.7.0";
27
+ readonly version = "2.8.2";
28
28
  private _userActionSub?;
29
29
  initialize(): void;
30
30
  /**
@@ -9,7 +9,7 @@ import { BaseInstrumentation } from '@grafana/faro-core';
9
9
  */
10
10
  export declare class ViewInstrumentation extends BaseInstrumentation {
11
11
  readonly name = "@grafana/faro-react-native:instrumentation-view";
12
- readonly version = "2.7.0";
12
+ readonly version = "2.8.2";
13
13
  private notifiedView;
14
14
  private metaUnsubscribe;
15
15
  private sendViewChangedEvent;
@@ -7,7 +7,7 @@ import { BaseInstrumentation } from '@grafana/faro-core';
7
7
  */
8
8
  export declare class XHRInstrumentation extends BaseInstrumentation {
9
9
  readonly name = "@grafana/faro-react-native:instrumentation-xhr";
10
- readonly version = "2.7.0";
10
+ readonly version = "2.8.2";
11
11
  private originalOpen?;
12
12
  private originalSend?;
13
13
  private ignoredUrls;
@@ -1,4 +1,5 @@
1
1
  import type { NavigationState, PartialState, Route } from '@react-navigation/native';
2
+ export declare const EVENT_NAVIGATION = "navigation";
2
3
  /**
3
4
  * Gets the currently active route from navigation state
4
5
  */
@@ -5,6 +5,6 @@
5
5
  import { BaseInstrumentation } from '@grafana/faro-core';
6
6
  export declare class TracingInstrumentation extends BaseInstrumentation {
7
7
  readonly name = "@grafana/faro-react-native-tracing";
8
- readonly version = "2.7.0";
8
+ readonly version = "2.8.2";
9
9
  initialize(): void;
10
10
  }
@@ -4,7 +4,7 @@ import type { ConsoleTransportOptions } from './types';
4
4
  export declare class ConsoleTransport extends BaseTransport {
5
5
  private options;
6
6
  readonly name = "@grafana/faro-react-native:transport-console";
7
- readonly version = "2.7.0";
7
+ readonly version = "2.8.2";
8
8
  constructor(options?: ConsoleTransportOptions);
9
9
  send(item: TransportItem): void;
10
10
  }
@@ -1,2 +1,2 @@
1
1
  export { FetchTransport } from './transport';
2
- export type { FetchTransportOptions, FetchTransportRequestOptions } from './types';
2
+ export type { FetchTransportOptions, FetchTransportRequestOptions, FetchTransportSendOutcome, FetchTransportSendResult, } from './types';
@@ -1,10 +1,10 @@
1
1
  import { BaseTransport } from '@grafana/faro-core';
2
2
  import type { Patterns, PromiseBuffer, TransportItem } from '@grafana/faro-core';
3
- import type { FetchTransportOptions } from './types';
3
+ import type { FetchTransportOptions, FetchTransportSendResult } from './types';
4
4
  export declare class FetchTransport extends BaseTransport {
5
5
  private options;
6
6
  readonly name = "@grafana/faro-react-native:transport-fetch";
7
- readonly version = "2.7.0";
7
+ readonly version = "2.8.2";
8
8
  promiseBuffer: PromiseBuffer<Response | void>;
9
9
  private readonly rateLimitBackoffMs;
10
10
  private readonly getNow;
@@ -28,6 +28,13 @@ export declare class FetchTransport extends BaseTransport {
28
28
  */
29
29
  private waitForSession;
30
30
  send(items: TransportItem[]): Promise<void>;
31
+ /**
32
+ * Sends a batch and reports whether the collector accepted it.
33
+ * The request header uses the live session for collector accounting. Payload
34
+ * metadata remains independent so delayed signals can retain their original
35
+ * session.
36
+ */
37
+ sendWithResult(items: TransportItem[]): Promise<FetchTransportSendResult>;
31
38
  getIgnoreUrls(): Patterns;
32
39
  isBatched(): boolean;
33
40
  private getRetryAfterDate;
@@ -11,3 +11,8 @@ export interface FetchTransportOptions {
11
11
  requestOptions?: FetchTransportRequestOptions;
12
12
  }
13
13
  export type ClockFn = () => number;
14
+ export type FetchTransportSendOutcome = 'accepted' | 'rejected' | 'failed' | 'skipped';
15
+ export interface FetchTransportSendResult {
16
+ outcome: FetchTransportSendOutcome;
17
+ status?: number;
18
+ }
@@ -5,7 +5,7 @@ import type { ConnectivityService } from './types';
5
5
  * Uses @react-native-community/netinfo when available, otherwise falls back
6
6
  * to basic fetch-based connectivity detection.
7
7
  *
8
- * Implementation follows Flutter SDK's InternetConnectivityService pattern.
8
+ * Monitors network connectivity for offline transport flush behavior.
9
9
  */
10
10
  export declare class DefaultConnectivityService implements ConnectivityService {
11
11
  private _isOnline;
@@ -3,7 +3,7 @@ import type { CachedPayload, OfflineCache } from './types';
3
3
  * File-based offline cache implementation using AsyncStorage.
4
4
  *
5
5
  * Stores cached telemetry payloads as JSONL (one JSON object per line)
6
- * following the Flutter SDK's offline transport pattern.
6
+ * Persistent offline telemetry cache backed by AsyncStorage.
7
7
  *
8
8
  * Uses a mutex pattern for thread-safe file access.
9
9
  */
@@ -22,7 +22,7 @@ export declare class AsyncStorageOfflineCache implements OfflineCache {
22
22
  getCount(): Promise<number>;
23
23
  /**
24
24
  * Execute an operation with a mutex lock to ensure thread-safe access.
25
- * Follows Flutter SDK's Completer-based lock pattern.
25
+ * Async lock to serialize cache read/write operations.
26
26
  */
27
27
  private withLock;
28
28
  private readAllInternal;
@@ -8,8 +8,7 @@ import type { OfflineTransportOptions } from './types';
8
8
  * When the device is offline, telemetry is cached to AsyncStorage.
9
9
  * When connectivity is restored, cached telemetry is replayed through the wrapped transports.
10
10
  *
11
- * Implementation follows Flutter SDK's OfflineTransport pattern:
12
- * - Uses AsyncStorage for persistent caching (matching Flutter's SharedPreferences)
11
+ * Uses AsyncStorage for persistent offline telemetry caching.
13
12
  * - Respects maxCacheDuration to skip expired entries
14
13
  * - Uses mutex pattern for thread-safe cache access
15
14
  * - Excludes itself when replaying to prevent infinite loops
@@ -31,7 +30,7 @@ import type { OfflineTransportOptions } from './types';
31
30
  */
32
31
  export declare class OfflineTransport extends BaseTransport {
33
32
  readonly name = "@grafana/faro-react-native:transport-offline";
34
- readonly version = "2.7.0";
33
+ readonly version = "2.8.2";
35
34
  private readonly cache;
36
35
  private readonly connectivityService;
37
36
  private readonly maxCacheDurationMs;
@@ -72,12 +71,12 @@ export declare class OfflineTransport extends BaseTransport {
72
71
  dispose(): void;
73
72
  /**
74
73
  * Replay cached payloads through other transports.
75
- * Follows Flutter SDK's _readFromFile pattern.
74
+ * Reads cached telemetry from AsyncStorage.
76
75
  */
77
76
  private replayCachedPayloads;
78
77
  /**
79
78
  * Send items to all other registered transports.
80
- * Follows Flutter SDK's _sendCachedData pattern.
79
+ * Sends cached telemetry when connectivity is restored.
81
80
  */
82
81
  private sendToOtherTransports;
83
82
  }
@@ -6,8 +6,7 @@ import type { UserPersistence, UserPersistenceOptions } from './types';
6
6
  * Stores user information persistently so it can be restored on app restart.
7
7
  * This ensures early telemetry events include user identification.
8
8
  *
9
- * Implementation follows Flutter SDK's UserPersistence pattern:
10
- * - Uses AsyncStorage for persistence (matching Flutter's SharedPreferences)
9
+ * Uses AsyncStorage for persistent user data across app sessions.
11
10
  * - Stores user as JSON
12
11
  * - Handles null/cleared users
13
12
  *
@@ -21,6 +21,17 @@ public class FaroCrashReporter: NSObject {
21
21
 
22
22
  private static var crashReporter: PLCrashReporter?
23
23
  private static var isEnabled = false
24
+ private static let stateLock = NSLock()
25
+ // activatedAt keeps the persisted context aligned with Android. iOS does not
26
+ // use it to match a pending PLCrashReporter report.
27
+ private static let sessionContextKeys = [
28
+ "sessionId",
29
+ "activatedAt",
30
+ "isSampled",
31
+ "appVersion",
32
+ "appRelease",
33
+ "appBundleId"
34
+ ]
24
35
 
25
36
  /// Signal descriptions for human-readable crash messages.
26
37
  private static let signalDescriptions: [String: String] = [
@@ -50,6 +61,12 @@ public class FaroCrashReporter: NSObject {
50
61
  ///
51
62
  /// - Returns: true if successfully enabled, false otherwise
52
63
  @objc public static func enable() -> Bool {
64
+ stateLock.lock()
65
+ defer { stateLock.unlock() }
66
+ return enableLocked()
67
+ }
68
+
69
+ private static func enableLocked() -> Bool {
53
70
  guard !isEnabled else {
54
71
  return true
55
72
  }
@@ -90,53 +107,145 @@ public class FaroCrashReporter: NSObject {
90
107
  }
91
108
  }
92
109
 
93
- /// Gets crash reports from previous sessions as JSON strings.
110
+ /// Records the active Faro session in PLCrashReporter's crash-safe custom data.
94
111
  ///
95
- /// Returns an array of JSON strings, each representing a crash report.
96
- /// The JSON format matches the Android implementation for consistency.
97
- /// After calling this method, pending crash reports are purged.
112
+ /// PLCrashReporter copies this data into the report at crash time, allowing a
113
+ /// recovered crash to retain its original session after the app restarts.
114
+ /// Keep this payload minimal because it is embedded in every crash report.
115
+ ///
116
+ /// - Parameter sessionContext: Sanitized session and app fields from JavaScript
117
+ /// - Returns: true when the context was stored, false when it was invalid
118
+ @objc public static func recordSessionContext(_ sessionContext: [String: Any]) -> Bool {
119
+ let sanitizedContext = sanitizeSessionContext(sessionContext)
120
+ guard sanitizedContext["sessionId"] != nil,
121
+ JSONSerialization.isValidJSONObject(sanitizedContext) else {
122
+ return false
123
+ }
124
+
125
+ do {
126
+ let data = try JSONSerialization.data(withJSONObject: sanitizedContext)
127
+ stateLock.lock()
128
+ defer { stateLock.unlock() }
129
+ guard enableLocked(), let reporter = crashReporter else {
130
+ return false
131
+ }
132
+ reporter.customData = data
133
+ return true
134
+ } catch {
135
+ print("[FaroCrashReporter] Failed to serialize crash session context: \(error)")
136
+ return false
137
+ }
138
+ }
139
+
140
+ /// Gets crash reports from previous sessions without deleting them.
141
+ ///
142
+ /// JavaScript acknowledges a report only after successful delivery or an
143
+ /// intentional local discard.
98
144
  ///
99
145
  /// - Returns: Array of crash report JSON strings, or nil if no crashes
100
- @objc public static func getCrashReports() -> [String]? {
101
- // Ensure crash reporter is enabled
102
- guard enable() else {
146
+ @objc public static func getPendingCrashReports() -> [String]? {
147
+ guard let reporter = enabledReporter(), reporter.hasPendingCrashReport() else {
103
148
  return nil
104
149
  }
105
150
 
106
- guard let reporter = crashReporter else {
151
+ do {
152
+ let data = try reporter.loadPendingCrashReportDataAndReturnError()
153
+ let reportId = reportIdentifier(for: data)
154
+
155
+ do {
156
+ let plcrReport = try PLCrashReport(data: data)
157
+ let crashReport = try FaroCrashReport(from: plcrReport)
158
+ if let json = exportCrashReportAsJSON(crashReport, reportId: reportId) {
159
+ return [json]
160
+ }
161
+ } catch {
162
+ print("[FaroCrashReporter] Failed to parse pending crash report: \(error)")
163
+ }
164
+
165
+ // Return a stable marker so JavaScript can acknowledge an unreadable
166
+ // report rather than assigning it to the new session or retrying forever.
167
+ return [malformedReportJSON(reportId: reportId)]
168
+ } catch {
169
+ print("[FaroCrashReporter] Failed to load pending crash report: \(error)")
170
+ _ = purgeUnreadablePendingReport(reporter)
107
171
  return nil
108
172
  }
173
+ }
109
174
 
175
+ /// Purges the pending report only when its stable ID was acknowledged.
176
+ ///
177
+ /// - Parameter reportIds: Stable IDs handled by JavaScript
178
+ /// - Returns: true when no matching report remains or the matching report was purged
179
+ @objc public static func acknowledgeCrashReports(_ reportIds: [String]) -> Bool {
180
+ guard let reporter = enabledReporter() else {
181
+ return false
182
+ }
110
183
  guard reporter.hasPendingCrashReport() else {
111
- return nil
184
+ return true
112
185
  }
113
186
 
114
- var crashReports: [String] = []
187
+ let acknowledgedIds = Set(
188
+ reportIds
189
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
190
+ .filter { !$0.isEmpty }
191
+ )
192
+ guard !acknowledgedIds.isEmpty else {
193
+ return true
194
+ }
115
195
 
116
196
  do {
117
197
  let data = try reporter.loadPendingCrashReportDataAndReturnError()
118
- let plcrReport = try PLCrashReport(data: data)
119
- let crashReport = try FaroCrashReport(from: plcrReport)
120
-
121
- // Convert to JSON matching Android format
122
- if let jsonString = exportCrashReportAsJSON(crashReport) {
123
- crashReports.append(jsonString)
198
+ guard acknowledgedIds.contains(reportIdentifier(for: data)) else {
199
+ return true
124
200
  }
201
+ return reporter.purgePendingCrashReport()
125
202
  } catch {
126
- print("[FaroCrashReporter] Failed to load crash report: \(error)")
203
+ print("[FaroCrashReporter] Failed to acknowledge pending crash report: \(error)")
204
+ return purgeUnreadablePendingReport(reporter)
127
205
  }
206
+ }
128
207
 
129
- // Purge the crash report after loading
130
- reporter.purgePendingCrashReport()
208
+ /// Gets crash reports from previous sessions as JSON strings.
209
+ ///
210
+ /// Returns an array of JSON strings, each representing a crash report.
211
+ /// The JSON format matches the Android implementation for consistency.
212
+ /// This legacy API eagerly purges returned reports for older JavaScript bundles
213
+ /// that cannot acknowledge delivery.
214
+ ///
215
+ /// - Returns: Array of crash report JSON strings, or nil if no crashes
216
+ @objc public static func getCrashReports() -> [String]? {
217
+ guard let crashReports = getPendingCrashReports() else {
218
+ return nil
219
+ }
131
220
 
132
- return crashReports.isEmpty ? nil : crashReports
221
+ let reportIds = crashReports.compactMap(reportIdentifier(from:))
222
+ _ = acknowledgeCrashReports(reportIds)
223
+ return crashReports
133
224
  }
134
225
 
135
226
  // MARK: - Private Helpers
136
227
 
228
+ private static func enabledReporter() -> PLCrashReporter? {
229
+ stateLock.lock()
230
+ defer { stateLock.unlock() }
231
+ guard enableLocked() else {
232
+ return nil
233
+ }
234
+ return crashReporter
235
+ }
236
+
237
+ private static func purgeUnreadablePendingReport(_ reporter: PLCrashReporter) -> Bool {
238
+ let purged = reporter.purgePendingCrashReport()
239
+ if !purged {
240
+ print("[FaroCrashReporter] Failed to purge unreadable pending crash report")
241
+ }
242
+ return purged
243
+ }
244
+
137
245
  /// Converts a FaroCrashReport to JSON string matching the Android format.
138
- private static func exportCrashReportAsJSON(_ crashReport: FaroCrashReport) -> String? {
246
+ private static func exportCrashReportAsJSON(_ crashReport: FaroCrashReport, reportId: String) -> String? {
139
247
  var json: [String: Any] = [:]
248
+ json["reportId"] = reportId
140
249
 
141
250
  // Reason - matches Android's "reason" field
142
251
  // For iOS, we use the signal name as the reason
@@ -176,6 +285,14 @@ public class FaroCrashReporter: NSObject {
176
285
  json["incidentId"] = incidentId
177
286
  }
178
287
 
288
+ if let sessionContext = decodeSessionContext(crashReport.contextData) {
289
+ for key in sessionContextKeys {
290
+ if let value = sessionContext[key] {
291
+ json[key] = value
292
+ }
293
+ }
294
+ }
295
+
179
296
  // Convert to JSON string
180
297
  do {
181
298
  let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
@@ -186,6 +303,82 @@ public class FaroCrashReporter: NSObject {
186
303
  }
187
304
  }
188
305
 
306
+ private static func sanitizeSessionContext(_ context: [String: Any]) -> [String: Any] {
307
+ var sanitized: [String: Any] = [:]
308
+
309
+ if let sessionId = nonEmptyString(context["sessionId"]) {
310
+ sanitized["sessionId"] = sessionId
311
+ }
312
+ // Android uses activatedAt to map a crash to persisted session history.
313
+ // iOS keeps it in the shared context shape, although PLCrashReporter
314
+ // already snapshots the one active context directly into the report.
315
+ if let activatedAt = context["activatedAt"] as? NSNumber,
316
+ activatedAt.doubleValue.isFinite,
317
+ activatedAt.doubleValue > 0 {
318
+ sanitized["activatedAt"] = activatedAt
319
+ }
320
+ if let isSampled = context["isSampled"] as? NSNumber {
321
+ sanitized["isSampled"] = isSampled.boolValue
322
+ }
323
+ for key in ["appVersion", "appRelease", "appBundleId"] {
324
+ if let value = nonEmptyString(context[key]) {
325
+ sanitized[key] = value
326
+ }
327
+ }
328
+
329
+ return sanitized
330
+ }
331
+
332
+ private static func decodeSessionContext(_ data: Data?) -> [String: Any]? {
333
+ guard let data else {
334
+ return nil
335
+ }
336
+ do {
337
+ guard let context = try JSONSerialization.jsonObject(with: data) as? [String: Any],
338
+ nonEmptyString(context["sessionId"]) != nil else {
339
+ return nil
340
+ }
341
+ return sanitizeSessionContext(context)
342
+ } catch {
343
+ print("[FaroCrashReporter] Failed to decode crash session context: \(error)")
344
+ return nil
345
+ }
346
+ }
347
+
348
+ private static func nonEmptyString(_ value: Any?) -> String? {
349
+ guard let string = value as? String else {
350
+ return nil
351
+ }
352
+ let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
353
+ return trimmed.isEmpty ? nil : trimmed
354
+ }
355
+
356
+ private static func malformedReportJSON(reportId: String) -> String {
357
+ let data = try? JSONSerialization.data(withJSONObject: ["reportId": reportId])
358
+ return data.flatMap { String(data: $0, encoding: .utf8) } ?? "{\"reportId\":\"\(reportId)\"}"
359
+ }
360
+
361
+ private static func reportIdentifier(from crashReportJSON: String) -> String? {
362
+ guard let data = crashReportJSON.data(using: .utf8),
363
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
364
+ return nil
365
+ }
366
+ return nonEmptyString(json["reportId"])
367
+ }
368
+
369
+ private static func reportIdentifier(for data: Data) -> String {
370
+ // PLCrashReporter stores a single immutable report. A deterministic hash
371
+ // gives both retrieval and acknowledgement the same stable opaque ID,
372
+ // including when the report itself cannot be parsed.
373
+ var hash: UInt64 = 14_695_981_039_346_656_037
374
+ for byte in data {
375
+ hash ^= UInt64(byte)
376
+ hash = hash &* 1_099_511_628_211
377
+ }
378
+ let hex = String(hash, radix: 16)
379
+ return "ios-\(String(repeating: "0", count: max(0, 16 - hex.count)))\(hex)"
380
+ }
381
+
189
382
  /// Creates a human-readable description of the crash.
190
383
  ///
191
384
  /// This returns just the descriptive portion. The TypeScript layer formats the
@@ -20,6 +20,15 @@ import Foundation
20
20
  /// but include improvements where applicable (e.g., better memory metric).
21
21
  @objc(FaroReactNative)
22
22
  public class FaroReactNative: NSObject {
23
+ /// Stable bundle identity used to isolate persisted session chains.
24
+ @objc public static func getSessionProcessIdentifier() -> String {
25
+ return Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName
26
+ }
27
+
28
+ /// App extensions have an NSExtension dictionary in their main bundle.
29
+ @objc public static func isMainSessionProcess() -> Bool {
30
+ return Bundle.main.object(forInfoDictionaryKey: "NSExtension") == nil
31
+ }
23
32
 
24
33
  // MARK: - App Startup Time
25
34
 
@@ -172,4 +181,19 @@ public class FaroReactNative: NSObject {
172
181
  @objc public static func getCrashReports() -> [String]? {
173
182
  return FaroCrashReporter.getCrashReports()
174
183
  }
184
+
185
+ /// Records the active Faro session in PLCrashReporter's custom crash data.
186
+ @objc public static func recordCrashSessionContext(_ sessionContext: [String: Any]) -> Bool {
187
+ return FaroCrashReporter.recordSessionContext(sessionContext)
188
+ }
189
+
190
+ /// Gets pending crash reports without deleting them.
191
+ @objc public static func getPendingCrashReports() -> [String]? {
192
+ return FaroCrashReporter.getPendingCrashReports()
193
+ }
194
+
195
+ /// Purges only the pending report whose stable ID was acknowledged.
196
+ @objc public static func acknowledgeCrashReports(_ reportIds: [String]) -> Bool {
197
+ return FaroCrashReporter.acknowledgeCrashReports(reportIds)
198
+ }
175
199
  }
@@ -1,4 +1,5 @@
1
1
  #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTInvalidating.h>
2
3
 
3
- @interface FaroReactNativeModule : NSObject <RCTBridgeModule>
4
+ @interface FaroReactNativeModule : NSObject <RCTBridgeModule, RCTInvalidating>
4
5
  @end