@faststats/web 0.2.7 → 0.2.8

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 (44) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/chunks/analytics-Cmawx5f9.js +1 -0
  3. package/dist/chunks/api-urls-BrkcoElX.js +1 -0
  4. package/dist/{error-asweBGYd.js → chunks/error-1K0dai1m.js} +2 -2
  5. package/dist/chunks/feature-flags-6rZlmhfu.d.ts +18 -0
  6. package/dist/chunks/feature-flags-BPRTgvIh.js +1 -0
  7. package/dist/chunks/replay-CtMtn0C-.js +1 -0
  8. package/dist/chunks/replay-IhsP2Ab4.d.ts +77 -0
  9. package/dist/chunks/rolldown-runtime-MP-BAFHD.js +1 -0
  10. package/dist/chunks/types-C3vW7XGe.js +1 -0
  11. package/dist/chunks/web-vitals-BooBPWJC.js +1 -0
  12. package/dist/error.d.ts +44 -0
  13. package/dist/error.js +1 -0
  14. package/dist/feature-flags.d.ts +2 -0
  15. package/dist/feature-flags.js +1 -0
  16. package/dist/index.d.ts +140 -0
  17. package/dist/index.js +1 -0
  18. package/dist/replay.d.ts +2 -0
  19. package/dist/replay.js +1 -0
  20. package/dist/web-vitals.d.ts +27 -0
  21. package/dist/web-vitals.js +1 -0
  22. package/package.json +32 -11
  23. package/scripts/check-bundle-size.mjs +90 -9
  24. package/src/analytics.ts +47 -41
  25. package/src/entries/error.ts +6 -0
  26. package/src/entries/feature-flags.ts +5 -0
  27. package/src/entries/main.ts +21 -0
  28. package/src/entries/replay.ts +1 -0
  29. package/src/entries/web-vitals.ts +1 -0
  30. package/src/env.d.ts +2 -0
  31. package/src/error.ts +5 -0
  32. package/src/replay.ts +104 -59
  33. package/src/sdk.ts +8 -0
  34. package/src/utils/types.ts +1 -1
  35. package/src/web-vitals.ts +32 -14
  36. package/tests/replay.test.ts +150 -21
  37. package/tsdown.config.ts +16 -1
  38. package/dist/analytics-Ct-lghlf.js +0 -1
  39. package/dist/module.d.ts +0 -531
  40. package/dist/module.js +0 -1
  41. package/dist/replay-DUA5c3Jf.js +0 -1
  42. package/dist/types-Df70G0eM.js +0 -1
  43. package/dist/web-vitals-gcUR-TX_.js +0 -1
  44. package/src/module.ts +0 -25
@@ -1,7 +1,13 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { gunzipSync } from "node:zlib";
2
3
  import ReplayTracker from "../src/replay";
3
4
  import { getOrCreateSessionId } from "../src/utils/identifiers";
4
5
 
