@grafana/faro-react-native 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/README.md +176 -30
  2. package/android/build.gradle +20 -15
  3. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashIds.kt +44 -0
  4. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +134 -38
  5. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashSessionStore.kt +433 -0
  6. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +11 -2
  7. package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +128 -17
  8. package/android/src/main/java/com/grafana/faro/reactnative/FaroSessionProcess.kt +88 -0
  9. package/android/src/main/java/com/grafana/faro/reactnative/FrameMonitor.kt +34 -42
  10. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterBelowApi30Test.kt +21 -0
  11. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterTest.kt +59 -0
  12. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashSessionStoreTest.kt +268 -0
  13. package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashTraceCacheTest.kt +52 -0
  14. package/android/src/test/java/com/grafana/faro/reactnative/FaroSessionProcessTest.kt +127 -0
  15. package/dist/cjs/config/getRNInstrumentations.js +6 -8
  16. package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
  17. package/dist/cjs/config/sampling.js.map +1 -1
  18. package/dist/cjs/config/types.js.map +1 -1
  19. package/dist/cjs/dataCollection/DataCollectionPolicy.js +1 -2
  20. package/dist/cjs/dataCollection/DataCollectionPolicy.js.map +1 -1
  21. package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
  22. package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
  23. package/dist/cjs/index.js +8 -3
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/instrumentations/anr/instrumentation.js +10 -1
  26. package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
  27. package/dist/cjs/instrumentations/appState/index.js +8 -2
  28. package/dist/cjs/instrumentations/appState/index.js.map +1 -1
  29. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +19 -6
  30. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
  31. package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +331 -0
  32. package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
  33. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
  34. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
  35. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
  36. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
  37. package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
  38. package/dist/cjs/instrumentations/errors/index.js +8 -3
  39. package/dist/cjs/instrumentations/errors/index.js.map +1 -1
  40. package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js +20 -31
  41. package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
  42. package/dist/cjs/instrumentations/frameMonitoring/types.js.map +1 -1
  43. package/dist/cjs/instrumentations/performance/index.js +2 -6
  44. package/dist/cjs/instrumentations/performance/index.js.map +1 -1
  45. package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js +48 -0
  46. package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
  47. package/dist/cjs/instrumentations/session/directSessionActivity.js +37 -0
  48. package/dist/cjs/instrumentations/session/directSessionActivity.js.map +1 -0
  49. package/dist/cjs/instrumentations/session/index.js +214 -45
  50. package/dist/cjs/instrumentations/session/index.js.map +1 -1
  51. package/dist/cjs/instrumentations/session/sessionActivity.js +43 -0
  52. package/dist/cjs/instrumentations/session/sessionActivity.js.map +1 -0
  53. package/dist/cjs/instrumentations/session/sessionAttributes.js +8 -8
  54. package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
  55. package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +199 -24
  56. package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
  57. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -13
  58. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  59. package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js +72 -0
  60. package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
  61. package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js +1 -1
  62. package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
  63. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js +124 -96
  64. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  65. package/dist/cjs/instrumentations/session/sessionManager/types.js.map +1 -1
  66. package/dist/cjs/instrumentations/session/sessionProcess.js +96 -0
  67. package/dist/cjs/instrumentations/session/sessionProcess.js.map +1 -0
  68. package/dist/cjs/instrumentations/startup/index.js +2 -6
  69. package/dist/cjs/instrumentations/startup/index.js.map +1 -1
  70. package/dist/cjs/navigation/utils.js +3 -1
  71. package/dist/cjs/navigation/utils.js.map +1 -1
  72. package/dist/cjs/transports/fetch/index.js.map +1 -1
  73. package/dist/cjs/transports/fetch/transport.js +56 -25
  74. package/dist/cjs/transports/fetch/transport.js.map +1 -1
  75. package/dist/cjs/transports/fetch/types.js.map +1 -1
  76. package/dist/cjs/transports/offline/ConnectivityService.js +1 -1
  77. package/dist/cjs/transports/offline/ConnectivityService.js.map +1 -1
  78. package/dist/cjs/transports/offline/OfflineCache.js +2 -2
  79. package/dist/cjs/transports/offline/OfflineCache.js.map +1 -1
  80. package/dist/cjs/transports/offline/transport.js +3 -4
  81. package/dist/cjs/transports/offline/transport.js.map +1 -1
  82. package/dist/cjs/userPersistence/UserPersistence.js +1 -2
  83. package/dist/cjs/userPersistence/UserPersistence.js.map +1 -1
  84. package/dist/esm/config/getRNInstrumentations.js +6 -8
  85. package/dist/esm/config/getRNInstrumentations.js.map +1 -1
  86. package/dist/esm/config/sampling.js.map +1 -1
  87. package/dist/esm/config/types.js.map +1 -1
  88. package/dist/esm/dataCollection/DataCollectionPolicy.js +1 -2
  89. package/dist/esm/dataCollection/DataCollectionPolicy.js.map +1 -1
  90. package/dist/esm/generated/faroRNPackageMeta.js +1 -1
  91. package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
  92. package/dist/esm/index.js +4 -2
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/instrumentations/anr/instrumentation.js +6 -1
  95. package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
  96. package/dist/esm/instrumentations/appState/index.js +8 -2
  97. package/dist/esm/instrumentations/appState/index.js.map +1 -1
  98. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +15 -4
  99. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
  100. package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +230 -0
  101. package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
  102. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
  103. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
  104. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
  105. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
  106. package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
  107. package/dist/esm/instrumentations/errors/index.js +8 -3
  108. package/dist/esm/instrumentations/errors/index.js.map +1 -1
  109. package/dist/esm/instrumentations/frameMonitoring/instrumentation.js +17 -32
  110. package/dist/esm/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
  111. package/dist/esm/instrumentations/frameMonitoring/types.js.map +1 -1
  112. package/dist/esm/instrumentations/performance/index.js +2 -6
  113. package/dist/esm/instrumentations/performance/index.js.map +1 -1
  114. package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js +34 -0
  115. package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
  116. package/dist/esm/instrumentations/session/directSessionActivity.js +32 -0
  117. package/dist/esm/instrumentations/session/directSessionActivity.js.map +1 -0
  118. package/dist/esm/instrumentations/session/index.js +207 -46
  119. package/dist/esm/instrumentations/session/index.js.map +1 -1
  120. package/dist/esm/instrumentations/session/sessionActivity.js +38 -0
  121. package/dist/esm/instrumentations/session/sessionActivity.js.map +1 -0
  122. package/dist/esm/instrumentations/session/sessionAttributes.js +7 -7
  123. package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
  124. package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +190 -27
  125. package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
  126. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -14
  127. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  128. package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js +56 -0
  129. package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
  130. package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js +1 -1
  131. package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
  132. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js +120 -44
  133. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  134. package/dist/esm/instrumentations/session/sessionManager/types.js.map +1 -1
  135. package/dist/esm/instrumentations/session/sessionProcess.js +89 -0
  136. package/dist/esm/instrumentations/session/sessionProcess.js.map +1 -0
  137. package/dist/esm/instrumentations/startup/index.js +2 -6
  138. package/dist/esm/instrumentations/startup/index.js.map +1 -1
  139. package/dist/esm/navigation/utils.js +2 -1
  140. package/dist/esm/navigation/utils.js.map +1 -1
  141. package/dist/esm/transports/fetch/index.js.map +1 -1
  142. package/dist/esm/transports/fetch/transport.js +38 -14
  143. package/dist/esm/transports/fetch/transport.js.map +1 -1
  144. package/dist/esm/transports/fetch/types.js.map +1 -1
  145. package/dist/esm/transports/offline/ConnectivityService.js +1 -1
  146. package/dist/esm/transports/offline/ConnectivityService.js.map +1 -1
  147. package/dist/esm/transports/offline/OfflineCache.js +2 -2
  148. package/dist/esm/transports/offline/OfflineCache.js.map +1 -1
  149. package/dist/esm/transports/offline/transport.js +3 -4
  150. package/dist/esm/transports/offline/transport.js.map +1 -1
  151. package/dist/esm/userPersistence/UserPersistence.js +1 -2
  152. package/dist/esm/userPersistence/UserPersistence.js.map +1 -1
  153. package/dist/types/config/getRNInstrumentations.d.ts +1 -2
  154. package/dist/types/config/sampling.d.ts +0 -3
  155. package/dist/types/config/types.d.ts +5 -3
  156. package/dist/types/dataCollection/DataCollectionPolicy.d.ts +1 -2
  157. package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
  158. package/dist/types/index.d.ts +4 -1
  159. package/dist/types/instrumentations/anr/instrumentation.d.ts +1 -1
  160. package/dist/types/instrumentations/appState/index.d.ts +1 -1
  161. package/dist/types/instrumentations/console/index.d.ts +1 -1
  162. package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
  163. package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +1 -1
  164. package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
  165. package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +3 -3
  166. package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +3 -3
  167. package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
  168. package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +3 -3
  169. package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
  170. package/dist/types/instrumentations/http/index.d.ts +1 -1
  171. package/dist/types/instrumentations/performance/index.d.ts +1 -3
  172. package/dist/types/instrumentations/session/FaroSessionActivityBoundary.d.ts +8 -0
  173. package/dist/types/instrumentations/session/directSessionActivity.d.ts +13 -0
  174. package/dist/types/instrumentations/session/index.d.ts +21 -2
  175. package/dist/types/instrumentations/session/sessionActivity.d.ts +9 -0
  176. package/dist/types/instrumentations/session/sessionAttributes.d.ts +6 -3
  177. package/dist/types/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.d.ts +16 -3
  178. package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +9 -3
  179. package/dist/types/instrumentations/session/sessionManager/persistentSessionRecord.d.ts +4 -0
  180. package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +19 -7
  181. package/dist/types/instrumentations/session/sessionManager/types.d.ts +6 -0
  182. package/dist/types/instrumentations/session/sessionProcess.d.ts +17 -0
  183. package/dist/types/instrumentations/startup/index.d.ts +3 -7
  184. package/dist/types/instrumentations/userActions/index.d.ts +1 -1
  185. package/dist/types/instrumentations/view/index.d.ts +1 -1
  186. package/dist/types/instrumentations/xhr/index.d.ts +1 -1
  187. package/dist/types/navigation/utils.d.ts +1 -0
  188. package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
  189. package/dist/types/transports/console/transport.d.ts +1 -1
  190. package/dist/types/transports/fetch/index.d.ts +1 -1
  191. package/dist/types/transports/fetch/transport.d.ts +9 -2
  192. package/dist/types/transports/fetch/types.d.ts +5 -0
  193. package/dist/types/transports/offline/ConnectivityService.d.ts +1 -1
  194. package/dist/types/transports/offline/OfflineCache.d.ts +2 -2
  195. package/dist/types/transports/offline/transport.d.ts +4 -5
  196. package/dist/types/userPersistence/UserPersistence.d.ts +1 -2
  197. package/ios/FaroCrashReporter.swift +214 -21
  198. package/ios/FaroReactNative.swift +24 -0
  199. package/ios/FaroReactNativeModule.h +2 -1
  200. package/ios/FaroReactNativeModule.mm +121 -6
  201. package/ios/RefreshRateVitals.swift +34 -38
  202. package/package.json +8 -6
  203. package/src/config/getRNInstrumentations.ts +6 -8
  204. package/src/config/makeRNConfig.test.ts +6 -0
  205. package/src/config/sampling.ts +0 -3
  206. package/src/config/types.ts +5 -3
  207. package/src/dataCollection/DataCollectionPolicy.ts +1 -2
  208. package/src/generated/faroRNPackageMeta.ts +1 -1
  209. package/src/index.ts +8 -2
  210. package/src/instrumentations/anr/instrumentation.ts +8 -1
  211. package/src/instrumentations/appState/index.ts +10 -2
  212. package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
  213. package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
  214. package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
  215. package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
  216. package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
  217. package/src/instrumentations/crashReporting/types.ts +7 -0
  218. package/src/instrumentations/errors/index.ts +12 -4
  219. package/src/instrumentations/errors/instrumentation.test.ts +35 -0
  220. package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
  221. package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
  222. package/src/instrumentations/frameMonitoring/types.ts +4 -6
  223. package/src/instrumentations/performance/index.ts +2 -6
  224. package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
  225. package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
  226. package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
  227. package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
  228. package/src/instrumentations/session/directSessionActivity.ts +36 -0
  229. package/src/instrumentations/session/index.ts +269 -46
  230. package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
  231. package/src/instrumentations/session/sessionActivity.test.ts +68 -0
  232. package/src/instrumentations/session/sessionActivity.ts +58 -0
  233. package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
  234. package/src/instrumentations/session/sessionAttributes.ts +12 -6
  235. package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
  236. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
  237. package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
  238. package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
  239. package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
  240. package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
  241. package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
  242. package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
  243. package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
  244. package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
  245. package/src/instrumentations/session/sessionManager/types.ts +8 -0
  246. package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
  247. package/src/instrumentations/session/sessionProcess.test.ts +136 -0
  248. package/src/instrumentations/session/sessionProcess.ts +114 -0
  249. package/src/instrumentations/session/startNewSession.test.ts +337 -0
  250. package/src/instrumentations/startup/index.ts +2 -6
  251. package/src/navigation/utils.ts +3 -1
  252. package/src/transports/fetch/index.ts +6 -1
  253. package/src/transports/fetch/transport.test.ts +86 -2
  254. package/src/transports/fetch/transport.ts +38 -16
  255. package/src/transports/fetch/types.ts +7 -0
  256. package/src/transports/offline/ConnectivityService.ts +1 -1
  257. package/src/transports/offline/OfflineCache.ts +2 -2
  258. package/src/transports/offline/transport.ts +3 -4
  259. package/src/userPersistence/UserPersistence.ts +1 -2
  260. package/dist/cjs/utils/throttle.js +0 -42
  261. package/dist/cjs/utils/throttle.js.map +0 -1
  262. package/dist/esm/utils/throttle.js +0 -34
  263. package/dist/esm/utils/throttle.js.map +0 -1
  264. package/dist/types/utils/throttle.d.ts +0 -9
  265. package/src/utils/throttle.test.ts +0 -141
  266. package/src/utils/throttle.ts +0 -34
