@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
@@ -2,10 +2,81 @@
2
2
  #import <React/RCTBridgeModule.h>
3
3
  #import "FaroReactNative-Swift.h"
4
4
 
5
+ static NSLock *FaroSessionPersistenceLock(void)
6
+ {
7
+ static NSLock *lock;
8
+ static dispatch_once_t onceToken;
9
+ dispatch_once(&onceToken, ^{
10
+ lock = [[NSLock alloc] init];
11
+ });
12
+ return lock;
13
+ }
14
+
15
+ // Weak ownership also clears the claim if teardown skips `invalidate`.
16
+ static __weak FaroReactNativeModule *FaroSessionPersistenceOwner;
17
+
18
+ @interface FaroReactNativeModule ()
19
+ - (BOOL)claimSessionPersistenceOwnership;
20
+ - (BOOL)releaseSessionPersistenceOwnership;
21
+ @end
22
+
5
23
  @implementation FaroReactNativeModule
6
24
 
7
25
  RCT_EXPORT_MODULE(FaroReactNativeModule)
8
26
 
27
+ /// Stable process identity used to isolate persisted session chains.
28
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getSessionProcessIdentifier)
29
+ {
30
+ return [FaroReactNative getSessionProcessIdentifier];
31
+ }
32
+
33
+ /// Whether this runtime belongs to the host application's main process.
34
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isMainSessionProcess)
35
+ {
36
+ return @([FaroReactNative isMainSessionProcess]);
37
+ }
38
+
39
+ /// Allows only one React Native runtime in a process to persist sessions.
40
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(claimSessionPersistence)
41
+ {
42
+ return @([self claimSessionPersistenceOwnership]);
43
+ }
44
+
45
+ /// Releases persistence only when this native module instance owns it.
46
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(releaseSessionPersistence)
47
+ {
48
+ return @([self releaseSessionPersistenceOwnership]);
49
+ }
50
+
51
+ - (BOOL)claimSessionPersistenceOwnership
52
+ {
53
+ NSLock *lock = FaroSessionPersistenceLock();
54
+ [lock lock];
55
+ BOOL claimed = FaroSessionPersistenceOwner == nil || FaroSessionPersistenceOwner == self;
56
+ if (claimed) {
57
+ FaroSessionPersistenceOwner = self;
58
+ }
59
+ [lock unlock];
60
+ return claimed;
61
+ }
62
+
63
+ - (BOOL)releaseSessionPersistenceOwnership
64
+ {
65
+ NSLock *lock = FaroSessionPersistenceLock();
66
+ [lock lock];
67
+ BOOL released = FaroSessionPersistenceOwner == self;
68
+ if (released) {
69
+ FaroSessionPersistenceOwner = nil;
70
+ }
71
+ [lock unlock];
72
+ return released;
73
+ }
74
+
75
+ - (void)invalidate
76
+ {
77
+ [self releaseSessionPersistenceOwnership];
78
+ }
79
+
9
80
  /// Synchronous method for immediate access from JavaScript
10
81
  /// Returns app startup duration in milliseconds from process start to current time
11
82
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getAppStartDuration)
@@ -34,9 +105,9 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCpuUsage)
34
105
  /// @param config Dictionary with targetFps, frozenFrameThresholdMs, normalizedRefreshRate
35
106
  RCT_EXPORT_METHOD(startFrameMonitoring:(NSDictionary *)config)
