@nathanclaire/edge-telemetry-sdk 3.0.0 → 4.0.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 (298) hide show
  1. package/README.md +831 -186
  2. package/dist/appLifecycle-CECgRVu_.cjs +2 -0
  3. package/dist/appLifecycle-CECgRVu_.cjs.map +1 -0
  4. package/dist/appLifecycle-DQErzJIR.js +22 -0
  5. package/dist/appLifecycle-DQErzJIR.js.map +1 -0
  6. package/dist/batch-CwoO7noL.js +56 -0
  7. package/dist/batch-CwoO7noL.js.map +1 -0
  8. package/dist/batch-DNu8Xr9T.cjs +2 -0
  9. package/dist/batch-DNu8Xr9T.cjs.map +1 -0
  10. package/dist/crashCapture-BOJ8nSAZ.cjs +4 -0
  11. package/dist/crashCapture-BOJ8nSAZ.cjs.map +1 -0
  12. package/dist/crashCapture-BW8WuyaU.js +70 -0
  13. package/dist/crashCapture-BW8WuyaU.js.map +1 -0
  14. package/dist/crashHandler.web-8F_k2veo.js +29 -0
  15. package/dist/crashHandler.web-8F_k2veo.js.map +1 -0
  16. package/dist/crashHandler.web-CWmF56mJ.cjs +2 -0
  17. package/dist/crashHandler.web-CWmF56mJ.cjs.map +1 -0
  18. package/dist/crashHandlerNative.native-C2kSikDt.cjs +2 -0
  19. package/dist/crashHandlerNative.native-C2kSikDt.cjs.map +1 -0
  20. package/dist/crashHandlerNative.native-FupMnr1U.js +39 -0
  21. package/dist/crashHandlerNative.native-FupMnr1U.js.map +1 -0
  22. package/dist/deviceInfo.native-BJ-3Jj1T.js +53 -0
  23. package/dist/deviceInfo.native-BJ-3Jj1T.js.map +1 -0
  24. package/dist/deviceInfo.native-BoeTZcD6.cjs +2 -0
  25. package/dist/deviceInfo.native-BoeTZcD6.cjs.map +1 -0
  26. package/dist/deviceInfo.web-3L3IDQQA.cjs +2 -0
  27. package/dist/deviceInfo.web-3L3IDQQA.cjs.map +1 -0
  28. package/dist/deviceInfo.web-BFB1-pcA.js +41 -0
  29. package/dist/deviceInfo.web-BFB1-pcA.js.map +1 -0
  30. package/dist/frameTracker-CHG6sIx9.cjs +2 -0
  31. package/dist/frameTracker-CHG6sIx9.cjs.map +1 -0
  32. package/dist/frameTracker-DdB8W_kC.js +54 -0
  33. package/dist/frameTracker-DdB8W_kC.js.map +1 -0
  34. package/dist/httpAttributes-BrrtM8zh.js +70 -0
  35. package/dist/httpAttributes-BrrtM8zh.js.map +1 -0
  36. package/dist/httpAttributes-D0m6UID7.cjs +2 -0
  37. package/dist/httpAttributes-D0m6UID7.cjs.map +1 -0
  38. package/dist/index-BtLlV1HA.js +57 -0
  39. package/dist/index-BtLlV1HA.js.map +1 -0
  40. package/dist/{deviceInfo.native-D2k0LDhI.js → index-C2XQ-EYw.js} +362 -411
  41. package/dist/index-C2XQ-EYw.js.map +1 -0
  42. package/dist/index-CXsVZY35.cjs +2 -0
  43. package/dist/index-CXsVZY35.cjs.map +1 -0
  44. package/dist/index-DLquF88M.cjs +2 -0
  45. package/dist/index-DLquF88M.cjs.map +1 -0
  46. package/dist/index.native.cjs +1 -1
  47. package/dist/index.native.cjs.map +1 -1
  48. package/dist/index.native.js +94 -59
  49. package/dist/index.native.js.map +1 -1
  50. package/dist/index.web.cjs +1 -1
  51. package/dist/index.web.cjs.map +1 -1
  52. package/dist/index.web.js +88 -33
  53. package/dist/index.web.js.map +1 -1
  54. package/dist/interactionWeb.web-Bl_cZlgh.cjs +2 -0
  55. package/dist/interactionWeb.web-Bl_cZlgh.cjs.map +1 -0
  56. package/dist/interactionWeb.web-CJAviVQ7.js +109 -0
  57. package/dist/interactionWeb.web-CJAviVQ7.js.map +1 -0
  58. package/dist/interceptFetchWeb.web-T88kO-bE.cjs +2 -0
  59. package/dist/interceptFetchWeb.web-T88kO-bE.cjs.map +1 -0
  60. package/dist/interceptFetchWeb.web-UXC2Lb6X.js +61 -0
  61. package/dist/interceptFetchWeb.web-UXC2Lb6X.js.map +1 -0
  62. package/dist/interceptHttpNative.native-BVpFuoNB.cjs +2 -0
  63. package/dist/interceptHttpNative.native-BVpFuoNB.cjs.map +1 -0
  64. package/dist/interceptHttpNative.native-rX-lPNST.js +18 -0
  65. package/dist/interceptHttpNative.native-rX-lPNST.js.map +1 -0
  66. package/dist/loadingTime-Bhomd9qq.cjs +2 -0
  67. package/dist/loadingTime-Bhomd9qq.cjs.map +1 -0
  68. package/dist/loadingTime-ZkYvFijA.js +70 -0
  69. package/dist/loadingTime-ZkYvFijA.js.map +1 -0
  70. package/dist/memoryNative.native-C42h9SjK.js +54 -0
  71. package/dist/memoryNative.native-C42h9SjK.js.map +1 -0
  72. package/dist/memoryNative.native-CA-7w9w1.cjs +2 -0
  73. package/dist/memoryNative.native-CA-7w9w1.cjs.map +1 -0
  74. package/dist/memoryStore-BIGZmmfD.cjs +2 -0
  75. package/dist/memoryStore-BIGZmmfD.cjs.map +1 -0
  76. package/dist/memoryStore-F5I-GQKM.js +193 -0
  77. package/dist/memoryStore-F5I-GQKM.js.map +1 -0
  78. package/dist/nativeSender-CfQaRuDK.cjs +2 -0
  79. package/dist/nativeSender-CfQaRuDK.cjs.map +1 -0
  80. package/dist/nativeSender-pgVSIv-X.js +59 -0
  81. package/dist/nativeSender-pgVSIv-X.js.map +1 -0
  82. package/dist/navigationRef-BzYbs8cm.js +28 -0
  83. package/dist/navigationRef-BzYbs8cm.js.map +1 -0
  84. package/dist/navigationRef-C-_qhb43.cjs +2 -0
  85. package/dist/navigationRef-C-_qhb43.cjs.map +1 -0
  86. package/dist/{navigationTracker-Cts68dgG.js → navigationTracker-BmywA_Bg.js} +4 -4
  87. package/dist/navigationTracker-BmywA_Bg.js.map +1 -0
  88. package/dist/navigationTracker-HkwVyywz.cjs +2 -0
  89. package/dist/navigationTracker-HkwVyywz.cjs.map +1 -0
  90. package/dist/navigationWeb.web-CW_z7Ca4.cjs +2 -0
  91. package/dist/navigationWeb.web-CW_z7Ca4.cjs.map +1 -0
  92. package/dist/navigationWeb.web-CcwgKuIZ.js +55 -0
  93. package/dist/navigationWeb.web-CcwgKuIZ.js.map +1 -0
  94. package/dist/{networkInfo.native-BK5fpAOL.js → networkInfo.native-CGrx9cnH.js} +15 -15
  95. package/dist/{networkInfo.native-BK5fpAOL.js.map → networkInfo.native-CGrx9cnH.js.map} +1 -1
  96. package/dist/{networkInfo.native-cxuw93Uy.cjs → networkInfo.native-YVySdv3S.cjs} +2 -2
  97. package/dist/{networkInfo.native-cxuw93Uy.cjs.map → networkInfo.native-YVySdv3S.cjs.map} +1 -1
  98. package/dist/{networkInfo.web-twcNn_hc.js → networkInfo.web-B2YlLNsI.js} +3 -3
  99. package/dist/networkInfo.web-B2YlLNsI.js.map +1 -0
  100. package/dist/networkInfo.web-PltJTHsy.cjs +2 -0
  101. package/dist/networkInfo.web-PltJTHsy.cjs.map +1 -0
  102. package/dist/runtimeReady.native-Bh7EcYmK.js +14 -0
  103. package/dist/runtimeReady.native-Bh7EcYmK.js.map +1 -0
  104. package/dist/runtimeReady.native-jUmuE1lV.cjs +2 -0
  105. package/dist/runtimeReady.native-jUmuE1lV.cjs.map +1 -0
  106. package/dist/runtimeReady.web-C8fdIbVR.js +25 -0
  107. package/dist/runtimeReady.web-C8fdIbVR.js.map +1 -0
  108. package/dist/runtimeReady.web-DHW682RN.cjs +2 -0
  109. package/dist/runtimeReady.web-DHW682RN.cjs.map +1 -0
  110. package/dist/seedRuntimeReady-BKwkBEAF.cjs +2 -0
  111. package/dist/seedRuntimeReady-BKwkBEAF.cjs.map +1 -0
  112. package/dist/seedRuntimeReady-Cz8nOKoo.js +10 -0
  113. package/dist/seedRuntimeReady-Cz8nOKoo.js.map +1 -0
  114. package/dist/src/adapters/appLifecycle.d.ts +8 -1
  115. package/dist/src/adapters/batch.d.ts +2 -0
  116. package/dist/src/adapters/crashCapture.d.ts +38 -9
  117. package/dist/src/adapters/failedEvents.d.ts +43 -0
  118. package/dist/src/adapters/frameAggregate.d.ts +8 -3
  119. package/dist/src/adapters/frameTracker.d.ts +30 -0
  120. package/dist/src/adapters/httpAttributes.d.ts +18 -0
  121. package/dist/src/adapters/loadingTime.d.ts +67 -0
  122. package/dist/src/adapters/native/interceptHttpNative.native.d.ts +18 -0
  123. package/dist/src/adapters/native/memoryNative.native.d.ts +15 -11
  124. package/dist/src/adapters/native/runtimeReady.native.d.ts +1 -0
  125. package/dist/src/adapters/native/store.native.d.ts +10 -0
  126. package/dist/src/adapters/nativeSender.d.ts +2 -2
  127. package/dist/src/adapters/navigationRef.d.ts +21 -0
  128. package/dist/src/adapters/navigationTracker.d.ts +1 -1
  129. package/dist/src/adapters/screenTiming.d.ts +8 -2
  130. package/dist/src/adapters/seedRuntimeReady.d.ts +8 -0
  131. package/dist/src/adapters/traceHeader.d.ts +33 -0
  132. package/dist/src/adapters/traceManager.d.ts +131 -0
  133. package/dist/src/adapters/uiInteraction.d.ts +111 -0
  134. package/dist/src/adapters/viewManager.d.ts +161 -0
  135. package/dist/src/adapters/viewport.d.ts +15 -0
  136. package/dist/src/adapters/web/interactionWeb.web.d.ts +36 -0
  137. package/dist/src/adapters/web/interceptFetchWeb.web.d.ts +3 -11
  138. package/dist/src/adapters/web/navigationWeb.web.d.ts +10 -0
  139. package/dist/src/adapters/web/runtimeReady.web.d.ts +5 -0
  140. package/dist/src/adapters/web/store.web.d.ts +12 -0
  141. package/dist/src/adapters/web/webVitals.web.d.ts +49 -0
  142. package/dist/src/adapters/webSender.d.ts +2 -2
  143. package/dist/src/adapters/webVitals.d.ts +25 -0
  144. package/dist/src/adapters/xhrIntercept.d.ts +53 -0
  145. package/dist/src/core/beforeSend.d.ts +20 -0
  146. package/dist/src/core/debug.d.ts +7 -0
  147. package/dist/src/core/memoryStore.d.ts +23 -0
  148. package/dist/src/core/store.d.ts +41 -0
  149. package/dist/src/core/telemetry.d.ts +305 -35
  150. package/dist/src/core/userProfile.d.ts +12 -0
  151. package/dist/src/core/utils/json.d.ts +7 -0
  152. package/dist/src/core/utils/uuid.d.ts +10 -1
  153. package/dist/src/createTelemetry.native.d.ts +7 -0
  154. package/dist/src/createTelemetry.web.d.ts +7 -0
  155. package/dist/src/index.base.d.ts +17 -1
  156. package/dist/src/index.native.d.ts +46 -9
  157. package/dist/src/index.web.d.ts +33 -2
  158. package/dist/store.native-BYBJcy6o.js +32 -0
  159. package/dist/store.native-BYBJcy6o.js.map +1 -0
  160. package/dist/store.native-TPqXngmF.cjs +2 -0
  161. package/dist/store.native-TPqXngmF.cjs.map +1 -0
  162. package/dist/store.web-CvqqN-_Z.cjs +2 -0
  163. package/dist/store.web-CvqqN-_Z.cjs.map +1 -0
  164. package/dist/store.web-DJAlN0nc.js +31 -0
  165. package/dist/store.web-DJAlN0nc.js.map +1 -0
  166. package/dist/telemetry-ByDJIz_9.js +1280 -0
  167. package/dist/telemetry-ByDJIz_9.js.map +1 -0
  168. package/dist/telemetry-CrU7i1Jc.cjs +2 -0
  169. package/dist/telemetry-CrU7i1Jc.cjs.map +1 -0
  170. package/dist/uiInteraction-CdzvEI1p.cjs +2 -0
  171. package/dist/uiInteraction-CdzvEI1p.cjs.map +1 -0
  172. package/dist/uiInteraction-DAqxxz0q.js +104 -0
  173. package/dist/uiInteraction-DAqxxz0q.js.map +1 -0
  174. package/dist/viewport-Dd85GUNO.cjs +2 -0
  175. package/dist/viewport-Dd85GUNO.cjs.map +1 -0
  176. package/dist/viewport-Dzmt5QwK.js +15 -0
  177. package/dist/viewport-Dzmt5QwK.js.map +1 -0
  178. package/dist/web-vitals.attribution-CVCPiX-h.cjs +2 -0
  179. package/dist/web-vitals.attribution-CVCPiX-h.cjs.map +1 -0
  180. package/dist/web-vitals.attribution-xw9N5MKQ.js +488 -0
  181. package/dist/web-vitals.attribution-xw9N5MKQ.js.map +1 -0
  182. package/dist/webSender-BIwslfpT.js +63 -0
  183. package/dist/webSender-BIwslfpT.js.map +1 -0
  184. package/dist/webSender-CVtE4sOF.cjs +2 -0
  185. package/dist/webSender-CVtE4sOF.cjs.map +1 -0
  186. package/dist/webVitals.web-CNIpsKlA.js +85 -0
  187. package/dist/webVitals.web-CNIpsKlA.js.map +1 -0
  188. package/dist/webVitals.web-DpWJ-iYp.cjs +2 -0
  189. package/dist/webVitals.web-DpWJ-iYp.cjs.map +1 -0
  190. package/dist/xhrIntercept-BADex6Cq.cjs +2 -0
  191. package/dist/xhrIntercept-BADex6Cq.cjs.map +1 -0
  192. package/dist/xhrIntercept-BNiA41Mj.js +53 -0
  193. package/dist/xhrIntercept-BNiA41Mj.js.map +1 -0
  194. package/docs/migration-v4.md +190 -0
  195. package/package.json +12 -4
  196. package/dist/appLifecycle-Cynw0kAv.js +0 -14
  197. package/dist/appLifecycle-Cynw0kAv.js.map +0 -1
  198. package/dist/appLifecycle-DvJIt7ym.cjs +0 -2
  199. package/dist/appLifecycle-DvJIt7ym.cjs.map +0 -1
  200. package/dist/batch-CLBFgYPC.cjs +0 -2
  201. package/dist/batch-CLBFgYPC.cjs.map +0 -1
  202. package/dist/batch-DLG_gtOm.js +0 -12
  203. package/dist/batch-DLG_gtOm.js.map +0 -1
  204. package/dist/crashCapture-BYTM4FLC.cjs +0 -2
  205. package/dist/crashCapture-BYTM4FLC.cjs.map +0 -1
  206. package/dist/crashCapture-D_rkkBVa.js +0 -31
  207. package/dist/crashCapture-D_rkkBVa.js.map +0 -1
  208. package/dist/crashHandler.web-BSxDpFTo.cjs +0 -2
  209. package/dist/crashHandler.web-BSxDpFTo.cjs.map +0 -1
  210. package/dist/crashHandler.web-D59bmhg9.js +0 -32
  211. package/dist/crashHandler.web-D59bmhg9.js.map +0 -1
  212. package/dist/crashHandlerNative.native-DeAFze4E.cjs +0 -2
  213. package/dist/crashHandlerNative.native-DeAFze4E.cjs.map +0 -1
  214. package/dist/crashHandlerNative.native-d7qJkzA6.js +0 -41
  215. package/dist/crashHandlerNative.native-d7qJkzA6.js.map +0 -1
  216. package/dist/debug-DsvQ9y3W.js +0 -68
  217. package/dist/debug-DsvQ9y3W.js.map +0 -1
  218. package/dist/debug-rnOOxFpN.cjs +0 -2
  219. package/dist/debug-rnOOxFpN.cjs.map +0 -1
  220. package/dist/deviceInfo.native-7hbRYixR.cjs +0 -2
  221. package/dist/deviceInfo.native-7hbRYixR.cjs.map +0 -1
  222. package/dist/deviceInfo.native-D2k0LDhI.js.map +0 -1
  223. package/dist/deviceInfo.web-ByNAlEJh.cjs +0 -2
  224. package/dist/deviceInfo.web-ByNAlEJh.cjs.map +0 -1
  225. package/dist/deviceInfo.web-DzQyEku6.js +0 -40
  226. package/dist/deviceInfo.web-DzQyEku6.js.map +0 -1
  227. package/dist/frameAggregate-C_HoVFcK.cjs +0 -2
  228. package/dist/frameAggregate-C_HoVFcK.cjs.map +0 -1
  229. package/dist/frameAggregate-Cwh1juw1.js +0 -17
  230. package/dist/frameAggregate-Cwh1juw1.js.map +0 -1
  231. package/dist/frameDropsNative.native-CP7zrXDf.js +0 -23
  232. package/dist/frameDropsNative.native-CP7zrXDf.js.map +0 -1
  233. package/dist/frameDropsNative.native-DKqEDqip.cjs +0 -2
  234. package/dist/frameDropsNative.native-DKqEDqip.cjs.map +0 -1
  235. package/dist/frameDropsWeb.web-C9Lt3Fuj.js +0 -22
  236. package/dist/frameDropsWeb.web-C9Lt3Fuj.js.map +0 -1
  237. package/dist/frameDropsWeb.web-azpwQCUz.cjs +0 -2
  238. package/dist/frameDropsWeb.web-azpwQCUz.cjs.map +0 -1
  239. package/dist/httpAttributes-BV50UKQU.js +0 -19
  240. package/dist/httpAttributes-BV50UKQU.js.map +0 -1
  241. package/dist/httpAttributes-xXOD9trQ.cjs +0 -2
  242. package/dist/httpAttributes-xXOD9trQ.cjs.map +0 -1
  243. package/dist/interaction-CH8yoY5-.js +0 -23
  244. package/dist/interaction-CH8yoY5-.js.map +0 -1
  245. package/dist/interaction-DcNFBW7a.cjs +0 -2
  246. package/dist/interaction-DcNFBW7a.cjs.map +0 -1
  247. package/dist/interceptFetchNative.native-VlhtMG-U.js +0 -49
  248. package/dist/interceptFetchNative.native-VlhtMG-U.js.map +0 -1
  249. package/dist/interceptFetchNative.native-jEAldH14.cjs +0 -2
  250. package/dist/interceptFetchNative.native-jEAldH14.cjs.map +0 -1
  251. package/dist/interceptFetchWeb.web-DFzZliik.cjs +0 -2
  252. package/dist/interceptFetchWeb.web-DFzZliik.cjs.map +0 -1
  253. package/dist/interceptFetchWeb.web-DfsMfoRP.js +0 -77
  254. package/dist/interceptFetchWeb.web-DfsMfoRP.js.map +0 -1
  255. package/dist/memoryNative.native-BWLh2S9L.js +0 -48
  256. package/dist/memoryNative.native-BWLh2S9L.js.map +0 -1
  257. package/dist/memoryNative.native-DCXSOd4Q.cjs +0 -2
  258. package/dist/memoryNative.native-DCXSOd4Q.cjs.map +0 -1
  259. package/dist/memoryWeb.web-B3YuV-cB.js +0 -48
  260. package/dist/memoryWeb.web-B3YuV-cB.js.map +0 -1
  261. package/dist/memoryWeb.web-CheXAcJn.cjs +0 -2
  262. package/dist/memoryWeb.web-CheXAcJn.cjs.map +0 -1
  263. package/dist/nativeSender-BE_mzw_S.js +0 -70
  264. package/dist/nativeSender-BE_mzw_S.js.map +0 -1
  265. package/dist/nativeSender-DyAUGY7c.cjs +0 -2
  266. package/dist/nativeSender-DyAUGY7c.cjs.map +0 -1
  267. package/dist/navigationNative.native-3tO5JxSd.cjs +0 -2
  268. package/dist/navigationNative.native-3tO5JxSd.cjs.map +0 -1
  269. package/dist/navigationNative.native-BUCGPQX2.js +0 -25
  270. package/dist/navigationNative.native-BUCGPQX2.js.map +0 -1
  271. package/dist/navigationTracker-B2GMwyKk.cjs +0 -2
  272. package/dist/navigationTracker-B2GMwyKk.cjs.map +0 -1
  273. package/dist/navigationTracker-Cts68dgG.js.map +0 -1
  274. package/dist/navigationWeb.web-B0necr5j.cjs +0 -2
  275. package/dist/navigationWeb.web-B0necr5j.cjs.map +0 -1
  276. package/dist/navigationWeb.web-CORzE_Id.js +0 -46
  277. package/dist/navigationWeb.web-CORzE_Id.js.map +0 -1
  278. package/dist/networkInfo.web-DJmw1sNK.cjs +0 -2
  279. package/dist/networkInfo.web-DJmw1sNK.cjs.map +0 -1
  280. package/dist/networkInfo.web-twcNn_hc.js.map +0 -1
  281. package/dist/src/adapters/interaction.d.ts +0 -17
  282. package/dist/src/adapters/native/frameDropsNative.native.d.ts +0 -16
  283. package/dist/src/adapters/native/interceptFetchNative.native.d.ts +0 -23
  284. package/dist/src/adapters/native/navigationNative.native.d.ts +0 -12
  285. package/dist/src/adapters/web/frameDropsWeb.web.d.ts +0 -16
  286. package/dist/src/adapters/web/memoryWeb.web.d.ts +0 -26
  287. package/dist/telemetry-Co4MYdwa.cjs +0 -2
  288. package/dist/telemetry-Co4MYdwa.cjs.map +0 -1
  289. package/dist/telemetry-KI0x4gzC.js +0 -389
  290. package/dist/telemetry-KI0x4gzC.js.map +0 -1
  291. package/dist/v4-BysszJq8.js +0 -31
  292. package/dist/v4-BysszJq8.js.map +0 -1
  293. package/dist/v4-CqdK_b8Y.cjs +0 -2
  294. package/dist/v4-CqdK_b8Y.cjs.map +0 -1
  295. package/dist/webSender-B27tmT1w.js +0 -42
  296. package/dist/webSender-B27tmT1w.js.map +0 -1
  297. package/dist/webSender-BWi4x1df.cjs +0 -2
  298. package/dist/webSender-BWi4x1df.cjs.map +0 -1
