@faststats/web 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -13
- package/dist/chunks/{identifiers-CpmDmpDv.js → identifiers-DOtve3E5.js} +1 -1
- package/dist/chunks/{send-data-DYZZjeM-.js → send-data-C76HbZ8d.js} +18 -6
- package/dist/chunks/{session-manager-CFQEWbgo.js → session-manager-DUpF3mi3.js} +4 -7
- package/dist/error.js +4 -4
- package/dist/flags.js +3 -3
- package/dist/index.d.ts +7 -0
- package/dist/index.js +62 -27
- package/dist/replay.d.ts +2 -1
- package/dist/replay.js +5 -6
- package/dist/web-vitals.d.ts +3 -1
- package/dist/web-vitals.js +66 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,32 +48,48 @@ Consent remains client-local: use `"anonymous"` for cookieless collection,
|
|
|
48
48
|
|
|
49
49
|
### Visit duration
|
|
50
50
|
|
|
51
|
-
`
|
|
52
|
-
that the page was visible and its document had focus. It pauses when the tab is
|
|
51
|
+
`properties.time_on_page` on `page_ping` and `page_leave` is the accumulated
|
|
52
|
+
time in milliseconds that the page was visible and its document had focus. It pauses when the tab is
|
|
53
53
|
hidden, the window loses focus, or the page enters the back-forward cache, and
|
|
54
54
|
resets on navigation, logout, or session rotation. A monotonic clock keeps
|
|
55
|
-
system-clock changes from affecting this duration.
|
|
56
|
-
|
|
55
|
+
system-clock changes from affecting this duration. It also pauses after 60
|
|
56
|
+
seconds without pointer, keyboard, or scroll input, unless a `<video>` or
|
|
57
|
+
`<audio>` element on the page is playing, and resumes on the next input. Time
|
|
58
|
+
the device spent asleep or the tab frozen is not counted.
|
|
57
59
|
|
|
58
60
|
Pageviews wait until the page becomes visible. Pages replaced or closed before
|
|
59
61
|
being shown do not emit pageviews or page-leave events.
|
|
60
62
|
|
|
61
|
-
The SDK sends cumulative `
|
|
62
|
-
and
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
session
|
|
63
|
+
The SDK sends a cumulative `page_ping` when activity pauses and periodically
|
|
64
|
+
while active: after 15 and 45 seconds, then every 60 seconds. A ping that adds no
|
|
65
|
+
time is suppressed. A visit ends with
|
|
66
|
+
exactly one `page_leave`, on navigation, a terminal `pagehide`, logout, a consent
|
|
67
|
+
change, or session rotation. A page the browser discards without notice never
|
|
68
|
+
sends one, so its last ping holds its duration. A pageview, its pings, and its
|
|
69
|
+
leave share `properties.visit_id`; consumers should use the maximum
|
|
70
|
+
`time_on_page` per visit across both events, never sum them.
|
|
71
|
+
|
|
72
|
+
Before 0.13 the pings were sent as `page_leave` every 15 seconds. The collector
|
|
73
|
+
and dashboards read both shapes; deploy them before releasing this SDK.
|
|
74
|
+
|
|
75
|
+
### Default context
|
|
76
|
+
|
|
77
|
+
Every event carries `timezone` (IANA name), `language`, and `screen`
|
|
78
|
+
(`1920x1080`, CSS pixels) beside the page context, separate from its properties.
|
|
79
|
+
Custom events and outbound links also carry the `visit_id` of the page view they
|
|
80
|
+
happened during.
|
|
66
81
|
|
|
67
82
|
Each visit retains its original identity, session, and page metadata. A session
|
|
68
83
|
or identity change starts a new visit, even on the same route. Both persistent
|
|
69
84
|
and cookieless sessions expire after 30 minutes of inactivity or 24 hours of age.
|
|
70
85
|
|
|
71
|
-
`session_duration` is elapsed wall-clock time
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
`session_duration` is elapsed wall-clock time from the session start to the last
|
|
87
|
+
counted activity. It includes time away between two active periods of the same
|
|
88
|
+
session, so it is separate from active time on the page. An idle visitor stops
|
|
89
|
+
extending the session, which then expires after 30 minutes like any other.
|
|
74
90
|
|
|
75
91
|
Delivery is best effort: a sudden process kill or network failure can lose time
|
|
76
|
-
since the last delivered
|
|
92
|
+
since the last delivered ping. The collector must accept successive events
|
|
77
93
|
for the same visitor and URL; deploy the removal of its five-second debounce
|
|
78
94
|
before releasing this SDK.
|
|
79
95
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as setStorageItem, o as getStorageItem, s as removeStorageItem, t as createId } from "./session-manager-
|
|
1
|
+
import { c as setStorageItem, o as getStorageItem, s as removeStorageItem, t as createId } from "./session-manager-DUpF3mi3.js";
|
|
2
2
|
//#region src/utils/identifiers.ts
|
|
3
3
|
const ANONYMOUS_ID_KEY = "faststats_anon_id_";
|
|
4
4
|
function getAnonymousId(siteKey, cookieless = false) {
|
|
@@ -27,18 +27,30 @@ function getReferrer() {
|
|
|
27
27
|
const raw = document.referrer;
|
|
28
28
|
if (!raw) return null;
|
|
29
29
|
try {
|
|
30
|
-
|
|
31
|
-
if (new URL(raw, location.href).origin === pageOrigin) return null;
|
|
30
|
+
if (new URL(raw, location.href).origin === location.origin) return null;
|
|
32
31
|
} catch {}
|
|
33
32
|
return sanitizeUrl(raw);
|
|
34
33
|
}
|
|
34
|
+
/** Visitor environment sent with every event; unavailable values are omitted. */
|
|
35
|
+
function getClientContext() {
|
|
36
|
+
let timezone;
|
|
37
|
+
try {
|
|
38
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
39
|
+
} catch {}
|
|
40
|
+
const { width, height } = globalThis.screen ?? {};
|
|
41
|
+
return {
|
|
42
|
+
timezone,
|
|
43
|
+
language: globalThis.navigator?.language,
|
|
44
|
+
screen: width && height ? `${width}x${height}` : void 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
35
47
|
function getPageContext(includeHash = false) {
|
|
36
48
|
const hash = includeHash ? location.hash : "";
|
|
37
49
|
const ctx = {
|
|
38
50
|
page: `${location.pathname}${hash}`,
|
|
39
51
|
url: sanitizeUrl(location.href, includeHash),
|
|
40
52
|
referrer: getReferrer(),
|
|
41
|
-
title: document.title
|
|
53
|
+
title: document.title
|
|
42
54
|
};
|
|
43
55
|
if (!location.search) return ctx;
|
|
44
56
|
const sp = new URLSearchParams(location.search);
|
|
@@ -53,8 +65,8 @@ function getPageContext(includeHash = false) {
|
|
|
53
65
|
async function sendData(options) {
|
|
54
66
|
const { url, data, contentType = "application/json", debug = false, debugPrefix = "[Analytics]", useBeacon = true, keepalive = true, settleRejected } = options;
|
|
55
67
|
if (useBeacon && typeof globalThis.navigator?.sendBeacon === "function") try {
|
|
56
|
-
const
|
|
57
|
-
if (globalThis.navigator.sendBeacon(url,
|
|
68
|
+
const body = data instanceof Blob ? data : new Blob([data], { type: contentType });
|
|
69
|
+
if (globalThis.navigator.sendBeacon(url, body)) {
|
|
58
70
|
if (debug) console.log(`${debugPrefix} Sent via beacon`);
|
|
59
71
|
return true;
|
|
60
72
|
}
|
|
@@ -81,4 +93,4 @@ async function sendData(options) {
|
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
//#endregion
|
|
84
|
-
export {
|
|
96
|
+
export { resolveBaseUrl as a, getPageContext as i, URLS as n, sanitizeUrl as o, getClientContext as r, sendData as t };
|
|
@@ -137,15 +137,12 @@ function resetSession(siteKey, cookieless = false) {
|
|
|
137
137
|
}
|
|
138
138
|
function onSessionRotated(siteKey, cookieless, listener) {
|
|
139
139
|
const key = memoryKey(siteKey, cookieless);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
listeners = /* @__PURE__ */ new Set();
|
|
143
|
-
rotationListeners.set(key, listeners);
|
|
144
|
-
}
|
|
140
|
+
const listeners = rotationListeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
141
|
+
rotationListeners.set(key, listeners);
|
|
145
142
|
listeners.add(listener);
|
|
146
143
|
return () => {
|
|
147
|
-
listeners
|
|
148
|
-
if (listeners
|
|
144
|
+
listeners.delete(listener);
|
|
145
|
+
if (listeners.size === 0) rotationListeners.delete(key);
|
|
149
146
|
};
|
|
150
147
|
}
|
|
151
148
|
//#endregion
|
package/dist/error.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as getSessionContext } from "./chunks/session-manager-
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
1
|
+
import { a as resolveBaseUrl, i as getPageContext, n as URLS, t as sendData } from "./chunks/send-data-C76HbZ8d.js";
|
|
2
|
+
import { n as getSessionContext } from "./chunks/session-manager-DUpF3mi3.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
|
|
4
4
|
//#region src/error.ts
|
|
5
5
|
const SDK_NAME = "@faststats/web";
|
|
6
|
-
const SDK_VERSION = "0.
|
|
6
|
+
const SDK_VERSION = "0.13.0";
|
|
7
7
|
function errorTracking(options = {}) {
|
|
8
8
|
return {
|
|
9
9
|
name: "error-tracking",
|
package/dist/flags.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as getSessionContext } from "./chunks/session-manager-
|
|
2
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
1
|
+
import { n as getSessionContext } from "./chunks/session-manager-DUpF3mi3.js";
|
|
2
|
+
import { t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
|
|
3
3
|
//#region src/flags.ts
|
|
4
4
|
const DEFAULT_ENDPOINT = "https://flags.faststats.dev";
|
|
5
5
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
@@ -138,7 +138,7 @@ var FeatureFlags = class {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
-
function featureFlags(options
|
|
141
|
+
function featureFlags(options) {
|
|
142
142
|
return new FeatureFlags(options);
|
|
143
143
|
}
|
|
144
144
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export declare class WebAnalytics {
|
|
|
38
38
|
private pageKey;
|
|
39
39
|
private visit;
|
|
40
40
|
private pendingPageview;
|
|
41
|
+
private lastInputAt;
|
|
41
42
|
private consentMode;
|
|
42
43
|
constructor(options: WebAnalyticsOptions);
|
|
43
44
|
start(): void;
|
|
@@ -65,7 +66,13 @@ export declare class WebAnalytics {
|
|
|
65
66
|
private readonly onPageHide;
|
|
66
67
|
private readonly onPageShow;
|
|
67
68
|
private readonly onResume;
|
|
69
|
+
private readonly onActivity;
|
|
70
|
+
private isIdle;
|
|
68
71
|
private leavePage;
|
|
72
|
+
/**
|
|
73
|
+
* Reports the visit's running totals: as a `page_ping` while it may still
|
|
74
|
+
* continue, and as its single `page_leave` once it has ended.
|
|
75
|
+
*/
|
|
69
76
|
private checkpoint;
|
|
70
77
|
private pauseVisit;
|
|
71
78
|
private resumeVisit;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as touchActivity, i as resetSession, n as getSessionContext, t as createId } from "./chunks/session-manager-
|
|
3
|
-
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-
|
|
1
|
+
import { a as resolveBaseUrl, i as getPageContext, n as URLS, o as sanitizeUrl, r as getClientContext, t as sendData } from "./chunks/send-data-C76HbZ8d.js";
|
|
2
|
+
import { a as touchActivity, i as resetSession, n as getSessionContext, t as createId } from "./chunks/session-manager-DUpF3mi3.js";
|
|
3
|
+
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
|
|
4
4
|
//#region src/extensions.ts
|
|
5
5
|
var ExtensionHost = class {
|
|
6
6
|
context;
|
|
@@ -40,11 +40,8 @@ var ExtensionHost = class {
|
|
|
40
40
|
...this.context,
|
|
41
41
|
on: (hook, handler) => {
|
|
42
42
|
if (this.stopped) return () => {};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
handlers = /* @__PURE__ */ new Set();
|
|
46
|
-
this.handlers.set(hook, handlers);
|
|
47
|
-
}
|
|
43
|
+
const handlers = this.handlers.get(hook) ?? /* @__PURE__ */ new Set();
|
|
44
|
+
this.handlers.set(hook, handlers);
|
|
48
45
|
handlers.add(handler);
|
|
49
46
|
const off = () => handlers.delete(handler);
|
|
50
47
|
subscriptions.push(off);
|
|
@@ -128,10 +125,12 @@ function observeHistory(listener) {
|
|
|
128
125
|
}
|
|
129
126
|
const RESERVED_EVENTS = /* @__PURE__ */ new Set([
|
|
130
127
|
"pageview",
|
|
128
|
+
"page_ping",
|
|
131
129
|
"page_leave",
|
|
132
130
|
"error",
|
|
133
131
|
"outbound_link"
|
|
134
132
|
]);
|
|
133
|
+
const IDLE_MS = 6e4;
|
|
135
134
|
var WebAnalytics = class {
|
|
136
135
|
options;
|
|
137
136
|
baseUrl;
|
|
@@ -143,6 +142,7 @@ var WebAnalytics = class {
|
|
|
143
142
|
pageKey = "";
|
|
144
143
|
visit = null;
|
|
145
144
|
pendingPageview = null;
|
|
145
|
+
lastInputAt = 0;
|
|
146
146
|
consentMode;
|
|
147
147
|
constructor(options) {
|
|
148
148
|
this.options = options;
|
|
@@ -170,7 +170,7 @@ var WebAnalytics = class {
|
|
|
170
170
|
const key = location.pathname + location.search + (this.options.trackHash ? location.hash : "");
|
|
171
171
|
if (key !== this.pageKey) {
|
|
172
172
|
this.leavePage();
|
|
173
|
-
if (this.pageKey) this.extensions?.emit("pageChange", { url: getPageContext(this.options.trackHash).url
|
|
173
|
+
if (this.pageKey) this.extensions?.emit("pageChange", { url: getPageContext(this.options.trackHash).url });
|
|
174
174
|
this.pageKey = key;
|
|
175
175
|
this.pendingPageview = null;
|
|
176
176
|
}
|
|
@@ -190,7 +190,10 @@ var WebAnalytics = class {
|
|
|
190
190
|
context,
|
|
191
191
|
duration: 0,
|
|
192
192
|
startedAt: null,
|
|
193
|
-
|
|
193
|
+
checkedAt: Date.now(),
|
|
194
|
+
activeAt: Date.now(),
|
|
195
|
+
reportedDuration: -1,
|
|
196
|
+
beats: 0
|
|
194
197
|
};
|
|
195
198
|
this.send("pageview", {
|
|
196
199
|
...properties,
|
|
@@ -291,6 +294,7 @@ var WebAnalytics = class {
|
|
|
291
294
|
}
|
|
292
295
|
context(session = this.session()) {
|
|
293
296
|
return {
|
|
297
|
+
...getClientContext(),
|
|
294
298
|
...getPageContext(this.options.trackHash),
|
|
295
299
|
userId: this.getAnonymousId() || void 0,
|
|
296
300
|
sessionId: session.sessionId,
|
|
@@ -326,12 +330,18 @@ var WebAnalytics = class {
|
|
|
326
330
|
this.bind(window, "focus", this.onResume);
|
|
327
331
|
this.bind(window, "blur", () => this.pauseVisit());
|
|
328
332
|
this.bind(window, "popstate", this.onNavigate);
|
|
333
|
+
for (const type of [
|
|
334
|
+
"pointerdown",
|
|
335
|
+
"pointermove",
|
|
336
|
+
"keydown",
|
|
337
|
+
"scroll"
|
|
338
|
+
]) this.bind(window, type, this.onActivity, true);
|
|
329
339
|
if (this.options.trackHash) this.bind(window, "hashchange", this.onNavigate);
|
|
330
340
|
this.cleanup.push(observeHistory(this.onNavigate), trackOutboundLinks((url) => this.send("outbound_link", { outbound_link: url })));
|
|
331
341
|
}
|
|
332
|
-
bind(target, type, listener) {
|
|
333
|
-
target.addEventListener(type, listener);
|
|
334
|
-
this.cleanup.push(() => target.removeEventListener(type, listener));
|
|
342
|
+
bind(target, type, listener, capture = false) {
|
|
343
|
+
target.addEventListener(type, listener, capture);
|
|
344
|
+
this.cleanup.push(() => target.removeEventListener(type, listener, capture));
|
|
335
345
|
}
|
|
336
346
|
onNavigate = () => this.pageview({ trigger: "navigation" });
|
|
337
347
|
onVisibilityChange = () => {
|
|
@@ -349,12 +359,12 @@ var WebAnalytics = class {
|
|
|
349
359
|
onPageHide = (event) => {
|
|
350
360
|
if (!this.canTrack()) return;
|
|
351
361
|
const persisted = event.persisted;
|
|
352
|
-
this.pauseVisit();
|
|
362
|
+
if (persisted) this.pauseVisit();
|
|
363
|
+
else this.leavePage();
|
|
353
364
|
this.extensions?.emit("pageHide", {
|
|
354
365
|
persisted,
|
|
355
366
|
terminal: !persisted
|
|
356
367
|
});
|
|
357
|
-
if (!persisted) this.leavePage();
|
|
358
368
|
};
|
|
359
369
|
onPageShow = (event) => {
|
|
360
370
|
if (!this.canTrack()) return;
|
|
@@ -365,49 +375,74 @@ var WebAnalytics = class {
|
|
|
365
375
|
this.pageview(this.pendingPageview ?? { trigger: "session" });
|
|
366
376
|
this.resumeVisit();
|
|
367
377
|
};
|
|
378
|
+
onActivity = () => {
|
|
379
|
+
this.lastInputAt = performance.now();
|
|
380
|
+
if (this.visit?.startedAt === null && document.hasFocus()) this.onResume();
|
|
381
|
+
};
|
|
382
|
+
isIdle() {
|
|
383
|
+
const now = performance.now();
|
|
384
|
+
if (now - this.lastInputAt < IDLE_MS) return false;
|
|
385
|
+
const playing = [...document.querySelectorAll("video,audio")].some((media) => !media.paused && !media.ended);
|
|
386
|
+
if (playing) this.lastInputAt = now;
|
|
387
|
+
return !playing;
|
|
388
|
+
}
|
|
368
389
|
leavePage() {
|
|
369
|
-
this.pauseVisit();
|
|
390
|
+
this.pauseVisit(true);
|
|
370
391
|
this.visit = null;
|
|
371
392
|
}
|
|
372
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Reports the visit's running totals: as a `page_ping` while it may still
|
|
395
|
+
* continue, and as its single `page_leave` once it has ended.
|
|
396
|
+
*/
|
|
397
|
+
checkpoint(leaving = false, periodic = false) {
|
|
373
398
|
const visit = this.visit;
|
|
374
399
|
if (!visit || !this.isActive()) return;
|
|
375
400
|
if (visit.startedAt !== null) {
|
|
376
401
|
const now = performance.now();
|
|
377
|
-
|
|
402
|
+
if (now - visit.startedAt < IDLE_MS) {
|
|
403
|
+
const active = Math.min(now, this.lastInputAt + IDLE_MS) - visit.startedAt;
|
|
404
|
+
visit.duration += active;
|
|
405
|
+
visit.activeAt = visit.checkedAt + active;
|
|
406
|
+
}
|
|
378
407
|
visit.startedAt = now;
|
|
408
|
+
visit.checkedAt = Date.now();
|
|
379
409
|
}
|
|
410
|
+
if (periodic && ++visit.beats !== 1 && visit.beats % 4 !== 3) return;
|
|
380
411
|
const duration = Math.round(visit.duration);
|
|
381
|
-
if (duration === visit.reportedDuration) return;
|
|
412
|
+
if (!leaving && duration === visit.reportedDuration) return;
|
|
382
413
|
visit.reportedDuration = duration;
|
|
383
|
-
this.send("page_leave", {
|
|
414
|
+
this.send(leaving ? "page_leave" : "page_ping", {
|
|
384
415
|
visit_id: visit.id,
|
|
385
416
|
time_on_page: duration,
|
|
386
|
-
session_duration: Math.max(0,
|
|
417
|
+
session_duration: Math.max(0, visit.activeAt - visit.session.sessionStart)
|
|
387
418
|
}, visit.context);
|
|
388
419
|
}
|
|
389
|
-
pauseVisit() {
|
|
420
|
+
pauseVisit(leaving = false) {
|
|
390
421
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
391
422
|
this.heartbeatTimer = null;
|
|
392
|
-
this.checkpoint();
|
|
423
|
+
this.checkpoint(leaving);
|
|
393
424
|
if (this.visit) this.visit.startedAt = null;
|
|
394
425
|
}
|
|
395
426
|
resumeVisit() {
|
|
396
427
|
if (!this.isActive() || !this.visit || this.visit.startedAt !== null || document.visibilityState !== "visible" || !document.hasFocus()) return;
|
|
397
|
-
this.visit.startedAt = performance.now();
|
|
428
|
+
this.visit.startedAt = this.lastInputAt = performance.now();
|
|
429
|
+
this.visit.checkedAt = Date.now();
|
|
398
430
|
this.heartbeatTimer = setInterval(() => {
|
|
399
|
-
if (document.visibilityState !== "visible" || !document.hasFocus()) {
|
|
431
|
+
if (document.visibilityState !== "visible" || !document.hasFocus() || this.isIdle()) {
|
|
400
432
|
this.pauseVisit();
|
|
401
433
|
return;
|
|
402
434
|
}
|
|
403
435
|
const visit = this.visit;
|
|
404
436
|
this.pageview({ trigger: "session" });
|
|
405
|
-
if (this.visit === visit) this.checkpoint();
|
|
437
|
+
if (this.visit === visit) this.checkpoint(false, true);
|
|
406
438
|
}, 15e3);
|
|
407
439
|
}
|
|
408
440
|
send(event, properties = {}, context) {
|
|
409
441
|
if (!this.isActive()) return;
|
|
410
|
-
context ??=
|
|
442
|
+
context ??= {
|
|
443
|
+
...this.context(),
|
|
444
|
+
visit_id: this.visit?.id
|
|
445
|
+
};
|
|
411
446
|
let data;
|
|
412
447
|
try {
|
|
413
448
|
data = JSON.stringify({
|
package/dist/replay.d.ts
CHANGED
package/dist/replay.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as touchActivity, c as setStorageItem, n as getSessionContext, o as getStorageItem, r as onSessionRotated, t as createId } from "./chunks/session-manager-
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
1
|
+
import { a as resolveBaseUrl, n as URLS, o as sanitizeUrl, t as sendData } from "./chunks/send-data-C76HbZ8d.js";
|
|
2
|
+
import { a as touchActivity, c as setStorageItem, n as getSessionContext, o as getStorageItem, r as onSessionRotated, t as createId } from "./chunks/session-manager-DUpF3mi3.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
|
|
4
4
|
import { record } from "@rrweb/record";
|
|
5
5
|
import { getRecordConsolePlugin } from "@rrweb/rrweb-plugin-console-record";
|
|
6
6
|
import { getRecordNetworkPlugin } from "@rrweb/rrweb-plugin-network-record";
|
|
@@ -26,7 +26,7 @@ var ReplayMutationThrottler = class {
|
|
|
26
26
|
if (data.source !== MUTATION_SOURCE_TYPE || !data.attributes?.length) return event;
|
|
27
27
|
const attributes = data.attributes.filter(({ id }) => this.consume(id));
|
|
28
28
|
if (attributes.length === data.attributes.length) return event;
|
|
29
|
-
if (attributes.length
|
|
29
|
+
if (!attributes.length && !data.adds?.length && !data.removes?.length && !data.texts?.length) return;
|
|
30
30
|
return {
|
|
31
31
|
...event,
|
|
32
32
|
data: {
|
|
@@ -482,12 +482,11 @@ var ReplayTracker = class {
|
|
|
482
482
|
return;
|
|
483
483
|
}
|
|
484
484
|
if (this.minLengthFlushTask) return;
|
|
485
|
-
const remainingMs = Math.max(0, this.minReplayLengthMs - (Date.now() - this.startTime));
|
|
486
485
|
this.minLengthFlushTask = setTimeout(() => {
|
|
487
486
|
this.minLengthFlushTask = null;
|
|
488
487
|
this.initialFlushPending = false;
|
|
489
488
|
this.requestFlush("minLength");
|
|
490
|
-
},
|
|
489
|
+
}, this.minReplayLengthMs - (Date.now() - this.startTime));
|
|
491
490
|
}
|
|
492
491
|
enqueueEventsIfReady(context, flushReason, maxBatchSizeBytes = this.maxBatchSizeBytes) {
|
|
493
492
|
if (this.events.length === 0) return;
|
package/dist/web-vitals.d.ts
CHANGED
|
@@ -16,7 +16,8 @@ export default class WebVitalsTracker {
|
|
|
16
16
|
private sending?;
|
|
17
17
|
private generation;
|
|
18
18
|
private unsubscribe?;
|
|
19
|
-
private
|
|
19
|
+
private unfollowSession?;
|
|
20
|
+
private sentMetrics;
|
|
20
21
|
private initialUrl;
|
|
21
22
|
private session;
|
|
22
23
|
private cookieless;
|
|
@@ -25,6 +26,7 @@ export default class WebVitalsTracker {
|
|
|
25
26
|
start(): void;
|
|
26
27
|
stop(discard?: boolean): void;
|
|
27
28
|
onPageHidden(persisted?: boolean): void;
|
|
29
|
+
private followSession;
|
|
28
30
|
private onMetric;
|
|
29
31
|
private discard;
|
|
30
32
|
private flush;
|
package/dist/web-vitals.js
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { n as getSessionContext } from "./chunks/session-manager-
|
|
1
|
+
import { a as resolveBaseUrl, n as URLS, o as sanitizeUrl, t as sendData } from "./chunks/send-data-C76HbZ8d.js";
|
|
2
|
+
import { n as getSessionContext, r as onSessionRotated } from "./chunks/session-manager-DUpF3mi3.js";
|
|
3
3
|
import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals/attribution";
|
|
4
4
|
//#region src/utils/web-vitals-collector.ts
|
|
5
5
|
const collectors = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function getCollector() {
|
|
7
|
+
const existing = collectors.get(document);
|
|
8
|
+
if (existing) return existing;
|
|
9
|
+
const collector = {
|
|
10
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
11
|
+
latest: /* @__PURE__ */ new Map(),
|
|
12
|
+
sent: /* @__PURE__ */ new Map()
|
|
13
|
+
};
|
|
14
|
+
collectors.set(document, collector);
|
|
15
|
+
const report = (metric) => {
|
|
16
|
+
const snapshot = { ...metric };
|
|
17
|
+
snapshot.attribution = { ...metric.attribution };
|
|
18
|
+
collector.latest.set(metric.name, snapshot);
|
|
19
|
+
for (const listener of collector.subscribers) listener(snapshot);
|
|
20
|
+
};
|
|
21
|
+
const changes = { reportAllChanges: true };
|
|
22
|
+
onCLS(report, changes);
|
|
23
|
+
onINP(report, changes);
|
|
24
|
+
onLCP(report, changes);
|
|
25
|
+
onFCP(report);
|
|
26
|
+
onTTFB(report);
|
|
27
|
+
return collector;
|
|
28
|
+
}
|
|
6
29
|
function subscribeToVitals(subscriber) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
subscribers: /* @__PURE__ */ new Set(),
|
|
11
|
-
latest: /* @__PURE__ */ new Map()
|
|
12
|
-
};
|
|
13
|
-
collectors.set(document, collector);
|
|
14
|
-
const { latest, subscribers } = collector;
|
|
15
|
-
const report = (metric) => {
|
|
16
|
-
const snapshot = { ...metric };
|
|
17
|
-
snapshot.attribution = { ...metric.attribution };
|
|
18
|
-
latest.set(metric.name, snapshot);
|
|
19
|
-
for (const listener of subscribers) listener(snapshot);
|
|
20
|
-
};
|
|
21
|
-
const changes = { reportAllChanges: true };
|
|
22
|
-
onCLS(report, changes);
|
|
23
|
-
onINP(report, changes);
|
|
24
|
-
onLCP(report, changes);
|
|
25
|
-
onFCP(report);
|
|
26
|
-
onTTFB(report);
|
|
27
|
-
}
|
|
28
|
-
collector.subscribers.add(subscriber);
|
|
29
|
-
for (const metric of collector.latest.values()) subscriber(metric);
|
|
30
|
+
const { subscribers, latest } = getCollector();
|
|
31
|
+
subscribers.add(subscriber);
|
|
32
|
+
for (const metric of latest.values()) subscriber(metric);
|
|
30
33
|
return () => {
|
|
31
|
-
|
|
34
|
+
subscribers.delete(subscriber);
|
|
32
35
|
};
|
|
33
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* What `destination` already received from this document. Shared so a
|
|
39
|
+
* re-created tracker does not resend the metrics replayed to it.
|
|
40
|
+
*/
|
|
41
|
+
function sentVitals(destination) {
|
|
42
|
+
const { sent } = getCollector();
|
|
43
|
+
const vitals = sent.get(destination) ?? /* @__PURE__ */ new Map();
|
|
44
|
+
sent.set(destination, vitals);
|
|
45
|
+
return vitals;
|
|
46
|
+
}
|
|
34
47
|
//#endregion
|
|
35
48
|
//#region src/web-vitals.ts
|
|
36
49
|
function webVitals() {
|
|
@@ -51,13 +64,6 @@ function webVitals() {
|
|
|
51
64
|
}
|
|
52
65
|
};
|
|
53
66
|
}
|
|
54
|
-
const METRIC_NAMES = /* @__PURE__ */ new Set([
|
|
55
|
-
"CLS",
|
|
56
|
-
"INP",
|
|
57
|
-
"LCP",
|
|
58
|
-
"FCP",
|
|
59
|
-
"TTFB"
|
|
60
|
-
]);
|
|
61
67
|
var WebVitalsTracker = class {
|
|
62
68
|
options;
|
|
63
69
|
endpoint;
|
|
@@ -66,6 +72,7 @@ var WebVitalsTracker = class {
|
|
|
66
72
|
sending;
|
|
67
73
|
generation = 0;
|
|
68
74
|
unsubscribe;
|
|
75
|
+
unfollowSession;
|
|
69
76
|
sentMetrics = /* @__PURE__ */ new Map();
|
|
70
77
|
initialUrl = "";
|
|
71
78
|
session = null;
|
|
@@ -78,21 +85,21 @@ var WebVitalsTracker = class {
|
|
|
78
85
|
setCookielessMode(cookieless) {
|
|
79
86
|
if (this.cookieless === cookieless) return;
|
|
80
87
|
this.cookieless = cookieless;
|
|
81
|
-
this.
|
|
82
|
-
this.session = this.started ? getSessionContext(this.options.siteKey, cookieless) : null;
|
|
88
|
+
if (this.started) this.followSession();
|
|
83
89
|
}
|
|
84
90
|
start() {
|
|
85
91
|
if (this.started || typeof window === "undefined") return;
|
|
86
92
|
this.started = true;
|
|
87
93
|
this.initialUrl = sanitizeUrl(window.performance?.getEntriesByType("navigation")[0]?.name || window.location.href, this.options.trackHash ?? false);
|
|
88
|
-
this.
|
|
94
|
+
this.followSession();
|
|
95
|
+
this.sentMetrics = sentVitals(`${this.endpoint}\0${this.options.siteKey}`);
|
|
89
96
|
this.unsubscribe = subscribeToVitals(this.onMetric);
|
|
90
97
|
}
|
|
91
98
|
stop(discard = false) {
|
|
92
99
|
if (!this.started) return;
|
|
93
100
|
this.started = false;
|
|
94
101
|
this.unsubscribe?.();
|
|
95
|
-
this.
|
|
102
|
+
this.unfollowSession?.();
|
|
96
103
|
if (discard) this.discard();
|
|
97
104
|
else this.flush();
|
|
98
105
|
}
|
|
@@ -103,26 +110,39 @@ var WebVitalsTracker = class {
|
|
|
103
110
|
if (generation === this.generation) this.flush();
|
|
104
111
|
});
|
|
105
112
|
}
|
|
113
|
+
followSession() {
|
|
114
|
+
const { siteKey } = this.options;
|
|
115
|
+
this.unfollowSession?.();
|
|
116
|
+
this.session = getSessionContext(siteKey, this.cookieless);
|
|
117
|
+
this.unfollowSession = onSessionRotated(siteKey, this.cookieless, (_prev, next) => {
|
|
118
|
+
this.session = next;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
106
121
|
onMetric = (metric) => {
|
|
107
|
-
if (!this.started || !
|
|
122
|
+
if (!this.started || !Number.isFinite(metric.value) || metric.value < 0) return;
|
|
108
123
|
this.metrics.set(metric.name, metric);
|
|
109
124
|
};
|
|
110
125
|
discard() {
|
|
111
126
|
this.generation++;
|
|
112
127
|
this.metrics.clear();
|
|
113
|
-
this.sentMetrics.clear();
|
|
114
128
|
}
|
|
115
129
|
async flush() {
|
|
116
130
|
const generation = this.generation;
|
|
117
131
|
while (this.sending) await this.sending;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
132
|
+
const session = this.session;
|
|
133
|
+
if (generation !== this.generation || !session) return;
|
|
134
|
+
const batch = [...this.metrics.values()].filter(({ name, id, value }) => {
|
|
135
|
+
const sent = this.sentMetrics.get(name);
|
|
136
|
+
return sent?.id !== id || sent.value !== value;
|
|
137
|
+
});
|
|
138
|
+
this.metrics = /* @__PURE__ */ new Map();
|
|
139
|
+
if (!batch.length) return;
|
|
140
|
+
const trackHash = this.options.trackHash ?? false;
|
|
121
141
|
const data = JSON.stringify({
|
|
122
142
|
token: this.options.siteKey,
|
|
123
143
|
sessionId: session.sessionId,
|
|
124
144
|
windowId: session.windowId,
|
|
125
|
-
vitals:
|
|
145
|
+
vitals: batch.map(({ name, value, id, rating, navigationType, attribution }) => {
|
|
126
146
|
const previous = this.sentMetrics.get(name);
|
|
127
147
|
return {
|
|
128
148
|
metric: name,
|
|
@@ -137,8 +157,7 @@ var WebVitalsTracker = class {
|
|
|
137
157
|
};
|
|
138
158
|
}),
|
|
139
159
|
metadata: { url: this.initialUrl }
|
|
140
|
-
});
|
|
141
|
-
this.metrics = /* @__PURE__ */ new Map();
|
|
160
|
+
}, (_key, value) => typeof value === "string" && /^https?:\/\//.test(value) ? sanitizeUrl(value, trackHash) : value);
|
|
142
161
|
this.sending = sendData({
|
|
143
162
|
url: this.endpoint,
|
|
144
163
|
data,
|
|
@@ -147,12 +166,8 @@ var WebVitalsTracker = class {
|
|
|
147
166
|
});
|
|
148
167
|
const sent = await this.sending;
|
|
149
168
|
this.sending = void 0;
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
id: vital.id,
|
|
153
|
-
value: vital.value
|
|
154
|
-
});
|
|
155
|
-
else if (!this.metrics.has(name)) this.metrics.set(name, vital);
|
|
169
|
+
for (const vital of batch) if (sent) this.sentMetrics.set(vital.name, vital);
|
|
170
|
+
else if (generation === this.generation && !this.metrics.has(vital.name)) this.metrics.set(vital.name, vital);
|
|
156
171
|
}
|
|
157
172
|
};
|
|
158
173
|
//#endregion
|