36
107
  {
37
- double targetFps = [[config objectForKey:@"targetFps"] doubleValue] ?: 60.0;
38
- double frozenFrameThresholdMs = [[config objectForKey:@"frozenFrameThresholdMs"] doubleValue] ?: 100.0;
39
- double normalizedRefreshRate = [[config objectForKey:@"normalizedRefreshRate"] doubleValue] ?: 60.0;
108
+ double targetFps = [[config objectForKey:@"targetFps"] doubleValue] ?: RefreshRateVitals.defaultTargetFps;
109
+ double frozenFrameThresholdMs = [[config objectForKey:@"frozenFrameThresholdMs"] doubleValue] ?: RefreshRateVitals.defaultFrozenFrameThresholdMs;
110
+ double normalizedRefreshRate = [[config objectForKey:@"normalizedRefreshRate"] doubleValue] ?: RefreshRateVitals.defaultNormalizedRefreshRate;
40
111
 
41
112
  dispatch_async(dispatch_get_main_queue(), ^{
42
113
  [[RefreshRateVitals shared] configureWithTargetFps:targetFps
@@ -79,12 +150,13 @@ RCT_EXPORT_METHOD(getFrameMetrics:(RCTPromiseResolveBlock)resolve
79
150
  {
80
151
  dispatch_async(dispatch_get_main_queue(), ^{
81
152
  RefreshRateVitals *vitals = [RefreshRateVitals shared];
82
-
153
+ NSDictionary *frozenMetrics = [vitals getAndResetFrozenMetrics];
154
+
83
155
  NSDictionary *metrics = @{
84
156
  @"refreshRate": @([vitals getRefreshRate]),
85
157
  @"slowFrames": @([vitals getAndResetSlowFrames]),
86
- @"frozenFrames": @([vitals getAndResetFrozenFrames]),
87
- @"frozenDurationMs": @([vitals getAndResetFrozenDuration])
158
+ @"frozenFrames": frozenMetrics[@"count"],
159
+ @"frozenDurationMs": frozenMetrics[@"durationMs"]
88
160
  };
89
161
 
90
162
  resolve(metrics);
@@ -121,4 +193,47 @@ RCT_EXPORT_METHOD(getCrashReport:(RCTPromiseResolveBlock)resolve
121
193
  }
122
194
  }
123
195
 
196
+ /// Store the active session synchronously so it is available if the process crashes.
197
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(recordCrashSessionContext:(NSDictionary *)sessionContext)
198
+ {
199
+ return @([FaroReactNative recordCrashSessionContext:sessionContext]);
200
+ }
201
+
202
+ /// Get pending crash reports without deleting them.
203
+ RCT_EXPORT_METHOD(getPendingCrashReports:(RCTPromiseResolveBlock)resolve
204
+ reject:(RCTPromiseRejectBlock)reject)
205
+ {
206
+ NSArray<NSString *> *crashReports = [FaroReactNative getPendingCrashReports];
207
+ if (crashReports != nil && crashReports.count > 0) {
208
+ resolve(crashReports);
209
+ } else {
210
+ resolve([NSNull null]);
211
+ }
212
+ }
213
+
214
+ /// Delete only crash reports that JavaScript has finished handling.
215
+ RCT_EXPORT_METHOD(acknowledgeCrashReports:(NSArray *)reportIds
216
+ resolve:(RCTPromiseResolveBlock)resolve
217
+ reject:(RCTPromiseRejectBlock)reject)
218
+ {
219
+ NSMutableArray<NSString *> *sanitizedReportIds = [NSMutableArray array];
220
+ for (id reportId in reportIds) {
221
+ if (![reportId isKindOfClass:[NSString class]]) {
222
+ continue;
223
+ }
224
+
225
+ NSString *trimmedReportId = [(NSString *)reportId stringByTrimmingCharactersInSet:
226
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
227
+ if (trimmedReportId.length > 0) {
228
+ [sanitizedReportIds addObject:trimmedReportId];
229
+ }
230
+ }
231
+
232
+ if ([FaroReactNative acknowledgeCrashReports:sanitizedReportIds]) {
233
+ resolve([NSNull null]);
234
+ } else {
235
+ reject(@"E_CRASH_ACK_FAILED", @"Failed to acknowledge recovered crash report", nil);
236
+ }
237
+ }
238
+
124
239
  @end
@@ -9,18 +9,22 @@ import UIKit
9
9
  /// Uses CADisplayLink to measure actual frame durations and calculate FPS.
10
10
  /// Handles ProMotion displays (120Hz) by normalizing to 60 FPS baseline.
11
11
  @objc public class RefreshRateVitals: NSObject {
12
+ @objc public static let defaultTargetFps: Double = 60.0
13
+ @objc public static let defaultFrozenFrameThresholdMs: Double = 700.0
14
+ @objc public static let defaultNormalizedRefreshRate: Double = 60.0
15
+
12
16
  private var displayLink: CADisplayLink?
13
17
  private var lastFrameTimestamp: CFTimeInterval?
14
18
  private var nextFrameDuration: CFTimeInterval?
15
19
 
16
20
  /// Normalized refresh rate for backend compatibility (default 60 FPS)
17
- private var normalizedRefreshRate: Double = 60.0
21
+ private var normalizedRefreshRate: Double = RefreshRateVitals.defaultNormalizedRefreshRate
18
22
 
19
23
  /// Target FPS for slow frame detection
20
- private var targetFps: Double = 60.0
24
+ private var targetFps: Double = RefreshRateVitals.defaultTargetFps
21
25
 
22
26
  /// Threshold in seconds for frozen frame detection
23
- private var frozenFrameThreshold: Double = 0.100
27
+ private var frozenFrameThreshold: Double = RefreshRateVitals.defaultFrozenFrameThresholdMs / 1000.0
24
28
 
25
29
  /// Last calculated refresh rate
26
30
  @objc public private(set) var lastRefreshRate: Double = 0.0
@@ -28,11 +32,11 @@ import UIKit
28
32
  /// Count of slow frame events since last reset (not individual frames)
29
33
  @objc public private(set) var slowFrameEventCount: Int = 0
30
34
 
31
- /// Count of frozen frames since last reset
32
- @objc public private(set) var frozenFrameCount: Int = 0
33
-
34
- /// Total duration of frozen frames in milliseconds
35
- @objc public private(set) var frozenFrameDurationMs: Double = 0
35
+ /// Lock for frozen frame count and duration (writer and poll snapshot).
36
+ private let frozenMetricsLock = NSLock()
37
+
38
+ private var frozenFrameCount: Int = 0
39
+ private var frozenFrameDurationMs: Double = 0
36
40
 
37
41
  /// Whether monitoring is active
38
42
  @objc public private(set) var isMonitoring: Bool = false
@@ -60,7 +64,7 @@ import UIKit
60
64
  /// Configure monitoring parameters
61
65
  /// - Parameters:
62
66
  /// - targetFps: Target FPS for slow frame detection (default 60)
63
- /// - frozenFrameThresholdMs: Threshold in ms for frozen frames (default 100)
67
+ /// - frozenFrameThresholdMs: Threshold in ms for frozen frames (default 700)
64
68
  /// - normalizedRefreshRate: Normalized rate for ProMotion displays (default 60)
65
69
  @objc public func configure(
66
70
  targetFps: Double,
@@ -82,8 +86,10 @@ import UIKit
82
86
 
83
87
  // Reset counters
84
88
  slowFrameEventCount = 0
89
+ frozenMetricsLock.lock()
85
90
  frozenFrameCount = 0
86
91
  frozenFrameDurationMs = 0
92
+ frozenMetricsLock.unlock()
87
93
  lastFrameTimestamp = nil
88
94
  nextFrameDuration = nil
89
95
  inSlowFrameEvent = false
@@ -111,18 +117,25 @@ import UIKit
111
117
  return count
112
118
  }
113
119
 
114
- /// Get and reset frozen frame count
115
- @objc public func getAndResetFrozenFrames() -> Int {
120
+ /// Get and reset frozen frame count and duration atomically.
121
+ @objc public func getAndResetFrozenMetrics() -> [String: NSNumber] {
122
+ frozenMetricsLock.lock()
116
123
  let count = frozenFrameCount
117
- frozenFrameCount = 0
118
- return count
119
- }
120
-
121
- /// Get and reset frozen frame duration in milliseconds
122
- @objc public func getAndResetFrozenDuration() -> Double {
123
124
  let duration = frozenFrameDurationMs
125
+ frozenFrameCount = 0
124
126
  frozenFrameDurationMs = 0
125
- return duration
127
+ frozenMetricsLock.unlock()
128
+ return [
129
+ "count": NSNumber(value: count),
130
+ "durationMs": NSNumber(value: duration),
131
+ ]
132
+ }
133
+
134
+ private func recordFrozenFrame(durationMs: Double) {
135
+ frozenMetricsLock.lock()
136
+ frozenFrameCount += 1
137
+ frozenFrameDurationMs += durationMs
138
+ frozenMetricsLock.unlock()
126
139
  }
127
140
 
128
141
  // MARK: - Private Methods
@@ -132,13 +145,7 @@ import UIKit
132
145
  if let lastTimestamp = lastFrameTimestamp {
133
146
  let frameDuration = link.timestamp - lastTimestamp
134
147
  if frameDuration > frozenFrameThreshold {
135
- frozenFrameCount += 1
136
-
137
- // Track duration in milliseconds
138
- let durationMs = frameDuration * 1000
139
- frozenFrameDurationMs += durationMs
140
-
141
- NSLog("[Faro] Frozen frame detected! Duration: %.0fms (threshold: %.0fms)", durationMs, frozenFrameThreshold * 1000)
148
+ recordFrozenFrame(durationMs: frameDuration * 1000)
142
149
  }
143
150
  }
144
151
 
@@ -150,12 +157,7 @@ import UIKit
150
157
  // A slow frame "event" is a period of consecutive frames below target FPS
151
158
  // This groups consecutive slow frames to report meaningful jank, not microsecond variations
152
159
  let isSlow = fps < targetFps
153
-
154
- // 🔍 TEMP DEBUG LOG - Remove after analysis
155
- if isSlow {
156
- NSLog("[Faro DEBUG IOS] 🐌 Slow frame detected: %.1f FPS (target: %.1f)", fps, targetFps)
157
- }
158
-
160
+
159
161
  if isSlow {
160
162
  if !inSlowFrameEvent {
161
163
  // Start new slow frame event
@@ -166,17 +168,11 @@ import UIKit
166
168
  // Frame is good - check if we should end the current slow frame event
167
169
  if inSlowFrameEvent {
168
170
  let eventDuration = link.timestamp - slowFrameEventStartTime
169
- let eventDurationMs = eventDuration * 1000
170
-
171
+
171
172
  // Only count as a slow frame event if it lasted long enough to be user-perceptible
172
173
  // This filters out single-frame dips that don't affect user experience
173
174
  if eventDuration >= slowFrameEventMinDuration {
174
175
  slowFrameEventCount += 1
175
- // 🔍 TEMP DEBUG LOG - Remove after analysis
176
- NSLog("[Faro DEBUG IOS] ✅ COUNTED as event (%.0fms)! Total events now: %d", eventDurationMs, slowFrameEventCount)
177
- } else {
178
- // 🔍 TEMP DEBUG LOG - Remove after analysis
179
- NSLog("[Faro DEBUG IOS] ❌ NOT counted (%.0fms is too short). Total events still: %d", eventDurationMs, slowFrameEventCount)
180
176
  }
181
177
 
182
178
  inSlowFrameEvent = false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/faro-react-native",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Faro instrumentations, metas, transports for React Native.",
5
5
  "keywords": [
6
6
  "observability",
@@ -56,7 +56,7 @@
56
56
  "version": "node ./scripts/sync-faro-rn-package-meta.mjs && git add src/generated/faroRNPackageMeta.ts"
57
57
  },
58
58
  "dependencies": {
59
- "@grafana/faro-core": "^2.7.0",
59
+ "@grafana/faro-core": "^2.8.2",
60
60
  "@react-native-async-storage/async-storage": "^1.21.0",
61
61
  "react-native-device-info": "^11.1.0"
62
62
  },
@@ -76,13 +76,15 @@
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/node": "24.12.4",
79
- "@types/react": "19.2.15",
79
+ "@types/react": "19.2.18",
80
80
  "@types/react-native": "0.73.0",
81
- "react-native-mmkv": "4.3.1",
82
- "react-native-nitro-modules": "0.35.6"
81
+ "@types/react-test-renderer": "19.1.0",
82
+ "react-native-mmkv": "4.3.2",
83
+ "react-native-nitro-modules": "0.36.1",
84
+ "react-test-renderer": "19.2.3"
83
85
  },
84
86
  "publishConfig": {
85
87
  "access": "public"
86
88
  },
87
- "gitHead": "7cd0482f59a7eefad5a812e468d259e17ad39ed6"
89
+ "gitHead": "38d87a3474585e1177c38d2e9acd04860a4236e6"
88
90
  }
@@ -26,8 +26,7 @@ function toRegExpArray(patterns: Patterns): RegExp[] {
26
26
  /**
27
27
  * Returns the default set of instrumentations for React Native.
28
28
  *
29
- * Reads all options from the Faro config (single source of truth).
30
- * Property names are aligned with Flutter SDK FaroConfig:
29
+ * Reads all options from the Faro config (single source of truth):
31
30
  * - cpuUsageVitals, memoryUsageVitals, anrTracking, refreshRateVitals
32
31
  * - enableCrashReporting, enableErrorReporting
33
32
  * - fetchVitalsInterval, ignoreUrls
@@ -47,7 +46,6 @@ function toRegExpArray(patterns: Patterns): RegExp[] {
47
46
  * ```
48
47
  */
49
48
  export function getRNInstrumentations(config: Partial<ReactNativeConfig> = {}): Instrumentation[] {
50
- // Aligned with Flutter SDK FaroConfig defaults
51
49
  const {
52
50
  // Error & crash tracking
53
51
  enableErrorReporting = true,
@@ -77,7 +75,7 @@ export function getRNInstrumentations(config: Partial<ReactNativeConfig> = {}):
77
75
 
78
76
  const instrumentations: Instrumentation[] = [];
79
77
 
80
- // Error reporting (Flutter: enableFlutterErrorReporting)
78
+ // Error reporting
81
79
  if (enableErrorReporting) {
82
80
  instrumentations.push(
83
81
  new ErrorsInstrumentation({
@@ -86,18 +84,18 @@ export function getRNInstrumentations(config: Partial<ReactNativeConfig> = {}):
86
84
  );
87
85
  }
88
86
 
89
- // Console capture - not in Flutter SDK, RN-specific
87
+ // Console capture
90
88
  if (enableConsoleCapture) {
91
89
  instrumentations.push(new ConsoleInstrumentation());
92
90
  }
93
91
 
94
- // Sessions - always enabled in Flutter, same here
92
+ // Sessions
95
93
  instrumentations.push(new SessionInstrumentation());
96
94
 
97
- // Views - always enabled in Flutter, same here
95
+ // Views
98
96
  instrumentations.push(new ViewInstrumentation());
99
97
 
100
- // App state - always enabled in Flutter, same here
98
+ // App state
101
99
  instrumentations.push(new AppStateInstrumentation());
102
100
 
103
101
  // User actions - enabled by default, opt-out via enableUserActions
@@ -108,6 +108,12 @@ describe('makeRNConfig', () => {
108
108
  });
109
109
  });
110
110
 
111
+ it('enables minimal session persistence by default', () => {
112
+ const cfg = makeRNConfig({ ...base });
113
+
114
+ expect(cfg.sessionTracking?.persistent).toBe(true);
115
+ });
116
+
111
117
  it('uses getRNInstrumentations for the instrumentation set from flags', () => {
112
118
  const minimal = makeRNConfig({
113
119
  ...base,
@@ -7,9 +7,6 @@ import type { Meta } from '@grafana/faro-core';
7
7
  * allowing sampling decisions based on session attributes, user info, app
8
8
  * environment, and other metadata.
9
9
  *
10
- * This aligns with the Faro Flutter SDK's `SamplingContext` which provides
11
- * `meta` to the sampler.
12
- *
13
10
  * @example
14
11
  * ```ts
15
12
  * sampling: SamplingFunction((context) => {
@@ -8,11 +8,13 @@ import type { Sampling } from './sampling';
8
8
 
9
9
  /**
10
10
  * React Native session tracking config.
11
- * Extends Config['sessionTracking'] with sampling (Flutter-style) and RN-specific props.
11
+ * Extends Config['sessionTracking'] with sampling and RN-specific props.
12
12
  * Excludes samplingRate and sampler in favor of sampling.
13
13
  *
14
- * When `persistent` is true, sessions are stored with [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv)
15
- * (synchronous). Add `react-native-mmkv` to your app and rebuild native projects.
14
+ * Persistence defaults to true and stores a minimal, versioned record with
15
+ * [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) (synchronous).
16
+ * Add `react-native-mmkv` to your app and rebuild native projects, or set
17
+ * `persistent` to false to use in-memory sessions.
16
18
  */
17
19
  export type ReactNativeSessionTrackingConfig = Omit<
18
20
  NonNullable<Config['sessionTracking']>,
@@ -13,8 +13,7 @@ const DEFAULT_STORAGE_KEY = 'faro_enable_data_collection';
13
13
  * Controls whether telemetry data is collected and sent.
14
14
  * The setting is persisted to AsyncStorage so it survives app restarts.
15
15
  *
16
- * Implementation follows Flutter SDK's DataCollectionPolicy pattern:
17
- * - Uses AsyncStorage for persistence (matching Flutter's SharedPreferences)
16
+ * Uses AsyncStorage for persisting data collection policy state.
18
17
  * - Provides enable/disable methods
19
18
  * - Initializes from persisted value or default
20
19
  *
@@ -2,4 +2,4 @@
2
2
  * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.
3
3
  */
4
4
  export const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;
5
- export const FARO_REACT_NATIVE_NPM_VERSION = '1.3.0' as const;
5
+ export const FARO_REACT_NATIVE_NPM_VERSION = '1.4.0' as const;
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ export { faro } from '@grafana/faro-core';
8
8
  // Export types
9
9
  export type { EnableTransportsConfig, ReactNativeConfig, ReactNativeSessionTrackingConfig } from './config/types';
10
10
 
11
- // Export sampling (Flutter-style interface)
11
+ // Export sampling
12
12
  export { SamplingFunction, SamplingRate, type Sampling, type SamplingContext } from './config/sampling';
13
13
 
14
14
  // Export instrumentation helpers
@@ -18,7 +18,12 @@ export { getRNInstrumentations } from './config/getRNInstrumentations';
18
18
  export { ErrorMechanism, ErrorsInstrumentation } from './instrumentations/errors';
19
19
  export type { ErrorMechanismType, ErrorsInstrumentationOptions } from './instrumentations/errors';
20
20
  export { ConsoleInstrumentation } from './instrumentations/console';
21
- export { SessionInstrumentation } from './instrumentations/session';
21
+ export { startNewSession, SessionInstrumentation } from './instrumentations/session';
22
+ export {
23
+ FaroSessionActivityBoundary,
24
+ type FaroSessionActivityBoundaryProps,
25
+ } from './instrumentations/session/FaroSessionActivityBoundary';
26
+ export { notifySessionActivity } from './instrumentations/session/directSessionActivity';
22
27
  export {
23
28
  getSessionAttributes,
24
29
  loadMobileMetaForInit,
@@ -70,6 +75,7 @@ export { getSdkMeta } from './metas/sdk';
70
75
 
71
76
  // Export transports
72
77
  export { FetchTransport } from './transports/fetch';
78
+ export type { FetchTransportSendOutcome, FetchTransportSendResult } from './transports/fetch';
73
79
  export { ConsoleTransport } from './transports/console';
74
80
  export type { ConsoleTransportOptions } from './transports/console';
75
81
  export { OfflineTransport } from './transports/offline';
@@ -73,7 +73,14 @@ export class ANRInstrumentation extends BaseInstrumentation {
73
73
  this.startNativeTracking(nativeModule);
74
74
 
75
75
  const emitter = new NativeEventEmitter(nativeModule);
76
- this.anrEventSubscription = emitter.addListener('onANRDetected', (anrJson: string) => {
76
+ this.anrEventSubscription = emitter.addListener('onANRDetected', (...args: readonly Object[]) => {
77
+ const [anrJson] = args;
78
+
79
+ if (typeof anrJson !== 'string') {
80
+ this.logWarn('Ignoring onANRDetected event without a string payload');
81
+ return;
82
+ }
83
+
77
84
  void this.handleLiveAnrEvent(nativeModule, anrJson);
78
85
  });
79
86
 
@@ -2,9 +2,16 @@ import { AppState, type AppStateStatus } from 'react-native';
2
2
 
3
3
  import { BaseInstrumentation, dateNow, VERSION } from '@grafana/faro-core';
4
4
 
5
- // Event name aligned with Faro Flutter SDK (app_lifecycle_changed)
6
5
  export const EVENT_APP_STATE_CHANGED = 'app_lifecycle_changed';
7
6
 
7
+ const APP_STATE_STATUSES = ['inactive', 'background', 'active', 'extension', 'unknown'] as const;
8
+
9
+ // RN types AppState.currentState as `null | undefined | string`: it stays null
10
+ // until the initial value is set, so narrow it instead of trusting the shape.
11
+ function isAppStateStatus(value: unknown): value is AppStateStatus {
12
+ return typeof value === 'string' && (APP_STATE_STATUSES as readonly string[]).includes(value);
13
+ }
14
+
8
15
  /**
9
16
  * AppState instrumentation for React Native
10
17
  * Tracks app foreground/background/inactive state changes
@@ -22,7 +29,8 @@ export class AppStateInstrumentation extends BaseInstrumentation {
22
29
 
23
30
  initialize(): void {
24
31
  // Get initial app state
25
- this.currentState = AppState.currentState;
32
+ const initialState = AppState.currentState;
33
+ this.currentState = isAppStateStatus(initialState) ? initialState : undefined;
26
34
  this.stateStartTime = dateNow();
27
35
 
28
36
  this.logInfo('AppState instrumentation initialized', {
@@ -1,10 +1,17 @@
1
1
  import { NativeModules } from 'react-native';
2
2
 
3
3
  import { BaseInstrumentation } from '@grafana/faro-core';
4
+ import type { ExceptionStackFrame } from '@grafana/faro-core';
4
5
 
5
6
  import { looksLikeNativeTombstoneTrace } from './android/nativeTombstoneTrace';
6
7
  import type { CrashReport, CrashReportingOptions } from './types';
7
8
 
9
+ interface CrashReportDetails {
10
+ context: Record<string, string>;
11
+ errorMessage: string;
12
+ stackFrames: ExceptionStackFrame[];
13
+ }
14
+
8
15
  /**
9
16
  * Abstract base class for platform-specific crash reporting implementations.
10
17
  *
@@ -39,6 +46,7 @@ export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentat
39
46
  }
40
47
 
41
48
  this.logDebug('Initializing crash reporting instrumentation');
49
+ await this.prepareCrashReporting(nativeModule);
42
50
 
43
51
  // Wait for session attributes before sending crash reports
44
52
  await this.waitForSessionAttributes();
@@ -82,7 +90,11 @@ export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentat
82
90
  */
83
91
  protected abstract getTraceUnavailableWarning(): string;
84
92
 
85
- private async waitForSessionAttributes(maxWaitMs = 10000): Promise<void> {
93
+ protected prepareCrashReporting(_nativeModule: typeof NativeModules.FaroReactNativeModule): void | Promise<void> {
94
+ // Optional platform-specific setup.
95
+ }
96
+
97
+ protected async waitForSessionAttributes(maxWaitMs = 10000): Promise<void> {
86
98
  const startTime = Date.now();
87
99
  const pollInterval = 200;
88
100
  let checkCount = 0;
@@ -134,7 +146,7 @@ export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentat
134
146
  );
135
147
  }
136
148
 
137
- private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {
149
+ protected getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {
138
150
  const { FaroReactNativeModule } = NativeModules;
139
151
 
140
152
  if (!FaroReactNativeModule) {
@@ -144,7 +156,7 @@ export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentat
144
156
  return FaroReactNativeModule;
145
157
  }
146
158
 
147
- private async processCrashReports(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {
159
+ protected async processCrashReports(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {
148
160
  try {
149
161
  if (typeof nativeModule.getCrashReport !== 'function') {
150
162
  this.logDebug('getCrashReport method not available');
@@ -201,15 +213,11 @@ export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentat
201
213
  }
202
214
  }
203
215
 
204
- private sendCrashReport(crash: CrashReport): void {
216
+ protected buildCrashReportDetails(crash: CrashReport): CrashReportDetails {
205
217
  // Platform-specific parsing
206
218
  const parsedTrace = crash.trace ? this.parseCrashTrace(crash.trace) : null;
207
219
  const errorMessage = this.resolveCrashErrorMessage(crash, parsedTrace).trim() || 'Application crash';
208
220
 
209
- // Use a message-only Error so pushError does not capture the JS reporter stack
210
- const error = new Error(errorMessage);
211
- error.stack = undefined;
212
-
213
221
  // Build context from crash data
214
222
  const context: Record<string, string> = {
215
223
  mechanism: 'crash',
@@ -258,6 +266,18 @@ export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentat
258
266
  );
259
267
  }
260
268
 
269
+ return {
270
+ context,
271
+ errorMessage,
272
+ stackFrames,
273
+ };
274
+ }
275
+
276
+ protected sendCrashReport(crash: CrashReport): void {
277
+ const { context, errorMessage, stackFrames } = this.buildCrashReportDetails(crash);
278
+ const error = new Error(errorMessage);
279
+ error.stack = undefined;
280
+
261
281
  this.api.pushError(error, {
262
282
  stackFrames,
263
283
  context,