@@ -0,0 +1,277 @@
1
+ import { NativeModules } from 'react-native';
2
+
3
+ import { TransportItemType } from '@grafana/faro-core';
4
+ import type { ExceptionEvent, Meta, TransportItem } from '@grafana/faro-core';
5
+
6
+ import { FetchTransport } from '../../transports/fetch';
7
+
8
+ import { BaseCrashReportingInstrumentation } from './BaseCrashReportingInstrumentation';
9
+ import type { CrashReport } from './types';
10
+
11
+ // Keep aligned with FaroCrashSessionStore.MAX_CONTEXT_AGE_MS on Android.
12
+ const MAX_REPLAY_AGE_MS = 7 * 24 * 60 * 60 * 1000;
13
+
14
+ type RecoveredCrashReport = CrashReport & {
15
+ reportId: string;
16
+ sessionId: string;
17
+ timestamp: number;
18
+ };
19
+
20
+ /**
21
+ * Shared recovered-crash delivery contract for native platforms.
22
+ *
23
+ * Native code owns crash-time persistence. JavaScript owns validation, transport
24
+ * acceptance, and acknowledgement so retryable failures never delete a report.
25
+ */
26
+ export abstract class RecoveredCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {
27
+ private nativeModule: typeof NativeModules.FaroReactNativeModule | null = null;
28
+ private supportsSessionReplay = false;
29
+ private metasListenerRegistered = false;
30
+ private lastRecordedSessionSignature: string | null = null;
31
+ private activeSessionId: string | null = null;
32
+ private activeSessionActivatedAtMs = 0;
33
+ private activeSessionIsSampled: boolean | undefined;
34
+
35
+ private readonly recordSessionContextListener = (meta: Meta): void => {
36
+ this.recordSessionContext(meta);
37
+ };
38
+
39
+ protected override prepareCrashReporting(nativeModule: typeof NativeModules.FaroReactNativeModule): void {
40
+ this.supportsSessionReplay =
41
+ typeof nativeModule.recordCrashSessionContext === 'function' &&
42
+ typeof nativeModule.getPendingCrashReports === 'function' &&
43
+ typeof nativeModule.acknowledgeCrashReports === 'function';
44
+
45
+ if (!this.supportsSessionReplay) {
46
+ this.logWarn('Native module does not support original-session crash replay; using legacy crash reporting');
47
+ return;
48
+ }
49
+
50
+ this.nativeModule = nativeModule;
51
+ this.recordSessionContext(this.metas.value);
52
+ if (!this.metasListenerRegistered) {
53
+ this.metas.addListener(this.recordSessionContextListener);
54
+ this.metasListenerRegistered = true;
55
+ }
56
+ }
57
+
58
+ protected override async processCrashReports(
59
+ nativeModule: typeof NativeModules.FaroReactNativeModule
60
+ ): Promise<void> {
61
+ if (!this.supportsSessionReplay) {
62
+ await super.processCrashReports(nativeModule);
63
+ return;
64
+ }
65
+
66
+ try {
67
+ const replayStartedAtMs = Date.now();
68
+ const crashReports = (await nativeModule.getPendingCrashReports()) as string[] | null;
69
+ if (!crashReports?.length) {
70
+ this.logDebug('No crash reports from previous session');
71
+ return;
72
+ }
73
+
74
+ const processedReportIds = new Set<string>();
75
+ for (const crashJson of crashReports) {
76
+ let parsedCrash: unknown;
77
+ try {
78
+ parsedCrash = JSON.parse(crashJson);
79
+ } catch (error) {
80
+ this.logError('Failed to parse recovered crash report', error);
81
+ continue;
82
+ }
83
+ if (!parsedCrash || typeof parsedCrash !== 'object' || Array.isArray(parsedCrash)) {
84
+ this.logWarn('Skipping malformed recovered crash report');
85
+ continue;
86
+ }
87
+ const crash = parsedCrash as CrashReport;
88
+
89
+ const reportId = crash.reportId?.trim();
90
+ if (!reportId) {
91
+ this.logWarn('Skipping malformed recovered crash report');
92
+ continue;
93
+ }
94
+ if (!this.hasValidTimestamp(crash)) {
95
+ this.logWarn(`Discarding malformed recovered crash ${reportId}`);
96
+ await this.acknowledgeCrashReport(nativeModule, reportId);
97
+ continue;
98
+ }
99
+ if (processedReportIds.has(reportId)) {
100
+ this.logDebug(`Skipping duplicate recovered crash report ${reportId}`);
101
+ continue;
102
+ }
103
+ processedReportIds.add(reportId);
104
+
105
+ if (!this.isWithinReplayWindow(crash.timestamp, replayStartedAtMs)) {
106
+ await this.acknowledgeCrashReport(nativeModule, reportId);
107
+ continue;
108
+ }
109
+ if (this.shouldSkipCrashReport(crash)) {
110
+ await this.acknowledgeCrashReport(nativeModule, reportId);
111
+ continue;
112
+ }
113
+
114
+ const sessionId = crash.sessionId?.trim();
115
+ if (!sessionId) {
116
+ this.logWarn(`Discarding recovered crash ${reportId} because its original session context is unavailable`);
117
+ await this.acknowledgeCrashReport(nativeModule, reportId);
118
+ continue;
119
+ }
120
+
121
+ const outcome = await this.replayCrashReport({
122
+ ...crash,
123
+ reportId,
124
+ sessionId,
125
+ });
126
+ if (outcome === 'accepted' || outcome === 'filtered') {
127
+ await this.acknowledgeCrashReport(nativeModule, reportId);
128
+ }
129
+ }
130
+ } catch (error) {
131
+ this.logError('Failed to process recovered crash reports', error);
132
+ }
133
+ }
134
+
135
+ private recordSessionContext(meta: Meta): void {
136
+ const sessionId = meta.session?.id?.trim();
137
+ if (!sessionId || !this.nativeModule) {
138
+ return;
139
+ }
140
+
141
+ if (this.activeSessionId !== sessionId) {
142
+ this.activeSessionId = sessionId;
143
+ this.activeSessionActivatedAtMs = Date.now();
144
+ this.activeSessionIsSampled = undefined;
145
+ }
146
+
147
+ const sampledValue = meta.session?.attributes?.['isSampled'];
148
+ const incomingIsSampled = sampledValue === 'true' ? true : sampledValue === 'false' ? false : undefined;
149
+ if (incomingIsSampled !== undefined) {
150
+ this.activeSessionIsSampled = incomingIsSampled;
151
+ }
152
+ const isSampled = incomingIsSampled ?? this.activeSessionIsSampled;
153
+ const sessionContext = {
154
+ sessionId,
155
+ activatedAt: this.activeSessionActivatedAtMs,
156
+ ...(isSampled === undefined ? {} : { isSampled }),
157
+ ...(meta.app?.version ? { appVersion: meta.app.version } : {}),
158
+ ...(meta.app?.release ? { appRelease: meta.app.release } : {}),
159
+ ...(meta.app?.bundleId ? { appBundleId: meta.app.bundleId } : {}),
160
+ };
161
+ const signature = JSON.stringify(sessionContext);
162
+ if (signature === this.lastRecordedSessionSignature) {
163
+ return;
164
+ }
165
+
166
+ try {
167
+ const stored = this.nativeModule.recordCrashSessionContext(sessionContext);
168
+ if (stored === true) {
169
+ this.lastRecordedSessionSignature = signature;
170
+ }
171
+ } catch (error) {
172
+ this.logError('Failed to persist crash session context', error);
173
+ }
174
+ }
175
+
176
+ private async replayCrashReport(crash: RecoveredCrashReport): Promise<'accepted' | 'filtered' | 'retry'> {
177
+ if (this.transports.isPaused()) {
178
+ return 'retry';
179
+ }
180
+
181
+ const fetchTransport = this.transports.transports.find(
182
+ (transport): transport is FetchTransport => transport instanceof FetchTransport
183
+ );
184
+ if (!fetchTransport) {
185
+ this.logWarn('Fetch transport is unavailable; leaving recovered crash pending');
186
+ return 'retry';
187
+ }
188
+
189
+ // Recovered crashes intentionally bypass transport fan-out. Native storage
190
+ // handles retries, and only FetchTransport reports collector acceptance before ack.
191
+ let item: TransportItem = this.buildRecoveredCrashItem(crash);
192
+ try {
193
+ for (const hook of this.transports.getBeforeSendHooks()) {
194
+ const modifiedItem = hook(item);
195
+ // Match faro-core's filter(Boolean) behavior for invalid hook results.
196
+ if (!modifiedItem) {
197
+ return 'filtered';
198
+ }
199
+ item = modifiedItem;
200
+ }
201
+ } catch (error) {
202
+ this.logError('Failed to apply beforeSend hook to recovered crash', error);
203
+ return 'retry';
204
+ }
205
+
206
+ const result = await fetchTransport.sendWithResult([item]);
207
+ return result.outcome === 'accepted' ? 'accepted' : 'retry';
208
+ }
209
+
210
+ private buildRecoveredCrashItem(crash: RecoveredCrashReport): TransportItem<ExceptionEvent> {
211
+ const { context, errorMessage, stackFrames } = this.buildCrashReportDetails(crash);
212
+ const currentMeta = this.metas.value;
213
+ const isSampledAttribute = crash.isSampled === undefined ? undefined : { isSampled: String(crash.isSampled) };
214
+ // Current user, view, and page metadata belong to the restarted session.
215
+ const meta: Meta = {
216
+ ...(currentMeta.sdk ? { sdk: { ...currentMeta.sdk } } : {}),
217
+ app: {
218
+ ...(currentMeta.app ?? {}),
219
+ ...(crash.appVersion ? { version: crash.appVersion } : {}),
220
+ ...(crash.appRelease ? { release: crash.appRelease } : {}),
221
+ ...(crash.appBundleId ? { bundleId: crash.appBundleId } : {}),
222
+ },
223
+ ...(currentMeta.os ? { os: { ...currentMeta.os } } : {}),
224
+ ...(currentMeta.device ? { device: { ...currentMeta.device } } : {}),
225
+ session: {
226
+ id: crash.sessionId,
227
+ ...(isSampledAttribute ? { attributes: isSampledAttribute } : {}),
228
+ },
229
+ };
230
+
231
+ return {
232
+ type: TransportItemType.EXCEPTION,
233
+ meta,
234
+ payload: {
235
+ type: 'crash',
236
+ value: errorMessage,
237
+ timestamp: new Date(crash.timestamp).toISOString(),
238
+ context,
239
+ fatal: true,
240
+ ...(stackFrames.length ? { stacktrace: { frames: stackFrames } } : {}),
241
+ },
242
+ };
243
+ }
244
+
245
+ private async acknowledgeCrashReport(
246
+ nativeModule: typeof NativeModules.FaroReactNativeModule,
247
+ reportId: string
248
+ ): Promise<void> {
249
+ try {
250
+ await nativeModule.acknowledgeCrashReports([reportId]);
251
+ } catch (error) {
252
+ this.logError(`Failed to acknowledge recovered crash ${reportId}`, error);
253
+ }
254
+ }
255
+
256
+ private hasValidTimestamp(crash: CrashReport): crash is CrashReport & { timestamp: number } {
257
+ return typeof crash.timestamp === 'number' && Number.isFinite(crash.timestamp) && crash.timestamp > 0;
258
+ }
259
+
260
+ private isWithinReplayWindow(timestampMs: number, replayStartedAtMs: number): boolean {
261
+ return timestampMs <= replayStartedAtMs && replayStartedAtMs - timestampMs <= MAX_REPLAY_AGE_MS;
262
+ }
263
+
264
+ override unpatch(): void {
265
+ if (this.metasListenerRegistered) {
266
+ this.metas.removeListener(this.recordSessionContextListener);
267
+ this.metasListenerRegistered = false;
268
+ }
269
+ this.nativeModule = null;
270
+ this.supportsSessionReplay = false;
271
+ this.lastRecordedSessionSignature = null;
272
+ this.activeSessionId = null;
273
+ this.activeSessionActivatedAtMs = 0;
274
+ this.activeSessionIsSampled = undefined;
275
+ super.unpatch();
276
+ }
277
+ }
@@ -1,6 +1,6 @@
1
1
  import { VERSION } from '@grafana/faro-core';
2
2
 
3
- import { BaseCrashReportingInstrumentation } from '../BaseCrashReportingInstrumentation';
3
+ import { RecoveredCrashReportingInstrumentation } from '../RecoveredCrashReportingInstrumentation';
4
4
  import type { CrashReport } from '../types';
5
5
 
6
6
  import { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';
@@ -18,7 +18,7 @@ import { parseAndroidCrashTrace, type ParsedAndroidCrashTrace } from './parseAnd
18
18
  *
19
19
  * ANRs are reported separately by ANRInstrumentation, not via ApplicationExitInfo replay.
20
20
  */
21
- export class AndroidCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {
21
+ export class AndroidCrashReportingInstrumentation extends RecoveredCrashReportingInstrumentation {
22
22
  readonly name = '@grafana/faro-react-native:instrumentation-crash-android';
23
23
  readonly version = VERSION;
24
24