@tracelog/lib 0.11.2 → 0.11.3

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 (539) hide show
  1. package/dist/public-api.cjs +4387 -0
  2. package/dist/public-api.cjs.map +1 -0
  3. package/dist/public-api.d.mts +913 -0
  4. package/dist/public-api.d.ts +913 -0
  5. package/dist/public-api.js +4348 -0
  6. package/dist/public-api.js.map +1 -0
  7. package/package.json +18 -18
  8. package/dist/browser/tracelog.esm.js +0 -2983
  9. package/dist/browser/tracelog.esm.js.map +0 -1
  10. package/dist/browser/tracelog.js +0 -8
  11. package/dist/browser/tracelog.js.map +0 -1
  12. package/dist/cjs/api.d.ts +0 -19
  13. package/dist/cjs/api.d.ts.map +0 -1
  14. package/dist/cjs/api.js +0 -183
  15. package/dist/cjs/api.js.map +0 -1
  16. package/dist/cjs/app.constants.d.ts +0 -80
  17. package/dist/cjs/app.constants.d.ts.map +0 -1
  18. package/dist/cjs/app.constants.js +0 -94
  19. package/dist/cjs/app.constants.js.map +0 -1
  20. package/dist/cjs/app.d.ts +0 -43
  21. package/dist/cjs/app.d.ts.map +0 -1
  22. package/dist/cjs/app.js +0 -165
  23. package/dist/cjs/app.js.map +0 -1
  24. package/dist/cjs/constants/config.constants.d.ts +0 -109
  25. package/dist/cjs/constants/config.constants.d.ts.map +0 -1
  26. package/dist/cjs/constants/config.constants.js +0 -216
  27. package/dist/cjs/constants/config.constants.js.map +0 -1
  28. package/dist/cjs/constants/error.constants.d.ts +0 -56
  29. package/dist/cjs/constants/error.constants.d.ts.map +0 -1
  30. package/dist/cjs/constants/error.constants.js +0 -89
  31. package/dist/cjs/constants/error.constants.js.map +0 -1
  32. package/dist/cjs/constants/index.d.ts +0 -5
  33. package/dist/cjs/constants/index.d.ts.map +0 -1
  34. package/dist/cjs/constants/index.js +0 -21
  35. package/dist/cjs/constants/index.js.map +0 -1
  36. package/dist/cjs/constants/performance.constants.d.ts +0 -29
  37. package/dist/cjs/constants/performance.constants.d.ts.map +0 -1
  38. package/dist/cjs/constants/performance.constants.js +0 -44
  39. package/dist/cjs/constants/performance.constants.js.map +0 -1
  40. package/dist/cjs/constants/storage.constants.d.ts +0 -11
  41. package/dist/cjs/constants/storage.constants.d.ts.map +0 -1
  42. package/dist/cjs/constants/storage.constants.js +0 -23
  43. package/dist/cjs/constants/storage.constants.js.map +0 -1
  44. package/dist/cjs/handlers/click.handler.d.ts +0 -36
  45. package/dist/cjs/handlers/click.handler.d.ts.map +0 -1
  46. package/dist/cjs/handlers/click.handler.js +0 -263
  47. package/dist/cjs/handlers/click.handler.js.map +0 -1
  48. package/dist/cjs/handlers/error.handler.d.ts +0 -28
  49. package/dist/cjs/handlers/error.handler.d.ts.map +0 -1
  50. package/dist/cjs/handlers/error.handler.js +0 -168
  51. package/dist/cjs/handlers/error.handler.js.map +0 -1
  52. package/dist/cjs/handlers/page-view.handler.d.ts +0 -17
  53. package/dist/cjs/handlers/page-view.handler.d.ts.map +0 -1
  54. package/dist/cjs/handlers/page-view.handler.js +0 -100
  55. package/dist/cjs/handlers/page-view.handler.js.map +0 -1
  56. package/dist/cjs/handlers/performance.handler.d.ts +0 -23
  57. package/dist/cjs/handlers/performance.handler.d.ts.map +0 -1
  58. package/dist/cjs/handlers/performance.handler.js +0 -274
  59. package/dist/cjs/handlers/performance.handler.js.map +0 -1
  60. package/dist/cjs/handlers/scroll.handler.d.ts +0 -40
  61. package/dist/cjs/handlers/scroll.handler.d.ts.map +0 -1
  62. package/dist/cjs/handlers/scroll.handler.js +0 -327
  63. package/dist/cjs/handlers/scroll.handler.js.map +0 -1
  64. package/dist/cjs/handlers/session.handler.d.ts +0 -16
  65. package/dist/cjs/handlers/session.handler.d.ts.map +0 -1
  66. package/dist/cjs/handlers/session.handler.js +0 -74
  67. package/dist/cjs/handlers/session.handler.js.map +0 -1
  68. package/dist/cjs/handlers/viewport.handler.d.ts +0 -44
  69. package/dist/cjs/handlers/viewport.handler.d.ts.map +0 -1
  70. package/dist/cjs/handlers/viewport.handler.js +0 -286
  71. package/dist/cjs/handlers/viewport.handler.js.map +0 -1
  72. package/dist/cjs/integrations/google-analytics.integration.d.ts +0 -18
  73. package/dist/cjs/integrations/google-analytics.integration.d.ts.map +0 -1
  74. package/dist/cjs/integrations/google-analytics.integration.js +0 -90
  75. package/dist/cjs/integrations/google-analytics.integration.js.map +0 -1
  76. package/dist/cjs/listeners/activity-listener-manager.d.ts +0 -9
  77. package/dist/cjs/listeners/activity-listener-manager.d.ts.map +0 -1
  78. package/dist/cjs/listeners/activity-listener-manager.js +0 -33
  79. package/dist/cjs/listeners/activity-listener-manager.js.map +0 -1
  80. package/dist/cjs/listeners/index.d.ts +0 -7
  81. package/dist/cjs/listeners/index.d.ts.map +0 -1
  82. package/dist/cjs/listeners/index.js +0 -15
  83. package/dist/cjs/listeners/index.js.map +0 -1
  84. package/dist/cjs/listeners/input-listener-managers.d.ts +0 -25
  85. package/dist/cjs/listeners/input-listener-managers.d.ts.map +0 -1
  86. package/dist/cjs/listeners/input-listener-managers.js +0 -50
  87. package/dist/cjs/listeners/input-listener-managers.js.map +0 -1
  88. package/dist/cjs/listeners/listeners.types.d.ts +0 -5
  89. package/dist/cjs/listeners/listeners.types.d.ts.map +0 -1
  90. package/dist/cjs/listeners/listeners.types.js +0 -3
  91. package/dist/cjs/listeners/listeners.types.js.map +0 -1
  92. package/dist/cjs/listeners/touch-listener-manager.d.ts +0 -9
  93. package/dist/cjs/listeners/touch-listener-manager.d.ts.map +0 -1
  94. package/dist/cjs/listeners/touch-listener-manager.js +0 -35
  95. package/dist/cjs/listeners/touch-listener-manager.js.map +0 -1
  96. package/dist/cjs/listeners/unload-listener-manager.d.ts +0 -9
  97. package/dist/cjs/listeners/unload-listener-manager.d.ts.map +0 -1
  98. package/dist/cjs/listeners/unload-listener-manager.js +0 -31
  99. package/dist/cjs/listeners/unload-listener-manager.js.map +0 -1
  100. package/dist/cjs/listeners/visibility-listener-manager.d.ts +0 -10
  101. package/dist/cjs/listeners/visibility-listener-manager.d.ts.map +0 -1
  102. package/dist/cjs/listeners/visibility-listener-manager.js +0 -48
  103. package/dist/cjs/listeners/visibility-listener-manager.js.map +0 -1
  104. package/dist/cjs/managers/event.manager.d.ts +0 -62
  105. package/dist/cjs/managers/event.manager.d.ts.map +0 -1
  106. package/dist/cjs/managers/event.manager.js +0 -508
  107. package/dist/cjs/managers/event.manager.js.map +0 -1
  108. package/dist/cjs/managers/sender.manager.d.ts +0 -32
  109. package/dist/cjs/managers/sender.manager.d.ts.map +0 -1
  110. package/dist/cjs/managers/sender.manager.js +0 -271
  111. package/dist/cjs/managers/sender.manager.js.map +0 -1
  112. package/dist/cjs/managers/session.manager.d.ts +0 -40
  113. package/dist/cjs/managers/session.manager.d.ts.map +0 -1
  114. package/dist/cjs/managers/session.manager.js +0 -282
  115. package/dist/cjs/managers/session.manager.js.map +0 -1
  116. package/dist/cjs/managers/state.manager.d.ts +0 -9
  117. package/dist/cjs/managers/state.manager.d.ts.map +0 -1
  118. package/dist/cjs/managers/state.manager.js +0 -27
  119. package/dist/cjs/managers/state.manager.js.map +0 -1
  120. package/dist/cjs/managers/storage.manager.d.ts +0 -60
  121. package/dist/cjs/managers/storage.manager.d.ts.map +0 -1
  122. package/dist/cjs/managers/storage.manager.js +0 -277
  123. package/dist/cjs/managers/storage.manager.js.map +0 -1
  124. package/dist/cjs/managers/user.manager.d.ts +0 -17
  125. package/dist/cjs/managers/user.manager.d.ts.map +0 -1
  126. package/dist/cjs/managers/user.manager.js +0 -31
  127. package/dist/cjs/managers/user.manager.js.map +0 -1
  128. package/dist/cjs/public-api.d.ts +0 -11
  129. package/dist/cjs/public-api.d.ts.map +0 -1
  130. package/dist/cjs/public-api.js +0 -32
  131. package/dist/cjs/public-api.js.map +0 -1
  132. package/dist/cjs/test-bridge.d.ts +0 -47
  133. package/dist/cjs/test-bridge.d.ts.map +0 -1
  134. package/dist/cjs/test-bridge.js +0 -165
  135. package/dist/cjs/test-bridge.js.map +0 -1
  136. package/dist/cjs/types/common.types.d.ts +0 -6
  137. package/dist/cjs/types/common.types.d.ts.map +0 -1
  138. package/dist/cjs/types/common.types.js +0 -3
  139. package/dist/cjs/types/common.types.js.map +0 -1
  140. package/dist/cjs/types/config.types.d.ts +0 -49
  141. package/dist/cjs/types/config.types.d.ts.map +0 -1
  142. package/dist/cjs/types/config.types.js +0 -9
  143. package/dist/cjs/types/config.types.js.map +0 -1
  144. package/dist/cjs/types/device.types.d.ts +0 -7
  145. package/dist/cjs/types/device.types.d.ts.map +0 -1
  146. package/dist/cjs/types/device.types.js +0 -11
  147. package/dist/cjs/types/device.types.js.map +0 -1
  148. package/dist/cjs/types/emitter.types.d.ts +0 -12
  149. package/dist/cjs/types/emitter.types.d.ts.map +0 -1
  150. package/dist/cjs/types/emitter.types.js +0 -9
  151. package/dist/cjs/types/emitter.types.js.map +0 -1
  152. package/dist/cjs/types/error.types.d.ts +0 -12
  153. package/dist/cjs/types/error.types.d.ts.map +0 -1
  154. package/dist/cjs/types/error.types.js +0 -23
  155. package/dist/cjs/types/error.types.js.map +0 -1
  156. package/dist/cjs/types/event.types.d.ts +0 -235
  157. package/dist/cjs/types/event.types.d.ts.map +0 -1
  158. package/dist/cjs/types/event.types.js +0 -48
  159. package/dist/cjs/types/event.types.js.map +0 -1
  160. package/dist/cjs/types/index.d.ts +0 -17
  161. package/dist/cjs/types/index.d.ts.map +0 -1
  162. package/dist/cjs/types/index.js +0 -33
  163. package/dist/cjs/types/index.js.map +0 -1
  164. package/dist/cjs/types/log.types.d.ts +0 -5
  165. package/dist/cjs/types/log.types.d.ts.map +0 -1
  166. package/dist/cjs/types/log.types.js +0 -3
  167. package/dist/cjs/types/log.types.js.map +0 -1
  168. package/dist/cjs/types/mode.types.d.ts +0 -7
  169. package/dist/cjs/types/mode.types.d.ts.map +0 -1
  170. package/dist/cjs/types/mode.types.js +0 -11
  171. package/dist/cjs/types/mode.types.js.map +0 -1
  172. package/dist/cjs/types/queue.types.d.ts +0 -19
  173. package/dist/cjs/types/queue.types.d.ts.map +0 -1
  174. package/dist/cjs/types/queue.types.js +0 -3
  175. package/dist/cjs/types/queue.types.js.map +0 -1
  176. package/dist/cjs/types/scroll.types.d.ts +0 -16
  177. package/dist/cjs/types/scroll.types.d.ts.map +0 -1
  178. package/dist/cjs/types/scroll.types.js +0 -12
  179. package/dist/cjs/types/scroll.types.js.map +0 -1
  180. package/dist/cjs/types/session.types.d.ts +0 -2
  181. package/dist/cjs/types/session.types.d.ts.map +0 -1
  182. package/dist/cjs/types/session.types.js +0 -3
  183. package/dist/cjs/types/session.types.js.map +0 -1
  184. package/dist/cjs/types/state.types.d.ts +0 -16
  185. package/dist/cjs/types/state.types.d.ts.map +0 -1
  186. package/dist/cjs/types/state.types.js +0 -3
  187. package/dist/cjs/types/state.types.js.map +0 -1
  188. package/dist/cjs/types/test-bridge.types.d.ts +0 -40
  189. package/dist/cjs/types/test-bridge.types.d.ts.map +0 -1
  190. package/dist/cjs/types/test-bridge.types.js +0 -3
  191. package/dist/cjs/types/test-bridge.types.js.map +0 -1
  192. package/dist/cjs/types/validation-error.types.d.ts +0 -44
  193. package/dist/cjs/types/validation-error.types.d.ts.map +0 -1
  194. package/dist/cjs/types/validation-error.types.js +0 -70
  195. package/dist/cjs/types/validation-error.types.js.map +0 -1
  196. package/dist/cjs/types/viewport.types.d.ts +0 -55
  197. package/dist/cjs/types/viewport.types.d.ts.map +0 -1
  198. package/dist/cjs/types/viewport.types.js +0 -3
  199. package/dist/cjs/types/viewport.types.js.map +0 -1
  200. package/dist/cjs/types/window.types.d.ts +0 -16
  201. package/dist/cjs/types/window.types.d.ts.map +0 -1
  202. package/dist/cjs/types/window.types.js +0 -3
  203. package/dist/cjs/types/window.types.js.map +0 -1
  204. package/dist/cjs/utils/browser/device-detector.utils.d.ts +0 -7
  205. package/dist/cjs/utils/browser/device-detector.utils.d.ts.map +0 -1
  206. package/dist/cjs/utils/browser/device-detector.utils.js +0 -50
  207. package/dist/cjs/utils/browser/device-detector.utils.js.map +0 -1
  208. package/dist/cjs/utils/browser/index.d.ts +0 -4
  209. package/dist/cjs/utils/browser/index.d.ts.map +0 -1
  210. package/dist/cjs/utils/browser/index.js +0 -20
  211. package/dist/cjs/utils/browser/index.js.map +0 -1
  212. package/dist/cjs/utils/browser/qa-mode.utils.d.ts +0 -14
  213. package/dist/cjs/utils/browser/qa-mode.utils.d.ts.map +0 -1
  214. package/dist/cjs/utils/browser/qa-mode.utils.js +0 -44
  215. package/dist/cjs/utils/browser/qa-mode.utils.js.map +0 -1
  216. package/dist/cjs/utils/browser/utm-params.utils.d.ts +0 -7
  217. package/dist/cjs/utils/browser/utm-params.utils.d.ts.map +0 -1
  218. package/dist/cjs/utils/browser/utm-params.utils.js +0 -23
  219. package/dist/cjs/utils/browser/utm-params.utils.js.map +0 -1
  220. package/dist/cjs/utils/data/index.d.ts +0 -2
  221. package/dist/cjs/utils/data/index.d.ts.map +0 -1
  222. package/dist/cjs/utils/data/index.js +0 -18
  223. package/dist/cjs/utils/data/index.js.map +0 -1
  224. package/dist/cjs/utils/data/uuid.utils.d.ts +0 -19
  225. package/dist/cjs/utils/data/uuid.utils.d.ts.map +0 -1
  226. package/dist/cjs/utils/data/uuid.utils.js +0 -57
  227. package/dist/cjs/utils/data/uuid.utils.js.map +0 -1
  228. package/dist/cjs/utils/emitter.utils.d.ts +0 -9
  229. package/dist/cjs/utils/emitter.utils.d.ts.map +0 -1
  230. package/dist/cjs/utils/emitter.utils.js +0 -36
  231. package/dist/cjs/utils/emitter.utils.js.map +0 -1
  232. package/dist/cjs/utils/index.d.ts +0 -8
  233. package/dist/cjs/utils/index.d.ts.map +0 -1
  234. package/dist/cjs/utils/index.js +0 -24
  235. package/dist/cjs/utils/index.js.map +0 -1
  236. package/dist/cjs/utils/logging.utils.d.ts +0 -22
  237. package/dist/cjs/utils/logging.utils.d.ts.map +0 -1
  238. package/dist/cjs/utils/logging.utils.js +0 -87
  239. package/dist/cjs/utils/logging.utils.js.map +0 -1
  240. package/dist/cjs/utils/network/index.d.ts +0 -2
  241. package/dist/cjs/utils/network/index.d.ts.map +0 -1
  242. package/dist/cjs/utils/network/index.js +0 -18
  243. package/dist/cjs/utils/network/index.js.map +0 -1
  244. package/dist/cjs/utils/network/url.utils.d.ts +0 -16
  245. package/dist/cjs/utils/network/url.utils.d.ts.map +0 -1
  246. package/dist/cjs/utils/network/url.utils.js +0 -92
  247. package/dist/cjs/utils/network/url.utils.js.map +0 -1
  248. package/dist/cjs/utils/security/index.d.ts +0 -2
  249. package/dist/cjs/utils/security/index.d.ts.map +0 -1
  250. package/dist/cjs/utils/security/index.js +0 -18
  251. package/dist/cjs/utils/security/index.js.map +0 -1
  252. package/dist/cjs/utils/security/sanitize.utils.d.ts +0 -14
  253. package/dist/cjs/utils/security/sanitize.utils.d.ts.map +0 -1
  254. package/dist/cjs/utils/security/sanitize.utils.js +0 -123
  255. package/dist/cjs/utils/security/sanitize.utils.js.map +0 -1
  256. package/dist/cjs/utils/validations/config-validations.utils.d.ts +0 -19
  257. package/dist/cjs/utils/validations/config-validations.utils.d.ts.map +0 -1
  258. package/dist/cjs/utils/validations/config-validations.utils.js +0 -236
  259. package/dist/cjs/utils/validations/config-validations.utils.js.map +0 -1
  260. package/dist/cjs/utils/validations/event-validations.utils.d.ts +0 -13
  261. package/dist/cjs/utils/validations/event-validations.utils.d.ts.map +0 -1
  262. package/dist/cjs/utils/validations/event-validations.utils.js +0 -37
  263. package/dist/cjs/utils/validations/event-validations.utils.js.map +0 -1
  264. package/dist/cjs/utils/validations/index.d.ts +0 -5
  265. package/dist/cjs/utils/validations/index.d.ts.map +0 -1
  266. package/dist/cjs/utils/validations/index.js +0 -21
  267. package/dist/cjs/utils/validations/index.js.map +0 -1
  268. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +0 -23
  269. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts.map +0 -1
  270. package/dist/cjs/utils/validations/metadata-validations.utils.js +0 -153
  271. package/dist/cjs/utils/validations/metadata-validations.utils.js.map +0 -1
  272. package/dist/cjs/utils/validations/type-guards.utils.d.ts +0 -9
  273. package/dist/cjs/utils/validations/type-guards.utils.d.ts.map +0 -1
  274. package/dist/cjs/utils/validations/type-guards.utils.js +0 -90
  275. package/dist/cjs/utils/validations/type-guards.utils.js.map +0 -1
  276. package/dist/esm/api.d.ts +0 -19
  277. package/dist/esm/api.d.ts.map +0 -1
  278. package/dist/esm/api.js +0 -173
  279. package/dist/esm/api.js.map +0 -1
  280. package/dist/esm/app.constants.d.ts +0 -80
  281. package/dist/esm/app.constants.d.ts.map +0 -1
  282. package/dist/esm/app.constants.js +0 -82
  283. package/dist/esm/app.constants.js.map +0 -1
  284. package/dist/esm/app.d.ts +0 -43
  285. package/dist/esm/app.d.ts.map +0 -1
  286. package/dist/esm/app.js +0 -161
  287. package/dist/esm/app.js.map +0 -1
  288. package/dist/esm/constants/config.constants.d.ts +0 -109
  289. package/dist/esm/constants/config.constants.d.ts.map +0 -1
  290. package/dist/esm/constants/config.constants.js +0 -212
  291. package/dist/esm/constants/config.constants.js.map +0 -1
  292. package/dist/esm/constants/error.constants.d.ts +0 -56
  293. package/dist/esm/constants/error.constants.d.ts.map +0 -1
  294. package/dist/esm/constants/error.constants.js +0 -86
  295. package/dist/esm/constants/error.constants.js.map +0 -1
  296. package/dist/esm/constants/index.d.ts +0 -5
  297. package/dist/esm/constants/index.d.ts.map +0 -1
  298. package/dist/esm/constants/index.js +0 -5
  299. package/dist/esm/constants/index.js.map +0 -1
  300. package/dist/esm/constants/performance.constants.d.ts +0 -29
  301. package/dist/esm/constants/performance.constants.d.ts.map +0 -1
  302. package/dist/esm/constants/performance.constants.js +0 -41
  303. package/dist/esm/constants/performance.constants.js.map +0 -1
  304. package/dist/esm/constants/storage.constants.d.ts +0 -11
  305. package/dist/esm/constants/storage.constants.d.ts.map +0 -1
  306. package/dist/esm/constants/storage.constants.js +0 -13
  307. package/dist/esm/constants/storage.constants.js.map +0 -1
  308. package/dist/esm/handlers/click.handler.d.ts +0 -36
  309. package/dist/esm/handlers/click.handler.d.ts.map +0 -1
  310. package/dist/esm/handlers/click.handler.js +0 -259
  311. package/dist/esm/handlers/click.handler.js.map +0 -1
  312. package/dist/esm/handlers/error.handler.d.ts +0 -28
  313. package/dist/esm/handlers/error.handler.d.ts.map +0 -1
  314. package/dist/esm/handlers/error.handler.js +0 -164
  315. package/dist/esm/handlers/error.handler.js.map +0 -1
  316. package/dist/esm/handlers/page-view.handler.d.ts +0 -17
  317. package/dist/esm/handlers/page-view.handler.d.ts.map +0 -1
  318. package/dist/esm/handlers/page-view.handler.js +0 -96
  319. package/dist/esm/handlers/page-view.handler.js.map +0 -1
  320. package/dist/esm/handlers/performance.handler.d.ts +0 -23
  321. package/dist/esm/handlers/performance.handler.d.ts.map +0 -1
  322. package/dist/esm/handlers/performance.handler.js +0 -237
  323. package/dist/esm/handlers/performance.handler.js.map +0 -1
  324. package/dist/esm/handlers/scroll.handler.d.ts +0 -40
  325. package/dist/esm/handlers/scroll.handler.d.ts.map +0 -1
  326. package/dist/esm/handlers/scroll.handler.js +0 -323
  327. package/dist/esm/handlers/scroll.handler.js.map +0 -1
  328. package/dist/esm/handlers/session.handler.d.ts +0 -16
  329. package/dist/esm/handlers/session.handler.d.ts.map +0 -1
  330. package/dist/esm/handlers/session.handler.js +0 -70
  331. package/dist/esm/handlers/session.handler.js.map +0 -1
  332. package/dist/esm/handlers/viewport.handler.d.ts +0 -44
  333. package/dist/esm/handlers/viewport.handler.d.ts.map +0 -1
  334. package/dist/esm/handlers/viewport.handler.js +0 -282
  335. package/dist/esm/handlers/viewport.handler.js.map +0 -1
  336. package/dist/esm/integrations/google-analytics.integration.d.ts +0 -18
  337. package/dist/esm/integrations/google-analytics.integration.d.ts.map +0 -1
  338. package/dist/esm/integrations/google-analytics.integration.js +0 -86
  339. package/dist/esm/integrations/google-analytics.integration.js.map +0 -1
  340. package/dist/esm/listeners/activity-listener-manager.d.ts +0 -9
  341. package/dist/esm/listeners/activity-listener-manager.d.ts.map +0 -1
  342. package/dist/esm/listeners/activity-listener-manager.js +0 -29
  343. package/dist/esm/listeners/activity-listener-manager.js.map +0 -1
  344. package/dist/esm/listeners/index.d.ts +0 -7
  345. package/dist/esm/listeners/index.d.ts.map +0 -1
  346. package/dist/esm/listeners/index.js +0 -6
  347. package/dist/esm/listeners/index.js.map +0 -1
  348. package/dist/esm/listeners/input-listener-managers.d.ts +0 -25
  349. package/dist/esm/listeners/input-listener-managers.d.ts.map +0 -1
  350. package/dist/esm/listeners/input-listener-managers.js +0 -45
  351. package/dist/esm/listeners/input-listener-managers.js.map +0 -1
  352. package/dist/esm/listeners/listeners.types.d.ts +0 -5
  353. package/dist/esm/listeners/listeners.types.d.ts.map +0 -1
  354. package/dist/esm/listeners/listeners.types.js +0 -2
  355. package/dist/esm/listeners/listeners.types.js.map +0 -1
  356. package/dist/esm/listeners/touch-listener-manager.d.ts +0 -9
  357. package/dist/esm/listeners/touch-listener-manager.d.ts.map +0 -1
  358. package/dist/esm/listeners/touch-listener-manager.js +0 -31
  359. package/dist/esm/listeners/touch-listener-manager.js.map +0 -1
  360. package/dist/esm/listeners/unload-listener-manager.d.ts +0 -9
  361. package/dist/esm/listeners/unload-listener-manager.d.ts.map +0 -1
  362. package/dist/esm/listeners/unload-listener-manager.js +0 -27
  363. package/dist/esm/listeners/unload-listener-manager.js.map +0 -1
  364. package/dist/esm/listeners/visibility-listener-manager.d.ts +0 -10
  365. package/dist/esm/listeners/visibility-listener-manager.d.ts.map +0 -1
  366. package/dist/esm/listeners/visibility-listener-manager.js +0 -44
  367. package/dist/esm/listeners/visibility-listener-manager.js.map +0 -1
  368. package/dist/esm/managers/event.manager.d.ts +0 -62
  369. package/dist/esm/managers/event.manager.d.ts.map +0 -1
  370. package/dist/esm/managers/event.manager.js +0 -504
  371. package/dist/esm/managers/event.manager.js.map +0 -1
  372. package/dist/esm/managers/sender.manager.d.ts +0 -32
  373. package/dist/esm/managers/sender.manager.d.ts.map +0 -1
  374. package/dist/esm/managers/sender.manager.js +0 -267
  375. package/dist/esm/managers/sender.manager.js.map +0 -1
  376. package/dist/esm/managers/session.manager.d.ts +0 -40
  377. package/dist/esm/managers/session.manager.d.ts.map +0 -1
  378. package/dist/esm/managers/session.manager.js +0 -278
  379. package/dist/esm/managers/session.manager.js.map +0 -1
  380. package/dist/esm/managers/state.manager.d.ts +0 -9
  381. package/dist/esm/managers/state.manager.d.ts.map +0 -1
  382. package/dist/esm/managers/state.manager.js +0 -21
  383. package/dist/esm/managers/state.manager.js.map +0 -1
  384. package/dist/esm/managers/storage.manager.d.ts +0 -60
  385. package/dist/esm/managers/storage.manager.d.ts.map +0 -1
  386. package/dist/esm/managers/storage.manager.js +0 -273
  387. package/dist/esm/managers/storage.manager.js.map +0 -1
  388. package/dist/esm/managers/user.manager.d.ts +0 -17
  389. package/dist/esm/managers/user.manager.d.ts.map +0 -1
  390. package/dist/esm/managers/user.manager.js +0 -27
  391. package/dist/esm/managers/user.manager.js.map +0 -1
  392. package/dist/esm/public-api.d.ts +0 -11
  393. package/dist/esm/public-api.d.ts.map +0 -1
  394. package/dist/esm/public-api.js +0 -15
  395. package/dist/esm/public-api.js.map +0 -1
  396. package/dist/esm/test-bridge.d.ts +0 -47
  397. package/dist/esm/test-bridge.d.ts.map +0 -1
  398. package/dist/esm/test-bridge.js +0 -161
  399. package/dist/esm/test-bridge.js.map +0 -1
  400. package/dist/esm/types/common.types.d.ts +0 -6
  401. package/dist/esm/types/common.types.d.ts.map +0 -1
  402. package/dist/esm/types/common.types.js +0 -2
  403. package/dist/esm/types/common.types.js.map +0 -1
  404. package/dist/esm/types/config.types.d.ts +0 -49
  405. package/dist/esm/types/config.types.d.ts.map +0 -1
  406. package/dist/esm/types/config.types.js +0 -6
  407. package/dist/esm/types/config.types.js.map +0 -1
  408. package/dist/esm/types/device.types.d.ts +0 -7
  409. package/dist/esm/types/device.types.d.ts.map +0 -1
  410. package/dist/esm/types/device.types.js +0 -8
  411. package/dist/esm/types/device.types.js.map +0 -1
  412. package/dist/esm/types/emitter.types.d.ts +0 -12
  413. package/dist/esm/types/emitter.types.d.ts.map +0 -1
  414. package/dist/esm/types/emitter.types.js +0 -6
  415. package/dist/esm/types/emitter.types.js.map +0 -1
  416. package/dist/esm/types/error.types.d.ts +0 -12
  417. package/dist/esm/types/error.types.d.ts.map +0 -1
  418. package/dist/esm/types/error.types.js +0 -19
  419. package/dist/esm/types/error.types.js.map +0 -1
  420. package/dist/esm/types/event.types.d.ts +0 -235
  421. package/dist/esm/types/event.types.d.ts.map +0 -1
  422. package/dist/esm/types/event.types.js +0 -45
  423. package/dist/esm/types/event.types.js.map +0 -1
  424. package/dist/esm/types/index.d.ts +0 -17
  425. package/dist/esm/types/index.d.ts.map +0 -1
  426. package/dist/esm/types/index.js +0 -17
  427. package/dist/esm/types/index.js.map +0 -1
  428. package/dist/esm/types/log.types.d.ts +0 -5
  429. package/dist/esm/types/log.types.d.ts.map +0 -1
  430. package/dist/esm/types/log.types.js +0 -2
  431. package/dist/esm/types/log.types.js.map +0 -1
  432. package/dist/esm/types/mode.types.d.ts +0 -7
  433. package/dist/esm/types/mode.types.d.ts.map +0 -1
  434. package/dist/esm/types/mode.types.js +0 -8
  435. package/dist/esm/types/mode.types.js.map +0 -1
  436. package/dist/esm/types/queue.types.d.ts +0 -19
  437. package/dist/esm/types/queue.types.d.ts.map +0 -1
  438. package/dist/esm/types/queue.types.js +0 -2
  439. package/dist/esm/types/queue.types.js.map +0 -1
  440. package/dist/esm/types/scroll.types.d.ts +0 -16
  441. package/dist/esm/types/scroll.types.d.ts.map +0 -1
  442. package/dist/esm/types/scroll.types.js +0 -8
  443. package/dist/esm/types/scroll.types.js.map +0 -1
  444. package/dist/esm/types/session.types.d.ts +0 -2
  445. package/dist/esm/types/session.types.d.ts.map +0 -1
  446. package/dist/esm/types/session.types.js +0 -2
  447. package/dist/esm/types/session.types.js.map +0 -1
  448. package/dist/esm/types/state.types.d.ts +0 -16
  449. package/dist/esm/types/state.types.d.ts.map +0 -1
  450. package/dist/esm/types/state.types.js +0 -2
  451. package/dist/esm/types/state.types.js.map +0 -1
  452. package/dist/esm/types/test-bridge.types.d.ts +0 -40
  453. package/dist/esm/types/test-bridge.types.d.ts.map +0 -1
  454. package/dist/esm/types/test-bridge.types.js +0 -2
  455. package/dist/esm/types/test-bridge.types.js.map +0 -1
  456. package/dist/esm/types/validation-error.types.d.ts +0 -44
  457. package/dist/esm/types/validation-error.types.d.ts.map +0 -1
  458. package/dist/esm/types/validation-error.types.js +0 -61
  459. package/dist/esm/types/validation-error.types.js.map +0 -1
  460. package/dist/esm/types/viewport.types.d.ts +0 -55
  461. package/dist/esm/types/viewport.types.d.ts.map +0 -1
  462. package/dist/esm/types/viewport.types.js +0 -2
  463. package/dist/esm/types/viewport.types.js.map +0 -1
  464. package/dist/esm/types/window.types.d.ts +0 -16
  465. package/dist/esm/types/window.types.d.ts.map +0 -1
  466. package/dist/esm/types/window.types.js +0 -2
  467. package/dist/esm/types/window.types.js.map +0 -1
  468. package/dist/esm/utils/browser/device-detector.utils.d.ts +0 -7
  469. package/dist/esm/utils/browser/device-detector.utils.d.ts.map +0 -1
  470. package/dist/esm/utils/browser/device-detector.utils.js +0 -46
  471. package/dist/esm/utils/browser/device-detector.utils.js.map +0 -1
  472. package/dist/esm/utils/browser/index.d.ts +0 -4
  473. package/dist/esm/utils/browser/index.d.ts.map +0 -1
  474. package/dist/esm/utils/browser/index.js +0 -4
  475. package/dist/esm/utils/browser/index.js.map +0 -1
  476. package/dist/esm/utils/browser/qa-mode.utils.d.ts +0 -14
  477. package/dist/esm/utils/browser/qa-mode.utils.d.ts.map +0 -1
  478. package/dist/esm/utils/browser/qa-mode.utils.js +0 -40
  479. package/dist/esm/utils/browser/qa-mode.utils.js.map +0 -1
  480. package/dist/esm/utils/browser/utm-params.utils.d.ts +0 -7
  481. package/dist/esm/utils/browser/utm-params.utils.d.ts.map +0 -1
  482. package/dist/esm/utils/browser/utm-params.utils.js +0 -19
  483. package/dist/esm/utils/browser/utm-params.utils.js.map +0 -1
  484. package/dist/esm/utils/data/index.d.ts +0 -2
  485. package/dist/esm/utils/data/index.d.ts.map +0 -1
  486. package/dist/esm/utils/data/index.js +0 -2
  487. package/dist/esm/utils/data/index.js.map +0 -1
  488. package/dist/esm/utils/data/uuid.utils.d.ts +0 -19
  489. package/dist/esm/utils/data/uuid.utils.d.ts.map +0 -1
  490. package/dist/esm/utils/data/uuid.utils.js +0 -52
  491. package/dist/esm/utils/data/uuid.utils.js.map +0 -1
  492. package/dist/esm/utils/emitter.utils.d.ts +0 -9
  493. package/dist/esm/utils/emitter.utils.d.ts.map +0 -1
  494. package/dist/esm/utils/emitter.utils.js +0 -32
  495. package/dist/esm/utils/emitter.utils.js.map +0 -1
  496. package/dist/esm/utils/index.d.ts +0 -8
  497. package/dist/esm/utils/index.d.ts.map +0 -1
  498. package/dist/esm/utils/index.js +0 -8
  499. package/dist/esm/utils/index.js.map +0 -1
  500. package/dist/esm/utils/logging.utils.d.ts +0 -22
  501. package/dist/esm/utils/logging.utils.d.ts.map +0 -1
  502. package/dist/esm/utils/logging.utils.js +0 -82
  503. package/dist/esm/utils/logging.utils.js.map +0 -1
  504. package/dist/esm/utils/network/index.d.ts +0 -2
  505. package/dist/esm/utils/network/index.d.ts.map +0 -1
  506. package/dist/esm/utils/network/index.js +0 -2
  507. package/dist/esm/utils/network/index.js.map +0 -1
  508. package/dist/esm/utils/network/url.utils.d.ts +0 -16
  509. package/dist/esm/utils/network/url.utils.d.ts.map +0 -1
  510. package/dist/esm/utils/network/url.utils.js +0 -87
  511. package/dist/esm/utils/network/url.utils.js.map +0 -1
  512. package/dist/esm/utils/security/index.d.ts +0 -2
  513. package/dist/esm/utils/security/index.d.ts.map +0 -1
  514. package/dist/esm/utils/security/index.js +0 -2
  515. package/dist/esm/utils/security/index.js.map +0 -1
  516. package/dist/esm/utils/security/sanitize.utils.d.ts +0 -14
  517. package/dist/esm/utils/security/sanitize.utils.d.ts.map +0 -1
  518. package/dist/esm/utils/security/sanitize.utils.js +0 -118
  519. package/dist/esm/utils/security/sanitize.utils.js.map +0 -1
  520. package/dist/esm/utils/validations/config-validations.utils.d.ts +0 -19
  521. package/dist/esm/utils/validations/config-validations.utils.d.ts.map +0 -1
  522. package/dist/esm/utils/validations/config-validations.utils.js +0 -231
  523. package/dist/esm/utils/validations/config-validations.utils.js.map +0 -1
  524. package/dist/esm/utils/validations/event-validations.utils.d.ts +0 -13
  525. package/dist/esm/utils/validations/event-validations.utils.d.ts.map +0 -1
  526. package/dist/esm/utils/validations/event-validations.utils.js +0 -33
  527. package/dist/esm/utils/validations/event-validations.utils.js.map +0 -1
  528. package/dist/esm/utils/validations/index.d.ts +0 -5
  529. package/dist/esm/utils/validations/index.d.ts.map +0 -1
  530. package/dist/esm/utils/validations/index.js +0 -5
  531. package/dist/esm/utils/validations/index.js.map +0 -1
  532. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +0 -23
  533. package/dist/esm/utils/validations/metadata-validations.utils.d.ts.map +0 -1
  534. package/dist/esm/utils/validations/metadata-validations.utils.js +0 -148
  535. package/dist/esm/utils/validations/metadata-validations.utils.js.map +0 -1
  536. package/dist/esm/utils/validations/type-guards.utils.d.ts +0 -9
  537. package/dist/esm/utils/validations/type-guards.utils.d.ts.map +0 -1
  538. package/dist/esm/utils/validations/type-guards.utils.js +0 -86
  539. package/dist/esm/utils/validations/type-guards.utils.js.map +0 -1
