@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.
- package/CHANGELOG.md +19 -0
- package/dist/chunks/analytics-Cmawx5f9.js +1 -0
- package/dist/chunks/api-urls-BrkcoElX.js +1 -0
- package/dist/{error-asweBGYd.js → chunks/error-1K0dai1m.js} +2 -2
- package/dist/chunks/feature-flags-6rZlmhfu.d.ts +18 -0
- package/dist/chunks/feature-flags-BPRTgvIh.js +1 -0
- package/dist/chunks/replay-CtMtn0C-.js +1 -0
- package/dist/chunks/replay-IhsP2Ab4.d.ts +77 -0
- package/dist/chunks/rolldown-runtime-MP-BAFHD.js +1 -0
- package/dist/chunks/types-C3vW7XGe.js +1 -0
- package/dist/chunks/web-vitals-BooBPWJC.js +1 -0
- package/dist/error.d.ts +44 -0
- package/dist/error.js +1 -0
- package/dist/feature-flags.d.ts +2 -0
- package/dist/feature-flags.js +1 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +1 -0
- package/dist/replay.d.ts +2 -0
- package/dist/replay.js +1 -0
- package/dist/web-vitals.d.ts +27 -0
- package/dist/web-vitals.js +1 -0
- package/package.json +32 -11
- package/scripts/check-bundle-size.mjs +90 -9
- package/src/analytics.ts +47 -41
- package/src/entries/error.ts +6 -0
- package/src/entries/feature-flags.ts +5 -0
- package/src/entries/main.ts +21 -0
- package/src/entries/replay.ts +1 -0
- package/src/entries/web-vitals.ts +1 -0
- package/src/env.d.ts +2 -0
- package/src/error.ts +5 -0
- package/src/replay.ts +104 -59
- package/src/sdk.ts +8 -0
- package/src/utils/types.ts +1 -1
- package/src/web-vitals.ts +32 -14
- package/tests/replay.test.ts +150 -21
- package/tsdown.config.ts +16 -1
- package/dist/analytics-Ct-lghlf.js +0 -1
- package/dist/module.d.ts +0 -531
- package/dist/module.js +0 -1
- package/dist/replay-DUA5c3Jf.js +0 -1
- package/dist/types-Df70G0eM.js +0 -1
- package/dist/web-vitals-gcUR-TX_.js +0 -1
- package/src/module.ts +0 -25
package/tests/replay.test.ts
CHANGED
|
@@ -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 |
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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};
|