6
+ function decodeBody(data: string | Uint8Array): string {
7
+ if (typeof data === "string") return data;
8
+ return gunzipSync(data).toString("utf8");
9
+ }
10
+
5
11
  class MockStorage {
6
12
  private readonly data = new Map<string, string>();
7
13
 
@@ -37,13 +43,14 @@ type ReplayBatch = {
37
43
  type ReplayTrackerInternals = {
38
44
  events: ReplayEvent[];
39
45
  pending: ReplayBatch[];
46
+ pendingSizeBytes: number;
40
47
  minLengthFlushTask: ReturnType<typeof setTimeout> | null;
41
48
  retryTask: ReturnType<typeof setTimeout> | null;
42
49
  startTime: number;
43
50
  onEvent: (event: ReplayEvent, isCheckout?: boolean) => void;
44
51
  flush: (lowLatency: boolean) => Promise<void>;
45
52
  send: (
46
- data: string | Blob,
53
+ data: string | Uint8Array,
47
54
  isCompressed: boolean,
48
55
  lowLatency: boolean,
49
56
  ) => Promise<boolean>;
@@ -167,13 +174,13 @@ describe("ReplayTracker", () => {
167
174
  minReplayLengthMs: 0,
168
175
  });
169
176
 
170
- let payload: ReplayBatch | null = null;
171
- let lowLatency = false;
177
+ const captured: { payload: ReplayBatch | null; lowLatency: boolean } = {
178
+ payload: null,
179
+ lowLatency: false,
180
+ };
172
181
  tracker.send = async (data, _isCompressed, nextLowLatency) => {
173
- payload = JSON.parse(
174
- typeof data === "string" ? data : await data.text(),
175
- ) as ReplayBatch;
176
- lowLatency = nextLowLatency;
182
+ captured.payload = JSON.parse(decodeBody(data)) as ReplayBatch;
183
+ captured.lowLatency = nextLowLatency;
177
184
  return true;
178
185
  };
179
186
  tracker.startTime = Date.now() - 1000;
@@ -181,11 +188,11 @@ describe("ReplayTracker", () => {
181
188
 
182
189
  await tracker.flush(true);
183
190
 
184
- expect(payload).not.toBeNull();
185
- expect(payload?.token).toBe("site_test");
186
- expect(payload?.sessionId).toBeTruthy();
187
- expect(payload?.events).toHaveLength(1);
188
- expect(lowLatency).toBe(true);
191
+ expect(captured.payload).not.toBeNull();
192
+ expect(captured.payload?.token).toBe("site_test");
193
+ expect(captured.payload?.sessionId).toBeTruthy();
194
+ expect(captured.payload?.events).toHaveLength(1);
195
+ expect(captured.lowLatency).toBe(true);
189
196
  expect(tracker.pending.length).toBe(0);
190
197
  });
191
198
 
@@ -196,11 +203,7 @@ describe("ReplayTracker", () => {
196
203
 
197
204
  const payloads: ReplayBatch[] = [];
198
205
  tracker.send = async (data) => {
199
- payloads.push(
200
- JSON.parse(
201
- typeof data === "string" ? data : await data.text(),
202
- ) as ReplayBatch,
203
- );
206
+ payloads.push(JSON.parse(decodeBody(data)) as ReplayBatch);
204
207
  return true;
205
208
  };
206
209
 
@@ -221,14 +224,16 @@ describe("ReplayTracker", () => {
221
224
  expect(payloads[1]?.sessionId).toBe(payloads[0]?.sessionId);
222
225
  });
223
226
 
224
- test("sends replay as plain JSON by default", async () => {
227
+ test("sends replay as plain JSON when CompressionStream is unavailable", async () => {
225
228
  const tracker = createTracker({
226
229
  minReplayLengthMs: 0,
227
230
  });
228
231
 
229
- let bodyType: "string" | "blob" | null = null;
232
+ const captured: { bodyType: "string" | "blob" | null } = {
233
+ bodyType: null,
234
+ };
230
235
  tracker.send = async (data) => {
231
- bodyType = typeof data === "string" ? "string" : "blob";
236
+ captured.bodyType = typeof data === "string" ? "string" : "blob";
232
237
  return true;
233
238
  };
234
239
  tracker.startTime = Date.now() - 1000;
@@ -236,7 +241,76 @@ describe("ReplayTracker", () => {
236
241
 
237
242
  await tracker.flush(false);
238
243
 
239
- expect(bodyType).toBe("string");
244
+ expect(captured.bodyType).toBe("string");
245
+ });
246
+
247
+ test("sends replay as gzip Uint8Array when CompressionStream is available", async () => {
248
+ setGlobal("window", {
249
+ ...(globalThis.window as unknown as Record<string, unknown>),
250
+ CompressionStream: globalThis.CompressionStream,
251
+ });
252
+
253
+ const tracker = createTracker({
254
+ minReplayLengthMs: 0,
255
+ });
256
+
257
+ const captured: {
258
+ bodyType: "string" | "binary" | null;
259
+ compressed: boolean;
260
+ size: number;
261
+ } = { bodyType: null, compressed: false, size: 0 };
262
+ tracker.send = async (data, isCompressed) => {
263
+ captured.bodyType = typeof data === "string" ? "string" : "binary";
264
+ captured.compressed = isCompressed;
265
+ if (data instanceof Uint8Array) {
266
+ captured.size = data.byteLength;
267
+ }
268
+ return true;
269
+ };
270
+ tracker.startTime = Date.now() - 1000;
271
+ for (let i = 0; i < 10; i++) {
272
+ tracker.onEvent(
273
+ {
274
+ type: 2,
275
+ timestamp: Date.now(),
276
+ data: { i, payload: "x".repeat(500) },
277
+ },
278
+ false,
279
+ );
280
+ }
281
+
282
+ await tracker.flush(false);
283
+
284
+ expect(captured.bodyType).toBe("binary");
285
+ expect(captured.compressed).toBe(true);
286
+ expect(captured.size).toBeGreaterThan(0);
287
+ expect(captured.size).toBeLessThan(5000);
288
+ });
289
+
290
+ test("compresses on unload (low latency) flush", async () => {
291
+ setGlobal("window", {
292
+ ...(globalThis.window as unknown as Record<string, unknown>),
293
+ CompressionStream: globalThis.CompressionStream,
294
+ });
295
+
296
+ const tracker = createTracker({
297
+ minReplayLengthMs: 0,
298
+ });
299
+
300
+ let compressedFlag = false;
301
+ let lowLatencyFlag = false;
302
+ tracker.send = async (_data, isCompressed, lowLatency) => {
303
+ compressedFlag = isCompressed;
304
+ lowLatencyFlag = lowLatency;
305
+ return true;
306
+ };
307
+ tracker.startTime = Date.now() - 1000;
308
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
309
+
310
+ await tracker.flush(true);
311
+
312
+ expect(compressedFlag).toBe(true);
313
+ expect(lowLatencyFlag).toBe(true);
240
314
  });
241
315
 
242
316
  test("keeps failed batches queued for retry", async () => {
@@ -255,4 +329,59 @@ describe("ReplayTracker", () => {
255
329
 
256
330
  if (tracker.retryTask) clearTimeout(tracker.retryTask);
257
331
  });
332
+
333
+ test("drops replay batches that exceed the queue byte limit", async () => {
334
+ const tracker = createTracker({
335
+ minReplayLengthMs: 0,
336
+ maxQueueSizeBytes: 600,
337
+ });
338
+
339
+ let sent = 0;
340
+ tracker.send = async () => {
341
+ sent++;
342
+ return true;
343
+ };
344
+ tracker.startTime = Date.now() - 1000;
345
+ tracker.onEvent(
346
+ {
347
+ type: 2,
348
+ timestamp: Date.now(),
349
+ data: { payload: "x".repeat(1000) },
350
+ },
351
+ false,
352
+ );
353
+
354
+ await tracker.flush(false);
355
+
356
+ expect(sent).toBe(0);
357
+ expect(tracker.pending.length).toBe(0);
358
+ expect(tracker.pendingSizeBytes).toBe(0);
359
+ });
360
+
361
+ test("keeps pending replay queue under the byte limit", async () => {
362
+ const tracker = createTracker({
363
+ minReplayLengthMs: 0,
364
+ maxQueueSizeBytes: 900,
365
+ });
366
+
367
+ tracker.send = async () => false;
368
+ tracker.startTime = Date.now() - 1000;
369
+
370
+ for (let i = 0; i < 4; i++) {
371
+ tracker.onEvent(
372
+ {
373
+ type: 2,
374
+ timestamp: Date.now(),
375
+ data: { i, payload: "x".repeat(300) },
376
+ },
377
+ false,
378
+ );
379
+ await tracker.flush(false);
380
+ }
381
+
382
+ expect(tracker.pending.length).toBeGreaterThan(0);
383
+ expect(tracker.pendingSizeBytes).toBeLessThanOrEqual(900);
384
+
385
+ if (tracker.retryTask) clearTimeout(tracker.retryTask);
386
+ });
258
387
  });
package/tsdown.config.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import { defineConfig } from "tsdown";
2
+ import pkg from "./package.json" with { type: "json" };
2
3
 
3
4
  export default defineConfig({
4
- entry: "src/module.ts",
5
+ entry: {
6
+ index: "src/entries/main.ts",
7
+ "feature-flags": "src/entries/feature-flags.ts",
8
+ replay: "src/entries/replay.ts",
9
+ error: "src/entries/error.ts",
10
+ "web-vitals": "src/entries/web-vitals.ts",
11
+ },
5
12
  format: ["esm"],
6
13
  platform: "browser",
7
14
  outDir: "dist",
@@ -9,4 +16,12 @@ export default defineConfig({
9
16
  minify: true,
10
17
  deps: { onlyBundle: false },
11
18
  checks: { pluginTimings: false },
19
+ define: {
20
+ __FASTSTATS_SDK_NAME__: JSON.stringify(pkg.name),
21
+ __FASTSTATS_SDK_VERSION__: JSON.stringify(pkg.version),
22
+ },
23
+ outputOptions: {
24
+ chunkFileNames: "chunks/[name]-[hash].js",
25
+ entryFileNames: "[name].js",
26
+ },
12
27
  });
@@ -1 +0,0 @@
1
- import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,r as l,s as u,t as d,u as f}from"./types-Df70G0eM.js";async function p(e,t){if(t.projectToken&&t.projectId)throw Error(`provide either projectToken or projectId, not both`);if(t.identifier&&t.externalId)throw Error(`provide either serverId or externalId, not both`);let n=t.projectToken,r=t.projectId,i=t.identifier,o=t.externalId;if(!n&&!r)throw Error(`feature flag check requires projectToken or projectId`);if(!i&&!o)throw Error(`feature flag check requires serverId or externalId`);let s={key:e,...r?{projectId:r}:{},...i?{identifier:i}:{externalId:o}};t.attributes&&Object.keys(t.attributes).length>0&&(s.attributes=t.attributes);let c={"Content-Type":`application/json`};n&&(c.Authorization=`Bearer ${n}`);let l=a(t.baseUrl),u=await fetch(l,{method:`POST`,headers:c,body:JSON.stringify(s),credentials:`omit`,signal:t.signal});if(!u.ok){let e=``;try{let t=await u.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${u.status}${e}`)}return await u.json()}function m(e){return e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0}function h(e,t,n){if(!(e.sessionReplays?.enabled??e.trackReplay??m(e)))return null;let r=e.replayOptions??{};return{siteKey:e.siteKey,baseUrl:t,debug:n,...r,samplingPercentage:d(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}let g=null,_;function v(){return g}function y(e,t){typeof window>`u`||E()||g?.track(e,t??{})}function b(e,t,n){typeof window>`u`||E()||g?.identify(e,t,n??{})}function x(e=!0){typeof window>`u`||E()||g?.logout(e)}function S(e){if(g){g.setConsentMode(e);return}_=e}function C(){S(`granted`)}function w(){S(`denied`)}function T(e){typeof window>`u`||E()||g?.reportError(e)}function E(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function D(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`,useBeacon:s=!0,keepalive:c=!0}=e;if(s&&typeof navigator<`u`&&typeof navigator.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}if(typeof fetch!=`function`)return a&&console.warn(`${o} Failed to send`),!1;try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),s=e.ok;return a&&(s?console.log(`${o} Sent via fetch`):console.warn(`${o} Failed: ${e.status}`)),s}catch{return a&&console.warn(`${o} Failed to send`),!1}}function O(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function k(){let e={};if(!location.search)return e;let t=new URLSearchParams(location.search);for(let n of[`utm_source`,`utm_medium`,`utm_campaign`,`utm_term`,`utm_content`]){let r=t.get(n);r&&(e[n]=r)}return e}var A=class{webEndpoint;baseUrl;featureFlagsBaseUrl;debug;started=!1;destroyed=!1;pageKey=``;navTimer=null;heartbeatTimer=null;scrollDepth=0;pageEntryTime=0;pagePath=``;pageUrl=``;pageHash=``;hasLeftCurrentPage=!1;scrollHandler=null;consentMode;cookielessWhilePending;cleanupCallbacks=[];childTrackers=[];pendingReportedErrors=[];errorTracker=null;handleVisibilityChange=()=>{document.visibilityState===`hidden`?(this.leavePage(),this.stopHeartbeat()):this.startHeartbeat()};handlePageHide=()=>{this.leavePage()};handlePopState=()=>{this.navigate()};handleHashChange=()=>{this.navigate()};constructor(e){this.options=e,this.baseUrl=n(e.baseUrl),this.featureFlagsBaseUrl=r(e.featureFlagsBaseUrl),this.webEndpoint=o(this.baseUrl),this.debug=e.debug??!1,this.consentMode=e.consent?.mode??`granted`,this.cookielessWhilePending=e.consent?.cookielessWhilePending??!0,_!==void 0&&(this.consentMode=_,_=void 0),(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(E()){this.log(`disabled`);return}g=this,t(this.isCookielessMode()),setTimeout(()=>void this.start(),0)}}registerCleanup(e){this.cleanupCallbacks.push(e)}addWindowListener(e,t){window.addEventListener(e,t),this.registerCleanup(()=>window.removeEventListener(e,t))}addDocumentListener(e,t){document.addEventListener(e,t),this.registerCleanup(()=>document.removeEventListener(e,t))}patchHistory(){let e=history.pushState.bind(history),t=history.replaceState.bind(history);history.pushState=(t,n,r)=>{e(t,n,r),this.navigate()},history.replaceState=(e,n,r)=>{t(e,n,r),this.navigate()},this.registerCleanup(()=>{history.pushState=e,history.replaceState=t})}ensureStarted(){return typeof window>`u`||this.destroyed||E()?!1:(this.started||this.start(),!0)}stopHeartbeat(){this.heartbeatTimer&&=(clearInterval(this.heartbeatTimer),null)}stopNavigationTimer(){this.navTimer&&=(clearTimeout(this.navTimer),null)}stopChildTrackers(){for(let e of this.childTrackers.splice(0))e.stop?.();this.errorTracker=null}registerChildTracker(e){return!this.started||this.destroyed||g!==this?(e.stop?.(),!1):(this.childTrackers.push(e),!0)}async startErrorTracker(){try{let{default:e}=await import(`./error-asweBGYd.js`);if(!this.started||this.destroyed||g!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug});if(t.start(),!this.registerChildTracker(t))return;for(this.errorTracker=t;this.pendingReportedErrors.length>0;){let e=this.pendingReportedErrors.shift();e&&t.captureError(e)}this.log(`error loaded`)}catch(e){this.log(`failed to initialize error tracker: ${String(e)}`)}}async startWebVitalsTracker(){try{let{default:e}=await import(`./web-vitals-gcUR-TX_.js`);if(!this.started||this.destroyed||g!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:d(this.options.webVitals?.sampling?.percentage)});if(t.start(),!this.registerChildTracker(t))return;this.log(`web-vitals loaded`)}catch(e){this.log(`failed to initialize web-vitals tracker: ${String(e)}`)}}async startReplayTracker(e){try{let{default:t}=await import(`./replay-DUA5c3Jf.js`);if(!this.started||this.destroyed||g!==this)return;let n=new t(e);if(n.start(),!this.registerChildTracker(n))return;this.log(`replay loaded`)}catch(e){this.log(`failed to initialize replay tracker: ${String(e)}`)}}async start(){if(this.started||this.destroyed||typeof window>`u`)return;if(g&&g!==this){this.log(`already started by another instance`);return}if(E()){this.log(`disabled`);return}this.started=!0,g=this,t(this.isCookielessMode()),l();let e=this.options,n=h(e,this.baseUrl,this.debug);n&&this.startReplayTracker(n),e.trackErrors&&this.startErrorTracker(),e.trackWebVitals&&this.startWebVitalsTracker(),this.enterPage(),this.pageview({trigger:`load`}),this.links(),this.trackScroll(),this.startHeartbeat(),this.addDocumentListener(`visibilitychange`,this.handleVisibilityChange),this.addWindowListener(`pagehide`,this.handlePageHide),this.addWindowListener(`popstate`,this.handlePopState),e.trackHash&&this.addWindowListener(`hashchange`,this.handleHashChange),this.patchHistory()}destroy(){if(!this.destroyed){for(this.started&&typeof window<`u`&&this.leavePage(),this.pendingReportedErrors.length=0,this.stopNavigationTimer(),this.stopHeartbeat(),this.scrollHandler&&typeof window<`u`&&(window.removeEventListener(`scroll`,this.scrollHandler),this.scrollHandler=null);this.cleanupCallbacks.length>0;)this.cleanupCallbacks.pop()?.();this.stopChildTrackers(),g===this&&(g=null),this.started=!1,this.destroyed=!0}}pageview(e={}){if(!this.ensureStarted())return;let t=`${location.pathname}|${this.options.trackHash??!1?location.hash:``}`;t!==this.pageKey&&(this.pageKey=t,this.send(`pageview`,e))}track(e,t={}){this.ensureStarted()&&this.send(e,t)}identify(e,t,n={}){if(!this.ensureStarted()||this.isCookielessMode())return;let r=e.trim(),i=t.trim();!r||!i||D({url:f(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:s(!1),externalId:r,email:i,name:n.name?.trim()||void 0,phone:n.phone?.trim()||void 0,avatarUrl:n.avatarUrl?.trim()||void 0,traits:n.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`})}logout(e=!0){this.ensureStarted()&&(e&&c(this.isCookielessMode()),u())}setConsentMode(e){this.consentMode=e,t(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return s(this.isCookielessMode())}getSessionId(){return l()}async checkFeatureFlag(e,t,n){if(typeof window>`u`||E())return{value:`false`};let r=n?.externalId?.trim();if(r)return p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,externalId:r,attributes:t,signal:n?.signal});let i=this.getAnonymousId();return i?p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,identifier:i,attributes:t,signal:n?.signal}):{value:`false`}}reportError(e){if(this.destroyed||typeof window>`u`||E()||!(this.options.trackErrors??!1)||!this.ensureStarted())return;let t=this.errorTracker;if(t){t.captureError(e);return}this.pendingReportedErrors.length>=50&&this.pendingReportedErrors.shift(),this.pendingReportedErrors.push(e)}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){if(typeof window>`u`||this.destroyed||E())return;let n=s(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:l(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...k(),...t}});this.log(e),D({url:this.webEndpoint,data:r,contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] ${e}`})}enterPage(){this.pageEntryTime=Date.now(),this.pagePath=location.pathname,this.pageUrl=location.href,this.pageHash=location.hash,this.scrollDepth=0,this.hasLeftCurrentPage=!1}leavePage(){if(this.destroyed||this.hasLeftCurrentPage)return;this.hasLeftCurrentPage=!0;let e=Date.now();this.send(`page_leave`,{page:this.pagePath,url:this.pageUrl,time_on_page:e-this.pageEntryTime,scroll_depth:this.scrollDepth,session_duration:e-i()})}trackScroll(){this.scrollHandler&&window.removeEventListener(`scroll`,this.scrollHandler);let e=()=>{let e=document.documentElement,t=document.body,n=window.innerHeight,r=Math.max(e.scrollHeight,t.scrollHeight);if(r<=n){this.scrollDepth=100;return}let i=Math.min(100,Math.round(((window.scrollY||e.scrollTop)+n)/r*100));i>this.scrollDepth&&(this.scrollDepth=i)};this.scrollHandler=e,e(),window.addEventListener(`scroll`,e,{passive:!0})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){this.stopHeartbeat();return}e()},300*1e3)}navigate(){!this.started||this.destroyed||(this.stopNavigationTimer(),this.navTimer=setTimeout(()=>{this.navTimer=null;let e=location.pathname!==this.pagePath,t=(this.options.trackHash??!1)&&location.hash!==this.pageHash;!e&&!t||(this.leavePage(),this.enterPage(),this.trackScroll(),this.pageview({trigger:`navigation`}))},300))}links(){let e=e=>{let t=O(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{x as a,T as c,y as d,p as f,E as i,D as l,v as n,C as o,b as r,w as s,A as t,S as u};