@@ -0,0 +1,4387 @@
1
+ 'use strict';
2
+
3
+ // src/constants/config.constants.ts
4
+ var DEFAULT_SESSION_TIMEOUT = 15 * 60 * 1e3;
5
+ var DUPLICATE_EVENT_THRESHOLD_MS = 500;
6
+ var EVENT_SENT_INTERVAL_MS = 1e4;
7
+ var SCROLL_DEBOUNCE_TIME_MS = 250;
8
+ var DEFAULT_VISIBILITY_TIMEOUT_MS = 2e3;
9
+ var DEFAULT_PAGE_VIEW_THROTTLE_MS = 1e3;
10
+ var DEFAULT_CLICK_THROTTLE_MS = 300;
11
+ var DEFAULT_VIEWPORT_COOLDOWN_PERIOD = 6e4;
12
+ var DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS = 100;
13
+ var VIEWPORT_MUTATION_DEBOUNCE_MS = 100;
14
+ var MAX_THROTTLE_CACHE_ENTRIES = 1e3;
15
+ var THROTTLE_ENTRY_TTL_MS = 3e5;
16
+ var THROTTLE_PRUNE_INTERVAL_MS = 3e4;
17
+ var EVENT_EXPIRY_HOURS = 2;
18
+ var MAX_EVENTS_QUEUE_LENGTH = 100;
19
+ var REQUEST_TIMEOUT_MS = 1e4;
20
+ var SIGNIFICANT_SCROLL_DELTA = 10;
21
+ var MIN_SCROLL_DEPTH_CHANGE = 5;
22
+ var SCROLL_MIN_EVENT_INTERVAL_MS = 500;
23
+ var MAX_SCROLL_EVENTS_PER_SESSION = 120;
24
+ var DEFAULT_SAMPLING_RATE = 1;
25
+ var RATE_LIMIT_WINDOW_MS = 1e3;
26
+ var MAX_EVENTS_PER_SECOND = 50;
27
+ var MAX_SAME_EVENT_PER_MINUTE = 60;
28
+ var PER_EVENT_RATE_LIMIT_WINDOW_MS = 6e4;
29
+ var MAX_EVENTS_PER_SESSION = 1e3;
30
+ var MAX_CLICKS_PER_SESSION = 500;
31
+ var MAX_PAGE_VIEWS_PER_SESSION = 100;
32
+ var MAX_CUSTOM_EVENTS_PER_SESSION = 500;
33
+ var MAX_VIEWPORT_EVENTS_PER_SESSION = 200;
34
+ var BATCH_SIZE_THRESHOLD = 50;
35
+ var MAX_PENDING_EVENTS_BUFFER = 100;
36
+ var MIN_SESSION_TIMEOUT_MS = 3e4;
37
+ var MAX_SESSION_TIMEOUT_MS = 864e5;
38
+ var MAX_CUSTOM_EVENT_NAME_LENGTH = 120;
39
+ var MAX_CUSTOM_EVENT_STRING_SIZE = 8 * 1024;
40
+ var MAX_CUSTOM_EVENT_KEYS = 10;
41
+ var MAX_CUSTOM_EVENT_ARRAY_SIZE = 10;
42
+ var MAX_NESTED_OBJECT_KEYS = 20;
43
+ var MAX_METADATA_NESTING_DEPTH = 1;
44
+ var MAX_TEXT_LENGTH = 255;
45
+ var MAX_STRING_LENGTH = 1e3;
46
+ var MAX_STRING_LENGTH_IN_ARRAY = 500;
47
+ var MAX_ARRAY_LENGTH = 100;
48
+ var MAX_OBJECT_DEPTH = 3;
49
+ var PRECISION_TWO_DECIMALS = 2;
50
+ var MAX_BEACON_PAYLOAD_SIZE = 64 * 1024;
51
+ var MAX_FINGERPRINTS = 1e3;
52
+ var FINGERPRINT_CLEANUP_MULTIPLIER = 10;
53
+ var MAX_FINGERPRINTS_HARD_LIMIT = 2e3;
54
+ var HTML_DATA_ATTR_PREFIX = "data-tlog";
55
+ var INTERACTIVE_SELECTORS = [
56
+ "button",
57
+ "a",
58
+ 'input[type="button"]',
59
+ 'input[type="submit"]',
60
+ 'input[type="reset"]',
61
+ 'input[type="checkbox"]',
62
+ 'input[type="radio"]',
63
+ "select",
64
+ "textarea",
65
+ '[role="button"]',
66
+ '[role="link"]',
67
+ '[role="tab"]',
68
+ '[role="menuitem"]',
69
+ '[role="option"]',
70
+ '[role="checkbox"]',
71
+ '[role="radio"]',
72
+ '[role="switch"]',
73
+ "[routerLink]",
74
+ "[ng-click]",
75
+ "[data-action]",
76
+ "[data-click]",
77
+ "[data-navigate]",
78
+ "[data-toggle]",
79
+ "[onclick]",
80
+ ".btn",
81
+ ".button",
82
+ ".clickable",
83
+ ".nav-link",
84
+ ".menu-item",
85
+ "[data-testid]",
86
+ '[tabindex="0"]'
87
+ ];
88
+ var UTM_PARAMS = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
89
+ var DEFAULT_SENSITIVE_QUERY_PARAMS = [
90
+ "token",
91
+ "auth",
92
+ "key",
93
+ "session",
94
+ "reset",
95
+ "password",
96
+ "api_key",
97
+ "apikey",
98
+ "secret",
99
+ "access_token",
100
+ "refresh_token",
101
+ "verification",
102
+ "code",
103
+ "otp"
104
+ ];
105
+ var INITIALIZATION_TIMEOUT_MS = 1e4;
106
+ var SCROLL_SUPPRESS_MULTIPLIER = 2;
107
+ var VALIDATION_MESSAGES = {
108
+ INVALID_SESSION_TIMEOUT: `Session timeout must be between ${MIN_SESSION_TIMEOUT_MS}ms (30 seconds) and ${MAX_SESSION_TIMEOUT_MS}ms (24 hours)`,
109
+ INVALID_SAMPLING_RATE: "Sampling rate must be between 0 and 1",
110
+ INVALID_ERROR_SAMPLING_RATE: "Error sampling must be between 0 and 1",
111
+ INVALID_TRACELOG_PROJECT_ID: "TraceLog project ID is required when integration is enabled",
112
+ INVALID_CUSTOM_API_URL: "Custom API URL is required when integration is enabled",
113
+ INVALID_GOOGLE_ANALYTICS_ID: "Google Analytics measurement ID is required when integration is enabled",
114
+ INVALID_GLOBAL_METADATA: "Global metadata must be an object",
115
+ INVALID_SENSITIVE_QUERY_PARAMS: "Sensitive query params must be an array of strings",
116
+ INVALID_PRIMARY_SCROLL_SELECTOR: "Primary scroll selector must be a non-empty string",
117
+ INVALID_PRIMARY_SCROLL_SELECTOR_SYNTAX: "Invalid CSS selector syntax for primaryScrollSelector",
118
+ INVALID_PAGE_VIEW_THROTTLE: "Page view throttle must be a non-negative number",
119
+ INVALID_CLICK_THROTTLE: "Click throttle must be a non-negative number",
120
+ INVALID_MAX_SAME_EVENT_PER_MINUTE: "Max same event per minute must be a positive number",
121
+ INVALID_VIEWPORT_CONFIG: "Viewport config must be an object",
122
+ INVALID_VIEWPORT_ELEMENTS: "Viewport elements must be a non-empty array",
123
+ INVALID_VIEWPORT_ELEMENT: "Each viewport element must have a valid selector string",
124
+ INVALID_VIEWPORT_ELEMENT_ID: "Viewport element id must be a non-empty string",
125
+ INVALID_VIEWPORT_ELEMENT_NAME: "Viewport element name must be a non-empty string",
126
+ INVALID_VIEWPORT_THRESHOLD: "Viewport threshold must be a number between 0 and 1",
127
+ INVALID_VIEWPORT_MIN_DWELL_TIME: "Viewport minDwellTime must be a non-negative number",
128
+ INVALID_VIEWPORT_COOLDOWN_PERIOD: "Viewport cooldownPeriod must be a non-negative number",
129
+ INVALID_VIEWPORT_MAX_TRACKED_ELEMENTS: "Viewport maxTrackedElements must be a positive number"
130
+ };
131
+ var XSS_PATTERNS = [
132
+ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
133
+ /javascript:/gi,
134
+ /on\w+\s*=/gi,
135
+ /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
136
+ /<embed\b[^>]*>/gi,
137
+ /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi
138
+ ];
139
+
140
+ // src/types/config.types.ts
141
+ var SpecialApiUrl = /* @__PURE__ */ ((SpecialApiUrl2) => {
142
+ SpecialApiUrl2["Localhost"] = "localhost:8080";
143
+ SpecialApiUrl2["Fail"] = "localhost:9999";
144
+ return SpecialApiUrl2;
145
+ })(SpecialApiUrl || {});
146
+
147
+ // src/types/device.types.ts
148
+ var DeviceType = /* @__PURE__ */ ((DeviceType2) => {
149
+ DeviceType2["Mobile"] = "mobile";
150
+ DeviceType2["Tablet"] = "tablet";
151
+ DeviceType2["Desktop"] = "desktop";
152
+ DeviceType2["Unknown"] = "unknown";
153
+ return DeviceType2;
154
+ })(DeviceType || {});
155
+
156
+ // src/types/emitter.types.ts
157
+ var EmitterEvent = /* @__PURE__ */ ((EmitterEvent2) => {
158
+ EmitterEvent2["EVENT"] = "event";
159
+ EmitterEvent2["QUEUE"] = "queue";
160
+ return EmitterEvent2;
161
+ })(EmitterEvent || {});
162
+
163
+ // src/types/error.types.ts
164
+ var PermanentError = class _PermanentError extends Error {
165
+ constructor(message, statusCode) {
166
+ super(message);
167
+ this.statusCode = statusCode;
168
+ this.name = "PermanentError";
169
+ if (Error.captureStackTrace) {
170
+ Error.captureStackTrace(this, _PermanentError);
171
+ }
172
+ }
173
+ };
174
+
175
+ // src/types/event.types.ts
176
+ var EventType = /* @__PURE__ */ ((EventType2) => {
177
+ EventType2["PAGE_VIEW"] = "page_view";
178
+ EventType2["CLICK"] = "click";
179
+ EventType2["SCROLL"] = "scroll";
180
+ EventType2["SESSION_START"] = "session_start";
181
+ EventType2["SESSION_END"] = "session_end";
182
+ EventType2["CUSTOM"] = "custom";
183
+ EventType2["WEB_VITALS"] = "web_vitals";
184
+ EventType2["ERROR"] = "error";
185
+ EventType2["VIEWPORT_VISIBLE"] = "viewport_visible";
186
+ return EventType2;
187
+ })(EventType || {});
188
+ var ScrollDirection = /* @__PURE__ */ ((ScrollDirection2) => {
189
+ ScrollDirection2["UP"] = "up";
190
+ ScrollDirection2["DOWN"] = "down";
191
+ return ScrollDirection2;
192
+ })(ScrollDirection || {});
193
+ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
194
+ ErrorType2["JS_ERROR"] = "js_error";
195
+ ErrorType2["PROMISE_REJECTION"] = "promise_rejection";
196
+ return ErrorType2;
197
+ })(ErrorType || {});
198
+
199
+ // src/types/mode.types.ts
200
+ var Mode = /* @__PURE__ */ ((Mode2) => {
201
+ Mode2["QA"] = "qa";
202
+ return Mode2;
203
+ })(Mode || {});
204
+
205
+ // src/types/scroll.types.ts
206
+ function isPrimaryScrollEvent(event2) {
207
+ return event2.type === "scroll" /* SCROLL */ && "scroll_data" in event2 && event2.scroll_data.is_primary === true;
208
+ }
209
+ function isSecondaryScrollEvent(event2) {
210
+ return event2.type === "scroll" /* SCROLL */ && "scroll_data" in event2 && event2.scroll_data.is_primary === false;
211
+ }
212
+
213
+ // src/types/validation-error.types.ts
214
+ var TraceLogValidationError = class extends Error {
215
+ constructor(message, errorCode, layer) {
216
+ super(message);
217
+ this.errorCode = errorCode;
218
+ this.layer = layer;
219
+ this.name = this.constructor.name;
220
+ if (Error.captureStackTrace) {
221
+ Error.captureStackTrace(this, this.constructor);
222
+ }
223
+ }
224
+ };
225
+ var AppConfigValidationError = class extends TraceLogValidationError {
226
+ constructor(message, layer = "config") {
227
+ super(message, "APP_CONFIG_INVALID", layer);
228
+ }
229
+ };
230
+ var SessionTimeoutValidationError = class extends TraceLogValidationError {
231
+ constructor(message, layer = "config") {
232
+ super(message, "SESSION_TIMEOUT_INVALID", layer);
233
+ }
234
+ };
235
+ var SamplingRateValidationError = class extends TraceLogValidationError {
236
+ constructor(message, layer = "config") {
237
+ super(message, "SAMPLING_RATE_INVALID", layer);
238
+ }
239
+ };
240
+ var IntegrationValidationError = class extends TraceLogValidationError {
241
+ constructor(message, layer = "config") {
242
+ super(message, "INTEGRATION_INVALID", layer);
243
+ }
244
+ };
245
+ var InitializationTimeoutError = class extends TraceLogValidationError {
246
+ constructor(message, timeoutMs, layer = "runtime") {
247
+ super(message, "INITIALIZATION_TIMEOUT", layer);
248
+ this.timeoutMs = timeoutMs;
249
+ }
250
+ };
251
+
252
+ // src/utils/logging.utils.ts
253
+ var formatLogMsg = (msg, error) => {
254
+ if (error) {
255
+ if (process.env.NODE_ENV !== "development" && error instanceof Error) {
256
+ const sanitizedMessage = error.message.replace(/\s+at\s+.*$/gm, "").replace(/\(.*?:\d+:\d+\)/g, "");
257
+ return `[TraceLog] ${msg}: ${sanitizedMessage}`;
258
+ }
259
+ return `[TraceLog] ${msg}: ${error instanceof Error ? error.message : "Unknown error"}`;
260
+ }
261
+ return `[TraceLog] ${msg}`;
262
+ };
263
+ var log = (type, msg, extra) => {
264
+ const { error, data, showToClient = false } = extra ?? {};
265
+ const formattedMsg = error ? formatLogMsg(msg, error) : `[TraceLog] ${msg}`;
266
+ const method = type === "error" ? "error" : type === "warn" ? "warn" : "log";
267
+ const isProduction = process.env.NODE_ENV !== "development";
268
+ if (isProduction) {
269
+ if (type === "debug") {
270
+ return;
271
+ }
272
+ if (type === "info" && !showToClient) {
273
+ return;
274
+ }
275
+ }
276
+ if (isProduction && data !== void 0) {
277
+ const sanitizedData = sanitizeLogData(data);
278
+ console[method](formattedMsg, sanitizedData);
279
+ } else if (data !== void 0) {
280
+ console[method](formattedMsg, data);
281
+ } else {
282
+ console[method](formattedMsg);
283
+ }
284
+ };
285
+ var sanitizeLogData = (data) => {
286
+ const sanitized = {};
287
+ const sensitiveKeys = ["token", "password", "secret", "key", "apikey", "api_key", "sessionid", "session_id"];
288
+ for (const [key, value] of Object.entries(data)) {
289
+ const lowerKey = key.toLowerCase();
290
+ if (sensitiveKeys.some((sensitiveKey) => lowerKey.includes(sensitiveKey))) {
291
+ sanitized[key] = "[REDACTED]";
292
+ } else {
293
+ sanitized[key] = value;
294
+ }
295
+ }
296
+ return sanitized;
297
+ };
298
+
299
+ // src/utils/browser/device-detector.utils.ts
300
+ var coarsePointerQuery;
301
+ var noHoverQuery;
302
+ var initMediaQueries = () => {
303
+ if (typeof window !== "undefined" && !coarsePointerQuery) {
304
+ coarsePointerQuery = window.matchMedia("(pointer: coarse)");
305
+ noHoverQuery = window.matchMedia("(hover: none)");
306
+ }
307
+ };
308
+ var getDeviceType = () => {
309
+ try {
310
+ const nav = navigator;
311
+ if (nav.userAgentData && typeof nav.userAgentData.mobile === "boolean") {
312
+ if (nav.userAgentData.platform && /ipad|tablet/i.test(nav.userAgentData.platform)) {
313
+ return "tablet" /* Tablet */;
314
+ }
315
+ const result = nav.userAgentData.mobile ? "mobile" /* Mobile */ : "desktop" /* Desktop */;
316
+ return result;
317
+ }
318
+ initMediaQueries();
319
+ const width = window.innerWidth;
320
+ const hasCoarsePointer = coarsePointerQuery?.matches ?? false;
321
+ const hasNoHover = noHoverQuery?.matches ?? false;
322
+ const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0;
323
+ const ua = navigator.userAgent.toLowerCase();
324
+ const isMobileUA = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/.test(ua);
325
+ const isTabletUA = /tablet|ipad|android(?!.*mobile)/.test(ua);
326
+ if (width <= 767 || isMobileUA && hasTouchSupport) {
327
+ return "mobile" /* Mobile */;
328
+ }
329
+ if (width >= 768 && width <= 1024 || isTabletUA || hasCoarsePointer && hasNoHover && hasTouchSupport) {
330
+ return "tablet" /* Tablet */;
331
+ }
332
+ return "desktop" /* Desktop */;
333
+ } catch (error) {
334
+ log("warn", "Device detection failed, defaulting to desktop", { error });
335
+ return "desktop" /* Desktop */;
336
+ }
337
+ };
338
+
339
+ // src/constants/storage.constants.ts
340
+ var STORAGE_BASE_KEY = "tlog";
341
+ var QA_MODE_KEY = `${STORAGE_BASE_KEY}:qa_mode`;
342
+ var USER_ID_KEY = `${STORAGE_BASE_KEY}:uid`;
343
+ var QUEUE_KEY = (id) => id ? `${STORAGE_BASE_KEY}:${id}:queue` : `${STORAGE_BASE_KEY}:queue`;
344
+ var SESSION_STORAGE_KEY = (id) => id ? `${STORAGE_BASE_KEY}:${id}:session` : `${STORAGE_BASE_KEY}:session`;
345
+ var BROADCAST_CHANNEL_NAME = (id) => id ? `${STORAGE_BASE_KEY}:${id}:broadcast` : `${STORAGE_BASE_KEY}:broadcast`;
346
+
347
+ // src/constants/performance.constants.ts
348
+ var WEB_VITALS_THRESHOLDS = {
349
+ LCP: 4e3,
350
+ FCP: 1800,
351
+ CLS: 0.25,
352
+ INP: 200,
353
+ TTFB: 800,
354
+ LONG_TASK: 50
355
+ };
356
+ var LONG_TASK_THROTTLE_MS = 1e3;
357
+ var MAX_NAVIGATION_HISTORY = 50;
358
+
359
+ // src/constants/error.constants.ts
360
+ var PII_PATTERNS = [
361
+ // Email addresses
362
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/gi,
363
+ // US Phone numbers (various formats)
364
+ /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
365
+ // Credit card numbers (16 digits with optional separators)
366
+ /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,
367
+ // IBAN (International Bank Account Number)
368
+ /\b[A-Z]{2}\d{2}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/gi,
369
+ // API keys/tokens (sk_test_, sk_live_, pk_test_, pk_live_, etc.)
370
+ /\b[sp]k_(test|live)_[a-zA-Z0-9]{10,}\b/gi,
371
+ // Bearer tokens (JWT-like patterns - matches complete and partial tokens)
372
+ /Bearer\s+[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)?(?:\.[A-Za-z0-9_-]+)?/gi,
373
+ // Passwords in connection strings (protocol://user:password@host)
374
+ /:\/\/[^:/]+:([^@]+)@/gi
375
+ ];
376
+ var MAX_ERROR_MESSAGE_LENGTH = 500;
377
+ var ERROR_SUPPRESSION_WINDOW_MS = 5e3;
378
+ var MAX_TRACKED_ERRORS = 50;
379
+ var MAX_TRACKED_ERRORS_HARD_LIMIT = MAX_TRACKED_ERRORS * 2;
380
+ var DEFAULT_ERROR_SAMPLING_RATE = 1;
381
+ var ERROR_BURST_WINDOW_MS = 1e3;
382
+ var ERROR_BURST_THRESHOLD = 10;
383
+ var ERROR_BURST_BACKOFF_MS = 5e3;
384
+ var PERMANENT_ERROR_LOG_THROTTLE_MS = 6e4;
385
+
386
+ // src/utils/browser/qa-mode.utils.ts
387
+ var QA_MODE_PARAM = "tlog_mode";
388
+ var QA_MODE_VALUE = "qa";
389
+ var detectQaMode = () => {
390
+ const stored = sessionStorage.getItem(QA_MODE_KEY);
391
+ if (stored === "true") {
392
+ return true;
393
+ }
394
+ const params = new URLSearchParams(window.location.search);
395
+ const modeParam = params.get(QA_MODE_PARAM);
396
+ const isQaMode = modeParam === QA_MODE_VALUE;
397
+ if (isQaMode) {
398
+ sessionStorage.setItem(QA_MODE_KEY, "true");
399
+ params.delete(QA_MODE_PARAM);
400
+ const newSearch = params.toString();
401
+ const newUrl = `${window.location.pathname}${newSearch ? "?" + newSearch : ""}${window.location.hash}`;
402
+ try {
403
+ window.history.replaceState({}, "", newUrl);
404
+ } catch (error) {
405
+ log("warn", "History API not available, cannot replace URL", { error });
406
+ }
407
+ console.log(
408
+ "%c[TraceLog] QA Mode ACTIVE",
409
+ "background: #ff9800; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;"
410
+ );
411
+ }
412
+ return isQaMode;
413
+ };
414
+
415
+ // src/utils/browser/utm-params.utils.ts
416
+ var getUTMParameters = () => {
417
+ const urlParams = new URLSearchParams(window.location.search);
418
+ const utmParams = {};
419
+ UTM_PARAMS.forEach((param) => {
420
+ const value = urlParams.get(param);
421
+ if (value) {
422
+ const key = param.split("utm_")[1];
423
+ utmParams[key] = value;
424
+ }
425
+ });
426
+ const result = Object.keys(utmParams).length ? utmParams : void 0;
427
+ return result;
428
+ };
429
+
430
+ // src/utils/data/uuid.utils.ts
431
+ var generateUUID = () => {
432
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
433
+ return crypto.randomUUID();
434
+ }
435
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
436
+ const r = Math.random() * 16 | 0;
437
+ const v = c === "x" ? r : r & 3 | 8;
438
+ return v.toString(16);
439
+ });
440
+ };
441
+ var generateEventId = () => {
442
+ const timestamp = Date.now();
443
+ let random = "";
444
+ try {
445
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
446
+ const bytes = crypto.getRandomValues(new Uint8Array(4));
447
+ if (bytes) {
448
+ random = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
449
+ }
450
+ }
451
+ } catch {
452
+ }
453
+ if (!random) {
454
+ random = Math.floor(Math.random() * 4294967295).toString(16).padStart(8, "0");
455
+ }
456
+ return `${timestamp}-${random}`;
457
+ };
458
+
459
+ // src/utils/network/url.utils.ts
460
+ var isValidUrl = (url, allowHttp = false) => {
461
+ try {
462
+ const parsed = new URL(url);
463
+ const isHttps = parsed.protocol === "https:";
464
+ const isHttp = parsed.protocol === "http:";
465
+ return isHttps || allowHttp && isHttp;
466
+ } catch {
467
+ return false;
468
+ }
469
+ };
470
+ var getCollectApiUrl = (config) => {
471
+ if (config.integrations?.tracelog?.projectId) {
472
+ try {
473
+ const url = new URL(window.location.href);
474
+ const host = url.hostname;
475
+ if (!host || typeof host !== "string") {
476
+ throw new Error("Invalid hostname");
477
+ }
478
+ const parts = host.split(".");
479
+ if (!parts || !Array.isArray(parts) || parts.length === 0 || parts.length === 1 && parts[0] === "") {
480
+ throw new Error("Invalid hostname structure");
481
+ }
482
+ const projectId = config.integrations.tracelog.projectId;
483
+ const cleanDomain = parts.slice(-2).join(".");
484
+ if (!cleanDomain) {
485
+ throw new Error("Invalid domain");
486
+ }
487
+ const collectApiUrl2 = `https://${projectId}.${cleanDomain}/collect`;
488
+ const isValid = isValidUrl(collectApiUrl2);
489
+ if (!isValid) {
490
+ throw new Error("Invalid URL");
491
+ }
492
+ return collectApiUrl2;
493
+ } catch (error) {
494
+ throw new Error(`Invalid URL configuration: ${error instanceof Error ? error.message : String(error)}`);
495
+ }
496
+ }
497
+ const collectApiUrl = config.integrations?.custom?.collectApiUrl;
498
+ if (collectApiUrl) {
499
+ const allowHttp = config.integrations?.custom?.allowHttp ?? false;
500
+ const isValid = isValidUrl(collectApiUrl, allowHttp);
501
+ if (!isValid) {
502
+ throw new Error("Invalid URL");
503
+ }
504
+ return collectApiUrl;
505
+ }
506
+ return "";
507
+ };
508
+ var normalizeUrl = (url, sensitiveQueryParams = []) => {
509
+ if (!url || typeof url !== "string") {
510
+ log("warn", "Invalid URL provided to normalizeUrl", { data: { url: String(url) } });
511
+ return url || "";
512
+ }
513
+ try {
514
+ const urlObject = new URL(url);
515
+ const searchParams = urlObject.searchParams;
516
+ const allSensitiveParams = [.../* @__PURE__ */ new Set([...DEFAULT_SENSITIVE_QUERY_PARAMS, ...sensitiveQueryParams])];
517
+ let hasChanged = false;
518
+ const removedParams = [];
519
+ allSensitiveParams.forEach((param) => {
520
+ if (searchParams.has(param)) {
521
+ searchParams.delete(param);
522
+ hasChanged = true;
523
+ removedParams.push(param);
524
+ }
525
+ });
526
+ if (!hasChanged && url.includes("?")) {
527
+ return url;
528
+ }
529
+ urlObject.search = searchParams.toString();
530
+ const result = urlObject.toString();
531
+ return result;
532
+ } catch (error) {
533
+ const urlPreview = url && typeof url === "string" ? url.slice(0, 100) : String(url);
534
+ log("warn", "URL normalization failed, returning original", { error, data: { url: urlPreview } });
535
+ return url;
536
+ }
537
+ };
538
+
539
+ // src/utils/security/sanitize.utils.ts
540
+ var sanitizeString = (value) => {
541
+ if (!value || typeof value !== "string" || value.trim().length === 0) {
542
+ return "";
543
+ }
544
+ let sanitized = value;
545
+ if (value.length > MAX_STRING_LENGTH) {
546
+ sanitized = value.slice(0, Math.max(0, MAX_STRING_LENGTH));
547
+ }
548
+ let xssPatternMatches = 0;
549
+ for (const pattern of XSS_PATTERNS) {
550
+ const beforeReplace = sanitized;
551
+ sanitized = sanitized.replace(pattern, "");
552
+ if (beforeReplace !== sanitized) {
553
+ xssPatternMatches++;
554
+ }
555
+ }
556
+ if (xssPatternMatches > 0) {
557
+ log("warn", "XSS patterns detected and removed", {
558
+ data: {
559
+ patternMatches: xssPatternMatches,
560
+ originalValue: value.slice(0, 100)
561
+ }
562
+ });
563
+ }
564
+ sanitized = sanitized.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#x27;").replaceAll("/", "&#x2F;");
565
+ const result = sanitized.trim();
566
+ return result;
567
+ };
568
+ var sanitizeValue = (value, depth = 0) => {
569
+ if (depth > MAX_OBJECT_DEPTH) {
570
+ return null;
571
+ }
572
+ if (value === null || value === void 0) {
573
+ return null;
574
+ }
575
+ if (typeof value === "string") {
576
+ return sanitizeString(value);
577
+ }
578
+ if (typeof value === "number") {
579
+ if (!Number.isFinite(value) || value < -Number.MAX_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {
580
+ return 0;
581
+ }
582
+ return value;
583
+ }
584
+ if (typeof value === "boolean") {
585
+ return value;
586
+ }
587
+ if (Array.isArray(value)) {
588
+ const limitedArray = value.slice(0, MAX_ARRAY_LENGTH);
589
+ const sanitizedArray = limitedArray.map((item) => sanitizeValue(item, depth + 1)).filter((item) => item !== null);
590
+ return sanitizedArray;
591
+ }
592
+ if (typeof value === "object") {
593
+ const sanitizedObject = {};
594
+ const entries = Object.entries(value);
595
+ const limitedEntries = entries.slice(0, MAX_NESTED_OBJECT_KEYS);
596
+ for (const [key, value_] of limitedEntries) {
597
+ const sanitizedKey = sanitizeString(key);
598
+ if (sanitizedKey) {
599
+ const sanitizedValue = sanitizeValue(value_, depth + 1);
600
+ if (sanitizedValue !== null) {
601
+ sanitizedObject[sanitizedKey] = sanitizedValue;
602
+ }
603
+ }
604
+ }
605
+ return sanitizedObject;
606
+ }
607
+ return null;
608
+ };
609
+ var sanitizeMetadata = (metadata) => {
610
+ if (typeof metadata !== "object" || metadata === null) {
611
+ return {};
612
+ }
613
+ try {
614
+ const sanitized = sanitizeValue(metadata);
615
+ const result = typeof sanitized === "object" && sanitized !== null ? sanitized : {};
616
+ return result;
617
+ } catch (error) {
618
+ const errorMessage = error instanceof Error ? error.message : String(error);
619
+ throw new Error(`[TraceLog] Metadata sanitization failed: ${errorMessage}`);
620
+ }
621
+ };
622
+
623
+ // src/utils/validations/config-validations.utils.ts
624
+ var validateAppConfig = (config) => {
625
+ if (config !== void 0 && (config === null || typeof config !== "object")) {
626
+ throw new AppConfigValidationError("Configuration must be an object", "config");
627
+ }
628
+ if (!config) {
629
+ return;
630
+ }
631
+ if (config.sessionTimeout !== void 0) {
632
+ if (typeof config.sessionTimeout !== "number" || config.sessionTimeout < MIN_SESSION_TIMEOUT_MS || config.sessionTimeout > MAX_SESSION_TIMEOUT_MS) {
633
+ throw new SessionTimeoutValidationError(VALIDATION_MESSAGES.INVALID_SESSION_TIMEOUT, "config");
634
+ }
635
+ }
636
+ if (config.globalMetadata !== void 0) {
637
+ if (typeof config.globalMetadata !== "object" || config.globalMetadata === null) {
638
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_GLOBAL_METADATA, "config");
639
+ }
640
+ }
641
+ if (config.integrations) {
642
+ validateIntegrations(config.integrations);
643
+ }
644
+ if (config.sensitiveQueryParams !== void 0) {
645
+ if (!Array.isArray(config.sensitiveQueryParams)) {
646
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_SENSITIVE_QUERY_PARAMS, "config");
647
+ }
648
+ for (const param of config.sensitiveQueryParams) {
649
+ if (typeof param !== "string") {
650
+ throw new AppConfigValidationError("All sensitive query params must be strings", "config");
651
+ }
652
+ }
653
+ }
654
+ if (config.errorSampling !== void 0) {
655
+ if (typeof config.errorSampling !== "number" || config.errorSampling < 0 || config.errorSampling > 1) {
656
+ throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_ERROR_SAMPLING_RATE, "config");
657
+ }
658
+ }
659
+ if (config.samplingRate !== void 0) {
660
+ if (typeof config.samplingRate !== "number" || config.samplingRate < 0 || config.samplingRate > 1) {
661
+ throw new SamplingRateValidationError(VALIDATION_MESSAGES.INVALID_SAMPLING_RATE, "config");
662
+ }
663
+ }
664
+ if (config.primaryScrollSelector !== void 0) {
665
+ if (typeof config.primaryScrollSelector !== "string" || !config.primaryScrollSelector.trim()) {
666
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_PRIMARY_SCROLL_SELECTOR, "config");
667
+ }
668
+ if (config.primaryScrollSelector !== "window") {
669
+ try {
670
+ document.querySelector(config.primaryScrollSelector);
671
+ } catch {
672
+ throw new AppConfigValidationError(
673
+ `${VALIDATION_MESSAGES.INVALID_PRIMARY_SCROLL_SELECTOR_SYNTAX}: "${config.primaryScrollSelector}"`,
674
+ "config"
675
+ );
676
+ }
677
+ }
678
+ }
679
+ if (config.pageViewThrottleMs !== void 0) {
680
+ if (typeof config.pageViewThrottleMs !== "number" || config.pageViewThrottleMs < 0) {
681
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_PAGE_VIEW_THROTTLE, "config");
682
+ }
683
+ }
684
+ if (config.clickThrottleMs !== void 0) {
685
+ if (typeof config.clickThrottleMs !== "number" || config.clickThrottleMs < 0) {
686
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_CLICK_THROTTLE, "config");
687
+ }
688
+ }
689
+ if (config.maxSameEventPerMinute !== void 0) {
690
+ if (typeof config.maxSameEventPerMinute !== "number" || config.maxSameEventPerMinute <= 0) {
691
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_MAX_SAME_EVENT_PER_MINUTE, "config");
692
+ }
693
+ }
694
+ if (config.viewport !== void 0) {
695
+ validateViewportConfig(config.viewport);
696
+ }
697
+ };
698
+ var validateViewportConfig = (viewport) => {
699
+ if (typeof viewport !== "object" || viewport === null) {
700
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_CONFIG, "config");
701
+ }
702
+ if (!viewport.elements || !Array.isArray(viewport.elements)) {
703
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENTS, "config");
704
+ }
705
+ if (viewport.elements.length === 0) {
706
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENTS, "config");
707
+ }
708
+ const uniqueSelectors = /* @__PURE__ */ new Set();
709
+ for (const element of viewport.elements) {
710
+ if (!element.selector || typeof element.selector !== "string" || !element.selector.trim()) {
711
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT, "config");
712
+ }
713
+ const normalizedSelector = element.selector.trim();
714
+ if (uniqueSelectors.has(normalizedSelector)) {
715
+ throw new AppConfigValidationError(
716
+ `Duplicate viewport selector found: "${normalizedSelector}". Each selector should appear only once.`,
717
+ "config"
718
+ );
719
+ }
720
+ uniqueSelectors.add(normalizedSelector);
721
+ if (element.id !== void 0 && (typeof element.id !== "string" || !element.id.trim())) {
722
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT_ID, "config");
723
+ }
724
+ if (element.name !== void 0 && (typeof element.name !== "string" || !element.name.trim())) {
725
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_ELEMENT_NAME, "config");
726
+ }
727
+ }
728
+ if (viewport.threshold !== void 0) {
729
+ if (typeof viewport.threshold !== "number" || viewport.threshold < 0 || viewport.threshold > 1) {
730
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_THRESHOLD, "config");
731
+ }
732
+ }
733
+ if (viewport.minDwellTime !== void 0) {
734
+ if (typeof viewport.minDwellTime !== "number" || viewport.minDwellTime < 0) {
735
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_MIN_DWELL_TIME, "config");
736
+ }
737
+ }
738
+ if (viewport.cooldownPeriod !== void 0) {
739
+ if (typeof viewport.cooldownPeriod !== "number" || viewport.cooldownPeriod < 0) {
740
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_COOLDOWN_PERIOD, "config");
741
+ }
742
+ }
743
+ if (viewport.maxTrackedElements !== void 0) {
744
+ if (typeof viewport.maxTrackedElements !== "number" || viewport.maxTrackedElements <= 0) {
745
+ throw new AppConfigValidationError(VALIDATION_MESSAGES.INVALID_VIEWPORT_MAX_TRACKED_ELEMENTS, "config");
746
+ }
747
+ }
748
+ };
749
+ var validateIntegrations = (integrations) => {
750
+ if (!integrations) {
751
+ return;
752
+ }
753
+ if (integrations.tracelog) {
754
+ if (!integrations.tracelog.projectId || typeof integrations.tracelog.projectId !== "string" || integrations.tracelog.projectId.trim() === "") {
755
+ throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_TRACELOG_PROJECT_ID, "config");
756
+ }
757
+ }
758
+ if (integrations.custom) {
759
+ if (!integrations.custom.collectApiUrl || typeof integrations.custom.collectApiUrl !== "string" || integrations.custom.collectApiUrl.trim() === "") {
760
+ throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_CUSTOM_API_URL, "config");
761
+ }
762
+ if (integrations.custom.allowHttp !== void 0 && typeof integrations.custom.allowHttp !== "boolean") {
763
+ throw new IntegrationValidationError("allowHttp must be a boolean", "config");
764
+ }
765
+ const collectApiUrl = integrations.custom.collectApiUrl.trim();
766
+ if (!collectApiUrl.startsWith("http://") && !collectApiUrl.startsWith("https://")) {
767
+ throw new IntegrationValidationError('Custom API URL must start with "http://" or "https://"', "config");
768
+ }
769
+ const allowHttp = integrations.custom.allowHttp ?? false;
770
+ if (!allowHttp && collectApiUrl.startsWith("http://")) {
771
+ throw new IntegrationValidationError(
772
+ "Custom API URL must use HTTPS in production. Set allowHttp: true in integration config to allow HTTP (not recommended)",
773
+ "config"
774
+ );
775
+ }
776
+ }
777
+ if (integrations.googleAnalytics) {
778
+ if (!integrations.googleAnalytics.measurementId || typeof integrations.googleAnalytics.measurementId !== "string" || integrations.googleAnalytics.measurementId.trim() === "") {
779
+ throw new IntegrationValidationError(VALIDATION_MESSAGES.INVALID_GOOGLE_ANALYTICS_ID, "config");
780
+ }
781
+ const measurementId = integrations.googleAnalytics.measurementId.trim();
782
+ if (!measurementId.match(/^(G-|UA-)/)) {
783
+ throw new IntegrationValidationError('Google Analytics measurement ID must start with "G-" or "UA-"', "config");
784
+ }
785
+ }
786
+ };
787
+ var validateAndNormalizeConfig = (config) => {
788
+ validateAppConfig(config);
789
+ const normalizedConfig = {
790
+ ...config ?? {},
791
+ sessionTimeout: config?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT,
792
+ globalMetadata: config?.globalMetadata ?? {},
793
+ sensitiveQueryParams: config?.sensitiveQueryParams ?? [],
794
+ errorSampling: config?.errorSampling ?? DEFAULT_ERROR_SAMPLING_RATE,
795
+ samplingRate: config?.samplingRate ?? DEFAULT_SAMPLING_RATE,
796
+ pageViewThrottleMs: config?.pageViewThrottleMs ?? DEFAULT_PAGE_VIEW_THROTTLE_MS,
797
+ clickThrottleMs: config?.clickThrottleMs ?? DEFAULT_CLICK_THROTTLE_MS,
798
+ maxSameEventPerMinute: config?.maxSameEventPerMinute ?? MAX_SAME_EVENT_PER_MINUTE
799
+ };
800
+ if (normalizedConfig.integrations?.custom) {
801
+ normalizedConfig.integrations.custom = {
802
+ ...normalizedConfig.integrations.custom,
803
+ allowHttp: normalizedConfig.integrations.custom.allowHttp ?? false
804
+ };
805
+ }
806
+ if (normalizedConfig.viewport) {
807
+ normalizedConfig.viewport = {
808
+ ...normalizedConfig.viewport,
809
+ threshold: normalizedConfig.viewport.threshold ?? 0.5,
810
+ minDwellTime: normalizedConfig.viewport.minDwellTime ?? DEFAULT_VISIBILITY_TIMEOUT_MS,
811
+ cooldownPeriod: normalizedConfig.viewport.cooldownPeriod ?? DEFAULT_VIEWPORT_COOLDOWN_PERIOD,
812
+ maxTrackedElements: normalizedConfig.viewport.maxTrackedElements ?? DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS
813
+ };
814
+ }
815
+ return normalizedConfig;
816
+ };
817
+
818
+ // src/utils/validations/type-guards.utils.ts
819
+ var isValidArrayItem = (item) => {
820
+ if (typeof item === "string") {
821
+ return true;
822
+ }
823
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
824
+ const entries = Object.entries(item);
825
+ if (entries.length > MAX_NESTED_OBJECT_KEYS) {
826
+ return false;
827
+ }
828
+ for (const [, value] of entries) {
829
+ if (value === null || value === void 0) {
830
+ continue;
831
+ }
832
+ const type = typeof value;
833
+ if (type !== "string" && type !== "number" && type !== "boolean") {
834
+ return false;
835
+ }
836
+ }
837
+ return true;
838
+ }
839
+ return false;
840
+ };
841
+ var isOnlyPrimitiveFields = (object, depth = 0) => {
842
+ if (typeof object !== "object" || object === null) {
843
+ return false;
844
+ }
845
+ if (depth > MAX_METADATA_NESTING_DEPTH) {
846
+ return false;
847
+ }
848
+ for (const value of Object.values(object)) {
849
+ if (value === null || value === void 0) {
850
+ continue;
851
+ }
852
+ const type = typeof value;
853
+ if (type === "string" || type === "number" || type === "boolean") {
854
+ continue;
855
+ }
856
+ if (Array.isArray(value)) {
857
+ if (value.length === 0) {
858
+ continue;
859
+ }
860
+ const firstItem = value[0];
861
+ const isStringArray = typeof firstItem === "string";
862
+ if (isStringArray) {
863
+ if (!value.every((item) => typeof item === "string")) {
864
+ return false;
865
+ }
866
+ } else {
867
+ if (!value.every((item) => isValidArrayItem(item))) {
868
+ return false;
869
+ }
870
+ }
871
+ continue;
872
+ }
873
+ if (type === "object" && depth === 0) {
874
+ if (!isOnlyPrimitiveFields(value, depth + 1)) {
875
+ return false;
876
+ }
877
+ continue;
878
+ }
879
+ return false;
880
+ }
881
+ return true;
882
+ };
883
+
884
+ // src/utils/validations/metadata-validations.utils.ts
885
+ var isValidEventName = (eventName) => {
886
+ if (typeof eventName !== "string") {
887
+ return {
888
+ valid: false,
889
+ error: "Event name must be a string"
890
+ };
891
+ }
892
+ if (eventName.length === 0) {
893
+ return {
894
+ valid: false,
895
+ error: "Event name cannot be empty"
896
+ };
897
+ }
898
+ if (eventName.length > MAX_CUSTOM_EVENT_NAME_LENGTH) {
899
+ return {
900
+ valid: false,
901
+ error: `Event name is too long (max ${MAX_CUSTOM_EVENT_NAME_LENGTH} characters)`
902
+ };
903
+ }
904
+ if (eventName.includes("<") || eventName.includes(">") || eventName.includes("&")) {
905
+ return {
906
+ valid: false,
907
+ error: "Event name contains invalid characters"
908
+ };
909
+ }
910
+ const reservedWords = ["constructor", "prototype", "__proto__", "eval", "function", "var", "let", "const"];
911
+ if (reservedWords.includes(eventName.toLowerCase())) {
912
+ return {
913
+ valid: false,
914
+ error: "Event name cannot be a reserved word"
915
+ };
916
+ }
917
+ return { valid: true };
918
+ };
919
+ var validateSingleMetadata = (eventName, metadata, type) => {
920
+ const sanitizedMetadata = sanitizeMetadata(metadata);
921
+ const intro = `${type} "${eventName}" metadata error` ;
922
+ if (!isOnlyPrimitiveFields(sanitizedMetadata)) {
923
+ return {
924
+ valid: false,
925
+ error: `${intro}: object has invalid types. Valid types are string, number, boolean or string arrays.`
926
+ };
927
+ }
928
+ let jsonString;
929
+ try {
930
+ jsonString = JSON.stringify(sanitizedMetadata);
931
+ } catch {
932
+ return {
933
+ valid: false,
934
+ error: `${intro}: object contains circular references or cannot be serialized.`
935
+ };
936
+ }
937
+ if (jsonString.length > MAX_CUSTOM_EVENT_STRING_SIZE) {
938
+ return {
939
+ valid: false,
940
+ error: `${intro}: object is too large (max ${MAX_CUSTOM_EVENT_STRING_SIZE / 1024} KB).`
941
+ };
942
+ }
943
+ const keyCount = Object.keys(sanitizedMetadata).length;
944
+ if (keyCount > MAX_CUSTOM_EVENT_KEYS) {
945
+ return {
946
+ valid: false,
947
+ error: `${intro}: object has too many keys (max ${MAX_CUSTOM_EVENT_KEYS} keys).`
948
+ };
949
+ }
950
+ for (const [key, value] of Object.entries(sanitizedMetadata)) {
951
+ if (Array.isArray(value)) {
952
+ if (value.length > MAX_CUSTOM_EVENT_ARRAY_SIZE) {
953
+ return {
954
+ valid: false,
955
+ error: `${intro}: array property "${key}" is too large (max ${MAX_CUSTOM_EVENT_ARRAY_SIZE} items).`
956
+ };
957
+ }
958
+ for (const item of value) {
959
+ if (typeof item === "string" && item.length > MAX_STRING_LENGTH_IN_ARRAY) {
960
+ return {
961
+ valid: false,
962
+ error: `${intro}: array property "${key}" contains strings that are too long (max ${MAX_STRING_LENGTH_IN_ARRAY} characters).`
963
+ };
964
+ }
965
+ }
966
+ }
967
+ if (typeof value === "string" && value.length > MAX_STRING_LENGTH) {
968
+ return {
969
+ valid: false,
970
+ error: `${intro}: property "${key}" is too long (max ${MAX_STRING_LENGTH} characters).`
971
+ };
972
+ }
973
+ }
974
+ return {
975
+ valid: true,
976
+ sanitizedMetadata
977
+ };
978
+ };
979
+ var isValidMetadata = (eventName, metadata, type) => {
980
+ if (Array.isArray(metadata)) {
981
+ const sanitizedArray = [];
982
+ const intro = `${type} "${eventName}" metadata error` ;
983
+ for (let i = 0; i < metadata.length; i++) {
984
+ const item = metadata[i];
985
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
986
+ return {
987
+ valid: false,
988
+ error: `${intro}: array item at index ${i} must be an object.`
989
+ };
990
+ }
991
+ const itemValidation = validateSingleMetadata(eventName, item, type);
992
+ if (!itemValidation.valid) {
993
+ return {
994
+ valid: false,
995
+ error: `${intro}: array item at index ${i} is invalid: ${itemValidation.error}`
996
+ };
997
+ }
998
+ if (itemValidation.sanitizedMetadata) {
999
+ sanitizedArray.push(itemValidation.sanitizedMetadata);
1000
+ }
1001
+ }
1002
+ return {
1003
+ valid: true,
1004
+ sanitizedMetadata: sanitizedArray
1005
+ };
1006
+ }
1007
+ return validateSingleMetadata(eventName, metadata, type);
1008
+ };
1009
+
1010
+ // src/utils/validations/event-validations.utils.ts
1011
+ var isEventValid = (eventName, metadata) => {
1012
+ const nameValidation = isValidEventName(eventName);
1013
+ if (!nameValidation.valid) {
1014
+ log("error", "Event name validation failed", {
1015
+ showToClient: true,
1016
+ data: { eventName, error: nameValidation.error }
1017
+ });
1018
+ return nameValidation;
1019
+ }
1020
+ if (!metadata) {
1021
+ return { valid: true };
1022
+ }
1023
+ const metadataValidation = isValidMetadata(eventName, metadata, "customEvent");
1024
+ if (!metadataValidation.valid) {
1025
+ log("error", "Event metadata validation failed", {
1026
+ showToClient: true,
1027
+ data: {
1028
+ eventName,
1029
+ error: metadataValidation.error
1030
+ }
1031
+ });
1032
+ }
1033
+ return metadataValidation;
1034
+ };
1035
+
1036
+ // src/utils/emitter.utils.ts
1037
+ var Emitter = class {
1038
+ listeners = /* @__PURE__ */ new Map();
1039
+ on(event2, callback) {
1040
+ if (!this.listeners.has(event2)) {
1041
+ this.listeners.set(event2, []);
1042
+ }
1043
+ this.listeners.get(event2).push(callback);
1044
+ }
1045
+ off(event2, callback) {
1046
+ const callbacks = this.listeners.get(event2);
1047
+ if (callbacks) {
1048
+ const index = callbacks.indexOf(callback);
1049
+ if (index > -1) {
1050
+ callbacks.splice(index, 1);
1051
+ }
1052
+ }
1053
+ }
1054
+ emit(event2, data) {
1055
+ const callbacks = this.listeners.get(event2);
1056
+ if (callbacks) {
1057
+ callbacks.forEach((callback) => {
1058
+ callback(data);
1059
+ });
1060
+ }
1061
+ }
1062
+ removeAllListeners() {
1063
+ this.listeners.clear();
1064
+ }
1065
+ };
1066
+
1067
+ // src/managers/state.manager.ts
1068
+ var globalState = {};
1069
+ var StateManager = class {
1070
+ get(key) {
1071
+ return globalState[key];
1072
+ }
1073
+ set(key, value) {
1074
+ globalState[key] = value;
1075
+ }
1076
+ getState() {
1077
+ return { ...globalState };
1078
+ }
1079
+ };
1080
+
1081
+ // src/managers/sender.manager.ts
1082
+ var SenderManager = class extends StateManager {
1083
+ storeManager;
1084
+ lastPermanentErrorLog = null;
1085
+ constructor(storeManager) {
1086
+ super();
1087
+ this.storeManager = storeManager;
1088
+ }
1089
+ getQueueStorageKey() {
1090
+ const userId = this.get("userId") || "anonymous";
1091
+ return QUEUE_KEY(userId);
1092
+ }
1093
+ sendEventsQueueSync(body) {
1094
+ if (this.shouldSkipSend()) {
1095
+ return true;
1096
+ }
1097
+ const config = this.get("config");
1098
+ if (config?.integrations?.custom?.collectApiUrl === "localhost:9999" /* Fail */) {
1099
+ log("warn", "Fail mode: simulating network failure (sync)", {
1100
+ data: { events: body.events.length }
1101
+ });
1102
+ return false;
1103
+ }
1104
+ return this.sendQueueSyncInternal(body);
1105
+ }
1106
+ async sendEventsQueue(body, callbacks) {
1107
+ try {
1108
+ const success = await this.send(body);
1109
+ if (success) {
1110
+ this.clearPersistedEvents();
1111
+ callbacks?.onSuccess?.(body.events.length, body.events, body);
1112
+ } else {
1113
+ this.persistEvents(body);
1114
+ callbacks?.onFailure?.();
1115
+ }
1116
+ return success;
1117
+ } catch (error) {
1118
+ if (error instanceof PermanentError) {
1119
+ this.logPermanentError("Permanent error, not retrying", error);
1120
+ this.clearPersistedEvents();
1121
+ callbacks?.onFailure?.();
1122
+ return false;
1123
+ }
1124
+ this.persistEvents(body);
1125
+ callbacks?.onFailure?.();
1126
+ return false;
1127
+ }
1128
+ }
1129
+ async recoverPersistedEvents(callbacks) {
1130
+ try {
1131
+ const persistedData = this.getPersistedData();
1132
+ if (!persistedData || !this.isDataRecent(persistedData) || persistedData.events.length === 0) {
1133
+ this.clearPersistedEvents();
1134
+ return;
1135
+ }
1136
+ const body = this.createRecoveryBody(persistedData);
1137
+ const success = await this.send(body);
1138
+ if (success) {
1139
+ this.clearPersistedEvents();
1140
+ callbacks?.onSuccess?.(persistedData.events.length, persistedData.events, body);
1141
+ } else {
1142
+ callbacks?.onFailure?.();
1143
+ }
1144
+ } catch (error) {
1145
+ if (error instanceof PermanentError) {
1146
+ this.logPermanentError("Permanent error during recovery, clearing persisted events", error);
1147
+ this.clearPersistedEvents();
1148
+ callbacks?.onFailure?.();
1149
+ return;
1150
+ }
1151
+ log("error", "Failed to recover persisted events", { error });
1152
+ }
1153
+ }
1154
+ stop() {
1155
+ }
1156
+ async send(body) {
1157
+ if (this.shouldSkipSend()) {
1158
+ return this.simulateSuccessfulSend();
1159
+ }
1160
+ const config = this.get("config");
1161
+ if (config?.integrations?.custom?.collectApiUrl === "localhost:9999" /* Fail */) {
1162
+ log("warn", "Fail mode: simulating network failure", {
1163
+ data: { events: body.events.length }
1164
+ });
1165
+ return false;
1166
+ }
1167
+ const { url, payload } = this.prepareRequest(body);
1168
+ try {
1169
+ const response = await this.sendWithTimeout(url, payload);
1170
+ return response.ok;
1171
+ } catch (error) {
1172
+ if (error instanceof PermanentError) {
1173
+ throw error;
1174
+ }
1175
+ log("error", "Send request failed", {
1176
+ error,
1177
+ data: {
1178
+ events: body.events.length,
1179
+ url: url.replace(/\/\/[^/]+/, "//[DOMAIN]")
1180
+ }
1181
+ });
1182
+ return false;
1183
+ }
1184
+ }
1185
+ async sendWithTimeout(url, payload) {
1186
+ const controller = new AbortController();
1187
+ const timeoutId = setTimeout(() => {
1188
+ controller.abort();
1189
+ }, REQUEST_TIMEOUT_MS);
1190
+ try {
1191
+ const response = await fetch(url, {
1192
+ method: "POST",
1193
+ body: payload,
1194
+ keepalive: true,
1195
+ credentials: "include",
1196
+ signal: controller.signal,
1197
+ headers: {
1198
+ "Content-Type": "application/json"
1199
+ }
1200
+ });
1201
+ if (!response.ok) {
1202
+ const isPermanentError = response.status >= 400 && response.status < 500;
1203
+ if (isPermanentError) {
1204
+ throw new PermanentError(`HTTP ${response.status}: ${response.statusText}`, response.status);
1205
+ }
1206
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1207
+ }
1208
+ return response;
1209
+ } finally {
1210
+ clearTimeout(timeoutId);
1211
+ }
1212
+ }
1213
+ sendQueueSyncInternal(body) {
1214
+ const { url, payload } = this.prepareRequest(body);
1215
+ if (payload.length > MAX_BEACON_PAYLOAD_SIZE) {
1216
+ log("warn", "Payload exceeds sendBeacon limit, persisting for recovery", {
1217
+ data: {
1218
+ size: payload.length,
1219
+ limit: MAX_BEACON_PAYLOAD_SIZE,
1220
+ events: body.events.length
1221
+ }
1222
+ });
1223
+ this.persistEvents(body);
1224
+ return false;
1225
+ }
1226
+ const blob = new Blob([payload], { type: "application/json" });
1227
+ if (!this.isSendBeaconAvailable()) {
1228
+ log("warn", "sendBeacon not available, persisting events for recovery");
1229
+ this.persistEvents(body);
1230
+ return false;
1231
+ }
1232
+ const accepted = navigator.sendBeacon(url, blob);
1233
+ if (!accepted) {
1234
+ log("warn", "sendBeacon rejected request, persisting events for recovery");
1235
+ this.persistEvents(body);
1236
+ }
1237
+ return accepted;
1238
+ }
1239
+ prepareRequest(body) {
1240
+ const enrichedBody = {
1241
+ ...body,
1242
+ _metadata: {
1243
+ referer: typeof window !== "undefined" ? window.location.href : void 0,
1244
+ timestamp: Date.now()
1245
+ }
1246
+ };
1247
+ return {
1248
+ url: this.get("collectApiUrl"),
1249
+ payload: JSON.stringify(enrichedBody)
1250
+ };
1251
+ }
1252
+ getPersistedData() {
1253
+ try {
1254
+ const storageKey = this.getQueueStorageKey();
1255
+ const persistedDataString = this.storeManager.getItem(storageKey);
1256
+ if (persistedDataString) {
1257
+ return JSON.parse(persistedDataString);
1258
+ }
1259
+ } catch (error) {
1260
+ log("warn", "Failed to parse persisted data", { error });
1261
+ this.clearPersistedEvents();
1262
+ }
1263
+ return null;
1264
+ }
1265
+ isDataRecent(data) {
1266
+ if (!data.timestamp || typeof data.timestamp !== "number") {
1267
+ return false;
1268
+ }
1269
+ const ageInHours = (Date.now() - data.timestamp) / (1e3 * 60 * 60);
1270
+ return ageInHours < EVENT_EXPIRY_HOURS;
1271
+ }
1272
+ createRecoveryBody(data) {
1273
+ const { timestamp, ...queue } = data;
1274
+ return queue;
1275
+ }
1276
+ persistEvents(body) {
1277
+ try {
1278
+ const persistedData = {
1279
+ ...body,
1280
+ timestamp: Date.now()
1281
+ };
1282
+ const storageKey = this.getQueueStorageKey();
1283
+ this.storeManager.setItem(storageKey, JSON.stringify(persistedData));
1284
+ return !!this.storeManager.getItem(storageKey);
1285
+ } catch (error) {
1286
+ log("warn", "Failed to persist events", { error });
1287
+ return false;
1288
+ }
1289
+ }
1290
+ clearPersistedEvents() {
1291
+ try {
1292
+ const key = this.getQueueStorageKey();
1293
+ this.storeManager.removeItem(key);
1294
+ } catch (error) {
1295
+ log("warn", "Failed to clear persisted events", { error });
1296
+ }
1297
+ }
1298
+ shouldSkipSend() {
1299
+ return !this.get("collectApiUrl");
1300
+ }
1301
+ async simulateSuccessfulSend() {
1302
+ const delay = Math.random() * 400 + 100;
1303
+ await new Promise((resolve) => setTimeout(resolve, delay));
1304
+ return true;
1305
+ }
1306
+ isSendBeaconAvailable() {
1307
+ return typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function";
1308
+ }
1309
+ logPermanentError(context, error) {
1310
+ const now = Date.now();
1311
+ const shouldLog = !this.lastPermanentErrorLog || this.lastPermanentErrorLog.statusCode !== error.statusCode || now - this.lastPermanentErrorLog.timestamp >= PERMANENT_ERROR_LOG_THROTTLE_MS;
1312
+ if (shouldLog) {
1313
+ log("error", context, {
1314
+ data: { status: error.statusCode, message: error.message }
1315
+ });
1316
+ this.lastPermanentErrorLog = { statusCode: error.statusCode, timestamp: now };
1317
+ }
1318
+ }
1319
+ };
1320
+
1321
+ // src/managers/event.manager.ts
1322
+ var EventManager = class extends StateManager {
1323
+ googleAnalytics;
1324
+ dataSender;
1325
+ emitter;
1326
+ eventsQueue = [];
1327
+ pendingEventsBuffer = [];
1328
+ recentEventFingerprints = /* @__PURE__ */ new Map();
1329
+ // Time-based deduplication cache
1330
+ sendIntervalId = null;
1331
+ rateLimitCounter = 0;
1332
+ rateLimitWindowStart = 0;
1333
+ perEventRateLimits = /* @__PURE__ */ new Map();
1334
+ sessionEventCounts = {
1335
+ total: 0,
1336
+ ["click" /* CLICK */]: 0,
1337
+ ["page_view" /* PAGE_VIEW */]: 0,
1338
+ ["custom" /* CUSTOM */]: 0,
1339
+ ["viewport_visible" /* VIEWPORT_VISIBLE */]: 0,
1340
+ ["scroll" /* SCROLL */]: 0
1341
+ };
1342
+ lastSessionId = null;
1343
+ constructor(storeManager, googleAnalytics = null, emitter = null) {
1344
+ super();
1345
+ this.googleAnalytics = googleAnalytics;
1346
+ this.dataSender = new SenderManager(storeManager);
1347
+ this.emitter = emitter;
1348
+ }
1349
+ async recoverPersistedEvents() {
1350
+ await this.dataSender.recoverPersistedEvents({
1351
+ onSuccess: (_eventCount, recoveredEvents, body) => {
1352
+ if (recoveredEvents && recoveredEvents.length > 0) {
1353
+ const eventIds = recoveredEvents.map((e) => e.id);
1354
+ this.removeProcessedEvents(eventIds);
1355
+ if (body) {
1356
+ this.emitEventsQueue(body);
1357
+ }
1358
+ }
1359
+ },
1360
+ onFailure: () => {
1361
+ log("warn", "Failed to recover persisted events");
1362
+ }
1363
+ });
1364
+ }
1365
+ track({
1366
+ type,
1367
+ page_url,
1368
+ from_page_url,
1369
+ scroll_data,
1370
+ click_data,
1371
+ custom_event,
1372
+ web_vitals,
1373
+ error_data,
1374
+ session_end_reason,
1375
+ viewport_data
1376
+ }) {
1377
+ if (!type) {
1378
+ log("error", "Event type is required - event will be ignored");
1379
+ return;
1380
+ }
1381
+ const currentSessionId = this.get("sessionId");
1382
+ if (!currentSessionId) {
1383
+ if (this.pendingEventsBuffer.length >= MAX_PENDING_EVENTS_BUFFER) {
1384
+ this.pendingEventsBuffer.shift();
1385
+ log("warn", "Pending events buffer full - dropping oldest event", {
1386
+ data: { maxBufferSize: MAX_PENDING_EVENTS_BUFFER }
1387
+ });
1388
+ }
1389
+ this.pendingEventsBuffer.push({
1390
+ type,
1391
+ page_url,
1392
+ from_page_url,
1393
+ scroll_data,
1394
+ click_data,
1395
+ custom_event,
1396
+ web_vitals,
1397
+ error_data,
1398
+ session_end_reason,
1399
+ viewport_data
1400
+ });
1401
+ return;
1402
+ }
1403
+ if (this.lastSessionId !== currentSessionId) {
1404
+ this.lastSessionId = currentSessionId;
1405
+ this.sessionEventCounts = {
1406
+ total: 0,
1407
+ ["click" /* CLICK */]: 0,
1408
+ ["page_view" /* PAGE_VIEW */]: 0,
1409
+ ["custom" /* CUSTOM */]: 0,
1410
+ ["viewport_visible" /* VIEWPORT_VISIBLE */]: 0,
1411
+ ["scroll" /* SCROLL */]: 0
1412
+ };
1413
+ }
1414
+ const isCriticalEvent = type === "session_start" /* SESSION_START */ || type === "session_end" /* SESSION_END */;
1415
+ if (!isCriticalEvent && !this.checkRateLimit()) {
1416
+ return;
1417
+ }
1418
+ const eventType = type;
1419
+ if (!isCriticalEvent) {
1420
+ if (this.sessionEventCounts.total >= MAX_EVENTS_PER_SESSION) {
1421
+ log("warn", "Session event limit reached", {
1422
+ data: {
1423
+ type: eventType,
1424
+ total: this.sessionEventCounts.total,
1425
+ limit: MAX_EVENTS_PER_SESSION
1426
+ }
1427
+ });
1428
+ return;
1429
+ }
1430
+ const typeLimit = this.getTypeLimitForEvent(eventType);
1431
+ if (typeLimit) {
1432
+ const currentCount = this.sessionEventCounts[eventType];
1433
+ if (currentCount !== void 0 && currentCount >= typeLimit) {
1434
+ log("warn", "Session event type limit reached", {
1435
+ data: {
1436
+ type: eventType,
1437
+ count: currentCount,
1438
+ limit: typeLimit
1439
+ }
1440
+ });
1441
+ return;
1442
+ }
1443
+ }
1444
+ }
1445
+ if (eventType === "custom" /* CUSTOM */ && custom_event?.name) {
1446
+ const maxSameEventPerMinute = this.get("config")?.maxSameEventPerMinute ?? MAX_SAME_EVENT_PER_MINUTE;
1447
+ if (!this.checkPerEventRateLimit(custom_event.name, maxSameEventPerMinute)) {
1448
+ return;
1449
+ }
1450
+ }
1451
+ const isSessionStart = eventType === "session_start" /* SESSION_START */;
1452
+ const currentPageUrl = page_url || this.get("pageUrl");
1453
+ const payload = this.buildEventPayload({
1454
+ type: eventType,
1455
+ page_url: currentPageUrl,
1456
+ from_page_url,
1457
+ scroll_data,
1458
+ click_data,
1459
+ custom_event,
1460
+ web_vitals,
1461
+ error_data,
1462
+ session_end_reason,
1463
+ viewport_data
1464
+ });
1465
+ if (!isCriticalEvent && !this.shouldSample()) {
1466
+ return;
1467
+ }
1468
+ if (isSessionStart) {
1469
+ const currentSessionId2 = this.get("sessionId");
1470
+ if (!currentSessionId2) {
1471
+ log("error", "Session start event requires sessionId - event will be ignored");
1472
+ return;
1473
+ }
1474
+ if (this.get("hasStartSession")) {
1475
+ log("warn", "Duplicate session_start detected", {
1476
+ data: { sessionId: currentSessionId2 }
1477
+ });
1478
+ return;
1479
+ }
1480
+ this.set("hasStartSession", true);
1481
+ }
1482
+ if (this.isDuplicateEvent(payload)) {
1483
+ return;
1484
+ }
1485
+ if (this.get("mode") === "qa" /* QA */ && eventType === "custom" /* CUSTOM */ && custom_event) {
1486
+ console.log("[TraceLog] Event", {
1487
+ name: custom_event.name,
1488
+ ...custom_event.metadata && { metadata: custom_event.metadata }
1489
+ });
1490
+ this.emitEvent(payload);
1491
+ return;
1492
+ }
1493
+ this.addToQueue(payload);
1494
+ if (!isCriticalEvent) {
1495
+ this.sessionEventCounts.total++;
1496
+ if (this.sessionEventCounts[eventType] !== void 0) {
1497
+ this.sessionEventCounts[eventType]++;
1498
+ }
1499
+ }
1500
+ }
1501
+ stop() {
1502
+ if (this.sendIntervalId) {
1503
+ clearInterval(this.sendIntervalId);
1504
+ this.sendIntervalId = null;
1505
+ }
1506
+ this.eventsQueue = [];
1507
+ this.pendingEventsBuffer = [];
1508
+ this.recentEventFingerprints.clear();
1509
+ this.rateLimitCounter = 0;
1510
+ this.rateLimitWindowStart = 0;
1511
+ this.perEventRateLimits.clear();
1512
+ this.sessionEventCounts = {
1513
+ total: 0,
1514
+ ["click" /* CLICK */]: 0,
1515
+ ["page_view" /* PAGE_VIEW */]: 0,
1516
+ ["custom" /* CUSTOM */]: 0,
1517
+ ["viewport_visible" /* VIEWPORT_VISIBLE */]: 0,
1518
+ ["scroll" /* SCROLL */]: 0
1519
+ };
1520
+ this.lastSessionId = null;
1521
+ this.dataSender.stop();
1522
+ }
1523
+ async flushImmediately() {
1524
+ return this.flushEvents(false);
1525
+ }
1526
+ flushImmediatelySync() {
1527
+ return this.flushEvents(true);
1528
+ }
1529
+ getQueueLength() {
1530
+ return this.eventsQueue.length;
1531
+ }
1532
+ flushPendingEvents() {
1533
+ if (this.pendingEventsBuffer.length === 0) {
1534
+ return;
1535
+ }
1536
+ const currentSessionId = this.get("sessionId");
1537
+ if (!currentSessionId) {
1538
+ log("warn", "Cannot flush pending events: session not initialized - keeping in buffer", {
1539
+ data: { bufferedEventCount: this.pendingEventsBuffer.length }
1540
+ });
1541
+ return;
1542
+ }
1543
+ const bufferedEvents = [...this.pendingEventsBuffer];
1544
+ this.pendingEventsBuffer = [];
1545
+ bufferedEvents.forEach((event2) => {
1546
+ this.track(event2);
1547
+ });
1548
+ }
1549
+ clearSendInterval() {
1550
+ if (this.sendIntervalId) {
1551
+ clearInterval(this.sendIntervalId);
1552
+ this.sendIntervalId = null;
1553
+ }
1554
+ }
1555
+ flushEvents(isSync) {
1556
+ if (this.eventsQueue.length === 0) {
1557
+ return isSync ? true : Promise.resolve(true);
1558
+ }
1559
+ const body = this.buildEventsPayload();
1560
+ const eventsToSend = [...this.eventsQueue];
1561
+ const eventIds = eventsToSend.map((e) => e.id);
1562
+ if (isSync) {
1563
+ const success = this.dataSender.sendEventsQueueSync(body);
1564
+ if (success) {
1565
+ this.removeProcessedEvents(eventIds);
1566
+ this.clearSendInterval();
1567
+ this.emitEventsQueue(body);
1568
+ }
1569
+ return success;
1570
+ } else {
1571
+ return this.dataSender.sendEventsQueue(body, {
1572
+ onSuccess: () => {
1573
+ this.removeProcessedEvents(eventIds);
1574
+ this.clearSendInterval();
1575
+ this.emitEventsQueue(body);
1576
+ },
1577
+ onFailure: () => {
1578
+ log("warn", "Async flush failed", {
1579
+ data: { eventCount: eventsToSend.length }
1580
+ });
1581
+ }
1582
+ });
1583
+ }
1584
+ }
1585
+ async sendEventsQueue() {
1586
+ if (!this.get("sessionId") || this.eventsQueue.length === 0) {
1587
+ return;
1588
+ }
1589
+ const body = this.buildEventsPayload();
1590
+ const eventsToSend = [...this.eventsQueue];
1591
+ const eventIds = eventsToSend.map((e) => e.id);
1592
+ await this.dataSender.sendEventsQueue(body, {
1593
+ onSuccess: () => {
1594
+ this.removeProcessedEvents(eventIds);
1595
+ this.emitEventsQueue(body);
1596
+ },
1597
+ onFailure: () => {
1598
+ log("warn", "Events send failed, keeping in queue", {
1599
+ data: { eventCount: eventsToSend.length }
1600
+ });
1601
+ }
1602
+ });
1603
+ }
1604
+ buildEventsPayload() {
1605
+ const eventMap = /* @__PURE__ */ new Map();
1606
+ const order = [];
1607
+ for (const event2 of this.eventsQueue) {
1608
+ const signature = this.createEventSignature(event2);
1609
+ if (!eventMap.has(signature)) {
1610
+ order.push(signature);
1611
+ }
1612
+ eventMap.set(signature, event2);
1613
+ }
1614
+ const events = order.map((signature) => eventMap.get(signature)).filter((event2) => Boolean(event2)).sort((a, b) => a.timestamp - b.timestamp);
1615
+ return {
1616
+ user_id: this.get("userId"),
1617
+ session_id: this.get("sessionId"),
1618
+ device: this.get("device"),
1619
+ events,
1620
+ ...this.get("config")?.globalMetadata && { global_metadata: this.get("config")?.globalMetadata }
1621
+ };
1622
+ }
1623
+ buildEventPayload(data) {
1624
+ const isSessionStart = data.type === "session_start" /* SESSION_START */;
1625
+ const currentPageUrl = data.page_url ?? this.get("pageUrl");
1626
+ const payload = {
1627
+ id: generateEventId(),
1628
+ type: data.type,
1629
+ page_url: currentPageUrl,
1630
+ timestamp: Date.now(),
1631
+ ...isSessionStart && { referrer: document.referrer || "Direct" },
1632
+ ...data.from_page_url && { from_page_url: data.from_page_url },
1633
+ ...data.scroll_data && { scroll_data: data.scroll_data },
1634
+ ...data.click_data && { click_data: data.click_data },
1635
+ ...data.custom_event && { custom_event: data.custom_event },
1636
+ ...data.web_vitals && { web_vitals: data.web_vitals },
1637
+ ...data.error_data && { error_data: data.error_data },
1638
+ ...data.session_end_reason && { session_end_reason: data.session_end_reason },
1639
+ ...data.viewport_data && { viewport_data: data.viewport_data },
1640
+ ...isSessionStart && getUTMParameters() && { utm: getUTMParameters() }
1641
+ };
1642
+ return payload;
1643
+ }
1644
+ /**
1645
+ * Checks if event is a duplicate using time-based cache
1646
+ * Tracks recent event fingerprints with timestamp-based cleanup
1647
+ */
1648
+ isDuplicateEvent(event2) {
1649
+ const now = Date.now();
1650
+ const fingerprint = this.createEventFingerprint(event2);
1651
+ const lastSeen = this.recentEventFingerprints.get(fingerprint);
1652
+ if (lastSeen && now - lastSeen < DUPLICATE_EVENT_THRESHOLD_MS) {
1653
+ this.recentEventFingerprints.set(fingerprint, now);
1654
+ return true;
1655
+ }
1656
+ this.recentEventFingerprints.set(fingerprint, now);
1657
+ if (this.recentEventFingerprints.size > MAX_FINGERPRINTS) {
1658
+ this.pruneOldFingerprints();
1659
+ }
1660
+ if (this.recentEventFingerprints.size > MAX_FINGERPRINTS_HARD_LIMIT) {
1661
+ this.recentEventFingerprints.clear();
1662
+ this.recentEventFingerprints.set(fingerprint, now);
1663
+ log("warn", "Event fingerprint cache exceeded hard limit, cleared", {
1664
+ data: { hardLimit: MAX_FINGERPRINTS_HARD_LIMIT }
1665
+ });
1666
+ }
1667
+ return false;
1668
+ }
1669
+ /**
1670
+ * Prunes old fingerprints from cache based on timestamp
1671
+ * Removes entries older than 10x the duplicate threshold (5 seconds)
1672
+ */
1673
+ pruneOldFingerprints() {
1674
+ const now = Date.now();
1675
+ const cutoff = DUPLICATE_EVENT_THRESHOLD_MS * FINGERPRINT_CLEANUP_MULTIPLIER;
1676
+ for (const [fingerprint, timestamp] of this.recentEventFingerprints.entries()) {
1677
+ if (now - timestamp > cutoff) {
1678
+ this.recentEventFingerprints.delete(fingerprint);
1679
+ }
1680
+ }
1681
+ log("debug", "Pruned old event fingerprints", {
1682
+ data: {
1683
+ remaining: this.recentEventFingerprints.size,
1684
+ cutoffMs: cutoff
1685
+ }
1686
+ });
1687
+ }
1688
+ createEventFingerprint(event2) {
1689
+ let fingerprint = `${event2.type}_${event2.page_url}`;
1690
+ if (event2.click_data) {
1691
+ const x = Math.round((event2.click_data.x || 0) / 10) * 10;
1692
+ const y = Math.round((event2.click_data.y || 0) / 10) * 10;
1693
+ fingerprint += `_click_${x}_${y}`;
1694
+ }
1695
+ if (event2.scroll_data) {
1696
+ fingerprint += `_scroll_${event2.scroll_data.depth}_${event2.scroll_data.direction}`;
1697
+ }
1698
+ if (event2.custom_event) {
1699
+ fingerprint += `_custom_${event2.custom_event.name}`;
1700
+ }
1701
+ if (event2.web_vitals) {
1702
+ fingerprint += `_vitals_${event2.web_vitals.type}`;
1703
+ }
1704
+ if (event2.error_data) {
1705
+ fingerprint += `_error_${event2.error_data.type}_${event2.error_data.message}`;
1706
+ }
1707
+ return fingerprint;
1708
+ }
1709
+ createEventSignature(event2) {
1710
+ return this.createEventFingerprint(event2);
1711
+ }
1712
+ addToQueue(event2) {
1713
+ this.eventsQueue.push(event2);
1714
+ this.emitEvent(event2);
1715
+ if (this.eventsQueue.length > MAX_EVENTS_QUEUE_LENGTH) {
1716
+ const nonCriticalIndex = this.eventsQueue.findIndex(
1717
+ (e) => e.type !== "session_start" /* SESSION_START */ && e.type !== "session_end" /* SESSION_END */
1718
+ );
1719
+ const removedEvent = nonCriticalIndex >= 0 ? this.eventsQueue.splice(nonCriticalIndex, 1)[0] : this.eventsQueue.shift();
1720
+ log("warn", "Event queue overflow, oldest non-critical event removed", {
1721
+ data: {
1722
+ maxLength: MAX_EVENTS_QUEUE_LENGTH,
1723
+ currentLength: this.eventsQueue.length,
1724
+ removedEventType: removedEvent?.type,
1725
+ wasCritical: removedEvent?.type === "session_start" /* SESSION_START */ || removedEvent?.type === "session_end" /* SESSION_END */
1726
+ }
1727
+ });
1728
+ }
1729
+ if (!this.sendIntervalId) {
1730
+ this.startSendInterval();
1731
+ }
1732
+ if (this.eventsQueue.length >= BATCH_SIZE_THRESHOLD) {
1733
+ void this.sendEventsQueue();
1734
+ }
1735
+ this.handleGoogleAnalyticsIntegration(event2);
1736
+ }
1737
+ startSendInterval() {
1738
+ this.sendIntervalId = window.setInterval(() => {
1739
+ if (this.eventsQueue.length > 0) {
1740
+ void this.sendEventsQueue();
1741
+ }
1742
+ }, EVENT_SENT_INTERVAL_MS);
1743
+ }
1744
+ handleGoogleAnalyticsIntegration(event2) {
1745
+ if (this.googleAnalytics && event2.type === "custom" /* CUSTOM */ && event2.custom_event) {
1746
+ if (this.get("mode") === "qa" /* QA */) {
1747
+ return;
1748
+ }
1749
+ this.googleAnalytics.trackEvent(event2.custom_event.name, event2.custom_event.metadata ?? {});
1750
+ }
1751
+ }
1752
+ shouldSample() {
1753
+ const samplingRate = this.get("config")?.samplingRate ?? 1;
1754
+ return Math.random() < samplingRate;
1755
+ }
1756
+ checkRateLimit() {
1757
+ const now = Date.now();
1758
+ if (now - this.rateLimitWindowStart > RATE_LIMIT_WINDOW_MS) {
1759
+ this.rateLimitCounter = 0;
1760
+ this.rateLimitWindowStart = now;
1761
+ }
1762
+ if (this.rateLimitCounter >= MAX_EVENTS_PER_SECOND) {
1763
+ return false;
1764
+ }
1765
+ this.rateLimitCounter++;
1766
+ return true;
1767
+ }
1768
+ /**
1769
+ * Checks per-event-name rate limiting to prevent infinite loops in user code
1770
+ * Tracks timestamps per event name and limits to maxSameEventPerMinute per minute
1771
+ */
1772
+ checkPerEventRateLimit(eventName, maxSameEventPerMinute) {
1773
+ const now = Date.now();
1774
+ const timestamps = this.perEventRateLimits.get(eventName) ?? [];
1775
+ const validTimestamps = timestamps.filter((ts) => now - ts < PER_EVENT_RATE_LIMIT_WINDOW_MS);
1776
+ if (validTimestamps.length >= maxSameEventPerMinute) {
1777
+ log("warn", "Per-event rate limit exceeded for custom event", {
1778
+ data: {
1779
+ eventName,
1780
+ limit: maxSameEventPerMinute,
1781
+ window: `${PER_EVENT_RATE_LIMIT_WINDOW_MS / 1e3}s`
1782
+ }
1783
+ });
1784
+ return false;
1785
+ }
1786
+ validTimestamps.push(now);
1787
+ this.perEventRateLimits.set(eventName, validTimestamps);
1788
+ return true;
1789
+ }
1790
+ /**
1791
+ * Gets the per-session limit for a specific event type (Phase 3)
1792
+ */
1793
+ getTypeLimitForEvent(type) {
1794
+ const limits = {
1795
+ ["click" /* CLICK */]: MAX_CLICKS_PER_SESSION,
1796
+ ["page_view" /* PAGE_VIEW */]: MAX_PAGE_VIEWS_PER_SESSION,
1797
+ ["custom" /* CUSTOM */]: MAX_CUSTOM_EVENTS_PER_SESSION,
1798
+ ["viewport_visible" /* VIEWPORT_VISIBLE */]: MAX_VIEWPORT_EVENTS_PER_SESSION,
1799
+ ["scroll" /* SCROLL */]: MAX_SCROLL_EVENTS_PER_SESSION
1800
+ };
1801
+ return limits[type] ?? null;
1802
+ }
1803
+ removeProcessedEvents(eventIds) {
1804
+ const eventIdSet = new Set(eventIds);
1805
+ this.eventsQueue = this.eventsQueue.filter((event2) => {
1806
+ return !eventIdSet.has(event2.id);
1807
+ });
1808
+ }
1809
+ emitEvent(eventData) {
1810
+ if (this.emitter) {
1811
+ this.emitter.emit("event" /* EVENT */, eventData);
1812
+ }
1813
+ }
1814
+ emitEventsQueue(queue) {
1815
+ if (this.emitter) {
1816
+ this.emitter.emit("queue" /* QUEUE */, queue);
1817
+ }
1818
+ }
1819
+ };
1820
+
1821
+ // src/managers/user.manager.ts
1822
+ var UserManager = class {
1823
+ /**
1824
+ * Gets or creates a unique user ID for the given project.
1825
+ * The user ID is persisted in localStorage and reused across sessions.
1826
+ *
1827
+ * @param storageManager - Storage manager instance
1828
+ * @param projectId - Project identifier for namespacing
1829
+ * @returns Persistent unique user ID
1830
+ */
1831
+ static getId(storageManager) {
1832
+ const storageKey = USER_ID_KEY;
1833
+ const storedUserId = storageManager.getItem(storageKey);
1834
+ if (storedUserId) {
1835
+ return storedUserId;
1836
+ }
1837
+ const newUserId = generateUUID();
1838
+ storageManager.setItem(storageKey, newUserId);
1839
+ return newUserId;
1840
+ }
1841
+ };
1842
+
1843
+ // src/managers/session.manager.ts
1844
+ var SessionManager = class extends StateManager {
1845
+ storageManager;
1846
+ eventManager;
1847
+ projectId;
1848
+ sessionTimeoutId = null;
1849
+ broadcastChannel = null;
1850
+ activityHandler = null;
1851
+ visibilityChangeHandler = null;
1852
+ beforeUnloadHandler = null;
1853
+ isTracking = false;
1854
+ constructor(storageManager, eventManager, projectId) {
1855
+ super();
1856
+ this.storageManager = storageManager;
1857
+ this.eventManager = eventManager;
1858
+ this.projectId = projectId;
1859
+ }
1860
+ initCrossTabSync() {
1861
+ if (typeof BroadcastChannel === "undefined") {
1862
+ log("warn", "BroadcastChannel not supported");
1863
+ return;
1864
+ }
1865
+ const projectId = this.getProjectId();
1866
+ this.broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME(projectId));
1867
+ this.broadcastChannel.onmessage = (event2) => {
1868
+ const { action, sessionId, timestamp, projectId: messageProjectId } = event2.data ?? {};
1869
+ if (messageProjectId !== projectId) {
1870
+ return;
1871
+ }
1872
+ if (action === "session_end") {
1873
+ this.resetSessionState();
1874
+ return;
1875
+ }
1876
+ if (sessionId && typeof timestamp === "number" && timestamp > Date.now() - 5e3) {
1877
+ this.set("sessionId", sessionId);
1878
+ this.set("hasStartSession", true);
1879
+ this.persistSession(sessionId, timestamp);
1880
+ if (this.isTracking) {
1881
+ this.setupSessionTimeout();
1882
+ }
1883
+ }
1884
+ };
1885
+ }
1886
+ shareSession(sessionId) {
1887
+ if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === "function") {
1888
+ this.broadcastChannel.postMessage({
1889
+ action: "session_start",
1890
+ projectId: this.getProjectId(),
1891
+ sessionId,
1892
+ timestamp: Date.now()
1893
+ });
1894
+ }
1895
+ }
1896
+ broadcastSessionEnd(sessionId, reason) {
1897
+ if (!sessionId) {
1898
+ return;
1899
+ }
1900
+ if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === "function") {
1901
+ try {
1902
+ this.broadcastChannel.postMessage({
1903
+ action: "session_end",
1904
+ projectId: this.getProjectId(),
1905
+ sessionId,
1906
+ reason,
1907
+ timestamp: Date.now()
1908
+ });
1909
+ } catch (error) {
1910
+ log("warn", "Failed to broadcast session end", { error, data: { sessionId, reason } });
1911
+ }
1912
+ }
1913
+ }
1914
+ cleanupCrossTabSync() {
1915
+ if (this.broadcastChannel) {
1916
+ if (typeof this.broadcastChannel.close === "function") {
1917
+ this.broadcastChannel.close();
1918
+ }
1919
+ this.broadcastChannel = null;
1920
+ }
1921
+ }
1922
+ recoverSession() {
1923
+ const storedSession = this.loadStoredSession();
1924
+ if (!storedSession) {
1925
+ return null;
1926
+ }
1927
+ const sessionTimeout = this.get("config")?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;
1928
+ if (Date.now() - storedSession.lastActivity > sessionTimeout) {
1929
+ this.clearStoredSession();
1930
+ return null;
1931
+ }
1932
+ return storedSession.id;
1933
+ }
1934
+ persistSession(sessionId, lastActivity = Date.now()) {
1935
+ this.saveStoredSession({
1936
+ id: sessionId,
1937
+ lastActivity
1938
+ });
1939
+ }
1940
+ clearStoredSession() {
1941
+ const storageKey = this.getSessionStorageKey();
1942
+ this.storageManager.removeItem(storageKey);
1943
+ }
1944
+ loadStoredSession() {
1945
+ const storageKey = this.getSessionStorageKey();
1946
+ const storedData = this.storageManager.getItem(storageKey);
1947
+ if (!storedData) {
1948
+ return null;
1949
+ }
1950
+ try {
1951
+ const parsed = JSON.parse(storedData);
1952
+ if (!parsed.id || typeof parsed.lastActivity !== "number") {
1953
+ return null;
1954
+ }
1955
+ return parsed;
1956
+ } catch {
1957
+ this.storageManager.removeItem(storageKey);
1958
+ return null;
1959
+ }
1960
+ }
1961
+ saveStoredSession(session) {
1962
+ const storageKey = this.getSessionStorageKey();
1963
+ this.storageManager.setItem(storageKey, JSON.stringify(session));
1964
+ }
1965
+ getSessionStorageKey() {
1966
+ return SESSION_STORAGE_KEY(this.getProjectId());
1967
+ }
1968
+ getProjectId() {
1969
+ return this.projectId;
1970
+ }
1971
+ startTracking() {
1972
+ if (this.isTracking) {
1973
+ log("warn", "Session tracking already active");
1974
+ return;
1975
+ }
1976
+ const recoveredSessionId = this.recoverSession();
1977
+ const sessionId = recoveredSessionId ?? this.generateSessionId();
1978
+ const isRecovered = Boolean(recoveredSessionId);
1979
+ this.isTracking = true;
1980
+ try {
1981
+ this.set("sessionId", sessionId);
1982
+ this.persistSession(sessionId);
1983
+ if (!isRecovered) {
1984
+ this.eventManager.track({
1985
+ type: "session_start" /* SESSION_START */
1986
+ });
1987
+ }
1988
+ this.initCrossTabSync();
1989
+ this.shareSession(sessionId);
1990
+ this.setupSessionTimeout();
1991
+ this.setupActivityListeners();
1992
+ this.setupLifecycleListeners();
1993
+ } catch (error) {
1994
+ this.isTracking = false;
1995
+ this.clearSessionTimeout();
1996
+ this.cleanupActivityListeners();
1997
+ this.cleanupLifecycleListeners();
1998
+ this.cleanupCrossTabSync();
1999
+ this.set("sessionId", null);
2000
+ throw error;
2001
+ }
2002
+ }
2003
+ generateSessionId() {
2004
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
2005
+ }
2006
+ setupSessionTimeout() {
2007
+ this.clearSessionTimeout();
2008
+ const sessionTimeout = this.get("config")?.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;
2009
+ this.sessionTimeoutId = setTimeout(() => {
2010
+ this.endSession("inactivity");
2011
+ }, sessionTimeout);
2012
+ }
2013
+ resetSessionTimeout() {
2014
+ this.setupSessionTimeout();
2015
+ const sessionId = this.get("sessionId");
2016
+ if (sessionId) {
2017
+ this.persistSession(sessionId);
2018
+ }
2019
+ }
2020
+ clearSessionTimeout() {
2021
+ if (this.sessionTimeoutId) {
2022
+ clearTimeout(this.sessionTimeoutId);
2023
+ this.sessionTimeoutId = null;
2024
+ }
2025
+ }
2026
+ setupActivityListeners() {
2027
+ this.activityHandler = () => {
2028
+ this.resetSessionTimeout();
2029
+ };
2030
+ document.addEventListener("click", this.activityHandler, { passive: true });
2031
+ document.addEventListener("keydown", this.activityHandler, { passive: true });
2032
+ document.addEventListener("scroll", this.activityHandler, { passive: true });
2033
+ }
2034
+ cleanupActivityListeners() {
2035
+ if (this.activityHandler) {
2036
+ document.removeEventListener("click", this.activityHandler);
2037
+ document.removeEventListener("keydown", this.activityHandler);
2038
+ document.removeEventListener("scroll", this.activityHandler);
2039
+ this.activityHandler = null;
2040
+ }
2041
+ }
2042
+ setupLifecycleListeners() {
2043
+ if (this.visibilityChangeHandler || this.beforeUnloadHandler) {
2044
+ return;
2045
+ }
2046
+ this.visibilityChangeHandler = () => {
2047
+ if (document.hidden) {
2048
+ this.clearSessionTimeout();
2049
+ } else {
2050
+ const sessionId = this.get("sessionId");
2051
+ if (sessionId) {
2052
+ this.setupSessionTimeout();
2053
+ }
2054
+ }
2055
+ };
2056
+ this.beforeUnloadHandler = () => {
2057
+ this.endSession("page_unload");
2058
+ };
2059
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
2060
+ window.addEventListener("beforeunload", this.beforeUnloadHandler);
2061
+ }
2062
+ cleanupLifecycleListeners() {
2063
+ if (this.visibilityChangeHandler) {
2064
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
2065
+ this.visibilityChangeHandler = null;
2066
+ }
2067
+ if (this.beforeUnloadHandler) {
2068
+ window.removeEventListener("beforeunload", this.beforeUnloadHandler);
2069
+ this.beforeUnloadHandler = null;
2070
+ }
2071
+ }
2072
+ endSession(reason) {
2073
+ const sessionId = this.get("sessionId");
2074
+ if (!sessionId) {
2075
+ log("warn", "endSession called without active session", { data: { reason } });
2076
+ this.resetSessionState(reason);
2077
+ return;
2078
+ }
2079
+ this.eventManager.track({
2080
+ type: "session_end" /* SESSION_END */,
2081
+ session_end_reason: reason
2082
+ });
2083
+ const flushResult = this.eventManager.flushImmediatelySync();
2084
+ if (!flushResult) {
2085
+ log("warn", "Sync flush failed during session end, events persisted for recovery", {
2086
+ data: { reason, sessionId }
2087
+ });
2088
+ }
2089
+ this.broadcastSessionEnd(sessionId, reason);
2090
+ this.resetSessionState(reason);
2091
+ }
2092
+ resetSessionState(reason) {
2093
+ this.clearSessionTimeout();
2094
+ this.cleanupActivityListeners();
2095
+ this.cleanupLifecycleListeners();
2096
+ this.cleanupCrossTabSync();
2097
+ if (reason !== "page_unload") {
2098
+ this.clearStoredSession();
2099
+ }
2100
+ this.set("sessionId", null);
2101
+ this.set("hasStartSession", false);
2102
+ this.isTracking = false;
2103
+ }
2104
+ stopTracking() {
2105
+ this.endSession("manual_stop");
2106
+ }
2107
+ destroy() {
2108
+ this.clearSessionTimeout();
2109
+ this.cleanupActivityListeners();
2110
+ this.cleanupCrossTabSync();
2111
+ this.cleanupLifecycleListeners();
2112
+ this.isTracking = false;
2113
+ this.set("hasStartSession", false);
2114
+ }
2115
+ };
2116
+
2117
+ // src/handlers/session.handler.ts
2118
+ var SessionHandler = class extends StateManager {
2119
+ eventManager;
2120
+ storageManager;
2121
+ sessionManager = null;
2122
+ destroyed = false;
2123
+ constructor(storageManager, eventManager) {
2124
+ super();
2125
+ this.eventManager = eventManager;
2126
+ this.storageManager = storageManager;
2127
+ }
2128
+ startTracking() {
2129
+ if (this.isActive()) {
2130
+ return;
2131
+ }
2132
+ if (this.destroyed) {
2133
+ log("warn", "Cannot start tracking on destroyed handler");
2134
+ return;
2135
+ }
2136
+ const config = this.get("config");
2137
+ const projectId = config?.integrations?.tracelog?.projectId ?? config?.integrations?.custom?.collectApiUrl ?? "default";
2138
+ if (!projectId) {
2139
+ throw new Error("Cannot start session tracking: config not available");
2140
+ }
2141
+ try {
2142
+ this.sessionManager = new SessionManager(this.storageManager, this.eventManager, projectId);
2143
+ this.sessionManager.startTracking();
2144
+ this.eventManager.flushPendingEvents();
2145
+ } catch (error) {
2146
+ if (this.sessionManager) {
2147
+ try {
2148
+ this.sessionManager.destroy();
2149
+ } catch {
2150
+ }
2151
+ this.sessionManager = null;
2152
+ }
2153
+ log("error", "Failed to start session tracking", { error });
2154
+ throw error;
2155
+ }
2156
+ }
2157
+ isActive() {
2158
+ return this.sessionManager !== null && !this.destroyed;
2159
+ }
2160
+ cleanupSessionManager() {
2161
+ if (this.sessionManager) {
2162
+ this.sessionManager.stopTracking();
2163
+ this.sessionManager.destroy();
2164
+ this.sessionManager = null;
2165
+ }
2166
+ }
2167
+ stopTracking() {
2168
+ this.cleanupSessionManager();
2169
+ }
2170
+ destroy() {
2171
+ if (this.destroyed) {
2172
+ return;
2173
+ }
2174
+ if (this.sessionManager) {
2175
+ this.sessionManager.destroy();
2176
+ this.sessionManager = null;
2177
+ }
2178
+ this.destroyed = true;
2179
+ this.set("hasStartSession", false);
2180
+ }
2181
+ };
2182
+
2183
+ // src/handlers/page-view.handler.ts
2184
+ var PageViewHandler = class extends StateManager {
2185
+ eventManager;
2186
+ onTrack;
2187
+ originalPushState;
2188
+ originalReplaceState;
2189
+ lastPageViewTime = 0;
2190
+ constructor(eventManager, onTrack) {
2191
+ super();
2192
+ this.eventManager = eventManager;
2193
+ this.onTrack = onTrack;
2194
+ }
2195
+ startTracking() {
2196
+ this.trackInitialPageView();
2197
+ window.addEventListener("popstate", this.trackCurrentPage, true);
2198
+ window.addEventListener("hashchange", this.trackCurrentPage, true);
2199
+ this.patchHistory("pushState");
2200
+ this.patchHistory("replaceState");
2201
+ }
2202
+ stopTracking() {
2203
+ window.removeEventListener("popstate", this.trackCurrentPage, true);
2204
+ window.removeEventListener("hashchange", this.trackCurrentPage, true);
2205
+ if (this.originalPushState) {
2206
+ window.history.pushState = this.originalPushState;
2207
+ }
2208
+ if (this.originalReplaceState) {
2209
+ window.history.replaceState = this.originalReplaceState;
2210
+ }
2211
+ this.lastPageViewTime = 0;
2212
+ }
2213
+ patchHistory(method) {
2214
+ const original = window.history[method];
2215
+ if (method === "pushState" && !this.originalPushState) {
2216
+ this.originalPushState = original;
2217
+ } else if (method === "replaceState" && !this.originalReplaceState) {
2218
+ this.originalReplaceState = original;
2219
+ }
2220
+ window.history[method] = (...args) => {
2221
+ original.apply(window.history, args);
2222
+ this.trackCurrentPage();
2223
+ };
2224
+ }
2225
+ trackCurrentPage = () => {
2226
+ const rawUrl = window.location.href;
2227
+ const normalizedUrl = normalizeUrl(rawUrl, this.get("config").sensitiveQueryParams);
2228
+ if (this.get("pageUrl") === normalizedUrl) {
2229
+ return;
2230
+ }
2231
+ const now = Date.now();
2232
+ const throttleMs = this.get("config").pageViewThrottleMs ?? DEFAULT_PAGE_VIEW_THROTTLE_MS;
2233
+ if (now - this.lastPageViewTime < throttleMs) {
2234
+ return;
2235
+ }
2236
+ this.lastPageViewTime = now;
2237
+ this.onTrack();
2238
+ const fromUrl = this.get("pageUrl");
2239
+ this.set("pageUrl", normalizedUrl);
2240
+ const pageViewData = this.extractPageViewData();
2241
+ this.eventManager.track({
2242
+ type: "page_view" /* PAGE_VIEW */,
2243
+ page_url: this.get("pageUrl"),
2244
+ from_page_url: fromUrl,
2245
+ ...pageViewData && { page_view: pageViewData }
2246
+ });
2247
+ };
2248
+ trackInitialPageView() {
2249
+ const normalizedUrl = normalizeUrl(window.location.href, this.get("config").sensitiveQueryParams);
2250
+ const pageViewData = this.extractPageViewData();
2251
+ this.lastPageViewTime = Date.now();
2252
+ this.eventManager.track({
2253
+ type: "page_view" /* PAGE_VIEW */,
2254
+ page_url: normalizedUrl,
2255
+ ...pageViewData && { page_view: pageViewData }
2256
+ });
2257
+ this.onTrack();
2258
+ }
2259
+ extractPageViewData() {
2260
+ const { pathname, search, hash } = window.location;
2261
+ const { referrer } = document;
2262
+ const { title } = document;
2263
+ if (!referrer && !title && !pathname && !search && !hash) {
2264
+ return void 0;
2265
+ }
2266
+ const data = {
2267
+ ...referrer && { referrer },
2268
+ ...title && { title },
2269
+ ...pathname && { pathname },
2270
+ ...search && { search },
2271
+ ...hash && { hash }
2272
+ };
2273
+ return data;
2274
+ }
2275
+ };
2276
+
2277
+ // src/handlers/click.handler.ts
2278
+ var ClickHandler = class extends StateManager {
2279
+ eventManager;
2280
+ lastClickTimes = /* @__PURE__ */ new Map();
2281
+ clickHandler;
2282
+ lastPruneTime = 0;
2283
+ constructor(eventManager) {
2284
+ super();
2285
+ this.eventManager = eventManager;
2286
+ }
2287
+ startTracking() {
2288
+ if (this.clickHandler) {
2289
+ return;
2290
+ }
2291
+ this.clickHandler = (event2) => {
2292
+ const mouseEvent = event2;
2293
+ const target = mouseEvent.target;
2294
+ const clickedElement = typeof HTMLElement !== "undefined" && target instanceof HTMLElement ? target : typeof HTMLElement !== "undefined" && target instanceof Node && target.parentElement instanceof HTMLElement ? target.parentElement : null;
2295
+ if (!clickedElement) {
2296
+ log("warn", "Click target not found or not an element");
2297
+ return;
2298
+ }
2299
+ if (this.shouldIgnoreElement(clickedElement)) {
2300
+ return;
2301
+ }
2302
+ const clickThrottleMs = this.get("config")?.clickThrottleMs ?? DEFAULT_CLICK_THROTTLE_MS;
2303
+ if (clickThrottleMs > 0 && !this.checkClickThrottle(clickedElement, clickThrottleMs)) {
2304
+ return;
2305
+ }
2306
+ const trackingElement = this.findTrackingElement(clickedElement);
2307
+ const relevantClickElement = this.getRelevantClickElement(clickedElement);
2308
+ const coordinates = this.calculateClickCoordinates(mouseEvent, clickedElement);
2309
+ if (trackingElement) {
2310
+ const trackingData = this.extractTrackingData(trackingElement);
2311
+ if (trackingData) {
2312
+ const attributeData = this.createCustomEventData(trackingData);
2313
+ this.eventManager.track({
2314
+ type: "custom" /* CUSTOM */,
2315
+ custom_event: {
2316
+ name: attributeData.name,
2317
+ ...attributeData.value && { metadata: { value: attributeData.value } }
2318
+ }
2319
+ });
2320
+ }
2321
+ }
2322
+ const clickData = this.generateClickData(clickedElement, relevantClickElement, coordinates);
2323
+ this.eventManager.track({
2324
+ type: "click" /* CLICK */,
2325
+ click_data: clickData
2326
+ });
2327
+ };
2328
+ window.addEventListener("click", this.clickHandler, true);
2329
+ }
2330
+ stopTracking() {
2331
+ if (this.clickHandler) {
2332
+ window.removeEventListener("click", this.clickHandler, true);
2333
+ this.clickHandler = void 0;
2334
+ }
2335
+ this.lastClickTimes.clear();
2336
+ this.lastPruneTime = 0;
2337
+ }
2338
+ shouldIgnoreElement(element) {
2339
+ if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {
2340
+ return true;
2341
+ }
2342
+ const parent = element.closest(`[${HTML_DATA_ATTR_PREFIX}-ignore]`);
2343
+ return parent !== null;
2344
+ }
2345
+ /**
2346
+ * Checks per-element click throttling to prevent double-clicks and rapid spam
2347
+ * Returns true if the click should be tracked, false if throttled
2348
+ */
2349
+ checkClickThrottle(element, throttleMs) {
2350
+ const signature = this.getElementSignature(element);
2351
+ const now = Date.now();
2352
+ this.pruneThrottleCache(now);
2353
+ const lastClickTime = this.lastClickTimes.get(signature);
2354
+ if (lastClickTime !== void 0 && now - lastClickTime < throttleMs) {
2355
+ log("debug", "ClickHandler: Click suppressed by throttle", {
2356
+ data: {
2357
+ signature,
2358
+ throttleRemaining: throttleMs - (now - lastClickTime)
2359
+ }
2360
+ });
2361
+ return false;
2362
+ }
2363
+ this.lastClickTimes.set(signature, now);
2364
+ return true;
2365
+ }
2366
+ /**
2367
+ * Prunes stale entries from the throttle cache to prevent memory leaks
2368
+ * Uses TTL-based eviction (5 minutes) and enforces max size limit
2369
+ * Called during checkClickThrottle with built-in rate limiting (every 30 seconds)
2370
+ */
2371
+ pruneThrottleCache(now) {
2372
+ if (now - this.lastPruneTime < THROTTLE_PRUNE_INTERVAL_MS) {
2373
+ return;
2374
+ }
2375
+ this.lastPruneTime = now;
2376
+ const cutoff = now - THROTTLE_ENTRY_TTL_MS;
2377
+ for (const [key, timestamp] of this.lastClickTimes.entries()) {
2378
+ if (timestamp < cutoff) {
2379
+ this.lastClickTimes.delete(key);
2380
+ }
2381
+ }
2382
+ if (this.lastClickTimes.size > MAX_THROTTLE_CACHE_ENTRIES) {
2383
+ const entries = Array.from(this.lastClickTimes.entries()).sort((a, b) => a[1] - b[1]);
2384
+ const excessCount = this.lastClickTimes.size - MAX_THROTTLE_CACHE_ENTRIES;
2385
+ const toDelete = entries.slice(0, excessCount);
2386
+ for (const [key] of toDelete) {
2387
+ this.lastClickTimes.delete(key);
2388
+ }
2389
+ log("debug", "ClickHandler: Pruned throttle cache", {
2390
+ data: {
2391
+ removed: toDelete.length,
2392
+ remaining: this.lastClickTimes.size
2393
+ }
2394
+ });
2395
+ }
2396
+ }
2397
+ /**
2398
+ * Creates a stable signature for an element to track throttling
2399
+ * Priority: id > data-testid > data-tlog-name > DOM path
2400
+ */
2401
+ getElementSignature(element) {
2402
+ if (element.id) {
2403
+ return `#${element.id}`;
2404
+ }
2405
+ const testId = element.getAttribute("data-testid");
2406
+ if (testId) {
2407
+ return `[data-testid="${testId}"]`;
2408
+ }
2409
+ const tlogName = element.getAttribute(`${HTML_DATA_ATTR_PREFIX}-name`);
2410
+ if (tlogName) {
2411
+ return `[${HTML_DATA_ATTR_PREFIX}-name="${tlogName}"]`;
2412
+ }
2413
+ return this.getElementPath(element);
2414
+ }
2415
+ /**
2416
+ * Generates a DOM path for an element (e.g., "body>div>button")
2417
+ */
2418
+ getElementPath(element) {
2419
+ const path = [];
2420
+ let current = element;
2421
+ while (current && current !== document.body) {
2422
+ let selector = current.tagName.toLowerCase();
2423
+ if (current.className) {
2424
+ const firstClass = current.className.split(" ")[0];
2425
+ if (firstClass) {
2426
+ selector += `.${firstClass}`;
2427
+ }
2428
+ }
2429
+ path.unshift(selector);
2430
+ current = current.parentElement;
2431
+ }
2432
+ return path.join(">") || "unknown";
2433
+ }
2434
+ findTrackingElement(element) {
2435
+ if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-name`)) {
2436
+ return element;
2437
+ }
2438
+ const closest = element.closest(`[${HTML_DATA_ATTR_PREFIX}-name]`);
2439
+ return closest;
2440
+ }
2441
+ getRelevantClickElement(element) {
2442
+ for (const selector of INTERACTIVE_SELECTORS) {
2443
+ try {
2444
+ if (element.matches(selector)) {
2445
+ return element;
2446
+ }
2447
+ const parent = element.closest(selector);
2448
+ if (parent) {
2449
+ return parent;
2450
+ }
2451
+ } catch (error) {
2452
+ log("warn", "Invalid selector in element search", { error, data: { selector } });
2453
+ continue;
2454
+ }
2455
+ }
2456
+ return element;
2457
+ }
2458
+ clamp(value) {
2459
+ return Math.max(0, Math.min(1, Number(value.toFixed(3))));
2460
+ }
2461
+ calculateClickCoordinates(event2, element) {
2462
+ const rect = element.getBoundingClientRect();
2463
+ const x = event2.clientX;
2464
+ const y = event2.clientY;
2465
+ const relativeX = rect.width > 0 ? this.clamp((x - rect.left) / rect.width) : 0;
2466
+ const relativeY = rect.height > 0 ? this.clamp((y - rect.top) / rect.height) : 0;
2467
+ return { x, y, relativeX, relativeY };
2468
+ }
2469
+ extractTrackingData(trackingElement) {
2470
+ const name = trackingElement.getAttribute(`${HTML_DATA_ATTR_PREFIX}-name`);
2471
+ const value = trackingElement.getAttribute(`${HTML_DATA_ATTR_PREFIX}-value`);
2472
+ if (!name) {
2473
+ return void 0;
2474
+ }
2475
+ return {
2476
+ element: trackingElement,
2477
+ name,
2478
+ ...value && { value }
2479
+ };
2480
+ }
2481
+ generateClickData(clickedElement, relevantElement, coordinates) {
2482
+ const { x, y, relativeX, relativeY } = coordinates;
2483
+ const text = this.getRelevantText(clickedElement, relevantElement);
2484
+ const attributes = this.extractElementAttributes(relevantElement);
2485
+ return {
2486
+ x,
2487
+ y,
2488
+ relativeX,
2489
+ relativeY,
2490
+ tag: relevantElement.tagName.toLowerCase(),
2491
+ ...relevantElement.id && { id: relevantElement.id },
2492
+ ...relevantElement.className && { class: relevantElement.className },
2493
+ ...text && { text },
2494
+ ...attributes.href && { href: attributes.href },
2495
+ ...attributes.title && { title: attributes.title },
2496
+ ...attributes.alt && { alt: attributes.alt },
2497
+ ...attributes.role && { role: attributes.role },
2498
+ ...attributes["aria-label"] && { ariaLabel: attributes["aria-label"] },
2499
+ ...Object.keys(attributes).length > 0 && { dataAttributes: attributes }
2500
+ };
2501
+ }
2502
+ sanitizeText(text) {
2503
+ let sanitized = text;
2504
+ for (const pattern of PII_PATTERNS) {
2505
+ const regex = new RegExp(pattern.source, pattern.flags);
2506
+ sanitized = sanitized.replace(regex, "[REDACTED]");
2507
+ }
2508
+ return sanitized;
2509
+ }
2510
+ getRelevantText(clickedElement, relevantElement) {
2511
+ const clickedText = clickedElement.textContent?.trim() ?? "";
2512
+ const relevantText = relevantElement.textContent?.trim() ?? "";
2513
+ if (!clickedText && !relevantText) {
2514
+ return "";
2515
+ }
2516
+ let finalText = "";
2517
+ if (clickedText && clickedText.length <= MAX_TEXT_LENGTH) {
2518
+ finalText = clickedText;
2519
+ } else if (relevantText.length <= MAX_TEXT_LENGTH) {
2520
+ finalText = relevantText;
2521
+ } else {
2522
+ finalText = relevantText.slice(0, MAX_TEXT_LENGTH - 3) + "...";
2523
+ }
2524
+ return this.sanitizeText(finalText);
2525
+ }
2526
+ extractElementAttributes(element) {
2527
+ const commonAttributes = [
2528
+ "id",
2529
+ "class",
2530
+ "data-testid",
2531
+ "aria-label",
2532
+ "title",
2533
+ "href",
2534
+ "type",
2535
+ "name",
2536
+ "alt",
2537
+ "role"
2538
+ ];
2539
+ const result = {};
2540
+ for (const attributeName of commonAttributes) {
2541
+ const value = element.getAttribute(attributeName);
2542
+ if (value) {
2543
+ result[attributeName] = value;
2544
+ }
2545
+ }
2546
+ return result;
2547
+ }
2548
+ createCustomEventData(trackingData) {
2549
+ return {
2550
+ name: trackingData.name,
2551
+ ...trackingData.value && { value: trackingData.value }
2552
+ };
2553
+ }
2554
+ };
2555
+
2556
+ // src/handlers/scroll.handler.ts
2557
+ var ScrollHandler = class extends StateManager {
2558
+ eventManager;
2559
+ containers = [];
2560
+ limitWarningLogged = false;
2561
+ minDepthChange = MIN_SCROLL_DEPTH_CHANGE;
2562
+ minIntervalMs = SCROLL_MIN_EVENT_INTERVAL_MS;
2563
+ maxEventsPerSession = MAX_SCROLL_EVENTS_PER_SESSION;
2564
+ retryTimeoutId = null;
2565
+ constructor(eventManager) {
2566
+ super();
2567
+ this.eventManager = eventManager;
2568
+ }
2569
+ startTracking() {
2570
+ this.limitWarningLogged = false;
2571
+ this.applyConfigOverrides();
2572
+ this.set("scrollEventCount", 0);
2573
+ this.tryDetectScrollContainers(0);
2574
+ }
2575
+ stopTracking() {
2576
+ if (this.retryTimeoutId !== null) {
2577
+ clearTimeout(this.retryTimeoutId);
2578
+ this.retryTimeoutId = null;
2579
+ }
2580
+ for (const container of this.containers) {
2581
+ this.clearContainerTimer(container);
2582
+ if (container.element === window) {
2583
+ window.removeEventListener("scroll", container.listener);
2584
+ } else {
2585
+ container.element.removeEventListener("scroll", container.listener);
2586
+ }
2587
+ }
2588
+ this.containers.length = 0;
2589
+ this.set("scrollEventCount", 0);
2590
+ this.limitWarningLogged = false;
2591
+ }
2592
+ tryDetectScrollContainers(attempt) {
2593
+ const elements = this.findScrollableElements();
2594
+ if (this.isWindowScrollable()) {
2595
+ this.setupScrollContainer(window, "window");
2596
+ }
2597
+ if (elements.length > 0) {
2598
+ for (const element of elements) {
2599
+ const selector = this.getElementSelector(element);
2600
+ this.setupScrollContainer(element, selector);
2601
+ }
2602
+ this.applyPrimaryScrollSelectorIfConfigured();
2603
+ return;
2604
+ }
2605
+ if (attempt < 5) {
2606
+ this.retryTimeoutId = window.setTimeout(() => {
2607
+ this.retryTimeoutId = null;
2608
+ this.tryDetectScrollContainers(attempt + 1);
2609
+ }, 200);
2610
+ return;
2611
+ }
2612
+ if (this.containers.length === 0) {
2613
+ this.setupScrollContainer(window, "window");
2614
+ }
2615
+ this.applyPrimaryScrollSelectorIfConfigured();
2616
+ }
2617
+ applyPrimaryScrollSelectorIfConfigured() {
2618
+ const config = this.get("config");
2619
+ if (config?.primaryScrollSelector) {
2620
+ this.applyPrimaryScrollSelector(config.primaryScrollSelector);
2621
+ }
2622
+ }
2623
+ findScrollableElements() {
2624
+ if (!document.body) {
2625
+ return [];
2626
+ }
2627
+ const elements = [];
2628
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {
2629
+ acceptNode: (node2) => {
2630
+ const element = node2;
2631
+ if (!element.isConnected || !element.offsetParent) {
2632
+ return NodeFilter.FILTER_SKIP;
2633
+ }
2634
+ const style = getComputedStyle(element);
2635
+ const hasVerticalScrollableStyle = style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll";
2636
+ return hasVerticalScrollableStyle ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
2637
+ }
2638
+ });
2639
+ let node;
2640
+ while ((node = walker.nextNode()) && elements.length < 10) {
2641
+ const element = node;
2642
+ if (this.isElementScrollable(element)) {
2643
+ elements.push(element);
2644
+ }
2645
+ }
2646
+ return elements;
2647
+ }
2648
+ getElementSelector(element) {
2649
+ if (element === window) {
2650
+ return "window";
2651
+ }
2652
+ const htmlElement = element;
2653
+ if (htmlElement.id) {
2654
+ return `#${htmlElement.id}`;
2655
+ }
2656
+ if (htmlElement.className && typeof htmlElement.className === "string") {
2657
+ const firstClass = htmlElement.className.split(" ").filter((c) => c.trim())[0];
2658
+ if (firstClass) {
2659
+ return `.${firstClass}`;
2660
+ }
2661
+ }
2662
+ return htmlElement.tagName.toLowerCase();
2663
+ }
2664
+ determineIfPrimary(element) {
2665
+ if (this.isWindowScrollable()) {
2666
+ return element === window;
2667
+ }
2668
+ return this.containers.length === 0;
2669
+ }
2670
+ setupScrollContainer(element, selector) {
2671
+ const alreadyTracking = this.containers.some((c) => c.element === element);
2672
+ if (alreadyTracking) {
2673
+ return;
2674
+ }
2675
+ if (element !== window && !this.isElementScrollable(element)) {
2676
+ return;
2677
+ }
2678
+ const initialScrollTop = this.getScrollTop(element);
2679
+ const initialDepth = this.calculateScrollDepth(
2680
+ initialScrollTop,
2681
+ this.getScrollHeight(element),
2682
+ this.getViewportHeight(element)
2683
+ );
2684
+ const isPrimary = this.determineIfPrimary(element);
2685
+ const container = {
2686
+ element,
2687
+ selector,
2688
+ isPrimary,
2689
+ lastScrollPos: initialScrollTop,
2690
+ lastDepth: initialDepth,
2691
+ lastDirection: "down" /* DOWN */,
2692
+ lastEventTime: 0,
2693
+ maxDepthReached: initialDepth,
2694
+ debounceTimer: null,
2695
+ listener: null
2696
+ // Will be assigned after handleScroll is defined
2697
+ };
2698
+ const handleScroll = () => {
2699
+ if (this.get("suppressNextScroll")) {
2700
+ return;
2701
+ }
2702
+ this.clearContainerTimer(container);
2703
+ container.debounceTimer = window.setTimeout(() => {
2704
+ const scrollData = this.calculateScrollData(container);
2705
+ if (scrollData) {
2706
+ const now = Date.now();
2707
+ this.processScrollEvent(container, scrollData, now);
2708
+ }
2709
+ container.debounceTimer = null;
2710
+ }, SCROLL_DEBOUNCE_TIME_MS);
2711
+ };
2712
+ container.listener = handleScroll;
2713
+ this.containers.push(container);
2714
+ if (element === window) {
2715
+ window.addEventListener("scroll", handleScroll, { passive: true });
2716
+ } else {
2717
+ element.addEventListener("scroll", handleScroll, { passive: true });
2718
+ }
2719
+ }
2720
+ processScrollEvent(container, scrollData, timestamp) {
2721
+ if (!this.shouldEmitScrollEvent(container, scrollData, timestamp)) {
2722
+ return;
2723
+ }
2724
+ container.lastEventTime = timestamp;
2725
+ container.lastDepth = scrollData.depth;
2726
+ container.lastDirection = scrollData.direction;
2727
+ const currentCount = this.get("scrollEventCount") ?? 0;
2728
+ this.set("scrollEventCount", currentCount + 1);
2729
+ this.eventManager.track({
2730
+ type: "scroll" /* SCROLL */,
2731
+ scroll_data: {
2732
+ ...scrollData,
2733
+ container_selector: container.selector,
2734
+ is_primary: container.isPrimary
2735
+ }
2736
+ });
2737
+ }
2738
+ shouldEmitScrollEvent(container, scrollData, timestamp) {
2739
+ if (this.hasReachedSessionLimit()) {
2740
+ this.logLimitOnce();
2741
+ return false;
2742
+ }
2743
+ if (!this.hasElapsedMinimumInterval(container, timestamp)) {
2744
+ return false;
2745
+ }
2746
+ if (!this.hasSignificantDepthChange(container, scrollData.depth)) {
2747
+ return false;
2748
+ }
2749
+ return true;
2750
+ }
2751
+ hasReachedSessionLimit() {
2752
+ const currentCount = this.get("scrollEventCount") ?? 0;
2753
+ return currentCount >= this.maxEventsPerSession;
2754
+ }
2755
+ hasElapsedMinimumInterval(container, timestamp) {
2756
+ if (container.lastEventTime === 0) {
2757
+ return true;
2758
+ }
2759
+ return timestamp - container.lastEventTime >= this.minIntervalMs;
2760
+ }
2761
+ hasSignificantDepthChange(container, newDepth) {
2762
+ return Math.abs(newDepth - container.lastDepth) >= this.minDepthChange;
2763
+ }
2764
+ logLimitOnce() {
2765
+ if (this.limitWarningLogged) {
2766
+ return;
2767
+ }
2768
+ this.limitWarningLogged = true;
2769
+ log("warn", "Max scroll events per session reached", {
2770
+ data: { limit: this.maxEventsPerSession }
2771
+ });
2772
+ }
2773
+ applyConfigOverrides() {
2774
+ this.minDepthChange = MIN_SCROLL_DEPTH_CHANGE;
2775
+ this.minIntervalMs = SCROLL_MIN_EVENT_INTERVAL_MS;
2776
+ this.maxEventsPerSession = MAX_SCROLL_EVENTS_PER_SESSION;
2777
+ }
2778
+ isWindowScrollable() {
2779
+ return document.documentElement.scrollHeight > window.innerHeight;
2780
+ }
2781
+ clearContainerTimer(container) {
2782
+ if (container.debounceTimer !== null) {
2783
+ clearTimeout(container.debounceTimer);
2784
+ container.debounceTimer = null;
2785
+ }
2786
+ }
2787
+ getScrollDirection(current, previous) {
2788
+ return current > previous ? "down" /* DOWN */ : "up" /* UP */;
2789
+ }
2790
+ calculateScrollDepth(scrollTop, scrollHeight, viewportHeight) {
2791
+ if (scrollHeight <= viewportHeight) {
2792
+ return 0;
2793
+ }
2794
+ const maxScrollTop = scrollHeight - viewportHeight;
2795
+ return Math.min(100, Math.max(0, Math.floor(scrollTop / maxScrollTop * 100)));
2796
+ }
2797
+ calculateScrollData(container) {
2798
+ const { element, lastScrollPos, lastEventTime } = container;
2799
+ const scrollTop = this.getScrollTop(element);
2800
+ const now = Date.now();
2801
+ const positionDelta = Math.abs(scrollTop - lastScrollPos);
2802
+ if (positionDelta < SIGNIFICANT_SCROLL_DELTA) {
2803
+ return null;
2804
+ }
2805
+ if (element === window && !this.isWindowScrollable()) {
2806
+ return null;
2807
+ }
2808
+ const viewportHeight = this.getViewportHeight(element);
2809
+ const scrollHeight = this.getScrollHeight(element);
2810
+ const direction = this.getScrollDirection(scrollTop, lastScrollPos);
2811
+ const depth = this.calculateScrollDepth(scrollTop, scrollHeight, viewportHeight);
2812
+ const timeDelta = lastEventTime > 0 ? now - lastEventTime : 0;
2813
+ const velocity = timeDelta > 0 ? Math.round(positionDelta / timeDelta * 1e3) : 0;
2814
+ if (depth > container.maxDepthReached) {
2815
+ container.maxDepthReached = depth;
2816
+ }
2817
+ container.lastScrollPos = scrollTop;
2818
+ return {
2819
+ depth,
2820
+ direction,
2821
+ velocity,
2822
+ max_depth_reached: container.maxDepthReached
2823
+ };
2824
+ }
2825
+ getScrollTop(element) {
2826
+ return element === window ? window.scrollY : element.scrollTop;
2827
+ }
2828
+ getViewportHeight(element) {
2829
+ return element === window ? window.innerHeight : element.clientHeight;
2830
+ }
2831
+ getScrollHeight(element) {
2832
+ return element === window ? document.documentElement.scrollHeight : element.scrollHeight;
2833
+ }
2834
+ isElementScrollable(element) {
2835
+ const style = getComputedStyle(element);
2836
+ const hasVerticalScrollableOverflow = style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll";
2837
+ const hasVerticalOverflowContent = element.scrollHeight > element.clientHeight;
2838
+ return hasVerticalScrollableOverflow && hasVerticalOverflowContent;
2839
+ }
2840
+ applyPrimaryScrollSelector(selector) {
2841
+ let targetElement;
2842
+ if (selector === "window") {
2843
+ targetElement = window;
2844
+ } else {
2845
+ const element = document.querySelector(selector);
2846
+ if (!(element instanceof HTMLElement)) {
2847
+ log("warn", `Selector "${selector}" did not match an HTMLElement`);
2848
+ return;
2849
+ }
2850
+ targetElement = element;
2851
+ }
2852
+ this.containers.forEach((container) => {
2853
+ this.updateContainerPrimary(container, container.element === targetElement);
2854
+ });
2855
+ const targetAlreadyTracked = this.containers.some((c) => c.element === targetElement);
2856
+ if (!targetAlreadyTracked && targetElement instanceof HTMLElement) {
2857
+ if (this.isElementScrollable(targetElement)) {
2858
+ this.setupScrollContainer(targetElement, selector);
2859
+ }
2860
+ }
2861
+ }
2862
+ updateContainerPrimary(container, isPrimary) {
2863
+ container.isPrimary = isPrimary;
2864
+ }
2865
+ };
2866
+
2867
+ // src/handlers/viewport.handler.ts
2868
+ var ViewportHandler = class extends StateManager {
2869
+ eventManager;
2870
+ trackedElements = /* @__PURE__ */ new Map();
2871
+ observer = null;
2872
+ mutationObserver = null;
2873
+ mutationDebounceTimer = null;
2874
+ config = null;
2875
+ constructor(eventManager) {
2876
+ super();
2877
+ this.eventManager = eventManager;
2878
+ }
2879
+ /**
2880
+ * Starts tracking viewport visibility for configured elements
2881
+ */
2882
+ startTracking() {
2883
+ const config = this.get("config");
2884
+ this.config = config.viewport ?? null;
2885
+ if (!this.config?.elements || this.config.elements.length === 0) {
2886
+ return;
2887
+ }
2888
+ const threshold = this.config.threshold ?? 0.5;
2889
+ const minDwellTime = this.config.minDwellTime ?? 1e3;
2890
+ if (threshold < 0 || threshold > 1) {
2891
+ log("warn", "ViewportHandler: Invalid threshold, must be between 0 and 1");
2892
+ return;
2893
+ }
2894
+ if (minDwellTime < 0) {
2895
+ log("warn", "ViewportHandler: Invalid minDwellTime, must be non-negative");
2896
+ return;
2897
+ }
2898
+ if (typeof IntersectionObserver === "undefined") {
2899
+ log("warn", "ViewportHandler: IntersectionObserver not supported in this browser");
2900
+ return;
2901
+ }
2902
+ this.observer = new IntersectionObserver(this.handleIntersection, {
2903
+ threshold
2904
+ });
2905
+ this.observeElements();
2906
+ this.setupMutationObserver();
2907
+ }
2908
+ /**
2909
+ * Stops tracking and cleans up resources
2910
+ */
2911
+ stopTracking() {
2912
+ if (this.observer) {
2913
+ this.observer.disconnect();
2914
+ this.observer = null;
2915
+ }
2916
+ if (this.mutationObserver) {
2917
+ this.mutationObserver.disconnect();
2918
+ this.mutationObserver = null;
2919
+ }
2920
+ if (this.mutationDebounceTimer !== null) {
2921
+ window.clearTimeout(this.mutationDebounceTimer);
2922
+ this.mutationDebounceTimer = null;
2923
+ }
2924
+ for (const tracked of this.trackedElements.values()) {
2925
+ if (tracked.timeoutId !== null) {
2926
+ window.clearTimeout(tracked.timeoutId);
2927
+ }
2928
+ }
2929
+ this.trackedElements.clear();
2930
+ }
2931
+ /**
2932
+ * Query and observe all elements matching configured elements
2933
+ */
2934
+ observeElements() {
2935
+ if (!this.config || !this.observer) return;
2936
+ const maxTrackedElements = this.config.maxTrackedElements ?? DEFAULT_VIEWPORT_MAX_TRACKED_ELEMENTS;
2937
+ let totalTracked = this.trackedElements.size;
2938
+ for (const elementConfig of this.config.elements) {
2939
+ try {
2940
+ const elements = document.querySelectorAll(elementConfig.selector);
2941
+ for (const element of Array.from(elements)) {
2942
+ if (totalTracked >= maxTrackedElements) {
2943
+ log("warn", "ViewportHandler: Maximum tracked elements reached", {
2944
+ data: {
2945
+ limit: maxTrackedElements,
2946
+ selector: elementConfig.selector,
2947
+ message: "Some elements will not be tracked. Consider more specific selectors."
2948
+ }
2949
+ });
2950
+ return;
2951
+ }
2952
+ if (element.hasAttribute(`${HTML_DATA_ATTR_PREFIX}-ignore`)) {
2953
+ continue;
2954
+ }
2955
+ if (this.trackedElements.has(element)) {
2956
+ continue;
2957
+ }
2958
+ this.trackedElements.set(element, {
2959
+ element,
2960
+ selector: elementConfig.selector,
2961
+ id: elementConfig.id,
2962
+ name: elementConfig.name,
2963
+ startTime: null,
2964
+ timeoutId: null,
2965
+ lastFiredTime: null
2966
+ });
2967
+ this.observer?.observe(element);
2968
+ totalTracked++;
2969
+ }
2970
+ } catch (error) {
2971
+ log("warn", `ViewportHandler: Invalid selector "${elementConfig.selector}"`, { error });
2972
+ }
2973
+ }
2974
+ log("debug", "ViewportHandler: Elements tracked", {
2975
+ data: { count: totalTracked, limit: maxTrackedElements }
2976
+ });
2977
+ }
2978
+ /**
2979
+ * Handles intersection events from IntersectionObserver
2980
+ */
2981
+ handleIntersection = (entries) => {
2982
+ if (!this.config) return;
2983
+ const minDwellTime = this.config.minDwellTime ?? 1e3;
2984
+ for (const entry of entries) {
2985
+ const tracked = this.trackedElements.get(entry.target);
2986
+ if (!tracked) continue;
2987
+ if (entry.isIntersecting) {
2988
+ if (tracked.startTime === null) {
2989
+ tracked.startTime = performance.now();
2990
+ tracked.timeoutId = window.setTimeout(() => {
2991
+ const visibilityRatio = Math.round(entry.intersectionRatio * 100) / 100;
2992
+ this.fireViewportEvent(tracked, visibilityRatio);
2993
+ }, minDwellTime);
2994
+ }
2995
+ } else {
2996
+ if (tracked.startTime !== null) {
2997
+ if (tracked.timeoutId !== null) {
2998
+ window.clearTimeout(tracked.timeoutId);
2999
+ tracked.timeoutId = null;
3000
+ }
3001
+ tracked.startTime = null;
3002
+ }
3003
+ }
3004
+ }
3005
+ };
3006
+ /**
3007
+ * Fires a viewport visible event
3008
+ */
3009
+ fireViewportEvent(tracked, visibilityRatio) {
3010
+ if (tracked.startTime === null) return;
3011
+ const dwellTime = Math.round(performance.now() - tracked.startTime);
3012
+ if (tracked.element.hasAttribute("data-tlog-ignore")) {
3013
+ return;
3014
+ }
3015
+ const cooldownPeriod = this.config?.cooldownPeriod ?? DEFAULT_VIEWPORT_COOLDOWN_PERIOD;
3016
+ const now = Date.now();
3017
+ if (tracked.lastFiredTime !== null && now - tracked.lastFiredTime < cooldownPeriod) {
3018
+ log("debug", "ViewportHandler: Event suppressed by cooldown period", {
3019
+ data: {
3020
+ selector: tracked.selector,
3021
+ cooldownRemaining: cooldownPeriod - (now - tracked.lastFiredTime)
3022
+ }
3023
+ });
3024
+ tracked.startTime = null;
3025
+ tracked.timeoutId = null;
3026
+ return;
3027
+ }
3028
+ const eventData = {
3029
+ selector: tracked.selector,
3030
+ dwellTime,
3031
+ visibilityRatio,
3032
+ ...tracked.id !== void 0 && { id: tracked.id },
3033
+ ...tracked.name !== void 0 && { name: tracked.name }
3034
+ };
3035
+ this.eventManager.track({
3036
+ type: "viewport_visible" /* VIEWPORT_VISIBLE */,
3037
+ viewport_data: eventData
3038
+ });
3039
+ tracked.startTime = null;
3040
+ tracked.timeoutId = null;
3041
+ tracked.lastFiredTime = now;
3042
+ }
3043
+ /**
3044
+ * Sets up MutationObserver to detect dynamically added elements
3045
+ */
3046
+ setupMutationObserver() {
3047
+ if (!this.config || typeof MutationObserver === "undefined") {
3048
+ return;
3049
+ }
3050
+ if (!document.body) {
3051
+ log("warn", "ViewportHandler: document.body not available, skipping MutationObserver setup");
3052
+ return;
3053
+ }
3054
+ this.mutationObserver = new MutationObserver((mutations) => {
3055
+ let hasAddedNodes = false;
3056
+ for (const mutation of mutations) {
3057
+ if (mutation.type === "childList") {
3058
+ if (mutation.addedNodes.length > 0) {
3059
+ hasAddedNodes = true;
3060
+ }
3061
+ if (mutation.removedNodes.length > 0) {
3062
+ this.cleanupRemovedNodes(mutation.removedNodes);
3063
+ }
3064
+ }
3065
+ }
3066
+ if (hasAddedNodes) {
3067
+ if (this.mutationDebounceTimer !== null) {
3068
+ window.clearTimeout(this.mutationDebounceTimer);
3069
+ }
3070
+ this.mutationDebounceTimer = window.setTimeout(() => {
3071
+ this.observeElements();
3072
+ this.mutationDebounceTimer = null;
3073
+ }, VIEWPORT_MUTATION_DEBOUNCE_MS);
3074
+ }
3075
+ });
3076
+ this.mutationObserver.observe(document.body, {
3077
+ childList: true,
3078
+ subtree: true
3079
+ });
3080
+ }
3081
+ /**
3082
+ * Cleans up tracking for removed DOM nodes
3083
+ */
3084
+ cleanupRemovedNodes(removedNodes) {
3085
+ removedNodes.forEach((node) => {
3086
+ if (node.nodeType !== 1) return;
3087
+ const element = node;
3088
+ const tracked = this.trackedElements.get(element);
3089
+ if (tracked) {
3090
+ if (tracked.timeoutId !== null) {
3091
+ window.clearTimeout(tracked.timeoutId);
3092
+ }
3093
+ this.observer?.unobserve(element);
3094
+ this.trackedElements.delete(element);
3095
+ }
3096
+ const descendants = Array.from(this.trackedElements.keys()).filter((el) => element.contains(el));
3097
+ descendants.forEach((el) => {
3098
+ const descendantTracked = this.trackedElements.get(el);
3099
+ if (descendantTracked && descendantTracked.timeoutId !== null) {
3100
+ window.clearTimeout(descendantTracked.timeoutId);
3101
+ }
3102
+ this.observer?.unobserve(el);
3103
+ this.trackedElements.delete(el);
3104
+ });
3105
+ });
3106
+ }
3107
+ };
3108
+
3109
+ // src/integrations/google-analytics.integration.ts
3110
+ var GoogleAnalyticsIntegration = class extends StateManager {
3111
+ isInitialized = false;
3112
+ async initialize() {
3113
+ if (this.isInitialized) {
3114
+ return;
3115
+ }
3116
+ const measurementId = this.get("config").integrations?.googleAnalytics?.measurementId;
3117
+ const userId = this.get("userId");
3118
+ if (!measurementId?.trim() || !userId?.trim()) {
3119
+ return;
3120
+ }
3121
+ try {
3122
+ if (this.isScriptAlreadyLoaded()) {
3123
+ this.isInitialized = true;
3124
+ return;
3125
+ }
3126
+ await this.loadScript(measurementId);
3127
+ this.configureGtag(measurementId, userId);
3128
+ this.isInitialized = true;
3129
+ } catch (error) {
3130
+ log("error", "Google Analytics initialization failed", { error });
3131
+ }
3132
+ }
3133
+ trackEvent(eventName, metadata) {
3134
+ if (!eventName?.trim() || !this.isInitialized || typeof window.gtag !== "function") {
3135
+ return;
3136
+ }
3137
+ try {
3138
+ const normalizedMetadata = Array.isArray(metadata) ? { items: metadata } : metadata;
3139
+ window.gtag("event", eventName, normalizedMetadata);
3140
+ } catch (error) {
3141
+ log("error", "Google Analytics event tracking failed", { error });
3142
+ }
3143
+ }
3144
+ cleanup() {
3145
+ this.isInitialized = false;
3146
+ const script = document.getElementById("tracelog-ga-script");
3147
+ if (script) {
3148
+ script.remove();
3149
+ }
3150
+ }
3151
+ isScriptAlreadyLoaded() {
3152
+ if (document.getElementById("tracelog-ga-script")) {
3153
+ return true;
3154
+ }
3155
+ const existingGAScript = document.querySelector('script[src*="googletagmanager.com/gtag/js"]');
3156
+ return !!existingGAScript;
3157
+ }
3158
+ async loadScript(measurementId) {
3159
+ return new Promise((resolve, reject) => {
3160
+ const script = document.createElement("script");
3161
+ script.id = "tracelog-ga-script";
3162
+ script.async = true;
3163
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
3164
+ script.onload = () => {
3165
+ resolve();
3166
+ };
3167
+ script.onerror = () => {
3168
+ reject(new Error("Failed to load Google Analytics script"));
3169
+ };
3170
+ document.head.appendChild(script);
3171
+ });
3172
+ }
3173
+ configureGtag(measurementId, userId) {
3174
+ const gaScriptConfig = document.createElement("script");
3175
+ gaScriptConfig.innerHTML = `
3176
+ window.dataLayer = window.dataLayer || [];
3177
+ function gtag(){dataLayer.push(arguments);}
3178
+ gtag('js', new Date());
3179
+ gtag('config', '${measurementId}', {
3180
+ 'user_id': '${userId}'
3181
+ });
3182
+ `;
3183
+ document.head.appendChild(gaScriptConfig);
3184
+ }
3185
+ };
3186
+
3187
+ // src/managers/storage.manager.ts
3188
+ var StorageManager = class {
3189
+ storage;
3190
+ sessionStorageRef;
3191
+ fallbackStorage = /* @__PURE__ */ new Map();
3192
+ fallbackSessionStorage = /* @__PURE__ */ new Map();
3193
+ hasQuotaExceededError = false;
3194
+ constructor() {
3195
+ this.storage = this.initializeStorage("localStorage");
3196
+ this.sessionStorageRef = this.initializeStorage("sessionStorage");
3197
+ if (!this.storage) {
3198
+ log("warn", "localStorage not available, using memory fallback");
3199
+ }
3200
+ if (!this.sessionStorageRef) {
3201
+ log("warn", "sessionStorage not available, using memory fallback");
3202
+ }
3203
+ }
3204
+ /**
3205
+ * Retrieves an item from storage
3206
+ */
3207
+ getItem(key) {
3208
+ try {
3209
+ if (this.storage) {
3210
+ return this.storage.getItem(key);
3211
+ }
3212
+ return this.fallbackStorage.get(key) ?? null;
3213
+ } catch {
3214
+ return this.fallbackStorage.get(key) ?? null;
3215
+ }
3216
+ }
3217
+ /**
3218
+ * Stores an item in storage
3219
+ */
3220
+ setItem(key, value) {
3221
+ this.fallbackStorage.set(key, value);
3222
+ try {
3223
+ if (this.storage) {
3224
+ this.storage.setItem(key, value);
3225
+ return;
3226
+ }
3227
+ } catch (error) {
3228
+ if (error instanceof DOMException && error.name === "QuotaExceededError") {
3229
+ this.hasQuotaExceededError = true;
3230
+ log("warn", "localStorage quota exceeded, attempting cleanup", {
3231
+ data: { key, valueSize: value.length }
3232
+ });
3233
+ const cleanedUp = this.cleanupOldData();
3234
+ if (cleanedUp) {
3235
+ try {
3236
+ if (this.storage) {
3237
+ this.storage.setItem(key, value);
3238
+ return;
3239
+ }
3240
+ } catch (retryError) {
3241
+ log("error", "localStorage quota exceeded even after cleanup - data will not persist", {
3242
+ error: retryError,
3243
+ data: { key, valueSize: value.length }
3244
+ });
3245
+ }
3246
+ } else {
3247
+ log("error", "localStorage quota exceeded and no data to cleanup - data will not persist", {
3248
+ error,
3249
+ data: { key, valueSize: value.length }
3250
+ });
3251
+ }
3252
+ }
3253
+ }
3254
+ }
3255
+ /**
3256
+ * Removes an item from storage
3257
+ */
3258
+ removeItem(key) {
3259
+ try {
3260
+ if (this.storage) {
3261
+ this.storage.removeItem(key);
3262
+ }
3263
+ } catch {
3264
+ }
3265
+ this.fallbackStorage.delete(key);
3266
+ }
3267
+ /**
3268
+ * Clears all TracLog-related items from storage
3269
+ */
3270
+ clear() {
3271
+ if (!this.storage) {
3272
+ this.fallbackStorage.clear();
3273
+ return;
3274
+ }
3275
+ try {
3276
+ const keysToRemove = [];
3277
+ for (let i = 0; i < this.storage.length; i++) {
3278
+ const key = this.storage.key(i);
3279
+ if (key?.startsWith("tracelog_")) {
3280
+ keysToRemove.push(key);
3281
+ }
3282
+ }
3283
+ keysToRemove.forEach((key) => {
3284
+ this.storage.removeItem(key);
3285
+ });
3286
+ this.fallbackStorage.clear();
3287
+ } catch (error) {
3288
+ log("error", "Failed to clear storage", { error });
3289
+ this.fallbackStorage.clear();
3290
+ }
3291
+ }
3292
+ /**
3293
+ * Checks if storage is available
3294
+ */
3295
+ isAvailable() {
3296
+ return this.storage !== null;
3297
+ }
3298
+ /**
3299
+ * Checks if a QuotaExceededError has occurred
3300
+ * This indicates localStorage is full and data may not persist
3301
+ */
3302
+ hasQuotaError() {
3303
+ return this.hasQuotaExceededError;
3304
+ }
3305
+ /**
3306
+ * Attempts to cleanup old TraceLog data from storage to free up space
3307
+ * Returns true if any data was removed, false otherwise
3308
+ */
3309
+ cleanupOldData() {
3310
+ if (!this.storage) {
3311
+ return false;
3312
+ }
3313
+ try {
3314
+ const tracelogKeys = [];
3315
+ const persistedEventsKeys = [];
3316
+ for (let i = 0; i < this.storage.length; i++) {
3317
+ const key = this.storage.key(i);
3318
+ if (key?.startsWith("tracelog_")) {
3319
+ tracelogKeys.push(key);
3320
+ if (key.startsWith("tracelog_persisted_events_")) {
3321
+ persistedEventsKeys.push(key);
3322
+ }
3323
+ }
3324
+ }
3325
+ if (persistedEventsKeys.length > 0) {
3326
+ persistedEventsKeys.forEach((key) => {
3327
+ try {
3328
+ this.storage.removeItem(key);
3329
+ } catch {
3330
+ }
3331
+ });
3332
+ return true;
3333
+ }
3334
+ const criticalPrefixes = ["tracelog_session_", "tracelog_user_id", "tracelog_device_id", "tracelog_config"];
3335
+ const nonCriticalKeys = tracelogKeys.filter((key) => {
3336
+ return !criticalPrefixes.some((prefix) => key.startsWith(prefix));
3337
+ });
3338
+ if (nonCriticalKeys.length > 0) {
3339
+ const keysToRemove = nonCriticalKeys.slice(0, 5);
3340
+ keysToRemove.forEach((key) => {
3341
+ try {
3342
+ this.storage.removeItem(key);
3343
+ } catch {
3344
+ }
3345
+ });
3346
+ return true;
3347
+ }
3348
+ return false;
3349
+ } catch (error) {
3350
+ log("error", "Failed to cleanup old data", { error });
3351
+ return false;
3352
+ }
3353
+ }
3354
+ /**
3355
+ * Initialize storage (localStorage or sessionStorage) with feature detection
3356
+ */
3357
+ initializeStorage(type) {
3358
+ if (typeof window === "undefined") {
3359
+ return null;
3360
+ }
3361
+ try {
3362
+ const storage = type === "localStorage" ? window.localStorage : window.sessionStorage;
3363
+ const testKey = "__tracelog_test__";
3364
+ storage.setItem(testKey, "test");
3365
+ storage.removeItem(testKey);
3366
+ return storage;
3367
+ } catch {
3368
+ return null;
3369
+ }
3370
+ }
3371
+ /**
3372
+ * Retrieves an item from sessionStorage
3373
+ */
3374
+ getSessionItem(key) {
3375
+ try {
3376
+ if (this.sessionStorageRef) {
3377
+ return this.sessionStorageRef.getItem(key);
3378
+ }
3379
+ return this.fallbackSessionStorage.get(key) ?? null;
3380
+ } catch {
3381
+ return this.fallbackSessionStorage.get(key) ?? null;
3382
+ }
3383
+ }
3384
+ /**
3385
+ * Stores an item in sessionStorage
3386
+ */
3387
+ setSessionItem(key, value) {
3388
+ this.fallbackSessionStorage.set(key, value);
3389
+ try {
3390
+ if (this.sessionStorageRef) {
3391
+ this.sessionStorageRef.setItem(key, value);
3392
+ return;
3393
+ }
3394
+ } catch (error) {
3395
+ if (error instanceof DOMException && error.name === "QuotaExceededError") {
3396
+ log("error", "sessionStorage quota exceeded - data will not persist", {
3397
+ error,
3398
+ data: { key, valueSize: value.length }
3399
+ });
3400
+ }
3401
+ }
3402
+ }
3403
+ /**
3404
+ * Removes an item from sessionStorage
3405
+ */
3406
+ removeSessionItem(key) {
3407
+ try {
3408
+ if (this.sessionStorageRef) {
3409
+ this.sessionStorageRef.removeItem(key);
3410
+ }
3411
+ } catch {
3412
+ }
3413
+ this.fallbackSessionStorage.delete(key);
3414
+ }
3415
+ };
3416
+
3417
+ // src/handlers/performance.handler.ts
3418
+ var PerformanceHandler = class extends StateManager {
3419
+ eventManager;
3420
+ reportedByNav = /* @__PURE__ */ new Map();
3421
+ navigationHistory = [];
3422
+ // FIFO queue for tracking navigation order
3423
+ observers = [];
3424
+ vitalThresholds = WEB_VITALS_THRESHOLDS;
3425
+ lastLongTaskSentAt = 0;
3426
+ constructor(eventManager) {
3427
+ super();
3428
+ this.eventManager = eventManager;
3429
+ }
3430
+ async startTracking() {
3431
+ await this.initWebVitals();
3432
+ this.observeLongTasks();
3433
+ }
3434
+ stopTracking() {
3435
+ this.observers.forEach((obs, index) => {
3436
+ try {
3437
+ obs.disconnect();
3438
+ } catch (error) {
3439
+ log("warn", "Failed to disconnect performance observer", { error, data: { observerIndex: index } });
3440
+ }
3441
+ });
3442
+ this.observers.length = 0;
3443
+ this.reportedByNav.clear();
3444
+ this.navigationHistory.length = 0;
3445
+ }
3446
+ observeWebVitalsFallback() {
3447
+ this.reportTTFB();
3448
+ this.safeObserve(
3449
+ "largest-contentful-paint",
3450
+ (list) => {
3451
+ const entries = list.getEntries();
3452
+ const last = entries[entries.length - 1];
3453
+ if (!last) {
3454
+ return;
3455
+ }
3456
+ this.sendVital({ type: "LCP", value: Number(last.startTime.toFixed(PRECISION_TWO_DECIMALS)) });
3457
+ },
3458
+ { type: "largest-contentful-paint", buffered: true },
3459
+ true
3460
+ );
3461
+ let clsValue = 0;
3462
+ let currentNavId = this.getNavigationId();
3463
+ this.safeObserve(
3464
+ "layout-shift",
3465
+ (list) => {
3466
+ const navId = this.getNavigationId();
3467
+ if (navId !== currentNavId) {
3468
+ clsValue = 0;
3469
+ currentNavId = navId;
3470
+ }
3471
+ const entries = list.getEntries();
3472
+ for (const entry of entries) {
3473
+ if (entry.hadRecentInput === true) {
3474
+ continue;
3475
+ }
3476
+ const value = typeof entry.value === "number" ? entry.value : 0;
3477
+ clsValue += value;
3478
+ }
3479
+ this.sendVital({ type: "CLS", value: Number(clsValue.toFixed(PRECISION_TWO_DECIMALS)) });
3480
+ },
3481
+ { type: "layout-shift", buffered: true }
3482
+ );
3483
+ this.safeObserve(
3484
+ "paint",
3485
+ (list) => {
3486
+ for (const entry of list.getEntries()) {
3487
+ if (entry.name === "first-contentful-paint") {
3488
+ this.sendVital({ type: "FCP", value: Number(entry.startTime.toFixed(PRECISION_TWO_DECIMALS)) });
3489
+ }
3490
+ }
3491
+ },
3492
+ { type: "paint", buffered: true },
3493
+ true
3494
+ );
3495
+ this.safeObserve(
3496
+ "event",
3497
+ (list) => {
3498
+ let worst = 0;
3499
+ const entries = list.getEntries();
3500
+ for (const entry of entries) {
3501
+ const dur = (entry.processingEnd ?? 0) - (entry.startTime ?? 0);
3502
+ worst = Math.max(worst, dur);
3503
+ }
3504
+ if (worst > 0) {
3505
+ this.sendVital({ type: "INP", value: Number(worst.toFixed(PRECISION_TWO_DECIMALS)) });
3506
+ }
3507
+ },
3508
+ { type: "event", buffered: true }
3509
+ );
3510
+ }
3511
+ async initWebVitals() {
3512
+ try {
3513
+ const { onLCP, onCLS, onFCP, onTTFB, onINP } = await import('web-vitals');
3514
+ const report = (type) => (metric) => {
3515
+ const value = Number(metric.value.toFixed(PRECISION_TWO_DECIMALS));
3516
+ this.sendVital({ type, value });
3517
+ };
3518
+ onLCP(report("LCP"), { reportAllChanges: false });
3519
+ onCLS(report("CLS"), { reportAllChanges: false });
3520
+ onFCP(report("FCP"), { reportAllChanges: false });
3521
+ onTTFB(report("TTFB"), { reportAllChanges: false });
3522
+ onINP(report("INP"), { reportAllChanges: false });
3523
+ } catch (error) {
3524
+ log("warn", "Failed to load web-vitals library, using fallback", { error });
3525
+ this.observeWebVitalsFallback();
3526
+ }
3527
+ }
3528
+ reportTTFB() {
3529
+ try {
3530
+ const nav = performance.getEntriesByType("navigation")[0];
3531
+ if (!nav) {
3532
+ return;
3533
+ }
3534
+ const ttfb = nav.responseStart;
3535
+ if (typeof ttfb === "number" && Number.isFinite(ttfb)) {
3536
+ this.sendVital({ type: "TTFB", value: Number(ttfb.toFixed(PRECISION_TWO_DECIMALS)) });
3537
+ }
3538
+ } catch (error) {
3539
+ log("warn", "Failed to report TTFB", { error });
3540
+ }
3541
+ }
3542
+ observeLongTasks() {
3543
+ this.safeObserve(
3544
+ "longtask",
3545
+ (list) => {
3546
+ const entries = list.getEntries();
3547
+ for (const entry of entries) {
3548
+ const duration = Number(entry.duration.toFixed(PRECISION_TWO_DECIMALS));
3549
+ const now = Date.now();
3550
+ if (now - this.lastLongTaskSentAt >= LONG_TASK_THROTTLE_MS) {
3551
+ if (this.shouldSendVital("LONG_TASK", duration)) {
3552
+ this.trackWebVital("LONG_TASK", duration);
3553
+ }
3554
+ this.lastLongTaskSentAt = now;
3555
+ }
3556
+ }
3557
+ },
3558
+ { type: "longtask", buffered: true }
3559
+ );
3560
+ }
3561
+ sendVital(sample) {
3562
+ if (!this.shouldSendVital(sample.type, sample.value)) {
3563
+ return;
3564
+ }
3565
+ const navId = this.getNavigationId();
3566
+ if (navId) {
3567
+ const reportedForNav = this.reportedByNav.get(navId);
3568
+ const isDuplicate = reportedForNav?.has(sample.type);
3569
+ if (isDuplicate) {
3570
+ return;
3571
+ }
3572
+ if (!reportedForNav) {
3573
+ this.reportedByNav.set(navId, /* @__PURE__ */ new Set([sample.type]));
3574
+ this.navigationHistory.push(navId);
3575
+ if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {
3576
+ const oldestNav = this.navigationHistory.shift();
3577
+ if (oldestNav) {
3578
+ this.reportedByNav.delete(oldestNav);
3579
+ }
3580
+ }
3581
+ } else {
3582
+ reportedForNav.add(sample.type);
3583
+ }
3584
+ }
3585
+ this.trackWebVital(sample.type, sample.value);
3586
+ }
3587
+ trackWebVital(type, value) {
3588
+ if (!Number.isFinite(value)) {
3589
+ log("warn", "Invalid web vital value", { data: { type, value } });
3590
+ return;
3591
+ }
3592
+ this.eventManager.track({
3593
+ type: "web_vitals" /* WEB_VITALS */,
3594
+ web_vitals: {
3595
+ type,
3596
+ value
3597
+ }
3598
+ });
3599
+ }
3600
+ getNavigationId() {
3601
+ try {
3602
+ const nav = performance.getEntriesByType("navigation")[0];
3603
+ if (!nav) {
3604
+ return null;
3605
+ }
3606
+ const timestamp = nav.startTime || performance.now();
3607
+ const random = Math.random().toString(36).substr(2, 5);
3608
+ return `${timestamp.toFixed(2)}_${window.location.pathname}_${random}`;
3609
+ } catch (error) {
3610
+ log("warn", "Failed to get navigation ID", { error });
3611
+ return null;
3612
+ }
3613
+ }
3614
+ isObserverSupported(type) {
3615
+ if (typeof PerformanceObserver === "undefined") return false;
3616
+ const supported = PerformanceObserver.supportedEntryTypes;
3617
+ return !supported || supported.includes(type);
3618
+ }
3619
+ safeObserve(type, cb, options, once = false) {
3620
+ try {
3621
+ if (!this.isObserverSupported(type)) {
3622
+ return false;
3623
+ }
3624
+ const obs = new PerformanceObserver((list, observer) => {
3625
+ try {
3626
+ cb(list, observer);
3627
+ } catch (callbackError) {
3628
+ log("warn", "Observer callback failed", {
3629
+ error: callbackError,
3630
+ data: { type }
3631
+ });
3632
+ }
3633
+ if (once) {
3634
+ try {
3635
+ observer.disconnect();
3636
+ } catch {
3637
+ }
3638
+ }
3639
+ });
3640
+ obs.observe(options ?? { type, buffered: true });
3641
+ if (!once) {
3642
+ this.observers.push(obs);
3643
+ }
3644
+ return true;
3645
+ } catch (error) {
3646
+ log("warn", "Failed to create performance observer", {
3647
+ error,
3648
+ data: { type }
3649
+ });
3650
+ return false;
3651
+ }
3652
+ }
3653
+ shouldSendVital(type, value) {
3654
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3655
+ log("warn", "Invalid web vital value", { data: { type, value } });
3656
+ return false;
3657
+ }
3658
+ const threshold = this.vitalThresholds[type];
3659
+ if (typeof threshold === "number" && value <= threshold) {
3660
+ return false;
3661
+ }
3662
+ return true;
3663
+ }
3664
+ };
3665
+
3666
+ // src/handlers/error.handler.ts
3667
+ var ErrorHandler = class extends StateManager {
3668
+ eventManager;
3669
+ recentErrors = /* @__PURE__ */ new Map();
3670
+ errorBurstCounter = 0;
3671
+ burstWindowStart = 0;
3672
+ burstBackoffUntil = 0;
3673
+ constructor(eventManager) {
3674
+ super();
3675
+ this.eventManager = eventManager;
3676
+ }
3677
+ startTracking() {
3678
+ window.addEventListener("error", this.handleError);
3679
+ window.addEventListener("unhandledrejection", this.handleRejection);
3680
+ }
3681
+ stopTracking() {
3682
+ window.removeEventListener("error", this.handleError);
3683
+ window.removeEventListener("unhandledrejection", this.handleRejection);
3684
+ this.recentErrors.clear();
3685
+ this.errorBurstCounter = 0;
3686
+ this.burstWindowStart = 0;
3687
+ this.burstBackoffUntil = 0;
3688
+ }
3689
+ /**
3690
+ * Checks sampling rate and burst detection (Phase 3)
3691
+ * Returns false if in cooldown period after burst detection
3692
+ */
3693
+ shouldSample() {
3694
+ const now = Date.now();
3695
+ if (now < this.burstBackoffUntil) {
3696
+ return false;
3697
+ }
3698
+ if (now - this.burstWindowStart > ERROR_BURST_WINDOW_MS) {
3699
+ this.errorBurstCounter = 0;
3700
+ this.burstWindowStart = now;
3701
+ }
3702
+ this.errorBurstCounter++;
3703
+ if (this.errorBurstCounter > ERROR_BURST_THRESHOLD) {
3704
+ this.burstBackoffUntil = now + ERROR_BURST_BACKOFF_MS;
3705
+ log("warn", "Error burst detected - entering cooldown", {
3706
+ data: {
3707
+ errorsInWindow: this.errorBurstCounter,
3708
+ cooldownMs: ERROR_BURST_BACKOFF_MS
3709
+ }
3710
+ });
3711
+ return false;
3712
+ }
3713
+ const config = this.get("config");
3714
+ const samplingRate = config?.errorSampling ?? DEFAULT_ERROR_SAMPLING_RATE;
3715
+ return Math.random() < samplingRate;
3716
+ }
3717
+ handleError = (event2) => {
3718
+ if (!this.shouldSample()) {
3719
+ return;
3720
+ }
3721
+ const sanitizedMessage = this.sanitize(event2.message || "Unknown error");
3722
+ if (this.shouldSuppressError("js_error" /* JS_ERROR */, sanitizedMessage)) {
3723
+ return;
3724
+ }
3725
+ this.eventManager.track({
3726
+ type: "error" /* ERROR */,
3727
+ error_data: {
3728
+ type: "js_error" /* JS_ERROR */,
3729
+ message: sanitizedMessage,
3730
+ ...event2.filename && { filename: event2.filename },
3731
+ ...event2.lineno && { line: event2.lineno },
3732
+ ...event2.colno && { column: event2.colno }
3733
+ }
3734
+ });
3735
+ };
3736
+ handleRejection = (event2) => {
3737
+ if (!this.shouldSample()) {
3738
+ return;
3739
+ }
3740
+ const message = this.extractRejectionMessage(event2.reason);
3741
+ const sanitizedMessage = this.sanitize(message);
3742
+ if (this.shouldSuppressError("promise_rejection" /* PROMISE_REJECTION */, sanitizedMessage)) {
3743
+ return;
3744
+ }
3745
+ this.eventManager.track({
3746
+ type: "error" /* ERROR */,
3747
+ error_data: {
3748
+ type: "promise_rejection" /* PROMISE_REJECTION */,
3749
+ message: sanitizedMessage
3750
+ }
3751
+ });
3752
+ };
3753
+ extractRejectionMessage(reason) {
3754
+ if (!reason) return "Unknown rejection";
3755
+ if (typeof reason === "string") return reason;
3756
+ if (reason instanceof Error) {
3757
+ return reason.stack ?? reason.message ?? reason.toString();
3758
+ }
3759
+ if (typeof reason === "object" && "message" in reason) {
3760
+ return String(reason.message);
3761
+ }
3762
+ try {
3763
+ return JSON.stringify(reason);
3764
+ } catch {
3765
+ return String(reason);
3766
+ }
3767
+ }
3768
+ sanitize(text) {
3769
+ let sanitized = text.length > MAX_ERROR_MESSAGE_LENGTH ? text.slice(0, MAX_ERROR_MESSAGE_LENGTH) + "..." : text;
3770
+ for (const pattern of PII_PATTERNS) {
3771
+ const regex = new RegExp(pattern.source, pattern.flags);
3772
+ sanitized = sanitized.replace(regex, "[REDACTED]");
3773
+ }
3774
+ return sanitized;
3775
+ }
3776
+ shouldSuppressError(type, message) {
3777
+ const now = Date.now();
3778
+ const key = `${type}:${message}`;
3779
+ const lastSeenAt = this.recentErrors.get(key);
3780
+ if (lastSeenAt && now - lastSeenAt < ERROR_SUPPRESSION_WINDOW_MS) {
3781
+ this.recentErrors.set(key, now);
3782
+ return true;
3783
+ }
3784
+ this.recentErrors.set(key, now);
3785
+ if (this.recentErrors.size > MAX_TRACKED_ERRORS_HARD_LIMIT) {
3786
+ this.recentErrors.clear();
3787
+ this.recentErrors.set(key, now);
3788
+ return false;
3789
+ }
3790
+ if (this.recentErrors.size > MAX_TRACKED_ERRORS) {
3791
+ this.pruneOldErrors();
3792
+ }
3793
+ return false;
3794
+ }
3795
+ pruneOldErrors() {
3796
+ const now = Date.now();
3797
+ for (const [key, timestamp] of this.recentErrors.entries()) {
3798
+ if (now - timestamp > ERROR_SUPPRESSION_WINDOW_MS) {
3799
+ this.recentErrors.delete(key);
3800
+ }
3801
+ }
3802
+ if (this.recentErrors.size <= MAX_TRACKED_ERRORS) {
3803
+ return;
3804
+ }
3805
+ const entries = Array.from(this.recentErrors.entries()).sort((a, b) => a[1] - b[1]);
3806
+ const excess = this.recentErrors.size - MAX_TRACKED_ERRORS;
3807
+ for (let index = 0; index < excess; index += 1) {
3808
+ const entry = entries[index];
3809
+ if (entry) {
3810
+ this.recentErrors.delete(entry[0]);
3811
+ }
3812
+ }
3813
+ }
3814
+ };
3815
+
3816
+ // src/app.ts
3817
+ var App = class extends StateManager {
3818
+ isInitialized = false;
3819
+ suppressNextScrollTimer = null;
3820
+ emitter = new Emitter();
3821
+ managers = {};
3822
+ handlers = {};
3823
+ integrations = {};
3824
+ get initialized() {
3825
+ return this.isInitialized;
3826
+ }
3827
+ async init(config = {}) {
3828
+ if (this.isInitialized) {
3829
+ return;
3830
+ }
3831
+ this.managers.storage = new StorageManager();
3832
+ try {
3833
+ this.setupState(config);
3834
+ await this.setupIntegrations();
3835
+ this.managers.event = new EventManager(this.managers.storage, this.integrations.googleAnalytics, this.emitter);
3836
+ this.initializeHandlers();
3837
+ await this.managers.event.recoverPersistedEvents().catch((error) => {
3838
+ log("warn", "Failed to recover persisted events", { error });
3839
+ });
3840
+ this.isInitialized = true;
3841
+ } catch (error) {
3842
+ this.destroy(true);
3843
+ const errorMessage = error instanceof Error ? error.message : String(error);
3844
+ throw new Error(`[TraceLog] TraceLog initialization failed: ${errorMessage}`);
3845
+ }
3846
+ }
3847
+ sendCustomEvent(name, metadata) {
3848
+ if (!this.managers.event) {
3849
+ return;
3850
+ }
3851
+ const { valid, error, sanitizedMetadata } = isEventValid(name, metadata);
3852
+ if (!valid) {
3853
+ if (this.get("mode") === "qa" /* QA */) {
3854
+ throw new Error(`[TraceLog] Custom event "${name}" validation failed: ${error}`);
3855
+ }
3856
+ return;
3857
+ }
3858
+ this.managers.event.track({
3859
+ type: "custom" /* CUSTOM */,
3860
+ custom_event: {
3861
+ name,
3862
+ ...sanitizedMetadata && { metadata: sanitizedMetadata }
3863
+ }
3864
+ });
3865
+ }
3866
+ on(event2, callback) {
3867
+ this.emitter.on(event2, callback);
3868
+ }
3869
+ off(event2, callback) {
3870
+ this.emitter.off(event2, callback);
3871
+ }
3872
+ destroy(force = false) {
3873
+ if (!this.isInitialized && !force) {
3874
+ return;
3875
+ }
3876
+ this.integrations.googleAnalytics?.cleanup();
3877
+ Object.values(this.handlers).filter(Boolean).forEach((handler) => {
3878
+ try {
3879
+ handler.stopTracking();
3880
+ } catch (error) {
3881
+ log("warn", "Failed to stop tracking", { error });
3882
+ }
3883
+ });
3884
+ if (this.suppressNextScrollTimer) {
3885
+ clearTimeout(this.suppressNextScrollTimer);
3886
+ this.suppressNextScrollTimer = null;
3887
+ }
3888
+ this.managers.event?.flushImmediatelySync();
3889
+ this.managers.event?.stop();
3890
+ this.emitter.removeAllListeners();
3891
+ this.set("hasStartSession", false);
3892
+ this.set("suppressNextScroll", false);
3893
+ this.set("sessionId", null);
3894
+ this.isInitialized = false;
3895
+ this.handlers = {};
3896
+ }
3897
+ setupState(config = {}) {
3898
+ this.set("config", config);
3899
+ const userId = UserManager.getId(this.managers.storage);
3900
+ this.set("userId", userId);
3901
+ const collectApiUrl = getCollectApiUrl(config);
3902
+ this.set("collectApiUrl", collectApiUrl);
3903
+ const device = getDeviceType();
3904
+ this.set("device", device);
3905
+ const pageUrl = normalizeUrl(window.location.href, config.sensitiveQueryParams);
3906
+ this.set("pageUrl", pageUrl);
3907
+ const mode = detectQaMode() ? "qa" /* QA */ : void 0;
3908
+ if (mode) {
3909
+ this.set("mode", mode);
3910
+ }
3911
+ }
3912
+ async setupIntegrations() {
3913
+ const config = this.get("config");
3914
+ const measurementId = config.integrations?.googleAnalytics?.measurementId;
3915
+ if (measurementId?.trim()) {
3916
+ try {
3917
+ this.integrations.googleAnalytics = new GoogleAnalyticsIntegration();
3918
+ await this.integrations.googleAnalytics.initialize();
3919
+ } catch {
3920
+ this.integrations.googleAnalytics = void 0;
3921
+ }
3922
+ }
3923
+ }
3924
+ initializeHandlers() {
3925
+ this.handlers.session = new SessionHandler(
3926
+ this.managers.storage,
3927
+ this.managers.event
3928
+ );
3929
+ this.handlers.session.startTracking();
3930
+ const onPageView = () => {
3931
+ this.set("suppressNextScroll", true);
3932
+ if (this.suppressNextScrollTimer) {
3933
+ clearTimeout(this.suppressNextScrollTimer);
3934
+ }
3935
+ this.suppressNextScrollTimer = window.setTimeout(() => {
3936
+ this.set("suppressNextScroll", false);
3937
+ }, SCROLL_DEBOUNCE_TIME_MS * SCROLL_SUPPRESS_MULTIPLIER);
3938
+ };
3939
+ this.handlers.pageView = new PageViewHandler(this.managers.event, onPageView);
3940
+ this.handlers.pageView.startTracking();
3941
+ this.handlers.click = new ClickHandler(this.managers.event);
3942
+ this.handlers.click.startTracking();
3943
+ this.handlers.scroll = new ScrollHandler(this.managers.event);
3944
+ this.handlers.scroll.startTracking();
3945
+ this.handlers.performance = new PerformanceHandler(this.managers.event);
3946
+ this.handlers.performance.startTracking().catch((error) => {
3947
+ log("warn", "Failed to start performance tracking", { error });
3948
+ });
3949
+ this.handlers.error = new ErrorHandler(this.managers.event);
3950
+ this.handlers.error.startTracking();
3951
+ if (this.get("config").viewport) {
3952
+ this.handlers.viewport = new ViewportHandler(this.managers.event);
3953
+ this.handlers.viewport.startTracking();
3954
+ }
3955
+ }
3956
+ };
3957
+
3958
+ // src/test-bridge.ts
3959
+ var TestBridge = class extends App {
3960
+ _isInitializing;
3961
+ _isDestroying = false;
3962
+ constructor(isInitializing2, isDestroying2) {
3963
+ super();
3964
+ this._isInitializing = isInitializing2;
3965
+ this._isDestroying = isDestroying2;
3966
+ }
3967
+ async init(config) {
3968
+ if (process.env.NODE_ENV !== "development") {
3969
+ throw new Error("[TraceLog] TestBridge is only available in development mode");
3970
+ }
3971
+ if (!__setAppInstance) {
3972
+ throw new Error("[TraceLog] __setAppInstance is not available (production build?)");
3973
+ }
3974
+ try {
3975
+ __setAppInstance(this);
3976
+ } catch {
3977
+ throw new Error("[TraceLog] TestBridge cannot sync with existing tracelog instance. Call destroy() first.");
3978
+ }
3979
+ try {
3980
+ await super.init(config);
3981
+ } catch (error) {
3982
+ if (__setAppInstance) {
3983
+ __setAppInstance(null);
3984
+ }
3985
+ throw error;
3986
+ }
3987
+ }
3988
+ isInitializing() {
3989
+ return this._isInitializing;
3990
+ }
3991
+ sendCustomEvent(name, data) {
3992
+ if (!this.initialized) {
3993
+ return;
3994
+ }
3995
+ super.sendCustomEvent(name, data);
3996
+ }
3997
+ getSessionData() {
3998
+ return {
3999
+ id: this.get("sessionId"),
4000
+ isActive: !!this.get("sessionId"),
4001
+ timeout: this.get("config")?.sessionTimeout ?? 15 * 60 * 1e3
4002
+ };
4003
+ }
4004
+ setSessionTimeout(timeout) {
4005
+ const config = this.get("config");
4006
+ if (config) {
4007
+ this.set("config", { ...config, sessionTimeout: timeout });
4008
+ }
4009
+ }
4010
+ getQueueLength() {
4011
+ return this.managers.event?.getQueueLength() ?? 0;
4012
+ }
4013
+ forceInitLock(enabled = true) {
4014
+ this._isInitializing = enabled;
4015
+ }
4016
+ simulatePersistedEvents(events) {
4017
+ const storageManager = this.managers?.storage;
4018
+ if (!storageManager) {
4019
+ throw new Error("Storage manager not available");
4020
+ }
4021
+ const config = this.get("config");
4022
+ const projectId = config?.integrations?.tracelog?.projectId ?? config?.integrations?.custom?.collectApiUrl ?? "test";
4023
+ const userId = this.get("userId");
4024
+ const sessionId = this.get("sessionId");
4025
+ if (!projectId || !userId) {
4026
+ throw new Error("Project ID or User ID not available. Initialize TraceLog first.");
4027
+ }
4028
+ const persistedData = {
4029
+ userId,
4030
+ sessionId: sessionId || `test-session-${Date.now()}`,
4031
+ device: "desktop",
4032
+ events,
4033
+ timestamp: Date.now()
4034
+ };
4035
+ const storageKey = `${STORAGE_BASE_KEY}:${projectId}:queue:${userId}`;
4036
+ storageManager.setItem(storageKey, JSON.stringify(persistedData));
4037
+ }
4038
+ get(key) {
4039
+ return super.get(key);
4040
+ }
4041
+ // Manager accessors
4042
+ getStorageManager() {
4043
+ return this.safeAccess(this.managers?.storage);
4044
+ }
4045
+ getEventManager() {
4046
+ return this.safeAccess(this.managers?.event);
4047
+ }
4048
+ // Handler accessors
4049
+ getSessionHandler() {
4050
+ return this.safeAccess(this.handlers?.session);
4051
+ }
4052
+ getPageViewHandler() {
4053
+ return this.safeAccess(this.handlers?.pageView);
4054
+ }
4055
+ getClickHandler() {
4056
+ return this.safeAccess(this.handlers?.click);
4057
+ }
4058
+ getScrollHandler() {
4059
+ return this.safeAccess(this.handlers?.scroll);
4060
+ }
4061
+ getPerformanceHandler() {
4062
+ return this.safeAccess(this.handlers?.performance);
4063
+ }
4064
+ getErrorHandler() {
4065
+ return this.safeAccess(this.handlers?.error);
4066
+ }
4067
+ // Integration accessors
4068
+ getGoogleAnalytics() {
4069
+ return this.safeAccess(this.integrations?.googleAnalytics);
4070
+ }
4071
+ destroy(force = false) {
4072
+ if (!this.initialized && !force) {
4073
+ return;
4074
+ }
4075
+ this.ensureNotDestroying();
4076
+ this._isDestroying = true;
4077
+ try {
4078
+ super.destroy(force);
4079
+ if (__setAppInstance) {
4080
+ __setAppInstance(null);
4081
+ }
4082
+ } finally {
4083
+ this._isDestroying = false;
4084
+ }
4085
+ }
4086
+ /**
4087
+ * Helper to safely access managers/handlers and convert undefined to null
4088
+ */
4089
+ safeAccess(value) {
4090
+ return value ?? null;
4091
+ }
4092
+ /**
4093
+ * Ensures destroy operation is not in progress, throws if it is
4094
+ */
4095
+ ensureNotDestroying() {
4096
+ if (this._isDestroying) {
4097
+ throw new Error("Destroy operation already in progress");
4098
+ }
4099
+ }
4100
+ };
4101
+
4102
+ // src/api.ts
4103
+ var pendingListeners = [];
4104
+ var app = null;
4105
+ var isInitializing = false;
4106
+ var isDestroying = false;
4107
+ var init = async (config) => {
4108
+ if (typeof window === "undefined" || typeof document === "undefined") {
4109
+ return;
4110
+ }
4111
+ if (window.__traceLogDisabled) {
4112
+ return;
4113
+ }
4114
+ if (app) {
4115
+ return;
4116
+ }
4117
+ if (isInitializing) {
4118
+ return;
4119
+ }
4120
+ isInitializing = true;
4121
+ try {
4122
+ const validatedConfig = validateAndNormalizeConfig(config ?? {});
4123
+ const instance = new App();
4124
+ try {
4125
+ pendingListeners.forEach(({ event: event2, callback }) => {
4126
+ instance.on(event2, callback);
4127
+ });
4128
+ pendingListeners.length = 0;
4129
+ const initPromise = instance.init(validatedConfig);
4130
+ const timeoutPromise = new Promise((_, reject) => {
4131
+ setTimeout(() => {
4132
+ reject(new Error(`[TraceLog] Initialization timeout after ${INITIALIZATION_TIMEOUT_MS}ms`));
4133
+ }, INITIALIZATION_TIMEOUT_MS);
4134
+ });
4135
+ await Promise.race([initPromise, timeoutPromise]);
4136
+ app = instance;
4137
+ } catch (error) {
4138
+ try {
4139
+ instance.destroy(true);
4140
+ } catch (cleanupError) {
4141
+ log("error", "Failed to cleanup partially initialized app", { error: cleanupError });
4142
+ }
4143
+ throw error;
4144
+ }
4145
+ } catch (error) {
4146
+ app = null;
4147
+ throw error;
4148
+ } finally {
4149
+ isInitializing = false;
4150
+ }
4151
+ };
4152
+ var event = (name, metadata) => {
4153
+ if (typeof window === "undefined" || typeof document === "undefined") {
4154
+ return;
4155
+ }
4156
+ if (!app) {
4157
+ throw new Error("[TraceLog] TraceLog not initialized. Please call init() first.");
4158
+ }
4159
+ if (isDestroying) {
4160
+ throw new Error("[TraceLog] Cannot send events while TraceLog is being destroyed");
4161
+ }
4162
+ app.sendCustomEvent(name, metadata);
4163
+ };
4164
+ var on = (event2, callback) => {
4165
+ if (typeof window === "undefined" || typeof document === "undefined") {
4166
+ return;
4167
+ }
4168
+ if (!app || isInitializing) {
4169
+ pendingListeners.push({ event: event2, callback });
4170
+ return;
4171
+ }
4172
+ app.on(event2, callback);
4173
+ };
4174
+ var off = (event2, callback) => {
4175
+ if (typeof window === "undefined" || typeof document === "undefined") {
4176
+ return;
4177
+ }
4178
+ if (!app) {
4179
+ const index = pendingListeners.findIndex((l) => l.event === event2 && l.callback === callback);
4180
+ if (index !== -1) {
4181
+ pendingListeners.splice(index, 1);
4182
+ }
4183
+ return;
4184
+ }
4185
+ app.off(event2, callback);
4186
+ };
4187
+ var isInitialized = () => {
4188
+ if (typeof window === "undefined" || typeof document === "undefined") {
4189
+ return false;
4190
+ }
4191
+ return app !== null;
4192
+ };
4193
+ var destroy = () => {
4194
+ if (typeof window === "undefined" || typeof document === "undefined") {
4195
+ return;
4196
+ }
4197
+ if (isDestroying) {
4198
+ throw new Error("[TraceLog] Destroy operation already in progress");
4199
+ }
4200
+ if (!app) {
4201
+ throw new Error("[TraceLog] App not initialized");
4202
+ }
4203
+ isDestroying = true;
4204
+ try {
4205
+ app.destroy();
4206
+ app = null;
4207
+ isInitializing = false;
4208
+ pendingListeners.length = 0;
4209
+ if (process.env.NODE_ENV === "development" && typeof window !== "undefined" && window.__traceLogBridge) {
4210
+ window.__traceLogBridge = void 0;
4211
+ }
4212
+ } catch (error) {
4213
+ app = null;
4214
+ isInitializing = false;
4215
+ pendingListeners.length = 0;
4216
+ log("warn", "Error during destroy, forced cleanup completed", { error });
4217
+ } finally {
4218
+ isDestroying = false;
4219
+ }
4220
+ };
4221
+ var __setAppInstance = (instance) => {
4222
+ if (process.env.NODE_ENV !== "development") {
4223
+ return;
4224
+ }
4225
+ if (instance !== null) {
4226
+ const hasRequiredMethods = typeof instance === "object" && "init" in instance && "destroy" in instance && "on" in instance && "off" in instance;
4227
+ if (!hasRequiredMethods) {
4228
+ throw new Error("[TraceLog] Invalid app instance type");
4229
+ }
4230
+ }
4231
+ if (app !== null && instance !== null && app !== instance) {
4232
+ throw new Error("[TraceLog] Cannot overwrite existing app instance. Call destroy() first.");
4233
+ }
4234
+ app = instance;
4235
+ };
4236
+ if (process.env.NODE_ENV === "development" && typeof window !== "undefined" && typeof document !== "undefined") {
4237
+ const injectTestingBridge = () => {
4238
+ window.__traceLogBridge = new TestBridge(isInitializing, isDestroying);
4239
+ };
4240
+ if (document.readyState === "loading") {
4241
+ document.addEventListener("DOMContentLoaded", injectTestingBridge);
4242
+ } else {
4243
+ injectTestingBridge();
4244
+ }
4245
+ }
4246
+
4247
+ // src/app.constants.ts
4248
+ var PERFORMANCE_CONFIG = {
4249
+ WEB_VITALS_THRESHOLDS
4250
+ // Business thresholds for performance analysis
4251
+ };
4252
+ var DATA_PROTECTION = {
4253
+ PII_PATTERNS
4254
+ // Patterns for sensitive data protection
4255
+ };
4256
+ var ENGAGEMENT_THRESHOLDS = {
4257
+ LOW_ACTIVITY_EVENT_COUNT: 50,
4258
+ HIGH_ACTIVITY_EVENT_COUNT: 1e3,
4259
+ MIN_EVENTS_FOR_DYNAMIC_CALCULATION: 100,
4260
+ MIN_EVENTS_FOR_TREND_ANALYSIS: 30,
4261
+ BOUNCE_RATE_SESSION_THRESHOLD: 1,
4262
+ // Sessions with 1 page view = bounce
4263
+ MIN_ENGAGED_SESSION_DURATION_MS: 30 * 1e3,
4264
+ MIN_SCROLL_DEPTH_ENGAGEMENT: 25
4265
+ // 25% scroll depth for engagement
4266
+ };
4267
+ var SESSION_ANALYTICS = {
4268
+ INACTIVITY_TIMEOUT_MS: 30 * 60 * 1e3,
4269
+ // 30min for analytics (vs 15min client)
4270
+ SHORT_SESSION_THRESHOLD_MS: 30 * 1e3,
4271
+ MEDIUM_SESSION_THRESHOLD_MS: 5 * 60 * 1e3,
4272
+ LONG_SESSION_THRESHOLD_MS: 30 * 60 * 1e3,
4273
+ MAX_REALISTIC_SESSION_DURATION_MS: 8 * 60 * 60 * 1e3
4274
+ // Filter outliers
4275
+ };
4276
+ var DEVICE_ANALYTICS = {
4277
+ MOBILE_MAX_WIDTH: 768,
4278
+ TABLET_MAX_WIDTH: 1024,
4279
+ MOBILE_PERFORMANCE_FACTOR: 1.5,
4280
+ // Mobile typically 1.5x slower
4281
+ TABLET_PERFORMANCE_FACTOR: 1.2
4282
+ };
4283
+ var CONTENT_ANALYTICS = {
4284
+ MIN_TEXT_LENGTH_FOR_ANALYSIS: 10,
4285
+ MIN_CLICKS_FOR_HOT_ELEMENT: 10,
4286
+ // Popular element threshold
4287
+ MIN_SCROLL_COMPLETION_PERCENT: 80,
4288
+ // Page consumption threshold
4289
+ MIN_TIME_ON_PAGE_FOR_READ_MS: 15 * 1e3
4290
+ };
4291
+ var INSIGHT_THRESHOLDS = {
4292
+ SIGNIFICANT_CHANGE_PERCENT: 20,
4293
+ MAJOR_CHANGE_PERCENT: 50,
4294
+ MIN_EVENTS_FOR_INSIGHT: 100,
4295
+ MIN_SESSIONS_FOR_INSIGHT: 10,
4296
+ MIN_CORRELATION_STRENGTH: 0.7,
4297
+ // Strong correlation threshold
4298
+ LOW_ERROR_RATE_PERCENT: 1,
4299
+ HIGH_ERROR_RATE_PERCENT: 5,
4300
+ CRITICAL_ERROR_RATE_PERCENT: 10
4301
+ };
4302
+ var TEMPORAL_ANALYSIS = {
4303
+ SHORT_TERM_TREND_HOURS: 24,
4304
+ MEDIUM_TERM_TREND_DAYS: 7,
4305
+ LONG_TERM_TREND_DAYS: 30,
4306
+ MIN_DATA_POINTS_FOR_TREND: 5,
4307
+ WEEKLY_PATTERN_MIN_WEEKS: 4,
4308
+ DAILY_PATTERN_MIN_DAYS: 14
4309
+ };
4310
+ var SEGMENTATION_ANALYTICS = {
4311
+ MIN_SEGMENT_SIZE: 10,
4312
+ MIN_COHORT_SIZE: 5,
4313
+ COHORT_ANALYSIS_DAYS: [1, 3, 7, 14, 30],
4314
+ MIN_FUNNEL_EVENTS: 20
4315
+ };
4316
+ var ANALYTICS_QUERY_LIMITS = {
4317
+ DEFAULT_EVENTS_LIMIT: 5,
4318
+ DEFAULT_SESSIONS_LIMIT: 5,
4319
+ DEFAULT_PAGES_LIMIT: 5,
4320
+ MAX_EVENTS_FOR_DEEP_ANALYSIS: 1e4,
4321
+ MAX_TIME_RANGE_DAYS: 365,
4322
+ ANALYTICS_BATCH_SIZE: 1e3
4323
+ // For historical analysis
4324
+ };
4325
+ var ANOMALY_DETECTION = {
4326
+ ANOMALY_THRESHOLD_SIGMA: 2.5,
4327
+ STRONG_ANOMALY_THRESHOLD_SIGMA: 3,
4328
+ TRAFFIC_DROP_ALERT_PERCENT: -30,
4329
+ TRAFFIC_SPIKE_ALERT_PERCENT: 200,
4330
+ MIN_BASELINE_DAYS: 7,
4331
+ MIN_EVENTS_FOR_ANOMALY_DETECTION: 50
4332
+ };
4333
+ var SPECIAL_PAGE_URLS = {
4334
+ PAGE_URL_EXCLUDED: "excluded",
4335
+ PAGE_URL_UNKNOWN: "unknown"
4336
+ };
4337
+
4338
+ // src/public-api.ts
4339
+ var tracelog = {
4340
+ init,
4341
+ event,
4342
+ on,
4343
+ off,
4344
+ isInitialized,
4345
+ destroy
4346
+ };
4347
+
4348
+ exports.ANALYTICS_QUERY_LIMITS = ANALYTICS_QUERY_LIMITS;
4349
+ exports.ANOMALY_DETECTION = ANOMALY_DETECTION;
4350
+ exports.AppConfigValidationError = AppConfigValidationError;
4351
+ exports.CONTENT_ANALYTICS = CONTENT_ANALYTICS;
4352
+ exports.DATA_PROTECTION = DATA_PROTECTION;
4353
+ exports.DEVICE_ANALYTICS = DEVICE_ANALYTICS;
4354
+ exports.DeviceType = DeviceType;
4355
+ exports.ENGAGEMENT_THRESHOLDS = ENGAGEMENT_THRESHOLDS;
4356
+ exports.EmitterEvent = EmitterEvent;
4357
+ exports.ErrorType = ErrorType;
4358
+ exports.EventType = EventType;
4359
+ exports.INSIGHT_THRESHOLDS = INSIGHT_THRESHOLDS;
4360
+ exports.InitializationTimeoutError = InitializationTimeoutError;
4361
+ exports.IntegrationValidationError = IntegrationValidationError;
4362
+ exports.MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH;
4363
+ exports.MAX_CUSTOM_EVENT_ARRAY_SIZE = MAX_CUSTOM_EVENT_ARRAY_SIZE;
4364
+ exports.MAX_CUSTOM_EVENT_KEYS = MAX_CUSTOM_EVENT_KEYS;
4365
+ exports.MAX_CUSTOM_EVENT_NAME_LENGTH = MAX_CUSTOM_EVENT_NAME_LENGTH;
4366
+ exports.MAX_CUSTOM_EVENT_STRING_SIZE = MAX_CUSTOM_EVENT_STRING_SIZE;
4367
+ exports.MAX_METADATA_NESTING_DEPTH = MAX_METADATA_NESTING_DEPTH;
4368
+ exports.MAX_NESTED_OBJECT_KEYS = MAX_NESTED_OBJECT_KEYS;
4369
+ exports.MAX_STRING_LENGTH = MAX_STRING_LENGTH;
4370
+ exports.MAX_STRING_LENGTH_IN_ARRAY = MAX_STRING_LENGTH_IN_ARRAY;
4371
+ exports.Mode = Mode;
4372
+ exports.PERFORMANCE_CONFIG = PERFORMANCE_CONFIG;
4373
+ exports.PermanentError = PermanentError;
4374
+ exports.SEGMENTATION_ANALYTICS = SEGMENTATION_ANALYTICS;
4375
+ exports.SESSION_ANALYTICS = SESSION_ANALYTICS;
4376
+ exports.SPECIAL_PAGE_URLS = SPECIAL_PAGE_URLS;
4377
+ exports.SamplingRateValidationError = SamplingRateValidationError;
4378
+ exports.ScrollDirection = ScrollDirection;
4379
+ exports.SessionTimeoutValidationError = SessionTimeoutValidationError;
4380
+ exports.SpecialApiUrl = SpecialApiUrl;
4381
+ exports.TEMPORAL_ANALYSIS = TEMPORAL_ANALYSIS;
4382
+ exports.TraceLogValidationError = TraceLogValidationError;
4383
+ exports.isPrimaryScrollEvent = isPrimaryScrollEvent;
4384
+ exports.isSecondaryScrollEvent = isSecondaryScrollEvent;
4385
+ exports.tracelog = tracelog;
4386
+ //# sourceMappingURL=public-api.cjs.map
4387
+ //# sourceMappingURL=public-api.cjs.map