@@ -0,0 +1,1280 @@
1
+ import { n as O, p as S, f as M, b as U, d as o, i as x, m as V } from "./memoryStore-F5I-GQKM.js";
2
+ import { N as F } from "./navigationTracker-BmywA_Bg.js";
3
+ import { N as I } from "./loadingTime-ZkYvFijA.js";
4
+ import { b as z } from "./crashCapture-BW8WuyaU.js";
5
+ class L {
6
+ constructor(e) {
7
+ this.startTimes = /* @__PURE__ */ new Map(), this.telemetry = e;
8
+ }
9
+ // Screen entry → v3 `navigation` (replaces v2 screen_view). Baseline shape follows the
10
+ // reference/Angular keys; iOS reconciliation is OPEN (see additions ledger).
11
+ async startScreen(e) {
12
+ const t = this.lastScreen;
13
+ this.markStart(e), this.lastScreen = e, await this.telemetry.log("navigation", {
14
+ "navigation.from_screen": t ?? null,
15
+ "navigation.to_screen": e,
16
+ "navigation.method": "screen_start",
17
+ "navigation.route_type": "screen"
18
+ }), await this.telemetry.enterView(e, "explicit");
19
+ }
20
+ /**
21
+ * Arm the dwell clock without emitting `navigation`. The route-change path (§4.11) has
22
+ * already emitted its own `navigation` with `method: "push"`, and emitting a second one
23
+ * with `method: "screen_start"` for the same transition would double the table.
24
+ */
25
+ markStart(e) {
26
+ this.startTimes.set(e, Date.now()), this.telemetry.currentScreen = e, this.lastScreen = e;
27
+ }
28
+ // Screen exit → v3 `screen.duration` with dwell ms (replaces screen_end + performance.screen_duration).
29
+ async endScreen(e) {
30
+ const t = this.startTimes.get(e);
31
+ if (!t) return;
32
+ const s = Date.now() - t;
33
+ this.startTimes.delete(e), await this.telemetry.log("screen.duration", {
34
+ "screen.name": e,
35
+ "screen.duration_ms": s,
36
+ "screen.exit_method": "navigation"
37
+ });
38
+ }
39
+ }
40
+ function u(i = 16) {
41
+ if (typeof globalThis.crypto?.getRandomValues != "function")
42
+ throw new Error(
43
+ "edge-telemetry: crypto.getRandomValues is unavailable, so no id can be minted. On React Native this means react-native-get-random-values failed to load — reinstall it and rebuild."
44
+ );
45
+ const e = new Uint8Array(Math.ceil(i / 2));
46
+ crypto.getRandomValues(e);
47
+ let t = "";
48
+ for (const s of e) t += s.toString(16).padStart(2, "0");
49
+ return t.slice(0, i);
50
+ }
51
+ const g = { none: 0, url: 1, route: 2, explicit: 3 }, _ = "unknown", H = 16;
52
+ function b() {
53
+ return `view_${Date.now()}_${u(16)}`;
54
+ }
55
+ class $ {
56
+ constructor(e) {
57
+ this.telemetry = e, this.activityListeners = /* @__PURE__ */ new Set(), this.boundaryListeners = /* @__PURE__ */ new Set(), this.retiredNames = /* @__PURE__ */ new Map(), this.host = typeof location < "u" && location?.origin ? location.origin : void 0;
58
+ const t = Date.now();
59
+ this.view = {
60
+ id: b(),
61
+ name: _,
62
+ source: "none",
63
+ referrer: "",
64
+ loadType: "initial_load",
65
+ elapsed: 0,
66
+ resumedAt: t,
67
+ errors: 0,
68
+ actions: 0,
69
+ // The launch view is the one `load_type` that seeds from the platform's
70
+ // runtime-ready marker: it is busy until the bundle has evaluated (§4.5.2).
71
+ settle: new I(t, { awaitRuntimeReady: !0 }),
72
+ // The launch root is live here — it is minted in the same `Telemetry` constructor,
73
+ // just above this one — so the initial view is its child and a web hard load
74
+ // reports `launch`, never `navigation` (§6.2).
75
+ span: e.trace?.viewSpan(t) ?? {}
76
+ };
77
+ }
78
+ /** The two keys the Context block carries on every row. Name resolves at log time. */
79
+ get id() {
80
+ return this.view.id;
81
+ }
82
+ get name() {
83
+ return this.view.name;
84
+ }
85
+ /**
86
+ * §3.1 — `view.name` for a **frozen** `view.id`. A span-carrying row pins itself to the
87
+ * view live at span start, and its name is still resolved at log time, so an
88
+ * `http.request` that outlives a route change reports the departing view's *current*
89
+ * best name rather than a stale copy taken at send.
90
+ *
91
+ * ponytail: a bounded ring of retired names, not a full history. A span outliving
92
+ * `MAX_RETIRED_VIEW_NAMES` boundaries is a request the settle cap (30 s) already gave up
93
+ * on; it degrades to `"unknown"`, which is what a never-named view reports anyway.
94
+ */
95
+ nameOf(e) {
96
+ return e === this.view.id ? this.view.name : this.retiredNames.get(e) ?? _;
97
+ }
98
+ /**
99
+ * The name ladder (§4.5.1), and the only path that mints a view from a *name*.
100
+ *
101
+ * - a **higher** rung re-stamps the name and never changes `view.id` — that is what
102
+ * "rank beats order" means, and it is why a route name arriving after a URL-derived
103
+ * one upgrades the view in place instead of splitting it in two;
104
+ * - a **lower** rung is ignored outright, whenever it arrives — ⚠ *including its
105
+ * boundary*. A host that mixes rung 1 with `attachNavigation` therefore pins the view
106
+ * to the `screenStart` name until the next `screenStart`: route changes stop minting
107
+ * successors, and dwell keeps accruing under the explicit name. That is what §4.5.1's
108
+ * "a lower rung arriving later does not overwrite a higher one" costs, and the two
109
+ * rungs describing one navigation (the upgrade window) is the case it exists for.
110
+ * It needs a contract ruling, not a local invention — see CLAUDE.md's known gaps;
111
+ * - the **same** rung naming a different screen is a genuine navigation, so it ends the
112
+ * view and mints a successor.
113
+ *
114
+ * Rungs 1 and 2 are handed through unnormalized: a host naming a screen "Step 2 of 3"
115
+ * must not receive "Step {id} of {id}". The caller normalizes rung 3, not this method.
116
+ */
117
+ async navigate(e, t) {
118
+ const s = g[t], r = g[this.view.source];
119
+ if (!(s < r)) {
120
+ if (s > r) {
121
+ this.view.name = e, this.view.source = t;
122
+ return;
123
+ }
124
+ e !== this.view.name && await this.exit("route_change", e, t);
125
+ }
126
+ }
127
+ /** `view.error_count` — `app.crash` rows sharing this `view.id`. A closed enumeration. */
128
+ countError() {
129
+ this.view.errors++;
130
+ }
131
+ /** `view.action_count` — interaction rows sharing this `view.id`. */
132
+ countAction() {
133
+ this.view.actions++;
134
+ }
135
+ /**
136
+ * An HTTP request started (§4.5.2). Returns its completion callback, **bound to the view
137
+ * that was live at start** — a request that finishes after a route change belongs to the
138
+ * view it started in and does not hold the arriving one open. The collector's own POST
139
+ * never reaches here; the interceptors filter it before calling.
140
+ */
141
+ requestStarted(e) {
142
+ return this.notifyActivity(), this.view.settle.requestStarted(e);
143
+ }
144
+ /**
145
+ * §4.6's dead-click gate, in the two places this manager already sees: a request started
146
+ * and a view minted (a route change, a resume, a session rotation). The web tracker adds
147
+ * DOM mutation on top, which it can observe on its own; these two it cannot.
148
+ *
149
+ * Deliberately fires for the background and rotation boundaries too. Those are not
150
+ * navigations the click caused, so counting them as aliveness **under-reports** dead
151
+ * clicks — which is the direction §4.6 requires: `ui.dead` must never falsely accuse.
152
+ *
153
+ * @returns an unsubscribe, so a tracker that is torn down does not keep the manager alive.
154
+ */
155
+ onActivity(e) {
156
+ return this.activityListeners.add(e), () => {
157
+ this.activityListeners.delete(e);
158
+ };
159
+ }
160
+ notifyActivity() {
161
+ for (const e of this.activityListeners) e();
162
+ }
163
+ /**
164
+ * §5.1's view-boundary reset. A subscriber is **awaited inside `beginView`, before the
165
+ * successor replaces the current view**, so anything it emits is stamped with the
166
+ * departing `view.id`. That ordering is the whole feature: a fixed 10 s frame window
167
+ * straddling a route change otherwise charges the departing screen's dropped frames to
168
+ * the arriving one — backwards for the one query the metric exists to serve.
169
+ *
170
+ * ⚠ **It does not fire on the `session_rotation` boundary**, and that is deliberate.
171
+ * `newSession()` installs the new `session.id` *before* minting the successor view, so a
172
+ * row emitted there would carry the new `session.id` with the departing `view.id` — the
173
+ * one thing §4.5's "`view.id` never spans a `session.id`" forbids. Emitting *before* the
174
+ * rotation instead is worse: `logMetric` re-checks session expiry, `lastActivity` is
175
+ * still stale at that point, and the emit would rotate the session a second time. The
176
+ * accepted residue is a frame window that carries across a rotation — at most one
177
+ * unflushed window, of a session that ended by idleness.
178
+ *
179
+ * The successor's load type is handed to the subscriber so it can tell the two firing
180
+ * boundaries apart: `frame_render_time` wants both, while §5.3's running CLS/INP values
181
+ * want **`"resume"` — the background boundary — only**. Emitting a page-load-scoped vital
182
+ * at a soft navigation would ship a second row for the same page load and stamp it with
183
+ * a `view.id` that is not the initial view's.
184
+ *
185
+ * @returns an unsubscribe, so a torn-down subscriber does not keep the manager alive.
186
+ */
187
+ onBoundary(e) {
188
+ return this.boundaryListeners.add(e), () => {
189
+ this.boundaryListeners.delete(e);
190
+ };
191
+ }
192
+ /**
193
+ * The platform's runtime-ready marker — `loadEventEnd` on web,
194
+ * `performance.rnStartupTiming` on native — or `undefined` where the platform has none.
195
+ *
196
+ * Forwarded to whatever view is current, which is safe because a view that was never gated
197
+ * *ignores* the seed — enforced in `NetworkSettle`, not assumed here. On web the `load`
198
+ * event routinely arrives after the first route change, and flooring that view's settle at
199
+ * the page's load time would charge the launch's cost to a route change.
200
+ */
201
+ seedRuntimeReady(e) {
202
+ this.view.settle.seedRuntimeReady(e);
203
+ }
204
+ /**
205
+ * Emit the `view` event for the current view. Its `view.name` is authoritative and its
206
+ * `view.name_source` reports the *final* rung; the Context-block copy on rows emitted
207
+ * early in the view may still carry a lower rung's name (§4.5.1).
208
+ *
209
+ * `view.loading_time` is **omitted when null**, following this contract's general
210
+ * absent-means-the-SDK-had-nothing discipline (§4.11 names `navigation.from_screen` as the
211
+ * only explicit wire null). `view.loading_time_outcome` always ships: it is what tells the
212
+ * three null causes apart, and reading p75-of-settled beside %-capped is the whole point.
213
+ */
214
+ async endView() {
215
+ const e = this.view, { loadingTime: t, outcome: s } = e.settle.resolve();
216
+ await this.telemetry.log("view", {
217
+ // A **point span** (§6.3): `span.start_time` from view entry and never
218
+ // `span.duration_ms`. View dwell is `view.time_spent`, not span width — a width
219
+ // here would stretch every tap-that-navigates envelope across the whole visit.
220
+ ...e.span,
221
+ ...this.host ? { "view.host": this.host } : {},
222
+ "view.referrer": e.referrer,
223
+ "view.load_type": e.loadType,
224
+ "view.name_source": e.source,
225
+ "view.time_spent": this.timeSpent(),
226
+ ...t === null ? {} : { "view.loading_time": t },
227
+ "view.loading_time_outcome": s,
228
+ "view.error_count": e.errors,
229
+ "view.action_count": e.actions,
230
+ "view.request_count": e.settle.requestCount
231
+ });
232
+ }
233
+ /**
234
+ * Mint the successor. Called directly by session rotation, which has to mint *after*
235
+ * the new `session.id` is in place — `view.id` never spans a `session.id` (§4.5).
236
+ * An unnamed successor carries the departing view's name: backgrounding and a session
237
+ * rotation do not move the user off the screen they were on.
238
+ */
239
+ async beginView(e, t, s) {
240
+ if (this.notifyActivity(), e !== "session_rotation")
241
+ for (const n of this.boundaryListeners)
242
+ try {
243
+ await n(e);
244
+ } catch {
245
+ }
246
+ const r = this.view, a = Date.now();
247
+ if (this.retiredNames.set(r.id, r.name), this.retiredNames.size > H) {
248
+ const n = this.retiredNames.keys().next();
249
+ n.done || this.retiredNames.delete(n.value);
250
+ }
251
+ this.view = {
252
+ id: b(),
253
+ name: t ?? r.name,
254
+ source: s ?? r.source,
255
+ referrer: r.name,
256
+ loadType: e,
257
+ elapsed: 0,
258
+ resumedAt: a,
259
+ errors: 0,
260
+ actions: 0,
261
+ // No runtime-ready seed: only `initial_load` has a platform marker to wait on.
262
+ settle: new I(a),
263
+ // A view start *extends* the live root; with none live it mints a `navigation`
264
+ // one (§6.2). Its exit does not extend anything (§6.7).
265
+ span: this.telemetry.trace?.viewSpan(a) ?? {}
266
+ };
267
+ }
268
+ /** A boundary that stays inside one session: emit, then mint the successor. */
269
+ async exit(e, t, s) {
270
+ await this.endView(), await this.beginView(e, t, s);
271
+ }
272
+ /**
273
+ * The background boundary. The successor is minted immediately — `view.id` must never
274
+ * be absent — but its clock starts paused, so a night spent backgrounded does not land
275
+ * as dwell on whatever screen the user left open.
276
+ */
277
+ async background() {
278
+ await this.exit("resume");
279
+ const e = this.view;
280
+ e.elapsed += this.sinceResume(), e.resumedAt = void 0;
281
+ }
282
+ /** The matching foreground edge: re-arm the paused clock. */
283
+ foreground() {
284
+ this.view.resumedAt ??= Date.now();
285
+ }
286
+ sinceResume() {
287
+ return this.view.resumedAt === void 0 ? 0 : Date.now() - this.view.resumedAt;
288
+ }
289
+ timeSpent() {
290
+ return Math.max(0, this.view.elapsed + this.sinceResume());
291
+ }
292
+ }
293
+ const j = 2e3, B = 1e4, W = () => u(32), N = () => u(16);
294
+ class K {
295
+ /**
296
+ * @param launchStartMs what `app.start` reports as `span.start_time` — and **neither
297
+ * value is a fork time** (§6.2). Web passes `performance.timeOrigin` (true navigation
298
+ * start); native leaves it defaulted to `initialize()`, because everything before the
299
+ * JS bundle loads is invisible and JS cannot see process fork. Do not compare the two
300
+ * platforms' launch envelopes as if they measured the same interval.
301
+ */
302
+ constructor(e, t) {
303
+ this.allowlist = O(t);
304
+ const s = Date.now();
305
+ this.launch = this.mint("launch", s, e ?? s);
306
+ }
307
+ /**
308
+ * §4.1's `sdk.trace_allowlist_size` — **count only, never the hosts**, so an operator can
309
+ * tell "nobody opted in" apart from "the header is being stripped" without the SDK
310
+ * shipping a customer's internal hostnames to the collector.
311
+ */
312
+ allowlistSize() {
313
+ return this.allowlist.size;
314
+ }
315
+ /**
316
+ * `app.start`'s own row — a root, so `span.id === rum.action.id` and there is no
317
+ * `parent.span.id`. No `span.duration_ms` either: roots derive theirs server-side.
318
+ */
319
+ launchRootAttributes() {
320
+ return l(this.launch, !0, this.launch.spanStartMs).keys;
321
+ }
322
+ /**
323
+ * Tier 1 for a `view`, captured at view **entry** — the action that opened the screen,
324
+ * not the one that closed it (§6.3). A **point span**: `span.start_time` and never
325
+ * `span.duration_ms`, because view dwell is not span duration and a width here would
326
+ * stretch every tap-that-navigates envelope across the whole time the user sat there.
327
+ */
328
+ viewSpan(e) {
329
+ const { root: t, isRoot: s } = this.attach("navigation", e);
330
+ return l(t, s, e).keys;
331
+ }
332
+ /**
333
+ * Tier 1 for a `ui.interaction`, captured at the click's **mint** (§4.6/§6.2). The one
334
+ * unconditional mint in the table: a tap is a new user action by definition, so it
335
+ * *replaces* whatever root was live rather than joining it — which is what makes the
336
+ * request a tap fires a child of the tap and not of the route change before it.
337
+ *
338
+ * A root, so `span.id === rum.action.id`, no `parent.span.id`, and no `span.duration_ms`
339
+ * (§6.1 — a root's width is derived server-side).
340
+ */
341
+ interactionSpan(e) {
342
+ const t = this.mint("interaction", e, e);
343
+ return l(t, !0, e).keys;
344
+ }
345
+ /**
346
+ * Tier 1 for an `http.request`, captured at **send** — the root live then is the parent,
347
+ * and `span.start_time` is the send — plus §6.5's outcome ladder and the one header the
348
+ * SDK ever writes. Returns the finisher, so duration is stamped from the same pair of
349
+ * timestamps the row's `http.duration_ms` uses.
350
+ *
351
+ * ponytail: a `send()` that throws synchronously never calls the finisher, so a root it
352
+ * minted lives on for up to 2 s with no row describing it. Rare enough to leave; give
353
+ * the finisher a `discard()` sibling if the orphan ratio ever shows up.
354
+ */
355
+ requestTrace(e, t) {
356
+ const s = this.decide(t);
357
+ if (s === "adopted") {
358
+ const d = S(t.consumerTraceparent), C = {
359
+ "trace.id": d.traceId,
360
+ "span.id": d.spanId,
361
+ "rum.action.id": d.spanId,
362
+ "span.start_time": new Date(e).toISOString(),
363
+ "traceparent.outcome": "adopted"
364
+ };
365
+ return { finish: () => C };
366
+ }
367
+ const { root: r, isRoot: a, attribution: n } = this.attach("request", e), h = s === "inject" ? n : s, c = l(r, a, e), w = {
368
+ ...c.keys,
369
+ ...h ? { "traceparent.outcome": h } : {}
370
+ }, y = a ? () => w : (d) => ({ ...w, "span.duration_ms": Math.max(0, d - e) });
371
+ return s === "inject" ? { finish: y, header: M(r.traceId, c.spanId) } : { finish: y };
372
+ }
373
+ /**
374
+ * §6.5's ladder above the `injected_*` split, in table order — the precedence *is* the
375
+ * order of these returns. `undefined` means the attribute is omitted entirely: absent
376
+ * means not traced, which is the honest report for a consumer who never opted in and for
377
+ * a sampled-out session (which injects no header at all, not a `flags=00` id).
378
+ */
379
+ decide(e) {
380
+ if (!(this.allowlist.size === 0 || !e.sampled))
381
+ return U(this.allowlist, e.url) ? e.noCors ? "skipped_no_cors" : e.consumerTraceparent != null ? S(e.consumerTraceparent) ? "adopted" : "skipped_consumer_set" : "inject" : "skipped_off_allowlist";
382
+ }
383
+ /**
384
+ * Tier 2 — annotation-only (§6.3): join the trace without occupying a span. No
385
+ * `span.id`, no parent, no duration, and **no minting** — §6.2 lists the four events
386
+ * that mint and these are not among them, so an untraced crash carries no trace keys
387
+ * rather than inventing a root nothing else will ever join.
388
+ */
389
+ annotate(e = Date.now()) {
390
+ const t = this.liveRoot(e);
391
+ return t ? {
392
+ "trace.id": t.traceId,
393
+ "rum.action.id": t.spanId,
394
+ "trace.root_type": t.rootType
395
+ } : {};
396
+ }
397
+ /**
398
+ * Drop the carrier. Background clears it on both builds, so a resumed app's first fetch
399
+ * mints its own root rather than joining an action from before the user left (§6.2);
400
+ * session rotation clears it because `trace.id` never spans a `session.id` (§6.6).
401
+ */
402
+ clear() {
403
+ this.root = void 0;
404
+ }
405
+ /**
406
+ * Retire the launch root and mint a replacement, keeping the *reported* launch time —
407
+ * the process really did start then. For a session rotation that lands **before**
408
+ * `app.start`, which is the expired-record cold launch and therefore §4.3's most common
409
+ * path of all.
410
+ *
411
+ * Clearing alone would not do: `app.start` reports the launch root at any age, so its
412
+ * root row would ship under the **new** session while the initial `view` it fathered
413
+ * shipped under the **old** one. `trace.id` would span a `session.id` (§6.6, invariant 2)
414
+ * and `GROUP BY rum.action.id` would never reassemble the launch envelope. Re-minting
415
+ * keeps the retired trace wholly inside the retired session.
416
+ *
417
+ * ⚠ The retired trace then has children and no root row — the initial `view` row is a
418
+ * rootless child. That is the same condition process death already produces by design
419
+ * (a killed view emits nothing), and it is the cheaper of the two wrongs.
420
+ */
421
+ restartLaunchRoot() {
422
+ this.launch = this.mint("launch", Date.now(), this.launch.spanStartMs);
423
+ }
424
+ /**
425
+ * Join the live root, or mint one of `mintAs`. A span's *start* extends the root.
426
+ *
427
+ * `attribution` is read **before** `liveRoot()` drops an expired carrier, which is the
428
+ * only place the two unattributed causes are still distinguishable: a carrier that just
429
+ * aged out is `injected_expired` (context lost), no carrier at all is
430
+ * `injected_unattributed` (no action) — the split §6.5 asks the backend to keep.
431
+ */
432
+ attach(e, t) {
433
+ const s = this.root !== void 0, r = this.liveRoot(t);
434
+ return r ? (r.lastTouch = t, { root: r, isRoot: !1, attribution: "injected_attributed" }) : {
435
+ root: this.mint(e, t, t),
436
+ isRoot: !0,
437
+ attribution: s ? "injected_expired" : "injected_unattributed"
438
+ };
439
+ }
440
+ mint(e, t, s) {
441
+ const r = {
442
+ traceId: W(),
443
+ spanId: N(),
444
+ rootType: e,
445
+ startedAt: t,
446
+ spanStartMs: s,
447
+ lastTouch: t
448
+ };
449
+ return this.root = r, r;
450
+ }
451
+ /**
452
+ * The carrier if it is still live, dropping it otherwise. Both boundaries are strict —
453
+ * a root sitting exactly on one has not crossed it — matching the session boundaries.
454
+ *
455
+ * A **negative** delta is clamped to expired (§6.7): `Date.now()` can move backwards on
456
+ * an NTP correction, and reading that as "fresh" would hold one root open indefinitely.
457
+ */
458
+ liveRoot(e) {
459
+ const t = this.root;
460
+ if (!t) return;
461
+ const s = e - t.lastTouch, r = e - t.startedAt;
462
+ if (s < 0 || r < 0 || s > j || r > B) {
463
+ this.root = void 0;
464
+ return;
465
+ }
466
+ return t;
467
+ }
468
+ }
469
+ function l(i, e, t) {
470
+ const s = e ? i.spanId : N();
471
+ return {
472
+ spanId: s,
473
+ keys: {
474
+ "trace.id": i.traceId,
475
+ "span.id": s,
476
+ ...e ? {} : { "parent.span.id": i.spanId },
477
+ "rum.action.id": i.spanId,
478
+ "trace.root_type": i.rootType,
479
+ "span.start_time": new Date(t).toISOString()
480
+ }
481
+ };
482
+ }
483
+ class J {
484
+ constructor(e = 20) {
485
+ this.max = e, this.items = [];
486
+ }
487
+ add(e) {
488
+ this.items.push(e), this.items.length > this.max && this.items.shift();
489
+ }
490
+ list() {
491
+ return [...this.items];
492
+ }
493
+ toJSON() {
494
+ return JSON.stringify(this.items);
495
+ }
496
+ }
497
+ const X = /* @__PURE__ */ new Set([
498
+ // `session.sequence` is not in the contract's §3.6 list, which names only `event.sequence`.
499
+ // Both are transmission ordinals with no scrubbing use case: (session.id, session.sequence)
500
+ // orders a session's batches (#92) and (session.id, event.sequence) is the backend's dedup
501
+ // key (#94, §2.4) — a hook that could delete either would break the thing it keys.
502
+ //
503
+ // `event.sequence` is stamped in `enqueue()` *after* this hook runs, so on the way in it is
504
+ // absent and the re-stamp is a no-op; it is listed because the hook must not be able to
505
+ // forge one, and because a re-run over an already-stamped event must leave it alone.
506
+ "session.id",
507
+ "session.start_time",
508
+ "session.sequence",
509
+ "event.sequence",
510
+ "device.platform",
511
+ "trace.id",
512
+ "span.id",
513
+ "parent.span.id",
514
+ "rum.action.id",
515
+ "view.id"
516
+ ]), k = (i) => X.has(i) || i.startsWith("sdk.") || i.startsWith("app."), m = "device.id";
517
+ function Y(i, e) {
518
+ let t;
519
+ try {
520
+ t = e({ ...i, attributes: { ...i.attributes } });
521
+ } catch (s) {
522
+ return o.warn("Telemetry: beforeSend threw — event dropped", s), { kind: "failed" };
523
+ }
524
+ return t === null ? { kind: "dropped" } : typeof t != "object" ? (o.warn("Telemetry: beforeSend returned a non-event — event dropped", t), { kind: "failed" }) : { kind: "kept", event: Q(i, t) };
525
+ }
526
+ function Q(i, e) {
527
+ const t = {};
528
+ for (const [a, n] of Object.entries(e.attributes ?? {}))
529
+ k(a) || (t[a] = n);
530
+ for (const [a, n] of Object.entries(i.attributes ?? {}))
531
+ k(a) && (t[a] = n);
532
+ const s = t[m];
533
+ if (typeof s != "string" || s === "") {
534
+ const a = i.attributes?.[m];
535
+ a === void 0 ? delete t[m] : t[m] = a;
536
+ }
537
+ const r = {
538
+ type: i.type,
539
+ timestamp: i.timestamp,
540
+ attributes: t
541
+ };
542
+ return i.eventName !== void 0 && (r.eventName = i.eventName), i.metricName !== void 0 && (r.metricName = i.metricName), i.type === "metric" && (r.value = e.value ?? i.value), r;
543
+ }
544
+ function q(i) {
545
+ try {
546
+ return JSON.stringify(i) ?? void 0;
547
+ } catch {
548
+ return;
549
+ }
550
+ }
551
+ const G = 255, Z = 255, ee = 50, T = 64, A = 64, p = 255;
552
+ let E = !1;
553
+ function f(i, e) {
554
+ if (typeof i != "string") return;
555
+ const t = i.slice(0, e);
556
+ return t.length ? t : void 0;
557
+ }
558
+ function te(i) {
559
+ if (i == null) return;
560
+ if (typeof i == "number" || typeof i == "boolean") return i;
561
+ if (typeof i == "string") return i.slice(0, p) || void 0;
562
+ const e = q(i);
563
+ return e === void 0 ? void 0 : e.slice(0, p) || void 0;
564
+ }
565
+ function se(i) {
566
+ const e = {};
567
+ if (!i) return e;
568
+ const t = f(i.fullName, G), s = f(i.email, Z), r = f(i.phone, ee);
569
+ t !== void 0 && (e["user.name"] = t), s !== void 0 && (e["user.email"] = s), r !== void 0 && (e["user.phone"] = r);
570
+ let a = 0, n = 0;
571
+ for (const h of Object.keys(i.customAttributes || {})) {
572
+ if (h.length > A || n >= T) {
573
+ a++;
574
+ continue;
575
+ }
576
+ const c = te(i.customAttributes[h]);
577
+ if (c === void 0) {
578
+ a++;
579
+ continue;
580
+ }
581
+ e[`user.custom.${h}`] = c, n++;
582
+ }
583
+ return a > 0 && (e["user.custom_dropped"] = a, x() && !E && (E = !0, console.warn(
584
+ `[edge-telemetry] identify(): ${a} custom attribute(s) dropped. user.custom.* is bounded at ${T} keys, ${A}-char keys and ${p}-char values; unserializable values are dropped.`
585
+ ))), e;
586
+ }
587
+ const ie = "4.0.0", re = (i) => i ? `react-native-${i}` : "react-native", ne = ie, ae = 1800 * 1e3, oe = 14400 * 1e3, he = 8, ce = 500, v = (i) => i.attributes?.["event.sequence"] ?? -1, P = (i) => i === "app.crash" || i === "app.error";
588
+ function de(i) {
589
+ const e = i.findIndex((t) => t.eventName !== "app.crash");
590
+ return e === -1 ? 0 : e;
591
+ }
592
+ const R = "telemetry_device_id", D = "telemetry_session", ue = 255, le = /* @__PURE__ */ new Set([
593
+ "session.started",
594
+ "session.finalized",
595
+ "app_lifecycle",
596
+ "navigation",
597
+ "screen.duration",
598
+ "http.request",
599
+ "network_change",
600
+ "user.profile.update",
601
+ "custom_event",
602
+ "app.crash",
603
+ "view",
604
+ // ⚠ `app.error` needs backend allowlist sign-off before it ships (#100, §4.7).
605
+ "app.error",
606
+ // §4.6/#102 — replaces `user.interaction`, which is off the list as of v4. The
607
+ // `rum_ui_interactions` columns are already built; the name still needs sign-off.
608
+ "ui.interaction",
609
+ // ⚠ `app.start` needs backend allowlist sign-off before it ships (#98, §4.3/§6.2) — an
610
+ // unlisted eventName is dropped on ingest. It is listed here, so it is being emitted.
611
+ "app.start",
612
+ "frame_render_time",
613
+ "memory_usage",
614
+ "LCP",
615
+ "FCP",
616
+ "CLS",
617
+ "INP",
618
+ "TTFB"
619
+ // ⚠ `page_load`, `resource_timing` and `long_task` are **retired, not deferred**
620
+ // (§10.2, #109) — as `user.interaction` was in #102, four in total for a net-zero v4.
621
+ // Do not add them back: `page_load`'s job is done three ways over (`view` +
622
+ // `view.loading_time` + the vitals), `resource_timing` would blow the 500-event queue on
623
+ // one asset-heavy view for a namespace with no column, and `long_task`'s only APIs are
624
+ // Chromium-only while `frame_render_time` detects jank on every runtime. A name off this
625
+ // list is rewritten to `custom_event`, so all four are unreachable, not merely unused.
626
+ ]), me = {
627
+ frame_render_time: "ms",
628
+ memory_usage: "MB",
629
+ LCP: "ms",
630
+ FCP: "ms",
631
+ INP: "ms",
632
+ TTFB: "ms",
633
+ CLS: "score"
634
+ }, fe = /* @__PURE__ */ new Set(["app.crash", "app.error", "custom_event"]);
635
+ function ve(i) {
636
+ return i === void 0 ? 1 : !Number.isFinite(i) || i < 0 || i > 1 ? (o.warn(`Telemetry: sessionSampleRate must be a number in [0,1] — got ${i}; using 1`), 1) : i;
637
+ }
638
+ class Ie {
639
+ constructor(e) {
640
+ this.queue = [], this.intervalId = null, this.userId = void 0, this.deviceIdEphemeral = !1, this.userProfile = void 0, this.eventCount = 0, this.breadcrumbs = new J(20), this.sessionSequence = 0, this.eventSequence = 0, this.sessionEventCount = 0, this.errorCount = 0, this.hookDropped = 0, this.hookFailed = 0, this.eventsDropped = 0, this.crashPersistedThrough = -1, this.appStartEmitted = !1, this.sender = e?.sender, this.batchSize = e?.batchSize ?? 50, this.flushIntervalMs = e?.flushIntervalMs ?? 3e4, this.endpoint = e?.endpoint, this.platform = e?.platform, this.store = e?.store ?? V({ unavailable: !0 }), this.beforeSend = e?.beforeSend, this.buildId = typeof e?.buildId == "string" && e.buildId !== "" ? e.buildId : void 0, this.configuredSampleRate = ve(e?.sessionSampleRate), this.sampleRate = this.configuredSampleRate, this.sampled = this.rollSample(), this.sessionId = e?.sessionId ?? this.generateSessionId(), e?.userId && this.setUserId(e.userId), this.sessionStart = Date.now(), this.sdkVersion = e?.sdkVersion ?? ne, this.sender?.replayFailed && this.sender.replayFailed().catch((t) => {
641
+ o.warn("Telemetry replay failed Telemetry class:", t);
642
+ }), this.flushIntervalMs > 0 && (this.intervalId = setInterval(() => this.flush().catch(() => {
643
+ }), this.flushIntervalMs)), this.navigationTracker = new F(this), this.screens = new L(this), this.deprecatedScreenFeeds = e?.deprecatedScreenFeeds ?? !0, this.trace = new K(e?.traceLaunchStart, e?.traceHostAllowlist), this.views = new $(this), this.deviceInfoHandler = e?.deviceInfoHandler ?? {
644
+ start: async () => Promise.resolve(),
645
+ collect: async () => Promise.resolve({
646
+ app: { name: "", version: "" },
647
+ device: { platform: "" }
648
+ })
649
+ }, this.networkInfoHandler = e?.networkInfoHandler ?? {
650
+ start: async () => Promise.resolve({}),
651
+ collect: async () => Promise.resolve({})
652
+ };
653
+ }
654
+ // ---------- Session & User APIs ----------
655
+ /**
656
+ * §4.7 — report a handled error. Accepts `unknown` on purpose: half of real `catch`
657
+ * blocks receive a string or an axios rejection object, and a consumer should not have
658
+ * to prove to TypeScript that it is an `Error` before reporting it.
659
+ *
660
+ * Emits `app.error`, **never** `app.crash`. There is deliberately no public path to
661
+ * `app.crash` — a consumer's own code must not be able to manufacture rows in the one
662
+ * table an unfiltered crash-free rate is read from.
663
+ *
664
+ * `source` is SDK-internal (`captureConsole` passes `"console"`); the public wrappers
665
+ * forward two arguments, so a consumer can only ever produce `"reported"`. Routing the
666
+ * console path through here rather than through `log()` directly is what keeps the
667
+ * `error.fatal` rule in one place.
668
+ */
669
+ captureError(e, t, s = "reported") {
670
+ const r = this.isNativePlatform() ? { fatal: !1 } : {};
671
+ return this.log("app.error", { ...t, ...z(s, e, r) });
672
+ }
673
+ /**
674
+ * Extend the breadcrumb trail without emitting anything. §4.7 demotes `console.warn` to
675
+ * exactly this — React's dev-mode warnings were the bulk of v3's `app.crash` volume.
676
+ *
677
+ * SDK-internal: `public` only so `adapters/crashCapture.ts` can reach it. Not on
678
+ * `TelemetryBase` and not part of the consumer API — a host app that wants a trail entry
679
+ * should `log()` an event, which extends the trail anyway.
680
+ */
681
+ addBreadcrumb(e, t) {
682
+ this.breadcrumbs.add({ ...t, name: e, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
683
+ }
684
+ trackErrors(e, t) {
685
+ this.crashHandler = e, e.attach(t).catch((s) => {
686
+ o.warn("Telemetry crashHandler attach failed:", s);
687
+ });
688
+ }
689
+ getDeviceInfo(e) {
690
+ this.deviceInfoHandler = e, e.start(this).catch((t) => {
691
+ o.warn("Telemetry deviceInfo tracking start failed:", t);
692
+ });
693
+ }
694
+ getNetworkInfo(e) {
695
+ this.networkInfoHandler = e, e.start(this).catch((t) => {
696
+ o.warn("Telemetry networkInfoHandler start failed:", t);
697
+ });
698
+ }
699
+ trackFrameDrops(e) {
700
+ this.frameDropsHandler = e, e.start().catch((t) => {
701
+ o.warn("Telemetry frameDropsHandler start failed:", t);
702
+ });
703
+ }
704
+ trackNetworkRequests(e) {
705
+ this.networkHandler = e, e.start().catch((t) => {
706
+ o.warn("Telemetry networkHandler start failed:", t);
707
+ });
708
+ }
709
+ trackMemoryUsage(e) {
710
+ this.memoryHandler?.stop?.(), this.memoryHandler = e, e.start().catch((t) => {
711
+ o.warn("Telemetry memoryHandler start failed:", t);
712
+ });
713
+ }
714
+ autoTrackNavigation(e) {
715
+ this.navigationHandler = e, e?.start().catch((t) => {
716
+ o.warn("Telemetry navigationHandler start failed:", t);
717
+ });
718
+ }
719
+ /**
720
+ * The per-session sample roll (§3.6). Never per-event: per-event sampling punches
721
+ * holes that desynchronise the per-view counters by a random factor per view.
722
+ *
723
+ * `Math.random()` is deliberate here where `randomHex` refuses it — this decides one
724
+ * boolean that dies with the session, not an id that persists forever, and a biased
725
+ * PRNG costs a fraction of a percent of sample accuracy rather than a permanent
726
+ * identity collision.
727
+ */
728
+ rollSample() {
729
+ return Math.random() < this.sampleRate;
730
+ }
731
+ /** §3.3 / §4.7's platform split, from the entry's declared `platform` opt — never a global. */
732
+ isNativePlatform() {
733
+ return this.platform === "ios" || this.platform === "android";
734
+ }
735
+ generateSessionId() {
736
+ const e = `session_${Date.now()}_${u(16)}`;
737
+ return this.platform ? `${e}_${this.platform}` : e;
738
+ }
739
+ setSessionId(e) {
740
+ this.sessionId = e, this.sessionStart = Date.now();
741
+ }
742
+ getSessionId() {
743
+ return this.sessionId;
744
+ }
745
+ /**
746
+ * Freeze the identity a deferred row must report (§4.6). Read **synchronously at the
747
+ * moment the thing happened**, so a `log()` that lands after a route change or a session
748
+ * rotation still books the click where it occurred.
749
+ */
750
+ snapshot(e) {
751
+ return {
752
+ at: e,
753
+ viewId: this.views.id,
754
+ sessionId: this.sessionId,
755
+ sessionStart: this.sessionStart
756
+ };
757
+ }
758
+ /** Collector endpoint, so fetch/XHR adapters can skip self-capturing the SDK's own POST. */
759
+ getEndpoint() {
760
+ return this.endpoint;
761
+ }
762
+ /**
763
+ * Sampling stays **session-level** (§6.5): an unsampled session injects no header at
764
+ * all — not a `flags=00` id. Read by the interceptors, which sit outside `log()`'s own
765
+ * sampled-out early return and so need the decision directly.
766
+ */
767
+ isSampled() {
768
+ return this.sampled;
769
+ }
770
+ // ---------- Session lifecycle (#29) ----------
771
+ /**
772
+ * The entry's one call at init (§4.2): resume the durable session when the gap is
773
+ * inside the idle window, otherwise close the stale one out and start fresh.
774
+ *
775
+ * Process death, tab close, hard reload and bfcache restore all land here, and all
776
+ * four **resume** — which is why a resumed session must not re-emit `session.started`
777
+ * (§4.1), or `COUNT(session.started)` stops equalling session count.
778
+ */
779
+ async resumeOrStartSession() {
780
+ await this.hydrateAndAnnounce(), await this.emitAppStart();
781
+ }
782
+ async hydrateAndAnnounce() {
783
+ const e = await this.hydrateSession();
784
+ if (e.kind === "resumed") return;
785
+ if (e.kind === "expired") {
786
+ await this.rotateSession(e.reason);
787
+ return;
788
+ }
789
+ if (this.lastActivity === void 0) {
790
+ await this.startSession("launch");
791
+ return;
792
+ }
793
+ const t = this.expiryReason(Date.now());
794
+ t && await this.rotateSession(t);
795
+ }
796
+ /**
797
+ * `app.start` (§4.3/§6.2): once per process, a **root** of `trace.root_type = launch`,
798
+ * carrying the launch root minted in the constructor. Idempotent because the web build
799
+ * re-runs `resumeOrStartSession()` on a bfcache restore, which is a resumed process and
800
+ * not a new one.
801
+ */
802
+ async emitAppStart() {
803
+ this.appStartEmitted || (this.appStartEmitted = !0, await this.log("app.start", this.trace.launchRootAttributes()));
804
+ }
805
+ /**
806
+ * Read the durable record and adopt it. Adoption happens even when the session has
807
+ * expired: `session.finalized` has to ship under the *old* `session.id` and
808
+ * `session.start_time` before the fresh one takes over.
809
+ */
810
+ async hydrateSession() {
811
+ const e = await this.store.get(D);
812
+ if (e.status === "unavailable")
813
+ return o.warn("Telemetry: session storage unavailable — this session cannot be resumed"), { kind: "nothing" };
814
+ if (e.status === "miss") return { kind: "nothing" };
815
+ let t;
816
+ try {
817
+ t = JSON.parse(e.value);
818
+ } catch {
819
+ return { kind: "nothing" };
820
+ }
821
+ if (typeof t?.id != "string" || typeof t.start != "number" || typeof t.lastActivity != "number") return { kind: "nothing" };
822
+ this.sessionId = t.id, this.sessionStart = t.start, this.lastActivity = t.lastActivity, this.sessionSequence = t.sequence ?? 0, this.eventSequence = t.eventSequence ?? 0, this.sessionEventCount = t.eventCount ?? 0, this.errorCount = t.errorCount ?? 0, typeof t.sampled == "boolean" && Number.isFinite(t.sampleRate) && t.sampleRate >= 0 && t.sampleRate <= 1 && (this.sampled = t.sampled, this.sampleRate = t.sampleRate);
823
+ const s = this.expiryReason(Date.now());
824
+ return s ? { kind: "expired", reason: s } : { kind: "resumed" };
825
+ }
826
+ /**
827
+ * Which boundary, if any, the current session has crossed. Idle is checked first:
828
+ * when a long session has also gone quiet, the reason it ended is that the user left.
829
+ *
830
+ * Inert until `lastActivity` is set, so a bare `new Telemetry()` that never started a
831
+ * session never rotates one.
832
+ */
833
+ expiryReason(e) {
834
+ if (this.lastActivity !== void 0) {
835
+ if (e - this.lastActivity > ae) return "idle";
836
+ if (e - this.sessionStart > oe) return "max_duration";
837
+ }
838
+ }
839
+ /**
840
+ * Write the session through the `Store` so the next process can resume it.
841
+ *
842
+ * ponytail: one write per event. Web's is a synchronous `localStorage` write; native's
843
+ * is an AsyncStorage round-trip — debounce here if it ever shows up in a trace.
844
+ */
845
+ async persistSession() {
846
+ const e = {
847
+ id: this.sessionId,
848
+ start: this.sessionStart,
849
+ lastActivity: this.lastActivity ?? this.sessionStart,
850
+ sequence: this.sessionSequence,
851
+ eventSequence: this.eventSequence,
852
+ eventCount: this.sessionEventCount,
853
+ errorCount: this.errorCount,
854
+ sampled: this.sampled,
855
+ sampleRate: this.sampleRate
856
+ };
857
+ (await this.store.set(D, JSON.stringify(e))).status === "unavailable" && o.log("Telemetry: session not persisted (store unavailable)");
858
+ }
859
+ /** Emit session.started for the current session. */
860
+ async startSession(e) {
861
+ this.lastActivity = Date.now(), await this.persistSession(), await this.log("session.started", {
862
+ "session.reason": e,
863
+ // §4.1, count only — never the hosts (#99). Ships on every session so a zero is
864
+ // legible as "nobody opted in" rather than as a stripped header.
865
+ "sdk.trace_allowlist_size": this.trace.allowlistSize()
866
+ });
867
+ }
868
+ /**
869
+ * Finalize the current session: journey summary + sdk.error_count, then an immediate flush.
870
+ *
871
+ * The backend discards `duration_ms` and `event_count` and derives both (§4.2) — they
872
+ * stay on the wire because the event survives as the carrier for `session.reason` and
873
+ * `sdk.error_count`, neither of which is derivable. Duration is stamped from
874
+ * `lastActivity`, never `now`: a lazily-detected rotation must report 2 minutes of use,
875
+ * not the 6 idle hours that followed it.
876
+ */
877
+ async finalizeSession(e) {
878
+ await this.log("session.finalized", {
879
+ "session.duration_ms": Math.max(0, (this.lastActivity ?? this.sessionStart) - this.sessionStart),
880
+ "session.event_count": this.sessionEventCount,
881
+ "sdk.error_count": this.errorCount,
882
+ "session.reason": e
883
+ }), await this.flush();
884
+ }
885
+ /** Begin a fresh session: new id/start, reset per-session counters, emit session.started. */
886
+ async newSession(e) {
887
+ this.sessionId = this.generateSessionId(), this.sessionStart = Date.now(), this.sessionSequence = 0, this.eventSequence = 0, this.crashPersistedThrough = -1, this.sessionEventCount = 0, this.errorCount = 0, this.sampleRate = this.configuredSampleRate, this.sampled = this.rollSample(), this.appStartEmitted ? this.trace.clear() : this.trace.restartLaunchRoot(), await this.views.beginView("session_rotation"), await this.startSession(e);
888
+ }
889
+ /** Boundary rotation: finalize the old session then start a fresh one (the pair). */
890
+ async rotateSession(e) {
891
+ await this.views.endView(), await this.finalizeSession(e), await this.newSession(e);
892
+ }
893
+ /** Truncated at source (§3.2): the collector caps at 255 and we must not be the one over. */
894
+ setUserId(e) {
895
+ this.userId = e ? e.slice(0, ue) : void 0;
896
+ }
897
+ getUserId() {
898
+ return this.userId;
899
+ }
900
+ /**
901
+ * The SDK-owned `device.id` (§3.2/§3.3): self-minted, written through the `Store`,
902
+ * uninstall-scoped, stable across process restarts, and never rotated by login,
903
+ * logout or a profile clear.
904
+ *
905
+ * The platform suffix is the entry's (ios|android|web), not the device-info adapter's:
906
+ * this id is persisted forever, so it must not depend on a call that can throw on first run.
907
+ *
908
+ * `getUniqueId()` is deliberately not used — it already carries two lifetimes on RN
909
+ * alone (ANDROID_ID survives reinstall, identifierForVendor does not), so one identity
910
+ * column would mean two things.
911
+ *
912
+ * When the Store reports `unavailable` — incognito, a partitioned iframe, ITP
913
+ * eviction, a full disk — the id lives for one process only and `device.id_ephemeral`
914
+ * rides the Context block to say so, because that population is otherwise
915
+ * indistinguishable from real installs and reads as traffic growth.
916
+ */
917
+ async getDeviceId() {
918
+ return this.deviceIdPromise ??= (async () => {
919
+ const e = await this.store.get(R);
920
+ if (e.status === "hit") return e.value;
921
+ const t = this.platform ? `_${this.platform}` : "", s = `device_${Date.now()}_${u(16)}${t}`, r = await this.store.set(R, s);
922
+ return this.deviceIdEphemeral = e.status === "unavailable" || r.status === "unavailable", s;
923
+ })(), this.deviceIdPromise;
924
+ }
925
+ // ---------- User Profile Management ----------
926
+ /**
927
+ * Set complete user profile information
928
+ */
929
+ setUserProfile(e) {
930
+ const t = Date.now(), s = !this.userProfile || e.userId && e.userId !== this.userProfile.userId;
931
+ this.userProfile = {
932
+ ...this.userProfile,
933
+ ...e,
934
+ updatedAt: t,
935
+ ...s && { createdAt: t }
936
+ }, e.userId ? (this.setUserId(e.userId), this.userProfile.userId = this.userId) : this.userProfile && !this.userProfile.userId && (this.userProfile.userId = this.userId), o.log("Telemetry: User profile updated", this.userProfile);
937
+ }
938
+ /**
939
+ * EdgeRum-style identify(): record the host-app profile and emit the one event that
940
+ * carries it — `user.profile.update` (§4.10, #107). The PII is no longer on the
941
+ * Context block of every event.
942
+ *
943
+ * `userId` is optional and new in v4: the one call that sends a profile should also
944
+ * set the key that profile attaches to, since `user.id` is consumer-supplied (§3.2)
945
+ * and an unkeyed profile has nothing to upsert on. Omitting it leaves `user.id`
946
+ * exactly as it was — identify never mints one.
947
+ */
948
+ async identify(e) {
949
+ e.userId !== void 0 && this.setUserId(e.userId), this.setUserProfile({
950
+ ...e.name !== void 0 ? { fullName: e.name } : {},
951
+ ...e.email !== void 0 ? { email: e.email } : {},
952
+ ...e.phone !== void 0 ? { phone: e.phone } : {},
953
+ ...e.avatar !== void 0 ? { avatar: e.avatar } : {},
954
+ ...e.customAttributes !== void 0 ? { customAttributes: e.customAttributes } : {}
955
+ }), await this.log("user.profile.update", se(this.userProfile));
956
+ }
957
+ /**
958
+ * Set user details with individual parameters
959
+ */
960
+ setUserDetails(e) {
961
+ this.setUserProfile(e);
962
+ }
963
+ /**
964
+ * Update specific user profile fields
965
+ */
966
+ updateUserProfile(e) {
967
+ if (!this.userProfile) {
968
+ this.setUserProfile(e);
969
+ return;
970
+ }
971
+ this.userProfile = {
972
+ ...this.userProfile,
973
+ ...e,
974
+ updatedAt: Date.now()
975
+ }, o.log("Telemetry: User profile updated", this.userProfile);
976
+ }
977
+ /**
978
+ * Get current user profile
979
+ */
980
+ getUserProfile() {
981
+ return this.userProfile;
982
+ }
983
+ /**
984
+ * Clear user profile data
985
+ */
986
+ clearUserProfile() {
987
+ this.userProfile = void 0, this.userId = void 0, o.log("Telemetry: User profile cleared");
988
+ }
989
+ /**
990
+ * Set user name (convenience method)
991
+ */
992
+ setUserName(e, t, s) {
993
+ this.setUserProfile({
994
+ fullName: e,
995
+ firstName: t,
996
+ lastName: s
997
+ });
998
+ }
999
+ /**
1000
+ * Set user contact info (convenience method)
1001
+ */
1002
+ setUserContact(e, t) {
1003
+ this.setUserProfile({
1004
+ email: e,
1005
+ phone: t
1006
+ });
1007
+ }
1008
+ // ---------- Logging APIs ----------
1009
+ /**
1010
+ * Log a named event. Keeps existing signature compatibility.
1011
+ * Automatically attaches userId and sessionId to every queued event.
1012
+ */
1013
+ async log(e, t, s) {
1014
+ this.eventCount++;
1015
+ let r = !1, a = !1;
1016
+ if (!e.startsWith("session.") && e !== "view") {
1017
+ const n = Date.now(), h = this.expiryReason(n);
1018
+ h && await this.rotateSession(h), this.lastActivity = n, this.sessionEventCount++, P(e) && this.errorCount++, r = !0;
1019
+ }
1020
+ if (this.sampled) {
1021
+ const n = le.has(e), h = n ? e : "custom_event", c = await this.collectContext(t);
1022
+ n || (c["event.name"] = e), fe.has(h) && Object.assign(c, this.trace.annotate()), h === "app.crash" ? (c["error.breadcrumbs"] = this.breadcrumbs.toJSON(), a = !0) : this.breadcrumbs.add({ name: h, timestamp: (/* @__PURE__ */ new Date()).toISOString() }), P(h) ? this.views.countError() : h === "ui.interaction" && this.views.countAction(), s && (c["view.id"] = s.viewId, c["view.name"] = this.views.nameOf(s.viewId), c["session.id"] = s.sessionId, c["session.start_time"] = new Date(s.sessionStart).toISOString()), this.enqueue({
1023
+ type: "event",
1024
+ eventName: h,
1025
+ timestamp: new Date(s?.at ?? Date.now()).toISOString(),
1026
+ attributes: c
1027
+ }), o.log("Telemetry queued event:", e, "Queue size:", this.queue.length), o.log("Event attributes:", c);
1028
+ }
1029
+ r && await this.persistSession(), a ? await this.flushCrash() : this.queue.length >= this.batchSize && this.flush();
1030
+ }
1031
+ /**
1032
+ * The crash path (§2 / §9.4): persist the whole queue, then send **one** batch reordered
1033
+ * so the crash rides in it. Not a drain — a dying process gets one round trip.
1034
+ *
1035
+ * ⚠ The web/native asymmetry is real and is not fixed here. On web the `Store` is
1036
+ * synchronous `localStorage`, so the persist has *landed* by the time the next line runs
1037
+ * and the loss window closes. On native it is an AsyncStorage round-trip that a SIGKILL
1038
+ * can outrun, so the window only narrows. Awaiting harder does not change that; the
1039
+ * asymmetry is the `Store` port's whole point (`core/store.ts`).
1040
+ *
1041
+ * The persisted copy is a safety copy, not a handoff: the queue is left intact, because
1042
+ * an `app.crash` is not always fatal — `window.onerror` fires and the page keeps running
1043
+ * — and the process usually lives on. A successful send therefore
1044
+ * leaves a duplicate on disk to replay next launch — which is what `event.sequence` and
1045
+ * the backend's `(session_id, event_sequence)` dedup exist for (§2.4).
1046
+ *
1047
+ * *One* duplicate. Each row is written at most once by this path, watermarked on
1048
+ * `event.sequence`: a chatty app crash-flushes often, and re-persisting the whole queue
1049
+ * each time would fill the store with copies of
1050
+ * its own backlog and book `store_full` drops that are not loss — corrupting the very
1051
+ * counter this change adds.
1052
+ */
1053
+ async flushCrash() {
1054
+ if (!this.sender || this.queue.length === 0) return;
1055
+ const e = (r) => r.eventName === "app.crash";
1056
+ this.queue = [...this.queue.filter(e), ...this.queue.filter((r) => !e(r))];
1057
+ const t = this.queue.filter((r) => v(r) > this.crashPersistedThrough);
1058
+ if (t.length > 0 && this.sender.onFailure)
1059
+ try {
1060
+ this.recordDrop("store_full", await this.sender.onFailure(t)), this.crashPersistedThrough = Math.max(this.crashPersistedThrough, ...t.map(v));
1061
+ } catch (r) {
1062
+ o.warn("Telemetry: crash-path persist failed:", r);
1063
+ }
1064
+ const s = this.queue.splice(0, this.batchSize);
1065
+ try {
1066
+ await this.sender.send(s), await this.ackBatch();
1067
+ } catch (r) {
1068
+ const a = s.filter((n) => v(n) > this.crashPersistedThrough);
1069
+ a.length > 0 && this.queue.unshift(...a), o.warn("Telemetry: crash-path send failed:", r);
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Build the v3 Context block that rides on every event AND metric: the flattened
1074
+ * device/network snapshot, the caller's data, identity + session + sdk fields, and the
1075
+ * user profile. Shared by log() and logMetric() so both carry the identical iOS-clean set.
1076
+ */
1077
+ async collectContext(e) {
1078
+ let t = {}, s = {};
1079
+ try {
1080
+ t = await this.deviceInfoHandler?.collect() || {};
1081
+ } catch (n) {
1082
+ o.warn("Telemetry: failed to fetch device info", n);
1083
+ }
1084
+ try {
1085
+ s = await this.networkInfoHandler?.collect() || {};
1086
+ } catch (n) {
1087
+ o.warn("Telemetry: failed to fetch network info", n);
1088
+ }
1089
+ const r = await this.getDeviceId();
1090
+ return {
1091
+ // deviceInfo already namespaces its own keys (app.*, device.*) — flatten flat
1092
+ ...this.flattenWithPrefix("", t),
1093
+ ...this.flattenWithPrefix("network", s),
1094
+ ...this.flattenWithPrefix("", e || {}),
1095
+ // Identity keys land after caller data: `data` may override app./device./network.*
1096
+ // but must never override these (§3.3).
1097
+ // With the identity keys, not with deviceInfo's `app.*`: it is the symbolication
1098
+ // join key, so a stray `log()` payload must not be able to shift which map a
1099
+ // crash resolves against.
1100
+ ...this.buildId ? { "app.build_id": this.buildId } : {},
1101
+ "device.id": r,
1102
+ ...this.deviceIdEphemeral ? { "device.id_ephemeral": !0 } : {},
1103
+ // Omitted entirely on anonymous traffic (§3.2) — no "", no placeholder.
1104
+ ...this.userId ? { "user.id": this.userId } : {},
1105
+ // Denormalized onto every row (§4.5) so "errors by screen" needs no join. The id
1106
+ // is the join key; the name is the view's current best, resolved here at log time
1107
+ // by lookup on that id, so a row can never carry a name that disagrees with it.
1108
+ "view.id": this.views.id,
1109
+ "view.name": this.views.name,
1110
+ "session.id": this.sessionId,
1111
+ "session.start_time": new Date(this.sessionStart).toISOString(),
1112
+ "session.sequence": this.sessionSequence,
1113
+ // Extrapolation is arithmetic when the rate is on the row: it survives a
1114
+ // consumer retuning mid-quarter, which config-in-a-spreadsheet does not.
1115
+ "session.sample_rate": this.sampleRate,
1116
+ // Monotonic and always present; the reason stays omitted until there is one,
1117
+ // so `sdk.drop_reason IS NOT NULL` is a usable filter for "this install lost data".
1118
+ "sdk.events_dropped": this.eventsDropped,
1119
+ ...this.dropReason ? { "sdk.drop_reason": this.dropReason } : {},
1120
+ "sdk.hook_dropped": this.hookDropped,
1121
+ "sdk.hook_failed": this.hookFailed,
1122
+ "sdk.platform": re(this.platform),
1123
+ "sdk.version": this.sdkVersion
1124
+ };
1125
+ }
1126
+ /**
1127
+ * Emit a metric on the v3 `type:"metric"` path: { type, metricName, value, timestamp, attributes }.
1128
+ * Carries the same Context block as events. Metrics are samples, not user actions, so they
1129
+ * don't extend the breadcrumb trail and don't count as session activity — a periodic sampler
1130
+ * (memory/frames) must not keep a session alive and defeat the 30-min idle rotation.
1131
+ */
1132
+ async logMetric(e, t, s) {
1133
+ this.eventCount++;
1134
+ const r = this.expiryReason(Date.now());
1135
+ if (r && await this.rotateSession(r), !this.sampled) return;
1136
+ const a = await this.collectContext(s), n = me[e];
1137
+ n && (a["metric.unit"] = n), this.enqueue({
1138
+ type: "metric",
1139
+ metricName: e,
1140
+ value: t,
1141
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1142
+ attributes: a
1143
+ }), this.queue.length >= this.batchSize && this.flush();
1144
+ }
1145
+ /**
1146
+ * The single enqueue point: neither the sample decision nor `beforeSend` can be
1147
+ * bypassed by a future emit path, and the hook runs *before* the queue — which is
1148
+ * what keeps scrubbed fields off disk when a send fails and the batch is persisted.
1149
+ *
1150
+ * The sample check is repeated by log()/logMetric() ahead of `collectContext()`, so
1151
+ * a sampled-out session does no work per event; this one is the backstop that makes
1152
+ * the guarantee structural rather than a convention two call sites happen to follow.
1153
+ *
1154
+ * The two counters are separate on purpose: "my volume is down 40%" has to
1155
+ * distinguish *my rule is too broad* from *my rule is crashing*, and one merged
1156
+ * counter answers neither.
1157
+ */
1158
+ enqueue(e) {
1159
+ if (!this.sampled) return;
1160
+ let t = e;
1161
+ if (this.beforeSend) {
1162
+ const s = Y(e, this.beforeSend);
1163
+ if (s.kind === "failed") {
1164
+ this.hookFailed++;
1165
+ return;
1166
+ }
1167
+ if (s.kind === "dropped") {
1168
+ this.hookDropped++;
1169
+ return;
1170
+ }
1171
+ t = s.event;
1172
+ }
1173
+ (t.attributes ??= {})["event.sequence"] = this.eventSequence++, this.queue.push(t), this.capQueue();
1174
+ }
1175
+ /**
1176
+ * Hold the in-memory queue at its cap (§9.4), booking what it costs.
1177
+ *
1178
+ * The count lands on the *next* event's Context block, not this one's — `collectContext()`
1179
+ * has already run by the time we get here. That is §3.7's stated behaviour, not a bug.
1180
+ */
1181
+ capQueue() {
1182
+ for (; this.queue.length > ce; )
1183
+ this.queue.splice(de(this.queue), 1), this.recordDrop("queue_full");
1184
+ }
1185
+ /**
1186
+ * Book dropped rows against the monotonic counter and the reason that shipped last.
1187
+ * Takes `Sender.onFailure`'s return shape as-is — a sender with no cap returns nothing.
1188
+ */
1189
+ recordDrop(e, t = 1) {
1190
+ !t || t <= 0 || (this.eventsDropped += t, this.dropReason = e);
1191
+ }
1192
+ flattenWithPrefix(e, t, s = 0) {
1193
+ const r = {};
1194
+ for (const a in t) {
1195
+ if (!Object.prototype.hasOwnProperty.call(t, a)) continue;
1196
+ const n = t[a], h = e ? `${e}.${a}` : a;
1197
+ if (n !== void 0)
1198
+ if (typeof n == "object" && n !== null && !Array.isArray(n) && s < he)
1199
+ Object.assign(r, this.flattenWithPrefix(h, n, s + 1));
1200
+ else if (typeof n == "object" && n !== null) {
1201
+ const c = q(n);
1202
+ c !== void 0 && (r[h] = c);
1203
+ } else
1204
+ r[h] = n;
1205
+ }
1206
+ return r;
1207
+ }
1208
+ /**
1209
+ * Explicit metric helper (optional convenience) — routes through the v3 metric path.
1210
+ */
1211
+ recordMetric(e, t, s) {
1212
+ return this.logMetric(e, t, s);
1213
+ }
1214
+ // ---------- Flush / Persistence ----------
1215
+ // Retry + backoff live in the Sender (see sendWithRetry in web/native senders),
1216
+ // which is also used by the standalone replay paths. flush() does a single send
1217
+ // and, on failure, hands the batch to the sender to persist (or requeues it).
1218
+ async flush() {
1219
+ if (!this.sender || this.queue.length === 0) return;
1220
+ const e = this.queue.splice(0, this.batchSize);
1221
+ try {
1222
+ await this.sender.send(e), await this.ackBatch();
1223
+ } catch (t) {
1224
+ if (this.sender.onFailure)
1225
+ try {
1226
+ this.recordDrop("store_full", await this.sender.onFailure(e));
1227
+ } catch (s) {
1228
+ this.queue.unshift(...e), o.warn("Sender.onFailure failed, requeued events:", s);
1229
+ }
1230
+ else
1231
+ this.queue.unshift(...e);
1232
+ throw o.error("Telemetry flush failed:", t), t;
1233
+ }
1234
+ }
1235
+ /**
1236
+ * Book an acknowledged (2xx) batch. `session.sequence` orders a session's batches (#29),
1237
+ * and it is persisted because a resumed session restarting at 0 would emit duplicate
1238
+ * (session.id, session.sequence) pairs and stop ordering anything (#92).
1239
+ */
1240
+ async ackBatch() {
1241
+ this.sessionSequence++, await this.persistSession();
1242
+ }
1243
+ getQueue() {
1244
+ return [...this.queue];
1245
+ }
1246
+ async shutdown() {
1247
+ this.intervalId && clearInterval(this.intervalId), this.memoryHandler?.stop?.(), await this.flush();
1248
+ }
1249
+ // Screen tracking lives in ScreenTimingTracker (this.screens), the single timed API.
1250
+ // Expose some internal counters (optional)
1251
+ getEventCount() {
1252
+ return this.eventCount;
1253
+ }
1254
+ /**
1255
+ * A route change: the view boundary (§4.5) plus, on native only, the two deprecated
1256
+ * feeds (§4.11). v3's two native screen paths were disjoint — `attachNavigation` never
1257
+ * touched `inst.screens`, so a React Navigation consumer emitted `navigation` on every
1258
+ * route change and never a single `screen.duration`. Unifying them here is what fixes
1259
+ * that, which is why a *deprecated* event starts firing where it never has.
1260
+ */
1261
+ async recordRouteChange(e, t) {
1262
+ this.currentScreen = t, this.deprecatedScreenFeeds && (await this.navigationTracker?.recordRouteChange(e, t), await this.screens.endScreen(e), this.screens.markStart(t)), await this.enterView(t, "route");
1263
+ }
1264
+ /**
1265
+ * Feed the name ladder (§4.5.1). Rungs 1 and 2 arrive unnormalized; rung 3's caller
1266
+ * normalizes before calling. Whether this re-stamps the current view or mints a
1267
+ * successor is the ladder's decision, not the caller's.
1268
+ */
1269
+ async enterView(e, t) {
1270
+ await this.views.navigate(e, t);
1271
+ }
1272
+ }
1273
+ export {
1274
+ R as DEVICE_ID_KEY,
1275
+ D as SESSION_KEY,
1276
+ Ie as Telemetry,
1277
+ de as evictIndex,
1278
+ P as isErrorName
1279
+ };
1280
+ //# sourceMappingURL=telemetry-ByDJIz_9.js.map