@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,89 @@
1
+ import { NativeModules } from 'react-native';
2
+ export const MAIN_PROCESS_SESSION_STORAGE_ID = 'grafana-faro-react-native-session';
3
+ let cachedProcessInfo;
4
+ let cachedPersistenceOwnership;
5
+ function getNativeModule() {
6
+ return NativeModules['FaroReactNativeModule'];
7
+ }
8
+ /** Stable native process identity used to isolate session chains. */
9
+ export function getSessionProcessInfo() {
10
+ if (cachedProcessInfo !== undefined) {
11
+ return cachedProcessInfo;
12
+ }
13
+ const nativeModule = getNativeModule();
14
+ if (typeof (nativeModule === null || nativeModule === void 0 ? void 0 : nativeModule.getSessionProcessIdentifier) !== 'function' ||
15
+ typeof nativeModule.isMainSessionProcess !== 'function') {
16
+ cachedProcessInfo = null;
17
+ return cachedProcessInfo;
18
+ }
19
+ try {
20
+ const identifier = nativeModule.getSessionProcessIdentifier();
21
+ const isMain = nativeModule.isMainSessionProcess();
22
+ if (typeof identifier !== 'string' || identifier.trim().length === 0 || typeof isMain !== 'boolean') {
23
+ cachedProcessInfo = null;
24
+ return cachedProcessInfo;
25
+ }
26
+ cachedProcessInfo = { identifier: identifier.trim(), isMain };
27
+ }
28
+ catch (_a) {
29
+ cachedProcessInfo = null;
30
+ }
31
+ return cachedProcessInfo;
32
+ }
33
+ /**
34
+ * Returns the MMKV instance ID owned by this runtime, or null when exclusive
35
+ * ownership cannot be established. The main process retains the legacy ID so
36
+ * upgrades keep their existing previous-session link.
37
+ */
38
+ export function claimSessionPersistenceStorageId() {
39
+ const processInfo = getSessionProcessInfo();
40
+ if (processInfo == null) {
41
+ return null;
42
+ }
43
+ if (cachedPersistenceOwnership === undefined) {
44
+ const nativeModule = getNativeModule();
45
+ if (typeof (nativeModule === null || nativeModule === void 0 ? void 0 : nativeModule.claimSessionPersistence) !== 'function') {
46
+ cachedPersistenceOwnership = false;
47
+ }
48
+ else {
49
+ try {
50
+ cachedPersistenceOwnership = nativeModule.claimSessionPersistence() === true;
51
+ }
52
+ catch (_a) {
53
+ cachedPersistenceOwnership = false;
54
+ }
55
+ }
56
+ }
57
+ if (!cachedPersistenceOwnership) {
58
+ return null;
59
+ }
60
+ return processInfo.isMain
61
+ ? MAIN_PROCESS_SESSION_STORAGE_ID
62
+ : `${MAIN_PROCESS_SESSION_STORAGE_ID}.${encodeURIComponent(processInfo.identifier)}`;
63
+ }
64
+ /** Releases storage ownership after MMKV initialization fails. */
65
+ export function releaseSessionPersistenceOwnership() {
66
+ if (cachedPersistenceOwnership !== true) {
67
+ return false;
68
+ }
69
+ const nativeModule = getNativeModule();
70
+ if (typeof (nativeModule === null || nativeModule === void 0 ? void 0 : nativeModule.releaseSessionPersistence) !== 'function') {
71
+ return false;
72
+ }
73
+ try {
74
+ const released = nativeModule.releaseSessionPersistence() === true;
75
+ if (released) {
76
+ cachedPersistenceOwnership = undefined;
77
+ }
78
+ return released;
79
+ }
80
+ catch (_a) {
81
+ return false;
82
+ }
83
+ }
84
+ /** @internal */
85
+ export function resetSessionProcessForTests() {
86
+ cachedProcessInfo = undefined;
87
+ cachedPersistenceOwnership = undefined;
88
+ }
89
+ //# sourceMappingURL=sessionProcess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionProcess.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/sessionProcess.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nexport const MAIN_PROCESS_SESSION_STORAGE_ID = 'grafana-faro-react-native-session';\n\ninterface SessionProcessNativeModule {\n claimSessionPersistence?: () => unknown;\n getSessionProcessIdentifier?: () => unknown;\n isMainSessionProcess?: () => unknown;\n releaseSessionPersistence?: () => unknown;\n}\n\nexport interface SessionProcessInfo {\n identifier: string;\n isMain: boolean;\n}\n\nlet cachedProcessInfo: SessionProcessInfo | null | undefined;\nlet cachedPersistenceOwnership: boolean | undefined;\n\nfunction getNativeModule(): SessionProcessNativeModule | undefined {\n return NativeModules['FaroReactNativeModule'] as SessionProcessNativeModule | undefined;\n}\n\n/** Stable native process identity used to isolate session chains. */\nexport function getSessionProcessInfo(): SessionProcessInfo | null {\n if (cachedProcessInfo !== undefined) {\n return cachedProcessInfo;\n }\n\n const nativeModule = getNativeModule();\n if (\n typeof nativeModule?.getSessionProcessIdentifier !== 'function' ||\n typeof nativeModule.isMainSessionProcess !== 'function'\n ) {\n cachedProcessInfo = null;\n return cachedProcessInfo;\n }\n\n try {\n const identifier = nativeModule.getSessionProcessIdentifier();\n const isMain = nativeModule.isMainSessionProcess();\n if (typeof identifier !== 'string' || identifier.trim().length === 0 || typeof isMain !== 'boolean') {\n cachedProcessInfo = null;\n return cachedProcessInfo;\n }\n\n cachedProcessInfo = { identifier: identifier.trim(), isMain };\n } catch {\n cachedProcessInfo = null;\n }\n\n return cachedProcessInfo;\n}\n\n/**\n * Returns the MMKV instance ID owned by this runtime, or null when exclusive\n * ownership cannot be established. The main process retains the legacy ID so\n * upgrades keep their existing previous-session link.\n */\nexport function claimSessionPersistenceStorageId(): string | null {\n const processInfo = getSessionProcessInfo();\n if (processInfo == null) {\n return null;\n }\n\n if (cachedPersistenceOwnership === undefined) {\n const nativeModule = getNativeModule();\n if (typeof nativeModule?.claimSessionPersistence !== 'function') {\n cachedPersistenceOwnership = false;\n } else {\n try {\n cachedPersistenceOwnership = nativeModule.claimSessionPersistence() === true;\n } catch {\n cachedPersistenceOwnership = false;\n }\n }\n }\n\n if (!cachedPersistenceOwnership) {\n return null;\n }\n\n return processInfo.isMain\n ? MAIN_PROCESS_SESSION_STORAGE_ID\n : `${MAIN_PROCESS_SESSION_STORAGE_ID}.${encodeURIComponent(processInfo.identifier)}`;\n}\n\n/** Releases storage ownership after MMKV initialization fails. */\nexport function releaseSessionPersistenceOwnership(): boolean {\n if (cachedPersistenceOwnership !== true) {\n return false;\n }\n\n const nativeModule = getNativeModule();\n if (typeof nativeModule?.releaseSessionPersistence !== 'function') {\n return false;\n }\n\n try {\n const released = nativeModule.releaseSessionPersistence() === true;\n if (released) {\n cachedPersistenceOwnership = undefined;\n }\n return released;\n } catch {\n return false;\n }\n}\n\n/** @internal */\nexport function resetSessionProcessForTests(): void {\n cachedProcessInfo = undefined;\n cachedPersistenceOwnership = undefined;\n}\n"]}
@@ -8,15 +8,12 @@ const { FaroReactNativeModule } = NativeModules;
8
8
  * - iOS: sysctl() to query kernel for process start time
9
9
  * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)
10
10
  *
11
- * Implementation aligned with Faro Flutter SDK:
12
- * https://github.com/grafana/faro-flutter-sdk
13
- *
14
11
  * **Key Features**:
15
12
  * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!
16
13
  * - ✅ Cold start: appStartDuration from native, coldStart: 1
17
14
  * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0
18
15
  *
19
- * **Metrics Captured** (matches Flutter SDK format):
16
+ * **Metrics Captured**:
20
17
  * - Cold start: `appStartDuration`, `coldStart: 1`
21
18
  * - Warm start: `appStartDuration`, `coldStart: 0`
22
19
  *
@@ -53,7 +50,7 @@ export class StartupInstrumentation extends BaseInstrumentation {
53
50
  }
54
51
  /**
55
52
  * Captures cold start duration from native (process start to Faro init).
56
- * Matches Flutter SDK: appStartDuration + coldStart: 1
53
+ * Sends appStartDuration with coldStart: 1.
57
54
  */
58
55
  captureColdStartMetrics() {
59
56
  try {
@@ -83,7 +80,6 @@ export class StartupInstrumentation extends BaseInstrumentation {
83
80
  }
84
81
  /**
85
82
  * Tracks warm start: when app resumes from background, measures time to first frame.
86
- * Matches Flutter SDK: setWarmStart on resume, getWarmStart after postFrameCallback.
87
83
  */
88
84
  setupWarmStartTracking() {
89
85
  this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/startup/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AppState, NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { StartupInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app startup time for both cold and warm starts.\n *\n * Uses native OS APIs for cold start and AppState for warm start:\n * - iOS: sysctl() to query kernel for process start time\n * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)\n *\n * Implementation aligned with Faro Flutter SDK:\n * https://github.com/grafana/faro-flutter-sdk\n *\n * **Key Features**:\n * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!\n * - ✅ Cold start: appStartDuration from native, coldStart: 1\n * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0\n *\n * **Metrics Captured** (matches Flutter SDK format):\n * - Cold start: `appStartDuration`, `coldStart: 1`\n * - Warm start: `appStartDuration`, `coldStart: 0`\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 24+ (Android 7.0 Nougat, ~99% of devices as of 2025)\n *\n * @example\n * ```tsx\n * import { initializeFaro } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * app: { name: 'my-app', version: '1.0.0' },\n * url: 'https://your-collector.com',\n * });\n * ```\n * StartupInstrumentation is included by default via makeRNConfig.\n */\nexport class StartupInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-startup';\n readonly version = VERSION;\n\n /** 0 = came from background (warm start eligible), null = never backgrounded */\n private warmStartTimestamp: number | null = null;\n private appStateSubscription: ReturnType<typeof AppState.addEventListener> | undefined;\n\n constructor(private options: StartupInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n if (this.options.enabled === false) {\n return;\n }\n\n this.captureColdStartMetrics();\n this.setupWarmStartTracking();\n }\n\n /**\n * Captures cold start duration from native (process start to Faro init).\n * Matches Flutter SDK: appStartDuration + coldStart: 1\n */\n private captureColdStartMetrics(): void {\n try {\n if (!FaroReactNativeModule?.getAppStartDuration) {\n this.logWarn(\n 'Native module not available. Startup instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n const appStartDuration = FaroReactNativeModule.getAppStartDuration();\n\n if (appStartDuration === 0) {\n this.logWarn(\n 'App startup duration is 0. This may indicate unsupported Android version (< API 24) ' +\n 'or an issue with the native module.'\n );\n return;\n }\n\n const values: Record<string, number> = {\n appStartDuration,\n coldStart: 1,\n };\n\n setTimeout(() => {\n this.api.pushMeasurement({ type: 'app_startup', values }, { skipDedupe: true });\n }, 100);\n\n this.logInfo(`Cold start metrics captured: ${appStartDuration}ms`);\n } catch (error) {\n this.logError('Failed to capture cold start metrics', error);\n }\n }\n\n /**\n * Tracks warm start: when app resumes from background, measures time to first frame.\n * Matches Flutter SDK: setWarmStart on resume, getWarmStart after postFrameCallback.\n */\n private setupWarmStartTracking(): void {\n this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {\n if (nextAppState === 'background' || nextAppState === 'inactive') {\n this.warmStartTimestamp = 0; // Sentinel: next 'active' is a warm start\n } else if (nextAppState === 'active' && this.warmStartTimestamp === 0) {\n // Came from background: measure time from resume to first frame\n const resumeTimestamp = Date.now();\n this.warmStartTimestamp = null;\n\n requestAnimationFrame(() => {\n const appStartDuration = Date.now() - resumeTimestamp;\n if (appStartDuration > 0) {\n this.api.pushMeasurement(\n {\n type: 'app_startup',\n values: { appStartDuration, coldStart: 0 },\n },\n { skipDedupe: true }\n );\n this.logInfo(`Warm start metrics captured: ${appStartDuration}ms`);\n }\n });\n }\n });\n }\n\n unpatch(): void {\n if (this.appStateSubscription) {\n this.appStateSubscription.remove();\n this.appStateSubscription = undefined;\n this.warmStartTimestamp = null;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/startup/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AppState, NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport type { StartupInstrumentationOptions } from './types';\n\nconst { FaroReactNativeModule } = NativeModules;\n\n/**\n * Measures React Native app startup time for both cold and warm starts.\n *\n * Uses native OS APIs for cold start and AppState for warm start:\n * - iOS: sysctl() to query kernel for process start time\n * - Android: Process.getStartElapsedRealtime() from Android OS (API 24+)\n *\n * **Key Features**:\n * - ✅ NO AppDelegate/MainActivity setup required - OS tracks process start automatically!\n * - ✅ Cold start: appStartDuration from native, coldStart: 1\n * - ✅ Warm start: appStartDuration (time to first frame after resume), coldStart: 0\n *\n * **Metrics Captured**:\n * - Cold start: `appStartDuration`, `coldStart: 1`\n * - Warm start: `appStartDuration`, `coldStart: 0`\n *\n * **Requirements**:\n * - iOS 13.4+ (any iOS that supports React Native)\n * - Android API 24+ (Android 7.0 Nougat, ~99% of devices as of 2025)\n *\n * @example\n * ```tsx\n * import { initializeFaro } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * app: { name: 'my-app', version: '1.0.0' },\n * url: 'https://your-collector.com',\n * });\n * ```\n * StartupInstrumentation is included by default via makeRNConfig.\n */\nexport class StartupInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-startup';\n readonly version = VERSION;\n\n /** 0 = came from background (warm start eligible), null = never backgrounded */\n private warmStartTimestamp: number | null = null;\n private appStateSubscription: ReturnType<typeof AppState.addEventListener> | undefined;\n\n constructor(private options: StartupInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n if (this.options.enabled === false) {\n return;\n }\n\n this.captureColdStartMetrics();\n this.setupWarmStartTracking();\n }\n\n /**\n * Captures cold start duration from native (process start to Faro init).\n * Sends appStartDuration with coldStart: 1.\n */\n private captureColdStartMetrics(): void {\n try {\n if (!FaroReactNativeModule?.getAppStartDuration) {\n this.logWarn(\n 'Native module not available. Startup instrumentation requires native module. ' +\n 'Run `cd ios && pod install` and rebuild the app.'\n );\n return;\n }\n\n const appStartDuration = FaroReactNativeModule.getAppStartDuration();\n\n if (appStartDuration === 0) {\n this.logWarn(\n 'App startup duration is 0. This may indicate unsupported Android version (< API 24) ' +\n 'or an issue with the native module.'\n );\n return;\n }\n\n const values: Record<string, number> = {\n appStartDuration,\n coldStart: 1,\n };\n\n setTimeout(() => {\n this.api.pushMeasurement({ type: 'app_startup', values }, { skipDedupe: true });\n }, 100);\n\n this.logInfo(`Cold start metrics captured: ${appStartDuration}ms`);\n } catch (error) {\n this.logError('Failed to capture cold start metrics', error);\n }\n }\n\n /**\n * Tracks warm start: when app resumes from background, measures time to first frame.\n */\n private setupWarmStartTracking(): void {\n this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => {\n if (nextAppState === 'background' || nextAppState === 'inactive') {\n this.warmStartTimestamp = 0; // Sentinel: next 'active' is a warm start\n } else if (nextAppState === 'active' && this.warmStartTimestamp === 0) {\n // Came from background: measure time from resume to first frame\n const resumeTimestamp = Date.now();\n this.warmStartTimestamp = null;\n\n requestAnimationFrame(() => {\n const appStartDuration = Date.now() - resumeTimestamp;\n if (appStartDuration > 0) {\n this.api.pushMeasurement(\n {\n type: 'app_startup',\n values: { appStartDuration, coldStart: 0 },\n },\n { skipDedupe: true }\n );\n this.logInfo(`Warm start metrics captured: ${appStartDuration}ms`);\n }\n });\n }\n });\n }\n\n unpatch(): void {\n if (this.appStateSubscription) {\n this.appStateSubscription.remove();\n this.appStateSubscription = undefined;\n this.warmStartTimestamp = null;\n }\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { faro } from '@grafana/faro-core';
2
2
  import { setCurrentScreen } from '../metas/screen';
3
+ export const EVENT_NAVIGATION = 'navigation';
3
4
  /**
4
5
  * Gets the currently active route from navigation state
5
6
  */
@@ -43,7 +44,7 @@ export function onNavigationStateChange(state) {
43
44
  (_a = faro.api) === null || _a === void 0 ? void 0 : _a.setView({ name: screenName });
44
45
  // Optionally push additional attributes if route has params
45
46
  if (currentRoute === null || currentRoute === void 0 ? void 0 : currentRoute.params) {
46
- (_b = faro.api) === null || _b === void 0 ? void 0 : _b.pushEvent('navigation', {
47
+ (_b = faro.api) === null || _b === void 0 ? void 0 : _b.pushEvent(EVENT_NAVIGATION, {
47
48
  screen: screenName,
48
49
  params: JSON.stringify(currentRoute.params),
49
50
  });
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/navigation/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAkE;;IAElE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,MAAA,KAAK,CAAC,KAAK,mCAAI,CAAC,CAAC,CAAC;IAE7C,mEAAmE;IACnE,IAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,KAAK,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,KAAK,CAAC,KAAwB,CAAC,CAAC;IACzD,CAAC;IAED,mDAAmD;IACnD,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1E,OAAO,KAAsB,CAAC;IAChC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgC;IAC3D,OAAO,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAkC;;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE9C,IAAI,UAAU,EAAE,CAAC;QACf,yBAAyB;QACzB,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE7B,6DAA6D;QAC7D,MAAA,IAAI,CAAC,GAAG,0CAAE,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAExC,4DAA4D;QAC5D,IAAI,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,MAAM,EAAE,CAAC;YACzB,MAAA,IAAI,CAAC,GAAG,0CAAE,SAAS,CAAC,YAAY,EAAE;gBAChC,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kCAAkC;IAChD,OAAO,uBAAuB,CAAC;AACjC,CAAC","sourcesContent":["import type { NavigationState, PartialState, Route } from '@react-navigation/native';\n\nimport { faro } from '@grafana/faro-core';\n\nimport { setCurrentScreen } from '../metas/screen';\n\n/**\n * Gets the currently active route from navigation state\n */\nexport function getCurrentRoute(\n state: NavigationState | PartialState<NavigationState> | undefined\n): Route<string> | undefined {\n if (!state || !state.routes || state.routes.length === 0) {\n return undefined;\n }\n\n const route = state.routes[state.index ?? 0];\n\n // If this route has nested state, recursively get the active route\n if (route?.state) {\n return getCurrentRoute(route.state as NavigationState);\n }\n\n // Only return if route has all required properties\n if (route && 'key' in route && 'name' in route && route.key && route.name) {\n return route as Route<string>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts the route name from a route object\n */\nexport function getRouteName(route: Route<string> | undefined): string | undefined {\n return route?.name;\n}\n\n/**\n * Handles navigation state changes and updates Faro with the new screen\n * @param state - The current navigation state\n */\nexport function onNavigationStateChange(state: NavigationState | undefined): void {\n if (!state) {\n return;\n }\n\n const currentRoute = getCurrentRoute(state);\n const screenName = getRouteName(currentRoute);\n\n if (screenName) {\n // Update the screen meta\n setCurrentScreen(screenName);\n\n // Update the view meta which will trigger VIEW_CHANGED event\n faro.api?.setView({ name: screenName });\n\n // Optionally push additional attributes if route has params\n if (currentRoute?.params) {\n faro.api?.pushEvent('navigation', {\n screen: screenName,\n params: JSON.stringify(currentRoute.params),\n });\n }\n }\n}\n\n/**\n * Creates a navigation state change handler\n * Use this with NavigationContainer's onStateChange prop\n *\n * @example\n * ```\n * import { NavigationContainer } from '@react-navigation/native';\n * import { createNavigationStateChangeHandler } from '@grafana/faro-react-native';\n *\n * const onStateChange = createNavigationStateChangeHandler();\n *\n * // In your component:\n * <NavigationContainer onStateChange={onStateChange}>\n * // your navigation\n * </NavigationContainer>\n * ```\n */\nexport function createNavigationStateChangeHandler(): (state: NavigationState | undefined) => void {\n return onNavigationStateChange;\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/navigation/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE7C;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAkE;;IAElE,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,MAAA,KAAK,CAAC,KAAK,mCAAI,CAAC,CAAC,CAAC;IAE7C,mEAAmE;IACnE,IAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,KAAK,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,KAAK,CAAC,KAAwB,CAAC,CAAC;IACzD,CAAC;IAED,mDAAmD;IACnD,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1E,OAAO,KAAsB,CAAC;IAChC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgC;IAC3D,OAAO,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAkC;;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE9C,IAAI,UAAU,EAAE,CAAC;QACf,yBAAyB;QACzB,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE7B,6DAA6D;QAC7D,MAAA,IAAI,CAAC,GAAG,0CAAE,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAExC,4DAA4D;QAC5D,IAAI,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,MAAM,EAAE,CAAC;YACzB,MAAA,IAAI,CAAC,GAAG,0CAAE,SAAS,CAAC,gBAAgB,EAAE;gBACpC,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kCAAkC;IAChD,OAAO,uBAAuB,CAAC;AACjC,CAAC","sourcesContent":["import type { NavigationState, PartialState, Route } from '@react-navigation/native';\n\nimport { faro } from '@grafana/faro-core';\n\nimport { setCurrentScreen } from '../metas/screen';\n\nexport const EVENT_NAVIGATION = 'navigation';\n\n/**\n * Gets the currently active route from navigation state\n */\nexport function getCurrentRoute(\n state: NavigationState | PartialState<NavigationState> | undefined\n): Route<string> | undefined {\n if (!state || !state.routes || state.routes.length === 0) {\n return undefined;\n }\n\n const route = state.routes[state.index ?? 0];\n\n // If this route has nested state, recursively get the active route\n if (route?.state) {\n return getCurrentRoute(route.state as NavigationState);\n }\n\n // Only return if route has all required properties\n if (route && 'key' in route && 'name' in route && route.key && route.name) {\n return route as Route<string>;\n }\n\n return undefined;\n}\n\n/**\n * Extracts the route name from a route object\n */\nexport function getRouteName(route: Route<string> | undefined): string | undefined {\n return route?.name;\n}\n\n/**\n * Handles navigation state changes and updates Faro with the new screen\n * @param state - The current navigation state\n */\nexport function onNavigationStateChange(state: NavigationState | undefined): void {\n if (!state) {\n return;\n }\n\n const currentRoute = getCurrentRoute(state);\n const screenName = getRouteName(currentRoute);\n\n if (screenName) {\n // Update the screen meta\n setCurrentScreen(screenName);\n\n // Update the view meta which will trigger VIEW_CHANGED event\n faro.api?.setView({ name: screenName });\n\n // Optionally push additional attributes if route has params\n if (currentRoute?.params) {\n faro.api?.pushEvent(EVENT_NAVIGATION, {\n screen: screenName,\n params: JSON.stringify(currentRoute.params),\n });\n }\n }\n}\n\n/**\n * Creates a navigation state change handler\n * Use this with NavigationContainer's onStateChange prop\n *\n * @example\n * ```\n * import { NavigationContainer } from '@react-navigation/native';\n * import { createNavigationStateChangeHandler } from '@grafana/faro-react-native';\n *\n * const onStateChange = createNavigationStateChangeHandler();\n *\n * // In your component:\n * <NavigationContainer onStateChange={onStateChange}>\n * // your navigation\n * </NavigationContainer>\n * ```\n */\nexport function createNavigationStateChangeHandler(): (state: NavigationState | undefined) => void {\n return onNavigationStateChange;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/transports/fetch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { FetchTransport } from './transport';\nexport type { FetchTransportOptions, FetchTransportRequestOptions } from './types';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/transports/fetch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { FetchTransport } from './transport';\nexport type {\n FetchTransportOptions,\n FetchTransportRequestOptions,\n FetchTransportSendOutcome,\n FetchTransportSendResult,\n} from './types';\n"]}
@@ -25,7 +25,6 @@ const DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;
25
25
  const MAX_CONSECUTIVE_FAILURES = 3;
26
26
  const FAILURE_BACKOFF_MS = 30000; // 30 seconds
27
27
  const TOO_MANY_REQUESTS = 429;
28
- const ACCEPTED = 202;
29
28
  export class FetchTransport extends BaseTransport {
30
29
  constructor(options) {
31
30
  var _a, _b, _c, _d;
@@ -111,19 +110,34 @@ export class FetchTransport extends BaseTransport {
111
110
  }
112
111
  send(items) {
113
112
  return __awaiter(this, void 0, void 0, function* () {
114
- // DEBUG: Log at the very start of send
115
- this.logDebug(`FetchTransport.send() called with ${items.length} items`);
113
+ yield this.sendWithResult(items);
114
+ });
115
+ }
116
+ /**
117
+ * Sends a batch and reports whether the collector accepted it.
118
+ * The request header uses the live session for collector accounting. Payload
119
+ * metadata remains independent so delayed signals can retain their original
120
+ * session.
121
+ */
122
+ sendWithResult(items) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ var _a;
125
+ this.logDebug(`FetchTransport.sendWithResult() called with ${items.length} items`);
126
+ if (items.length === 0) {
127
+ return { outcome: 'skipped' };
128
+ }
116
129
  try {
117
130
  const now = new Date(this.getNow());
118
131
  // Check if we're in backoff period
119
132
  if (this.disabledUntil > now) {
120
133
  this.logDebug(`FetchTransport: in backoff period until ${this.disabledUntil}`);
121
- return Promise.resolve();
134
+ return { outcome: 'skipped' };
122
135
  }
123
- // Wait for session to be ready before sending
124
- // This prevents 400 errors from missing X-Faro-Session-Id header
136
+ // The collector uses the header for session validation and accounting,
137
+ // independently of the session carried by each payload.
125
138
  yield this.waitForSession();
126
- yield this.promiseBuffer.add(() => {
139
+ const sessionId = (_a = this.metas.value.session) === null || _a === void 0 ? void 0 : _a.id;
140
+ const response = yield this.promiseBuffer.add(() => {
127
141
  const transportBody = getTransportBody(items);
128
142
  const body = JSON.stringify(transportBody);
129
143
  // DEBUG: Log measurement payloads to see what's being sent
@@ -134,11 +148,6 @@ export class FetchTransport extends BaseTransport {
134
148
  }
135
149
  const { url, requestOptions, apiKey } = this.options;
136
150
  const _a = requestOptions !== null && requestOptions !== void 0 ? requestOptions : {}, { headers } = _a, restOfRequestOptions = __rest(_a, ["headers"]);
137
- let sessionId;
138
- const sessionMeta = this.metas.value.session;
139
- if (sessionMeta != null) {
140
- sessionId = sessionMeta.id;
141
- }
142
151
  // DEBUG: Log fetch attempt
143
152
  this.logDebug(`FetchTransport: sending ${items.length} items to ${url}`);
144
153
  // Create an AbortController for timeout
@@ -159,7 +168,7 @@ export class FetchTransport extends BaseTransport {
159
168
  this.logDebug(`FetchTransport: rate limited, disabled until ${this.disabledUntil}`);
160
169
  }
161
170
  // Log non-success responses for debugging
162
- if (response.status !== ACCEPTED && response.status !== 200) {
171
+ if (response.status < 200 || response.status >= 300) {
163
172
  const text = yield response.text().catch(() => '');
164
173
  this.logDebug(`FetchTransport: non-success response: ${response.status} ${text.slice(0, 200)}`);
165
174
  }
@@ -180,12 +189,27 @@ export class FetchTransport extends BaseTransport {
180
189
  }
181
190
  // Do NOT log errors to console - this causes infinite loops in React Native
182
191
  // when the DevTools console override intercepts even unpatchedConsole calls
192
+ return undefined;
183
193
  });
184
194
  });
195
+ if (!response) {
196
+ return { outcome: 'failed' };
197
+ }
198
+ if (response.status >= 200 && response.status < 300) {
199
+ return {
200
+ outcome: 'accepted',
201
+ status: response.status,
202
+ };
203
+ }
204
+ return {
205
+ outcome: 'rejected',
206
+ status: response.status,
207
+ };
185
208
  }
186
- catch (_a) {
209
+ catch (_b) {
187
210
  // Buffer full error - Do NOT log to console as it creates infinite loops
188
211
  // The error is typically "Task buffer full" when the device is offline
212
+ return { outcome: 'failed' };
189
213
  }
190
214
  });
191
215
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/transports/fetch/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseTransport, createPromiseBuffer, getTransportBody, VERSION } from '@grafana/faro-core';\nimport type { Patterns, PromiseBuffer, TransportItem } from '@grafana/faro-core';\n\nimport type { FetchTransportOptions } from './types';\n\nconst DEFAULT_BUFFER_SIZE = 30;\nconst DEFAULT_CONCURRENCY = 5;\nconst DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;\nconst MAX_CONSECUTIVE_FAILURES = 3;\nconst FAILURE_BACKOFF_MS = 30000; // 30 seconds\n\nconst TOO_MANY_REQUESTS = 429;\nconst ACCEPTED = 202;\n\nexport class FetchTransport extends BaseTransport {\n readonly name = '@grafana/faro-react-native:transport-fetch';\n readonly version = VERSION;\n\n promiseBuffer: PromiseBuffer<Response | void>;\n\n private readonly rateLimitBackoffMs: number;\n private readonly getNow: () => number;\n private disabledUntil: Date;\n private consecutiveFailures: number = 0;\n private sessionReadyPromise: Promise<void> | null = null;\n private sessionReadyResolve: (() => void) | null = null;\n private sessionReady: boolean = false;\n private metasListenerRegistered: boolean = false;\n\n constructor(private options: FetchTransportOptions) {\n super();\n\n this.rateLimitBackoffMs = options.defaultRateLimitBackoffMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS;\n this.getNow = options.getNow ?? (() => Date.now());\n // Align with getNow so tests (and apps) that supply a clock do not see spurious backoff\n this.disabledUntil = new Date(this.getNow());\n\n this.promiseBuffer = createPromiseBuffer({\n size: options.bufferSize ?? DEFAULT_BUFFER_SIZE,\n concurrency: options.concurrency ?? DEFAULT_CONCURRENCY,\n });\n }\n\n /**\n * Register a listener for metas changes to detect when session becomes available.\n * Uses faro-core's metas listener pattern instead of polling with setTimeout.\n */\n private registerSessionListener(): void {\n if (this.metasListenerRegistered || !this.metas?.addListener) {\n return;\n }\n\n this.metasListenerRegistered = true;\n\n this.metas.addListener((meta) => {\n if (meta.session?.id && this.sessionReadyResolve) {\n this.sessionReady = true;\n this.sessionReadyResolve();\n this.sessionReadyResolve = null;\n }\n });\n }\n\n /**\n * Wait for session to be available before sending.\n * This prevents 400 errors from the collector due to missing X-Faro-Session-Id header.\n *\n * Only waits if session tracking is enabled. If disabled, returns immediately.\n */\n private waitForSession(): Promise<void> {\n // Only wait for session if session tracking is enabled\n const sessionTrackingEnabled = this.config?.sessionTracking?.enabled;\n if (!sessionTrackingEnabled) {\n return Promise.resolve();\n }\n\n // Already have session\n if (this.sessionReady) {\n return Promise.resolve();\n }\n\n // Check if session is now available\n const sessionMeta = this.metas?.value?.session;\n if (sessionMeta?.id) {\n this.sessionReady = true;\n return Promise.resolve();\n }\n\n // Return existing promise if we're already waiting\n if (this.sessionReadyPromise) {\n return this.sessionReadyPromise;\n }\n\n // Register listener to be notified when session becomes available\n this.registerSessionListener();\n\n // Create a promise that resolves when session becomes available via listener\n this.sessionReadyPromise = new Promise<void>((resolve) => {\n this.sessionReadyResolve = resolve;\n\n // Also check immediately in case session was set between our check and listener registration\n const sessionMeta = this.metas?.value?.session;\n if (sessionMeta?.id) {\n this.sessionReady = true;\n resolve();\n this.sessionReadyResolve = null;\n }\n });\n\n return this.sessionReadyPromise;\n }\n\n async send(items: TransportItem[]): Promise<void> {\n // DEBUG: Log at the very start of send\n this.logDebug(`FetchTransport.send() called with ${items.length} items`);\n\n try {\n const now = new Date(this.getNow());\n\n // Check if we're in backoff period\n if (this.disabledUntil > now) {\n this.logDebug(`FetchTransport: in backoff period until ${this.disabledUntil}`);\n return Promise.resolve();\n }\n\n // Wait for session to be ready before sending\n // This prevents 400 errors from missing X-Faro-Session-Id header\n await this.waitForSession();\n\n await this.promiseBuffer.add(() => {\n const transportBody = getTransportBody(items);\n const body = JSON.stringify(transportBody);\n\n // DEBUG: Log measurement payloads to see what's being sent\n if (transportBody.measurements && transportBody.measurements.length > 0) {\n for (const m of transportBody.measurements) {\n this.logDebug(`FetchTransport: measurement payload - type=${m.type}, values=${JSON.stringify(m.values)}`);\n }\n }\n\n const { url, requestOptions, apiKey } = this.options;\n\n const { headers, ...restOfRequestOptions } = requestOptions ?? {};\n\n let sessionId;\n const sessionMeta = this.metas.value.session;\n if (sessionMeta != null) {\n sessionId = sessionMeta.id;\n }\n\n // DEBUG: Log fetch attempt\n this.logDebug(`FetchTransport: sending ${items.length} items to ${url}`);\n\n // Create an AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n this.logDebug(`FetchTransport: request timed out after 10s`);\n }, 10000);\n\n return fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(headers ?? {}),\n ...(apiKey ? { 'x-api-key': apiKey } : {}),\n ...(sessionId ? { 'x-faro-session-id': sessionId } : {}),\n },\n body,\n signal: controller.signal,\n // Note: React Native doesn't support keepalive\n // keepalive: body.length <= BEACON_BODY_SIZE_LIMIT,\n ...(restOfRequestOptions ?? {}),\n })\n .then(async (response) => {\n clearTimeout(timeoutId);\n // DEBUG: Log response status\n this.logDebug(`FetchTransport: response status ${response.status}`);\n\n // Reset failure counter on success\n this.consecutiveFailures = 0;\n\n if (response.status === TOO_MANY_REQUESTS) {\n this.disabledUntil = this.getRetryAfterDate(response);\n this.logDebug(`FetchTransport: rate limited, disabled until ${this.disabledUntil}`);\n }\n\n // Log non-success responses for debugging\n if (response.status !== ACCEPTED && response.status !== 200) {\n const text = await response.text().catch(() => '');\n this.logDebug(`FetchTransport: non-success response: ${response.status} ${text.slice(0, 200)}`);\n }\n\n return response;\n })\n .catch((error) => {\n clearTimeout(timeoutId);\n // DEBUG: Log the error\n this.logDebug(`FetchTransport: fetch failed - ${error?.message || error}`);\n\n // Increment failure counter\n this.consecutiveFailures++;\n\n // After MAX_CONSECUTIVE_FAILURES, enable circuit breaker to prevent infinite loops\n if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\n this.disabledUntil = new Date(this.getNow() + FAILURE_BACKOFF_MS);\n // Reset counter so we can try again after backoff\n this.consecutiveFailures = 0;\n this.logDebug(`FetchTransport: circuit breaker activated, disabled for 30s`);\n }\n\n // Do NOT log errors to console - this causes infinite loops in React Native\n // when the DevTools console override intercepts even unpatchedConsole calls\n });\n });\n } catch {\n // Buffer full error - Do NOT log to console as it creates infinite loops\n // The error is typically \"Task buffer full\" when the device is offline\n }\n }\n\n override getIgnoreUrls(): Patterns {\n return ([this.options.url] as Patterns).concat(this.config.ignoreUrls ?? []);\n }\n\n override isBatched(): boolean {\n return true;\n }\n\n private getRetryAfterDate(response: Response): Date {\n const now = this.getNow();\n const retryAfterHeader = response.headers.get('Retry-After');\n\n if (retryAfterHeader) {\n const delay = Number(retryAfterHeader);\n\n if (!isNaN(delay)) {\n return new Date(delay * 1000 + now);\n }\n\n const date = Date.parse(retryAfterHeader);\n\n if (!isNaN(date)) {\n return new Date(date);\n }\n }\n\n return new Date(now + this.rateLimitBackoffMs);\n }\n}\n"]}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/transports/fetch/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseTransport, createPromiseBuffer, getTransportBody, VERSION } from '@grafana/faro-core';\nimport type { Patterns, PromiseBuffer, TransportItem } from '@grafana/faro-core';\n\nimport type { FetchTransportOptions, FetchTransportSendResult } from './types';\n\nconst DEFAULT_BUFFER_SIZE = 30;\nconst DEFAULT_CONCURRENCY = 5;\nconst DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;\nconst MAX_CONSECUTIVE_FAILURES = 3;\nconst FAILURE_BACKOFF_MS = 30000; // 30 seconds\n\nconst TOO_MANY_REQUESTS = 429;\nexport class FetchTransport extends BaseTransport {\n readonly name = '@grafana/faro-react-native:transport-fetch';\n readonly version = VERSION;\n\n promiseBuffer: PromiseBuffer<Response | void>;\n\n private readonly rateLimitBackoffMs: number;\n private readonly getNow: () => number;\n private disabledUntil: Date;\n private consecutiveFailures: number = 0;\n private sessionReadyPromise: Promise<void> | null = null;\n private sessionReadyResolve: (() => void) | null = null;\n private sessionReady: boolean = false;\n private metasListenerRegistered: boolean = false;\n\n constructor(private options: FetchTransportOptions) {\n super();\n\n this.rateLimitBackoffMs = options.defaultRateLimitBackoffMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS;\n this.getNow = options.getNow ?? (() => Date.now());\n // Align with getNow so tests (and apps) that supply a clock do not see spurious backoff\n this.disabledUntil = new Date(this.getNow());\n\n this.promiseBuffer = createPromiseBuffer({\n size: options.bufferSize ?? DEFAULT_BUFFER_SIZE,\n concurrency: options.concurrency ?? DEFAULT_CONCURRENCY,\n });\n }\n\n /**\n * Register a listener for metas changes to detect when session becomes available.\n * Uses faro-core's metas listener pattern instead of polling with setTimeout.\n */\n private registerSessionListener(): void {\n if (this.metasListenerRegistered || !this.metas?.addListener) {\n return;\n }\n\n this.metasListenerRegistered = true;\n\n this.metas.addListener((meta) => {\n if (meta.session?.id && this.sessionReadyResolve) {\n this.sessionReady = true;\n this.sessionReadyResolve();\n this.sessionReadyResolve = null;\n }\n });\n }\n\n /**\n * Wait for session to be available before sending.\n * This prevents 400 errors from the collector due to missing X-Faro-Session-Id header.\n *\n * Only waits if session tracking is enabled. If disabled, returns immediately.\n */\n private waitForSession(): Promise<void> {\n // Only wait for session if session tracking is enabled\n const sessionTrackingEnabled = this.config?.sessionTracking?.enabled;\n if (!sessionTrackingEnabled) {\n return Promise.resolve();\n }\n\n // Already have session\n if (this.sessionReady) {\n return Promise.resolve();\n }\n\n // Check if session is now available\n const sessionMeta = this.metas?.value?.session;\n if (sessionMeta?.id) {\n this.sessionReady = true;\n return Promise.resolve();\n }\n\n // Return existing promise if we're already waiting\n if (this.sessionReadyPromise) {\n return this.sessionReadyPromise;\n }\n\n // Register listener to be notified when session becomes available\n this.registerSessionListener();\n\n // Create a promise that resolves when session becomes available via listener\n this.sessionReadyPromise = new Promise<void>((resolve) => {\n this.sessionReadyResolve = resolve;\n\n // Also check immediately in case session was set between our check and listener registration\n const sessionMeta = this.metas?.value?.session;\n if (sessionMeta?.id) {\n this.sessionReady = true;\n resolve();\n this.sessionReadyResolve = null;\n }\n });\n\n return this.sessionReadyPromise;\n }\n\n async send(items: TransportItem[]): Promise<void> {\n await this.sendWithResult(items);\n }\n\n /**\n * Sends a batch and reports whether the collector accepted it.\n * The request header uses the live session for collector accounting. Payload\n * metadata remains independent so delayed signals can retain their original\n * session.\n */\n async sendWithResult(items: TransportItem[]): Promise<FetchTransportSendResult> {\n this.logDebug(`FetchTransport.sendWithResult() called with ${items.length} items`);\n\n if (items.length === 0) {\n return { outcome: 'skipped' };\n }\n\n try {\n const now = new Date(this.getNow());\n\n // Check if we're in backoff period\n if (this.disabledUntil > now) {\n this.logDebug(`FetchTransport: in backoff period until ${this.disabledUntil}`);\n return { outcome: 'skipped' };\n }\n\n // The collector uses the header for session validation and accounting,\n // independently of the session carried by each payload.\n await this.waitForSession();\n const sessionId = this.metas.value.session?.id;\n\n const response = await this.promiseBuffer.add(() => {\n const transportBody = getTransportBody(items);\n const body = JSON.stringify(transportBody);\n\n // DEBUG: Log measurement payloads to see what's being sent\n if (transportBody.measurements && transportBody.measurements.length > 0) {\n for (const m of transportBody.measurements) {\n this.logDebug(`FetchTransport: measurement payload - type=${m.type}, values=${JSON.stringify(m.values)}`);\n }\n }\n\n const { url, requestOptions, apiKey } = this.options;\n\n const { headers, ...restOfRequestOptions } = requestOptions ?? {};\n\n // DEBUG: Log fetch attempt\n this.logDebug(`FetchTransport: sending ${items.length} items to ${url}`);\n\n // Create an AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n this.logDebug(`FetchTransport: request timed out after 10s`);\n }, 10000);\n\n return fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(headers ?? {}),\n ...(apiKey ? { 'x-api-key': apiKey } : {}),\n ...(sessionId ? { 'x-faro-session-id': sessionId } : {}),\n },\n body,\n signal: controller.signal,\n // Note: React Native doesn't support keepalive\n // keepalive: body.length <= BEACON_BODY_SIZE_LIMIT,\n ...(restOfRequestOptions ?? {}),\n })\n .then(async (response) => {\n clearTimeout(timeoutId);\n // DEBUG: Log response status\n this.logDebug(`FetchTransport: response status ${response.status}`);\n\n // Reset failure counter on success\n this.consecutiveFailures = 0;\n\n if (response.status === TOO_MANY_REQUESTS) {\n this.disabledUntil = this.getRetryAfterDate(response);\n this.logDebug(`FetchTransport: rate limited, disabled until ${this.disabledUntil}`);\n }\n\n // Log non-success responses for debugging\n if (response.status < 200 || response.status >= 300) {\n const text = await response.text().catch(() => '');\n this.logDebug(`FetchTransport: non-success response: ${response.status} ${text.slice(0, 200)}`);\n }\n\n return response;\n })\n .catch((error) => {\n clearTimeout(timeoutId);\n // DEBUG: Log the error\n this.logDebug(`FetchTransport: fetch failed - ${error?.message || error}`);\n\n // Increment failure counter\n this.consecutiveFailures++;\n\n // After MAX_CONSECUTIVE_FAILURES, enable circuit breaker to prevent infinite loops\n if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\n this.disabledUntil = new Date(this.getNow() + FAILURE_BACKOFF_MS);\n // Reset counter so we can try again after backoff\n this.consecutiveFailures = 0;\n this.logDebug(`FetchTransport: circuit breaker activated, disabled for 30s`);\n }\n\n // Do NOT log errors to console - this causes infinite loops in React Native\n // when the DevTools console override intercepts even unpatchedConsole calls\n return undefined;\n });\n });\n\n if (!response) {\n return { outcome: 'failed' };\n }\n if (response.status >= 200 && response.status < 300) {\n return {\n outcome: 'accepted',\n status: response.status,\n };\n }\n return {\n outcome: 'rejected',\n status: response.status,\n };\n } catch {\n // Buffer full error - Do NOT log to console as it creates infinite loops\n // The error is typically \"Task buffer full\" when the device is offline\n return { outcome: 'failed' };\n }\n }\n\n override getIgnoreUrls(): Patterns {\n return ([this.options.url] as Patterns).concat(this.config.ignoreUrls ?? []);\n }\n\n override isBatched(): boolean {\n return true;\n }\n\n private getRetryAfterDate(response: Response): Date {\n const now = this.getNow();\n const retryAfterHeader = response.headers.get('Retry-After');\n\n if (retryAfterHeader) {\n const delay = Number(retryAfterHeader);\n\n if (!isNaN(delay)) {\n return new Date(delay * 1000 + now);\n }\n\n const date = Date.parse(retryAfterHeader);\n\n if (!isNaN(date)) {\n return new Date(date);\n }\n }\n\n return new Date(now + this.rateLimitBackoffMs);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/transports/fetch/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface FetchTransportRequestOptions extends Omit<RequestInit, 'body' | 'headers'> {\n headers?: Record<string, string>;\n}\n\nexport interface FetchTransportOptions {\n // url of the collector endpoint\n url: string;\n\n // will be added as `x-api-key` header\n apiKey?: string;\n // how many requests to buffer in total\n bufferSize?: number;\n // how many requests to execute concurrently\n concurrency?: number;\n // if rate limit response does not include a Retry-After header,\n // how many milliseconds to back off before attempting a request.\n // intermediate events will be dropped, not buffered\n defaultRateLimitBackoffMs?: number;\n // get current date. for mocking purposes in tests\n getNow?: ClockFn;\n // addition options for global.Fetch\n requestOptions?: FetchTransportRequestOptions;\n}\n\nexport type ClockFn = () => number;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/transports/fetch/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface FetchTransportRequestOptions extends Omit<RequestInit, 'body' | 'headers'> {\n headers?: Record<string, string>;\n}\n\nexport interface FetchTransportOptions {\n // url of the collector endpoint\n url: string;\n\n // will be added as `x-api-key` header\n apiKey?: string;\n // how many requests to buffer in total\n bufferSize?: number;\n // how many requests to execute concurrently\n concurrency?: number;\n // if rate limit response does not include a Retry-After header,\n // how many milliseconds to back off before attempting a request.\n // intermediate events will be dropped, not buffered\n defaultRateLimitBackoffMs?: number;\n // get current date. for mocking purposes in tests\n getNow?: ClockFn;\n // addition options for global.Fetch\n requestOptions?: FetchTransportRequestOptions;\n}\n\nexport type ClockFn = () => number;\n\nexport type FetchTransportSendOutcome = 'accepted' | 'rejected' | 'failed' | 'skipped';\n\nexport interface FetchTransportSendResult {\n outcome: FetchTransportSendOutcome;\n status?: number;\n}\n"]}
@@ -25,7 +25,7 @@ const DEFAULT_CHECK_INTERVAL_MS = 30000;
25
25
  * Uses @react-native-community/netinfo when available, otherwise falls back
26
26
  * to basic fetch-based connectivity detection.
27
27
  *
28
- * Implementation follows Flutter SDK's InternetConnectivityService pattern.
28
+ * Monitors network connectivity for offline transport flush behavior.
29
29
  */
30
30
  export class DefaultConnectivityService {
31
31
  constructor(checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS) {
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectivityService.js","sourceRoot":"","sources":["../../../../src/transports/offline/ConnectivityService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConnectivityService } from './types';\n\n// Try to use NetInfo if available, otherwise fall back to basic online detection\nlet NetInfo: {\n fetch: () => Promise<{ isConnected: boolean | null }>;\n addEventListener: (callback: (state: { isConnected: boolean | null }) => void) => () => void;\n} | null = null;\n\ntry {\n NetInfo = require('@react-native-community/netinfo').default;\n} catch {\n // NetInfo not available, will fall back to basic detection\n}\n\n/**\n * Default connectivity check interval in milliseconds.\n */\nconst DEFAULT_CHECK_INTERVAL_MS = 30000;\n\n/**\n * Service for detecting network connectivity changes.\n *\n * Uses @react-native-community/netinfo when available, otherwise falls back\n * to basic fetch-based connectivity detection.\n *\n * Implementation follows Flutter SDK's InternetConnectivityService pattern.\n */\nexport class DefaultConnectivityService implements ConnectivityService {\n private _isOnline: boolean = true;\n private readonly subscribers: Set<(isOnline: boolean) => void> = new Set();\n private unsubscribeNetInfo: (() => void) | null = null;\n private checkIntervalId: ReturnType<typeof setInterval> | null = null;\n private readonly checkIntervalMs: number;\n\n constructor(checkIntervalMs: number = DEFAULT_CHECK_INTERVAL_MS) {\n this.checkIntervalMs = checkIntervalMs;\n this.initialize();\n }\n\n get isOnline(): boolean {\n return this._isOnline;\n }\n\n subscribe(callback: (isOnline: boolean) => void): () => void {\n this.subscribers.add(callback);\n // Immediately notify subscriber of current state\n callback(this._isOnline);\n\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n dispose(): void {\n if (this.unsubscribeNetInfo) {\n this.unsubscribeNetInfo();\n this.unsubscribeNetInfo = null;\n }\n if (this.checkIntervalId) {\n clearInterval(this.checkIntervalId);\n this.checkIntervalId = null;\n }\n this.subscribers.clear();\n }\n\n private initialize(): void {\n if (NetInfo) {\n this.initializeWithNetInfo();\n } else {\n this.initializeWithPolling();\n }\n }\n\n private initializeWithNetInfo(): void {\n if (!NetInfo) return;\n\n // Initial check\n NetInfo.fetch().then((state) => {\n this.setOnline(state.isConnected ?? true);\n });\n\n // Subscribe to changes\n this.unsubscribeNetInfo = NetInfo.addEventListener((state) => {\n this.setOnline(state.isConnected ?? true);\n });\n }\n\n private initializeWithPolling(): void {\n // Initial check\n this.checkConnectivity();\n\n // Poll periodically\n this.checkIntervalId = setInterval(() => {\n this.checkConnectivity();\n }, this.checkIntervalMs);\n }\n\n private async checkConnectivity(): Promise<void> {\n try {\n // Simple fetch-based connectivity check\n // We use a small, fast endpoint\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5000);\n\n await fetch('https://one.one.one.one/cdn-cgi/trace', {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n this.setOnline(true);\n } catch {\n this.setOnline(false);\n }\n }\n\n private setOnline(isOnline: boolean): void {\n if (this._isOnline !== isOnline) {\n this._isOnline = isOnline;\n this.notifySubscribers();\n }\n }\n\n private notifySubscribers(): void {\n this.subscribers.forEach((subscriber) => {\n try {\n subscriber(this._isOnline);\n } catch {\n // Ignore subscriber errors\n }\n });\n }\n}\n"]}
1
+ {"version":3,"file":"ConnectivityService.js","sourceRoot":"","sources":["../../../../src/transports/offline/ConnectivityService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConnectivityService } from './types';\n\n// Try to use NetInfo if available, otherwise fall back to basic online detection\nlet NetInfo: {\n fetch: () => Promise<{ isConnected: boolean | null }>;\n addEventListener: (callback: (state: { isConnected: boolean | null }) => void) => () => void;\n} | null = null;\n\ntry {\n NetInfo = require('@react-native-community/netinfo').default;\n} catch {\n // NetInfo not available, will fall back to basic detection\n}\n\n/**\n * Default connectivity check interval in milliseconds.\n */\nconst DEFAULT_CHECK_INTERVAL_MS = 30000;\n\n/**\n * Service for detecting network connectivity changes.\n *\n * Uses @react-native-community/netinfo when available, otherwise falls back\n * to basic fetch-based connectivity detection.\n *\n * Monitors network connectivity for offline transport flush behavior.\n */\nexport class DefaultConnectivityService implements ConnectivityService {\n private _isOnline: boolean = true;\n private readonly subscribers: Set<(isOnline: boolean) => void> = new Set();\n private unsubscribeNetInfo: (() => void) | null = null;\n private checkIntervalId: ReturnType<typeof setInterval> | null = null;\n private readonly checkIntervalMs: number;\n\n constructor(checkIntervalMs: number = DEFAULT_CHECK_INTERVAL_MS) {\n this.checkIntervalMs = checkIntervalMs;\n this.initialize();\n }\n\n get isOnline(): boolean {\n return this._isOnline;\n }\n\n subscribe(callback: (isOnline: boolean) => void): () => void {\n this.subscribers.add(callback);\n // Immediately notify subscriber of current state\n callback(this._isOnline);\n\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n dispose(): void {\n if (this.unsubscribeNetInfo) {\n this.unsubscribeNetInfo();\n this.unsubscribeNetInfo = null;\n }\n if (this.checkIntervalId) {\n clearInterval(this.checkIntervalId);\n this.checkIntervalId = null;\n }\n this.subscribers.clear();\n }\n\n private initialize(): void {\n if (NetInfo) {\n this.initializeWithNetInfo();\n } else {\n this.initializeWithPolling();\n }\n }\n\n private initializeWithNetInfo(): void {\n if (!NetInfo) return;\n\n // Initial check\n NetInfo.fetch().then((state) => {\n this.setOnline(state.isConnected ?? true);\n });\n\n // Subscribe to changes\n this.unsubscribeNetInfo = NetInfo.addEventListener((state) => {\n this.setOnline(state.isConnected ?? true);\n });\n }\n\n private initializeWithPolling(): void {\n // Initial check\n this.checkConnectivity();\n\n // Poll periodically\n this.checkIntervalId = setInterval(() => {\n this.checkConnectivity();\n }, this.checkIntervalMs);\n }\n\n private async checkConnectivity(): Promise<void> {\n try {\n // Simple fetch-based connectivity check\n // We use a small, fast endpoint\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5000);\n\n await fetch('https://one.one.one.one/cdn-cgi/trace', {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n this.setOnline(true);\n } catch {\n this.setOnline(false);\n }\n }\n\n private setOnline(isOnline: boolean): void {\n if (this._isOnline !== isOnline) {\n this._isOnline = isOnline;\n this.notifySubscribers();\n }\n }\n\n private notifySubscribers(): void {\n this.subscribers.forEach((subscriber) => {\n try {\n subscriber(this._isOnline);\n } catch {\n // Ignore subscriber errors\n }\n });\n }\n}\n"]}
@@ -20,7 +20,7 @@ const DEFAULT_MAX_CACHE_SIZE = 1000;
20
20
  * File-based offline cache implementation using AsyncStorage.
21
21
  *
22
22
  * Stores cached telemetry payloads as JSONL (one JSON object per line)
23
- * following the Flutter SDK's offline transport pattern.
23
+ * Persistent offline telemetry cache backed by AsyncStorage.
24
24
  *
25
25
  * Uses a mutex pattern for thread-safe file access.
26
26
  */
@@ -79,7 +79,7 @@ export class AsyncStorageOfflineCache {
79
79
  }
80
80
  /**
81
81
  * Execute an operation with a mutex lock to ensure thread-safe access.
82
- * Follows Flutter SDK's Completer-based lock pattern.
82
+ * Async lock to serialize cache read/write operations.
83
83
  */
84
84
  withLock(operation) {
85
85
  return __awaiter(this, void 0, void 0, function* () {
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineCache.js","sourceRoot":"","sources":["../../../../src/transports/offline/OfflineCache.ts"],"names":[],"mappings":"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","sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage';\n\nimport type { CachedPayload, OfflineCache } from './types';\n\n/**\n * Default storage key prefix for the offline cache.\n */\nconst DEFAULT_STORAGE_KEY_PREFIX = 'faro_offline_cache';\n\n/**\n * Default maximum number of cached payloads.\n */\nconst DEFAULT_MAX_CACHE_SIZE = 1000;\n\n/**\n * File-based offline cache implementation using AsyncStorage.\n *\n * Stores cached telemetry payloads as JSONL (one JSON object per line)\n * following the Flutter SDK's offline transport pattern.\n *\n * Uses a mutex pattern for thread-safe file access.\n */\nexport class AsyncStorageOfflineCache implements OfflineCache {\n private readonly storageKey: string;\n private readonly maxCacheSize: number;\n private lockPromise: Promise<void> = Promise.resolve();\n\n constructor(options: { storageKeyPrefix?: string; maxCacheSize?: number } = {}) {\n this.storageKey = `${options.storageKeyPrefix ?? DEFAULT_STORAGE_KEY_PREFIX}_data`;\n this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;\n }\n\n async write(payload: CachedPayload): Promise<void> {\n await this.withLock(async () => {\n const payloads = await this.readAllInternal();\n\n // Add new payload\n payloads.push(payload);\n\n // Trim to max size (remove oldest entries)\n while (payloads.length > this.maxCacheSize) {\n payloads.shift();\n }\n\n await this.writeAll(payloads);\n });\n }\n\n async readAll(): Promise<CachedPayload[]> {\n return this.withLock(async () => {\n return this.readAllInternal();\n });\n }\n\n async clear(): Promise<void> {\n await this.withLock(async () => {\n await AsyncStorage.removeItem(this.storageKey);\n });\n }\n\n async removeByTimestamps(timestamps: number[]): Promise<void> {\n if (timestamps.length === 0) return;\n\n const timestampSet = new Set(timestamps);\n\n await this.withLock(async () => {\n const payloads = await this.readAllInternal();\n const remaining = payloads.filter((p) => !timestampSet.has(p.timestamp));\n await this.writeAll(remaining);\n });\n }\n\n async getCount(): Promise<number> {\n const payloads = await this.readAllInternal();\n return payloads.length;\n }\n\n /**\n * Execute an operation with a mutex lock to ensure thread-safe access.\n * Follows Flutter SDK's Completer-based lock pattern.\n */\n private async withLock<T>(operation: () => Promise<T>): Promise<T> {\n const currentLock = this.lockPromise;\n let resolveLock: () => void;\n this.lockPromise = new Promise<void>((resolve) => {\n resolveLock = resolve;\n });\n\n try {\n await currentLock;\n return await operation();\n } finally {\n resolveLock!();\n }\n }\n\n private async readAllInternal(): Promise<CachedPayload[]> {\n try {\n const data = await AsyncStorage.getItem(this.storageKey);\n if (!data) {\n return [];\n }\n\n const parsed = JSON.parse(data);\n if (!Array.isArray(parsed)) {\n return [];\n }\n\n return parsed.filter(this.isValidCachedPayload);\n } catch {\n // If parsing fails, return empty array\n return [];\n }\n }\n\n private async writeAll(payloads: CachedPayload[]): Promise<void> {\n if (payloads.length === 0) {\n await AsyncStorage.removeItem(this.storageKey);\n } else {\n await AsyncStorage.setItem(this.storageKey, JSON.stringify(payloads));\n }\n }\n\n private isValidCachedPayload(payload: unknown): payload is CachedPayload {\n if (typeof payload !== 'object' || payload === null) {\n return false;\n }\n\n const p = payload as Record<string, unknown>;\n return typeof p['timestamp'] === 'number' && Array.isArray(p['items']);\n }\n}\n"]}
1
+ {"version":3,"file":"OfflineCache.js","sourceRoot":"","sources":["../../../../src/transports/offline/OfflineCache.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,YAAY,MAAM,2CAA2C,CAAC;AAIrE;;GAEG;AACH,MAAM,0BAA0B,GAAG,oBAAoB,CAAC;AAExD;;GAEG;AACH,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,OAAO,wBAAwB;IAKnC,YAAY,UAAgE,EAAE;;QAFtE,gBAAW,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;QAGrD,IAAI,CAAC,UAAU,GAAG,GAAG,MAAA,OAAO,CAAC,gBAAgB,mCAAI,0BAA0B,OAAO,CAAC;QACnF,IAAI,CAAC,YAAY,GAAG,MAAA,OAAO,CAAC,YAAY,mCAAI,sBAAsB,CAAC;IACrE,CAAC;IAEK,KAAK,CAAC,OAAsB;;YAChC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAS,EAAE;gBAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;gBAE9C,kBAAkB;gBAClB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAEvB,2CAA2C;gBAC3C,OAAO,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;oBAC3C,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACnB,CAAC;gBAED,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC,CAAA,CAAC,CAAC;QACL,CAAC;KAAA;IAEK,OAAO;;YACX,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAS,EAAE;gBAC9B,OAAO,IAAI,CAAC,eAAe,EAAE,CAAC;YAChC,CAAC,CAAA,CAAC,CAAC;QACL,CAAC;KAAA;IAEK,KAAK;;YACT,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAS,EAAE;gBAC7B,MAAM,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC,CAAA,CAAC,CAAC;QACL,CAAC;KAAA;IAEK,kBAAkB,CAAC,UAAoB;;YAC3C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAEpC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;YAEzC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAS,EAAE;gBAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC9C,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;gBACzE,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACjC,CAAC,CAAA,CAAC,CAAC;QACL,CAAC;KAAA;IAEK,QAAQ;;YACZ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;YAC9C,OAAO,QAAQ,CAAC,MAAM,CAAC;QACzB,CAAC;KAAA;IAED;;;OAGG;IACW,QAAQ,CAAI,SAA2B;;YACnD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;YACrC,IAAI,WAAuB,CAAC;YAC5B,IAAI,CAAC,WAAW,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAC/C,WAAW,GAAG,OAAO,CAAC;YACxB,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,MAAM,WAAW,CAAC;gBAClB,OAAO,MAAM,SAAS,EAAE,CAAC;YAC3B,CAAC;oBAAS,CAAC;gBACT,WAAY,EAAE,CAAC;YACjB,CAAC;QACH,CAAC;KAAA;IAEa,eAAe;;YAC3B,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACzD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,OAAO,EAAE,CAAC;gBACZ,CAAC;gBAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3B,OAAO,EAAE,CAAC;gBACZ,CAAC;gBAED,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAClD,CAAC;YAAC,WAAM,CAAC;gBACP,uCAAuC;gBACvC,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;KAAA;IAEa,QAAQ,CAAC,QAAyB;;YAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC;iBAAM,CAAC;gBACN,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;KAAA;IAEO,oBAAoB,CAAC,OAAgB;QAC3C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACpD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,CAAC,GAAG,OAAkC,CAAC;QAC7C,OAAO,OAAO,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,CAAC;CACF","sourcesContent":["import AsyncStorage from '@react-native-async-storage/async-storage';\n\nimport type { CachedPayload, OfflineCache } from './types';\n\n/**\n * Default storage key prefix for the offline cache.\n */\nconst DEFAULT_STORAGE_KEY_PREFIX = 'faro_offline_cache';\n\n/**\n * Default maximum number of cached payloads.\n */\nconst DEFAULT_MAX_CACHE_SIZE = 1000;\n\n/**\n * File-based offline cache implementation using AsyncStorage.\n *\n * Stores cached telemetry payloads as JSONL (one JSON object per line)\n * Persistent offline telemetry cache backed by AsyncStorage.\n *\n * Uses a mutex pattern for thread-safe file access.\n */\nexport class AsyncStorageOfflineCache implements OfflineCache {\n private readonly storageKey: string;\n private readonly maxCacheSize: number;\n private lockPromise: Promise<void> = Promise.resolve();\n\n constructor(options: { storageKeyPrefix?: string; maxCacheSize?: number } = {}) {\n this.storageKey = `${options.storageKeyPrefix ?? DEFAULT_STORAGE_KEY_PREFIX}_data`;\n this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;\n }\n\n async write(payload: CachedPayload): Promise<void> {\n await this.withLock(async () => {\n const payloads = await this.readAllInternal();\n\n // Add new payload\n payloads.push(payload);\n\n // Trim to max size (remove oldest entries)\n while (payloads.length > this.maxCacheSize) {\n payloads.shift();\n }\n\n await this.writeAll(payloads);\n });\n }\n\n async readAll(): Promise<CachedPayload[]> {\n return this.withLock(async () => {\n return this.readAllInternal();\n });\n }\n\n async clear(): Promise<void> {\n await this.withLock(async () => {\n await AsyncStorage.removeItem(this.storageKey);\n });\n }\n\n async removeByTimestamps(timestamps: number[]): Promise<void> {\n if (timestamps.length === 0) return;\n\n const timestampSet = new Set(timestamps);\n\n await this.withLock(async () => {\n const payloads = await this.readAllInternal();\n const remaining = payloads.filter((p) => !timestampSet.has(p.timestamp));\n await this.writeAll(remaining);\n });\n }\n\n async getCount(): Promise<number> {\n const payloads = await this.readAllInternal();\n return payloads.length;\n }\n\n /**\n * Execute an operation with a mutex lock to ensure thread-safe access.\n * Async lock to serialize cache read/write operations.\n */\n private async withLock<T>(operation: () => Promise<T>): Promise<T> {\n const currentLock = this.lockPromise;\n let resolveLock: () => void;\n this.lockPromise = new Promise<void>((resolve) => {\n resolveLock = resolve;\n });\n\n try {\n await currentLock;\n return await operation();\n } finally {\n resolveLock!();\n }\n }\n\n private async readAllInternal(): Promise<CachedPayload[]> {\n try {\n const data = await AsyncStorage.getItem(this.storageKey);\n if (!data) {\n return [];\n }\n\n const parsed = JSON.parse(data);\n if (!Array.isArray(parsed)) {\n return [];\n }\n\n return parsed.filter(this.isValidCachedPayload);\n } catch {\n // If parsing fails, return empty array\n return [];\n }\n }\n\n private async writeAll(payloads: CachedPayload[]): Promise<void> {\n if (payloads.length === 0) {\n await AsyncStorage.removeItem(this.storageKey);\n } else {\n await AsyncStorage.setItem(this.storageKey, JSON.stringify(payloads));\n }\n }\n\n private isValidCachedPayload(payload: unknown): payload is CachedPayload {\n if (typeof payload !== 'object' || payload === null) {\n return false;\n }\n\n const p = payload as Record<string, unknown>;\n return typeof p['timestamp'] === 'number' && Array.isArray(p['items']);\n }\n}\n"]}
@@ -21,8 +21,7 @@ const DEFAULT_MAX_CACHE_DURATION_MS = 3 * 24 * 60 * 60 * 1000;
21
21
  * When the device is offline, telemetry is cached to AsyncStorage.
22
22
  * When connectivity is restored, cached telemetry is replayed through the wrapped transports.
23
23
  *
24
- * Implementation follows Flutter SDK's OfflineTransport pattern:
25
- * - Uses AsyncStorage for persistent caching (matching Flutter's SharedPreferences)
24
+ * Uses AsyncStorage for persistent offline telemetry caching.
26
25
  * - Respects maxCacheDuration to skip expired entries
27
26
  * - Uses mutex pattern for thread-safe cache access
28
27
  * - Excludes itself when replaying to prevent infinite loops
@@ -150,7 +149,7 @@ export class OfflineTransport extends BaseTransport {
150
149
  }
151
150
  /**
152
151
  * Replay cached payloads through other transports.
153
- * Follows Flutter SDK's _readFromFile pattern.
152
+ * Reads cached telemetry from AsyncStorage.
154
153
  */
155
154
  replayCachedPayloads() {
156
155
  return __awaiter(this, void 0, void 0, function* () {
@@ -194,7 +193,7 @@ export class OfflineTransport extends BaseTransport {
194
193
  }
195
194
  /**
196
195
  * Send items to all other registered transports.
197
- * Follows Flutter SDK's _sendCachedData pattern.
196
+ * Sends cached telemetry when connectivity is restored.
198
197
  */
199
198
  sendToOtherTransports(items) {
200
199
  return __awaiter(this, void 0, void 0, function* () {
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/transports/offline/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseTransport, VERSION } from '@grafana/faro-core';\nimport type { Patterns, Transport, TransportItem } from '@grafana/faro-core';\n\nimport { DefaultConnectivityService } from './ConnectivityService';\nimport { AsyncStorageOfflineCache } from './OfflineCache';\nimport type { CachedPayload, ConnectivityService, OfflineCache, OfflineTransportOptions } from './types';\n\n/**\n * Default maximum cache duration: 3 days in milliseconds.\n */\nconst DEFAULT_MAX_CACHE_DURATION_MS = 3 * 24 * 60 * 60 * 1000;\n\n/**\n * OfflineTransport - Caches telemetry when offline and replays when online.\n *\n * This transport wraps other transports and provides offline caching functionality.\n * When the device is offline, telemetry is cached to AsyncStorage.\n * When connectivity is restored, cached telemetry is replayed through the wrapped transports.\n *\n * Implementation follows Flutter SDK's OfflineTransport pattern:\n * - Uses AsyncStorage for persistent caching (matching Flutter's SharedPreferences)\n * - Respects maxCacheDuration to skip expired entries\n * - Uses mutex pattern for thread-safe cache access\n * - Excludes itself when replaying to prevent infinite loops\n *\n * @example\n * ```typescript\n * import { initializeFaro, FetchTransport, OfflineTransport } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * transports: [\n * new OfflineTransport({\n * maxCacheDurationMs: 3 * 24 * 60 * 60 * 1000, // 3 days\n * }),\n * new FetchTransport({ url: 'https://collector.example.com' }),\n * ],\n * });\n * ```\n */\nexport class OfflineTransport extends BaseTransport {\n readonly name = '@grafana/faro-react-native:transport-offline';\n readonly version = VERSION;\n\n private readonly cache: OfflineCache;\n private readonly connectivityService: ConnectivityService;\n private readonly maxCacheDurationMs: number;\n private readonly otherTransports: Transport[] = [];\n private unsubscribeConnectivity: (() => void) | null = null;\n private isReplaying: boolean = false;\n\n constructor(options: OfflineTransportOptions = {}) {\n super();\n\n this.maxCacheDurationMs = options.maxCacheDurationMs ?? DEFAULT_MAX_CACHE_DURATION_MS;\n\n this.cache = new AsyncStorageOfflineCache({\n storageKeyPrefix: options.storageKeyPrefix,\n maxCacheSize: options.maxCacheSize,\n });\n\n this.connectivityService = new DefaultConnectivityService(options.connectivityCheckIntervalMs);\n\n // Subscribe to connectivity changes\n this.unsubscribeConnectivity = this.connectivityService.subscribe((isOnline) => {\n if (isOnline && !this.isReplaying) {\n this.replayCachedPayloads();\n }\n });\n }\n\n /**\n * Send telemetry items.\n *\n * When offline, items are cached for later replay.\n * When online, this transport does nothing (other transports handle sending).\n */\n async send(items: TransportItem | TransportItem[]): Promise<void> {\n const itemsArray = Array.isArray(items) ? items : [items];\n\n if (itemsArray.length === 0) {\n return;\n }\n\n if (!this.connectivityService.isOnline) {\n // Cache items when offline\n const payload: CachedPayload = {\n timestamp: Date.now(),\n items: itemsArray,\n };\n\n try {\n await this.cache.write(payload);\n this.logDebug(`Cached ${itemsArray.length} items for offline replay`);\n } catch (error) {\n this.logError('Failed to cache offline payload', error);\n }\n }\n // When online, do nothing - other transports handle sending\n }\n\n override getIgnoreUrls(): Patterns {\n return [];\n }\n\n override isBatched(): boolean {\n return true;\n }\n\n /**\n * Register other transports for replay.\n * Called by the transport system after initialization.\n */\n setOtherTransports(transports: Transport[]): void {\n this.otherTransports.length = 0;\n for (const transport of transports) {\n if (transport !== this) {\n this.otherTransports.push(transport);\n }\n }\n }\n\n /**\n * Get the current number of cached payloads.\n */\n async getCachedCount(): Promise<number> {\n return this.cache.getCount();\n }\n\n /**\n * Manually trigger replay of cached payloads.\n * Useful for testing or when you want to force a replay attempt.\n */\n async forceReplay(): Promise<void> {\n if (this.connectivityService.isOnline) {\n await this.replayCachedPayloads();\n }\n }\n\n /**\n * Clear all cached payloads.\n */\n async clearCache(): Promise<void> {\n await this.cache.clear();\n }\n\n /**\n * Clean up resources.\n */\n dispose(): void {\n if (this.unsubscribeConnectivity) {\n this.unsubscribeConnectivity();\n this.unsubscribeConnectivity = null;\n }\n this.connectivityService.dispose();\n }\n\n /**\n * Replay cached payloads through other transports.\n * Follows Flutter SDK's _readFromFile pattern.\n */\n private async replayCachedPayloads(): Promise<void> {\n if (this.isReplaying) {\n return;\n }\n\n this.isReplaying = true;\n\n try {\n const payloads = await this.cache.readAll();\n\n if (payloads.length === 0) {\n return;\n }\n\n this.logDebug(`Replaying ${payloads.length} cached payloads`);\n\n const now = Date.now();\n const successfulTimestamps: number[] = [];\n\n for (const payload of payloads) {\n // Skip expired payloads\n if (now - payload.timestamp > this.maxCacheDurationMs) {\n this.logDebug(`Skipping expired payload from ${new Date(payload.timestamp).toISOString()}`);\n successfulTimestamps.push(payload.timestamp);\n continue;\n }\n\n const success = await this.sendToOtherTransports(payload.items);\n\n if (success) {\n successfulTimestamps.push(payload.timestamp);\n }\n }\n\n // Remove successfully sent or expired payloads\n if (successfulTimestamps.length > 0) {\n await this.cache.removeByTimestamps(successfulTimestamps);\n this.logDebug(`Removed ${successfulTimestamps.length} payloads from cache`);\n }\n } catch (error) {\n this.logError('Failed to replay cached payloads', error);\n } finally {\n this.isReplaying = false;\n }\n }\n\n /**\n * Send items to all other registered transports.\n * Follows Flutter SDK's _sendCachedData pattern.\n */\n private async sendToOtherTransports(items: TransportItem[]): Promise<boolean> {\n if (this.otherTransports.length === 0) {\n this.logWarn('No other transports registered for offline replay');\n return false;\n }\n\n try {\n for (const transport of this.otherTransports) {\n await transport.send(items);\n }\n return true;\n } catch (error) {\n this.logError('Failed to send cached payload to transports', error);\n return false;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/transports/offline/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseTransport, VERSION } from '@grafana/faro-core';\nimport type { Patterns, Transport, TransportItem } from '@grafana/faro-core';\n\nimport { DefaultConnectivityService } from './ConnectivityService';\nimport { AsyncStorageOfflineCache } from './OfflineCache';\nimport type { CachedPayload, ConnectivityService, OfflineCache, OfflineTransportOptions } from './types';\n\n/**\n * Default maximum cache duration: 3 days in milliseconds.\n */\nconst DEFAULT_MAX_CACHE_DURATION_MS = 3 * 24 * 60 * 60 * 1000;\n\n/**\n * OfflineTransport - Caches telemetry when offline and replays when online.\n *\n * This transport wraps other transports and provides offline caching functionality.\n * When the device is offline, telemetry is cached to AsyncStorage.\n * When connectivity is restored, cached telemetry is replayed through the wrapped transports.\n *\n * Uses AsyncStorage for persistent offline telemetry caching.\n * - Respects maxCacheDuration to skip expired entries\n * - Uses mutex pattern for thread-safe cache access\n * - Excludes itself when replaying to prevent infinite loops\n *\n * @example\n * ```typescript\n * import { initializeFaro, FetchTransport, OfflineTransport } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * transports: [\n * new OfflineTransport({\n * maxCacheDurationMs: 3 * 24 * 60 * 60 * 1000, // 3 days\n * }),\n * new FetchTransport({ url: 'https://collector.example.com' }),\n * ],\n * });\n * ```\n */\nexport class OfflineTransport extends BaseTransport {\n readonly name = '@grafana/faro-react-native:transport-offline';\n readonly version = VERSION;\n\n private readonly cache: OfflineCache;\n private readonly connectivityService: ConnectivityService;\n private readonly maxCacheDurationMs: number;\n private readonly otherTransports: Transport[] = [];\n private unsubscribeConnectivity: (() => void) | null = null;\n private isReplaying: boolean = false;\n\n constructor(options: OfflineTransportOptions = {}) {\n super();\n\n this.maxCacheDurationMs = options.maxCacheDurationMs ?? DEFAULT_MAX_CACHE_DURATION_MS;\n\n this.cache = new AsyncStorageOfflineCache({\n storageKeyPrefix: options.storageKeyPrefix,\n maxCacheSize: options.maxCacheSize,\n });\n\n this.connectivityService = new DefaultConnectivityService(options.connectivityCheckIntervalMs);\n\n // Subscribe to connectivity changes\n this.unsubscribeConnectivity = this.connectivityService.subscribe((isOnline) => {\n if (isOnline && !this.isReplaying) {\n this.replayCachedPayloads();\n }\n });\n }\n\n /**\n * Send telemetry items.\n *\n * When offline, items are cached for later replay.\n * When online, this transport does nothing (other transports handle sending).\n */\n async send(items: TransportItem | TransportItem[]): Promise<void> {\n const itemsArray = Array.isArray(items) ? items : [items];\n\n if (itemsArray.length === 0) {\n return;\n }\n\n if (!this.connectivityService.isOnline) {\n // Cache items when offline\n const payload: CachedPayload = {\n timestamp: Date.now(),\n items: itemsArray,\n };\n\n try {\n await this.cache.write(payload);\n this.logDebug(`Cached ${itemsArray.length} items for offline replay`);\n } catch (error) {\n this.logError('Failed to cache offline payload', error);\n }\n }\n // When online, do nothing - other transports handle sending\n }\n\n override getIgnoreUrls(): Patterns {\n return [];\n }\n\n override isBatched(): boolean {\n return true;\n }\n\n /**\n * Register other transports for replay.\n * Called by the transport system after initialization.\n */\n setOtherTransports(transports: Transport[]): void {\n this.otherTransports.length = 0;\n for (const transport of transports) {\n if (transport !== this) {\n this.otherTransports.push(transport);\n }\n }\n }\n\n /**\n * Get the current number of cached payloads.\n */\n async getCachedCount(): Promise<number> {\n return this.cache.getCount();\n }\n\n /**\n * Manually trigger replay of cached payloads.\n * Useful for testing or when you want to force a replay attempt.\n */\n async forceReplay(): Promise<void> {\n if (this.connectivityService.isOnline) {\n await this.replayCachedPayloads();\n }\n }\n\n /**\n * Clear all cached payloads.\n */\n async clearCache(): Promise<void> {\n await this.cache.clear();\n }\n\n /**\n * Clean up resources.\n */\n dispose(): void {\n if (this.unsubscribeConnectivity) {\n this.unsubscribeConnectivity();\n this.unsubscribeConnectivity = null;\n }\n this.connectivityService.dispose();\n }\n\n /**\n * Replay cached payloads through other transports.\n * Reads cached telemetry from AsyncStorage.\n */\n private async replayCachedPayloads(): Promise<void> {\n if (this.isReplaying) {\n return;\n }\n\n this.isReplaying = true;\n\n try {\n const payloads = await this.cache.readAll();\n\n if (payloads.length === 0) {\n return;\n }\n\n this.logDebug(`Replaying ${payloads.length} cached payloads`);\n\n const now = Date.now();\n const successfulTimestamps: number[] = [];\n\n for (const payload of payloads) {\n // Skip expired payloads\n if (now - payload.timestamp > this.maxCacheDurationMs) {\n this.logDebug(`Skipping expired payload from ${new Date(payload.timestamp).toISOString()}`);\n successfulTimestamps.push(payload.timestamp);\n continue;\n }\n\n const success = await this.sendToOtherTransports(payload.items);\n\n if (success) {\n successfulTimestamps.push(payload.timestamp);\n }\n }\n\n // Remove successfully sent or expired payloads\n if (successfulTimestamps.length > 0) {\n await this.cache.removeByTimestamps(successfulTimestamps);\n this.logDebug(`Removed ${successfulTimestamps.length} payloads from cache`);\n }\n } catch (error) {\n this.logError('Failed to replay cached payloads', error);\n } finally {\n this.isReplaying = false;\n }\n }\n\n /**\n * Send items to all other registered transports.\n * Sends cached telemetry when connectivity is restored.\n */\n private async sendToOtherTransports(items: TransportItem[]): Promise<boolean> {\n if (this.otherTransports.length === 0) {\n this.logWarn('No other transports registered for offline replay');\n return false;\n }\n\n try {\n for (const transport of this.otherTransports) {\n await transport.send(items);\n }\n return true;\n } catch (error) {\n this.logError('Failed to send cached payload to transports', error);\n return false;\n }\n }\n}\n"]}
@@ -18,8 +18,7 @@ const DEFAULT_STORAGE_KEY = 'faro_persisted_user';
18
18
  * Stores user information persistently so it can be restored on app restart.
19
19
  * This ensures early telemetry events include user identification.
20
20
  *
21
- * Implementation follows Flutter SDK's UserPersistence pattern:
22
- * - Uses AsyncStorage for persistence (matching Flutter's SharedPreferences)
21
+ * Uses AsyncStorage for persistent user data across app sessions.
23
22
  * - Stores user as JSON
24
23
  * - Handles null/cleared users
25
24
  *