@faststats/web 0.8.1 → 0.9.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 +32 -1
- package/dist/chunks/{identifiers-KgQDX74Q.js → identifiers-xX7o7oZz.js} +1 -1
- package/dist/chunks/{send-data-DFZnsaL2.js → send-data-Dplz7m4c.js} +26 -59
- package/dist/error.d.ts +6 -7
- package/dist/error.js +3 -3
- package/dist/index.d.ts +17 -23
- package/dist/index.js +111 -114
- package/dist/outbound-links.d.ts +3 -4
- package/dist/replay.d.ts +7 -7
- package/dist/replay.js +15 -3
- package/dist/web-vitals.d.ts +10 -9
- package/dist/web-vitals.js +95 -55
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ init({
|
|
|
19
19
|
extensions: [
|
|
20
20
|
outboundLinks(),
|
|
21
21
|
errorTracking(),
|
|
22
|
-
webVitals(
|
|
22
|
+
webVitals(),
|
|
23
23
|
sessionReplay({ recordConsole: true }),
|
|
24
24
|
],
|
|
25
25
|
});
|
|
@@ -47,6 +47,37 @@ analytics.destroy();
|
|
|
47
47
|
Consent remains client-local: use `"anonymous"` for cookieless collection,
|
|
48
48
|
`"granted"` for normal collection, or `"denied"` to keep the client dormant.
|
|
49
49
|
|
|
50
|
+
### Visit duration
|
|
51
|
+
|
|
52
|
+
`page_leave.properties.time_on_page` is the accumulated time in milliseconds
|
|
53
|
+
that the page was visible and its document had focus. It pauses when the tab is
|
|
54
|
+
hidden, the window loses focus, or the page enters the back-forward cache, and
|
|
55
|
+
resets on navigation, logout, or session rotation. A monotonic clock keeps
|
|
56
|
+
system-clock changes from affecting this duration. Reading without interacting still counts while the page has focus;
|
|
57
|
+
this metric does not infer whether a person is idle.
|
|
58
|
+
|
|
59
|
+
Pageviews wait until the page becomes visible. Pages replaced or closed before
|
|
60
|
+
being shown do not emit pageviews or page-leave events.
|
|
61
|
+
|
|
62
|
+
The SDK sends cumulative `page_leave` checkpoints every 15 seconds while active
|
|
63
|
+
and when activity pauses or the visit ends. Unchanged checkpoints are suppressed.
|
|
64
|
+
Pageviews and their checkpoints share `properties.visit_id`; consumers should use
|
|
65
|
+
the maximum `time_on_page` per visit, never sum its checkpoints. The existing
|
|
66
|
+
session-duration and bounce-rate queries already use maximum values.
|
|
67
|
+
|
|
68
|
+
Each visit retains its original identity, session, and page metadata. A session
|
|
69
|
+
or identity change starts a new visit, even on the same route. Both persistent
|
|
70
|
+
and cookieless sessions expire after 30 minutes of inactivity or 24 hours of age.
|
|
71
|
+
|
|
72
|
+
`session_duration` is elapsed wall-clock time since the session started, including
|
|
73
|
+
time away. It is separate from active time on the page. Paused visits do not keep
|
|
74
|
+
sending larger session durations while the user is away.
|
|
75
|
+
|
|
76
|
+
Delivery is best effort: a sudden process kill or network failure can lose time
|
|
77
|
+
since the last delivered checkpoint. The collector must accept successive events
|
|
78
|
+
for the same visitor and URL; deploy the removal of its five-second debounce
|
|
79
|
+
before releasing this SDK.
|
|
80
|
+
|
|
50
81
|
### Custom extensions
|
|
51
82
|
|
|
52
83
|
An extension registers only the lifecycle hooks it needs. Hook failures are
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as removeStorageItem, l as setStorageItem, n as createId, s as getStorageItem } from "./send-data-
|
|
1
|
+
import { c as removeStorageItem, l as setStorageItem, n as createId, s as getStorageItem } from "./send-data-Dplz7m4c.js";
|
|
2
2
|
//#region src/utils/identifiers.ts
|
|
3
3
|
const ANONYMOUS_ID_KEY = "faststats_anon_id_";
|
|
4
4
|
function getOrCreateAnonymousId(siteKey) {
|
|
@@ -64,17 +64,13 @@ function readSession(siteKey) {
|
|
|
64
64
|
if (!raw) return memory;
|
|
65
65
|
try {
|
|
66
66
|
const session = JSON.parse(raw);
|
|
67
|
-
return session.id ? session : memory;
|
|
67
|
+
return typeof session?.id === "string" && session.id && Number.isFinite(session.start) && Number.isFinite(session.activity) ? session : memory;
|
|
68
68
|
} catch {}
|
|
69
69
|
return memory;
|
|
70
70
|
}
|
|
71
71
|
function writeSession(siteKey, session) {
|
|
72
72
|
if (!setStorageItem("localStorage", `${SESSION_KEY}${siteKey}`, JSON.stringify(session))) writeMemory(siteKey, false, session);
|
|
73
73
|
}
|
|
74
|
-
function clearSession(siteKey, cookieless) {
|
|
75
|
-
memorySessions.delete(memoryKey(siteKey, cookieless));
|
|
76
|
-
if (!cookieless) removeStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
77
|
-
}
|
|
78
74
|
function expired(session, now) {
|
|
79
75
|
return now - session.activity >= IDLE_MS || now - session.start >= MAX_AGE_MS;
|
|
80
76
|
}
|
|
@@ -83,41 +79,27 @@ function emitRotation(siteKey, cookieless, prev, next) {
|
|
|
83
79
|
}
|
|
84
80
|
function resolveSession(siteKey, cookieless, touch) {
|
|
85
81
|
const now = Date.now();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
session = {
|
|
90
|
-
id: createId(),
|
|
91
|
-
start: now,
|
|
92
|
-
activity: now
|
|
93
|
-
};
|
|
94
|
-
writeMemory(siteKey, true, session);
|
|
95
|
-
} else if (touch) session.activity = now;
|
|
96
|
-
return {
|
|
97
|
-
session,
|
|
98
|
-
rotatedFrom: null
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
const existing = readSession(siteKey);
|
|
102
|
-
if (existing && !expired(existing, now)) {
|
|
103
|
-
if (touch) {
|
|
104
|
-
existing.activity = now;
|
|
105
|
-
writeSession(siteKey, existing);
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
session: existing,
|
|
109
|
-
rotatedFrom: null
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
const session = {
|
|
82
|
+
const existing = cookieless ? readMemory(siteKey, true) : readSession(siteKey);
|
|
83
|
+
const rotatedFrom = existing && expired(existing, now) ? existing : null;
|
|
84
|
+
const session = existing && !rotatedFrom ? existing : {
|
|
113
85
|
id: createId(),
|
|
114
86
|
start: now,
|
|
115
87
|
activity: now
|
|
116
88
|
};
|
|
117
|
-
|
|
89
|
+
if (touch) session.activity = now;
|
|
90
|
+
if (touch || session !== existing) {
|
|
91
|
+
if (cookieless) writeMemory(siteKey, true, session);
|
|
92
|
+
else writeSession(siteKey, session);
|
|
93
|
+
}
|
|
94
|
+
const context = toContext(siteKey, session, cookieless);
|
|
95
|
+
const rotation = rotatedFrom ? {
|
|
96
|
+
prev: toContext(siteKey, rotatedFrom, cookieless),
|
|
97
|
+
next: context
|
|
98
|
+
} : null;
|
|
99
|
+
if (rotation) emitRotation(siteKey, cookieless, rotation.prev, rotation.next);
|
|
118
100
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
101
|
+
context,
|
|
102
|
+
rotation
|
|
121
103
|
};
|
|
122
104
|
}
|
|
123
105
|
function windowId(siteKey, cookieless, sessionId) {
|
|
@@ -128,41 +110,26 @@ function windowId(siteKey, cookieless, sessionId) {
|
|
|
128
110
|
const id = createId();
|
|
129
111
|
return setStorageItem("sessionStorage", key, id) ? id : sessionId;
|
|
130
112
|
}
|
|
131
|
-
function toContext(siteKey, session, cookieless
|
|
113
|
+
function toContext(siteKey, session, cookieless) {
|
|
132
114
|
return {
|
|
133
115
|
sessionId: session.id,
|
|
134
|
-
windowId:
|
|
116
|
+
windowId: windowId(siteKey, cookieless, session.id),
|
|
135
117
|
sessionStart: session.start
|
|
136
118
|
};
|
|
137
119
|
}
|
|
138
120
|
function getSessionContext(siteKey, cookieless = false) {
|
|
139
|
-
|
|
140
|
-
const next = toContext(siteKey, session, cookieless);
|
|
141
|
-
if (rotatedFrom) emitRotation(siteKey, cookieless, toContext(siteKey, rotatedFrom, cookieless, next.windowId), next);
|
|
142
|
-
return next;
|
|
121
|
+
return resolveSession(siteKey, cookieless, false).context;
|
|
143
122
|
}
|
|
144
123
|
function touchActivity(siteKey, cookieless = false) {
|
|
145
|
-
|
|
146
|
-
if (!rotatedFrom) return null;
|
|
147
|
-
const wid = windowId(siteKey, cookieless, session.id);
|
|
148
|
-
const rotation = {
|
|
149
|
-
prev: toContext(siteKey, rotatedFrom, cookieless, wid),
|
|
150
|
-
next: toContext(siteKey, session, cookieless, wid)
|
|
151
|
-
};
|
|
152
|
-
emitRotation(siteKey, cookieless, rotation.prev, rotation.next);
|
|
153
|
-
return rotation;
|
|
124
|
+
return resolveSession(siteKey, cookieless, true).rotation;
|
|
154
125
|
}
|
|
155
126
|
function resetSession(siteKey, cookieless = false) {
|
|
156
127
|
const previous = getSessionContext(siteKey, cookieless);
|
|
157
|
-
|
|
158
|
-
if (cookieless) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
start: now,
|
|
163
|
-
activity: now
|
|
164
|
-
});
|
|
165
|
-
} else removeStorageItem("sessionStorage", `faststats_window_id_${siteKey}`);
|
|
128
|
+
memorySessions.delete(memoryKey(siteKey, cookieless));
|
|
129
|
+
if (!cookieless) {
|
|
130
|
+
removeStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
131
|
+
removeStorageItem("sessionStorage", `faststats_window_id_${siteKey}`);
|
|
132
|
+
}
|
|
166
133
|
const next = getSessionContext(siteKey, cookieless);
|
|
167
134
|
if (previous.sessionId !== next.sessionId) emitRotation(siteKey, cookieless, previous, next);
|
|
168
135
|
return next;
|
package/dist/error.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
2
|
//#region src/error.d.ts
|
|
3
|
-
type ErrorEntry = {
|
|
3
|
+
export type ErrorEntry = {
|
|
4
4
|
error: string;
|
|
5
5
|
message?: string;
|
|
6
6
|
stack?: string[];
|
|
7
7
|
handled?: boolean;
|
|
8
8
|
};
|
|
9
|
-
interface ErrorTrackingOptions {
|
|
9
|
+
export interface ErrorTrackingOptions {
|
|
10
10
|
siteKey: string;
|
|
11
11
|
baseUrl?: string;
|
|
12
12
|
debug?: boolean;
|
|
@@ -17,9 +17,9 @@ interface ErrorTrackingOptions {
|
|
|
17
17
|
sdkName?: string;
|
|
18
18
|
sdkVersion?: string;
|
|
19
19
|
}
|
|
20
|
-
type ErrorTrackingExtensionOptions = Omit<ErrorTrackingOptions, "siteKey" | "baseUrl" | "debug" | "trackHash" | "cookieless" | "sdkName" | "sdkVersion">;
|
|
21
|
-
declare function errorTracking(options?: ErrorTrackingExtensionOptions): AnalyticsExtension;
|
|
22
|
-
|
|
20
|
+
export type ErrorTrackingExtensionOptions = Omit<ErrorTrackingOptions, "siteKey" | "baseUrl" | "debug" | "trackHash" | "cookieless" | "sdkName" | "sdkVersion">;
|
|
21
|
+
export declare function errorTracking(options?: ErrorTrackingExtensionOptions): AnalyticsExtension;
|
|
22
|
+
export default class ErrorTracker {
|
|
23
23
|
private readonly options;
|
|
24
24
|
private readonly endpoint;
|
|
25
25
|
private queue;
|
|
@@ -38,5 +38,4 @@ declare class ErrorTracker {
|
|
|
38
38
|
private enqueue;
|
|
39
39
|
private flush;
|
|
40
40
|
}
|
|
41
|
-
//#endregion
|
|
42
|
-
export { ErrorEntry, ErrorTrackingExtensionOptions, ErrorTrackingOptions, ErrorTracker as default, errorTracking };
|
|
41
|
+
//#endregion
|
package/dist/error.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { i as resolveBaseUrl, n as URLS, r as getPageContext, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
2
|
+
import { r as getSessionContext, t as sendData } from "./chunks/send-data-Dplz7m4c.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-xX7o7oZz.js";
|
|
4
4
|
//#region src/error.ts
|
|
5
5
|
const SDK_NAME = "@faststats/web";
|
|
6
|
-
const SDK_VERSION = "0.
|
|
6
|
+
const SDK_VERSION = "0.9.0";
|
|
7
7
|
function errorTracking(options = {}) {
|
|
8
8
|
return {
|
|
9
9
|
name: "error-tracking",
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ interface WebAnalyticsOptions {
|
|
|
27
27
|
/** @internal */
|
|
28
28
|
sdkVersion?: string;
|
|
29
29
|
}
|
|
30
|
-
declare class WebAnalytics {
|
|
30
|
+
export declare class WebAnalytics {
|
|
31
31
|
private readonly options;
|
|
32
32
|
private readonly baseUrl;
|
|
33
33
|
private readonly debug;
|
|
@@ -36,11 +36,8 @@ declare class WebAnalytics {
|
|
|
36
36
|
private heartbeatTimer;
|
|
37
37
|
private extensions;
|
|
38
38
|
private pageKey;
|
|
39
|
-
private
|
|
40
|
-
private
|
|
41
|
-
private visitDuration;
|
|
42
|
-
private visitStartedAt;
|
|
43
|
-
private pendingPageviewTrigger;
|
|
39
|
+
private visit;
|
|
40
|
+
private pendingPageview;
|
|
44
41
|
private consentMode;
|
|
45
42
|
constructor(options: WebAnalyticsOptions);
|
|
46
43
|
start(): void;
|
|
@@ -55,7 +52,7 @@ declare class WebAnalytics {
|
|
|
55
52
|
reportError(error: Error): void;
|
|
56
53
|
private canTrack;
|
|
57
54
|
private isCookieless;
|
|
58
|
-
private
|
|
55
|
+
private isActive;
|
|
59
56
|
private activate;
|
|
60
57
|
private startExtensions;
|
|
61
58
|
private stopExtensions;
|
|
@@ -65,30 +62,27 @@ declare class WebAnalytics {
|
|
|
65
62
|
private readonly onVisibilityChange;
|
|
66
63
|
private readonly onPageHide;
|
|
67
64
|
private readonly onPageShow;
|
|
68
|
-
private
|
|
69
|
-
private enterPage;
|
|
65
|
+
private readonly onResume;
|
|
70
66
|
private leavePage;
|
|
67
|
+
private checkpoint;
|
|
71
68
|
private pauseVisit;
|
|
72
69
|
private resumeVisit;
|
|
73
|
-
private touch;
|
|
74
|
-
private startHeartbeat;
|
|
75
|
-
private stopHeartbeat;
|
|
76
70
|
private send;
|
|
77
71
|
}
|
|
78
72
|
//#endregion
|
|
79
73
|
//#region src/client.d.ts
|
|
80
74
|
/** Creates an independent client. Call `start()` when it should begin tracking. */
|
|
81
|
-
declare function createClient(options: WebAnalyticsOptions): WebAnalytics;
|
|
75
|
+
export declare function createClient(options: WebAnalyticsOptions): WebAnalytics;
|
|
82
76
|
/** Initializes and starts the shared client used by the convenience functions. */
|
|
83
|
-
declare function init(options: WebAnalyticsOptions): WebAnalytics;
|
|
77
|
+
export declare function init(options: WebAnalyticsOptions): WebAnalytics;
|
|
84
78
|
/** Stops and forgets the shared client. Independent clients are unaffected. */
|
|
85
|
-
declare function shutdown(): void;
|
|
86
|
-
declare function pageview(properties?: Record<string, unknown>): void;
|
|
87
|
-
declare function track(name: string, properties?: Record<string, unknown>): void;
|
|
88
|
-
declare function identify(user: IdentifyUser): Promise<boolean>;
|
|
89
|
-
declare function identify(externalId: string, email?: string, options?: IdentifyOptions): Promise<boolean>;
|
|
90
|
-
declare function logout(resetAnonymousIdentity?: boolean): void;
|
|
91
|
-
declare function setConsentMode(mode: ConsentMode): void;
|
|
92
|
-
declare function reportError(error: Error): void;
|
|
79
|
+
export declare function shutdown(): void;
|
|
80
|
+
export declare function pageview(properties?: Record<string, unknown>): void;
|
|
81
|
+
export declare function track(name: string, properties?: Record<string, unknown>): void;
|
|
82
|
+
export declare function identify(user: IdentifyUser): Promise<boolean>;
|
|
83
|
+
export declare function identify(externalId: string, email?: string, options?: IdentifyOptions): Promise<boolean>;
|
|
84
|
+
export declare function logout(resetAnonymousIdentity?: boolean): void;
|
|
85
|
+
export declare function setConsentMode(mode: ConsentMode): void;
|
|
86
|
+
export declare function reportError(error: Error): void;
|
|
93
87
|
//#endregion
|
|
94
|
-
export {
|
|
88
|
+
export type { AnalyticsExtension, AnalyticsExtensionContext, ConsentMode, ExtensionCleanup, ExtensionHandler, ExtensionHook, ExtensionHooks, IdentifyOptions, IdentifyUser, WebAnalyticsOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as resetSession, o as touchActivity, r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-
|
|
1
|
+
import { i as resolveBaseUrl, n as URLS, r as getPageContext, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
+
import { a as resetSession, n as createId, o as touchActivity, r as getSessionContext, t as sendData } from "./chunks/send-data-Dplz7m4c.js";
|
|
3
|
+
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-xX7o7oZz.js";
|
|
4
4
|
//#region src/extensions.ts
|
|
5
5
|
var ExtensionHost = class {
|
|
6
6
|
runtime;
|
|
@@ -104,15 +104,8 @@ var WebAnalytics = class {
|
|
|
104
104
|
heartbeatTimer = null;
|
|
105
105
|
extensions = null;
|
|
106
106
|
pageKey = "";
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
url: "",
|
|
110
|
-
search: ""
|
|
111
|
-
};
|
|
112
|
-
left = false;
|
|
113
|
-
visitDuration = 0;
|
|
114
|
-
visitStartedAt = null;
|
|
115
|
-
pendingPageviewTrigger = null;
|
|
107
|
+
visit = null;
|
|
108
|
+
pendingPageview = null;
|
|
116
109
|
consentMode;
|
|
117
110
|
constructor(options) {
|
|
118
111
|
this.options = options;
|
|
@@ -129,29 +122,58 @@ var WebAnalytics = class {
|
|
|
129
122
|
}
|
|
130
123
|
destroy() {
|
|
131
124
|
if (this.started === null) return;
|
|
132
|
-
|
|
125
|
+
this.leavePage();
|
|
133
126
|
this.started = null;
|
|
134
|
-
this.
|
|
135
|
-
this.stopHeartbeat();
|
|
127
|
+
this.pendingPageview = null;
|
|
136
128
|
for (const cleanup of this.cleanup.splice(0).reverse()) cleanup();
|
|
137
129
|
this.stopExtensions(false);
|
|
138
130
|
}
|
|
139
131
|
pageview(properties = {}) {
|
|
140
|
-
if (!this.
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
if (!this.isActive()) return;
|
|
133
|
+
const key = location.pathname + location.search + (this.options.trackHash ? location.hash : "");
|
|
134
|
+
if (key !== this.pageKey) {
|
|
135
|
+
this.leavePage();
|
|
136
|
+
if (this.pageKey) this.extensions?.emit("pageChange", { url: getPageContext(this.options.trackHash).url ?? "" });
|
|
137
|
+
this.pageKey = key;
|
|
138
|
+
}
|
|
139
|
+
if (document.visibilityState !== "visible") {
|
|
140
|
+
this.pendingPageview = properties;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.pendingPageview = null;
|
|
144
|
+
if (document.hasFocus()) touchActivity(this.options.siteKey, this.isCookieless());
|
|
145
|
+
const session = getSessionContext(this.options.siteKey, this.isCookieless());
|
|
146
|
+
const userId = this.getAnonymousId();
|
|
147
|
+
if (this.visit?.session.sessionId === session.sessionId && this.visit.context.userId === (userId || void 0)) return;
|
|
148
|
+
this.leavePage();
|
|
149
|
+
this.visit = {
|
|
150
|
+
id: createId(),
|
|
151
|
+
session,
|
|
152
|
+
context: {
|
|
153
|
+
...getPageContext(this.options.trackHash),
|
|
154
|
+
userId: userId || void 0,
|
|
155
|
+
sessionId: session.sessionId,
|
|
156
|
+
windowId: session.windowId
|
|
157
|
+
},
|
|
158
|
+
duration: 0,
|
|
159
|
+
startedAt: null,
|
|
160
|
+
reportedDuration: -1
|
|
161
|
+
};
|
|
162
|
+
this.send("pageview", {
|
|
163
|
+
...properties,
|
|
164
|
+
visit_id: this.visit.id
|
|
165
|
+
}, this.visit.context);
|
|
166
|
+
this.resumeVisit();
|
|
146
167
|
}
|
|
147
168
|
track(name, properties = {}) {
|
|
148
|
-
if (!this.
|
|
169
|
+
if (!this.isActive()) return;
|
|
149
170
|
const eventName = name.trim();
|
|
150
171
|
if (!eventName || eventName === "pageview" || eventName === "page_leave" || eventName === "error" || eventName === "outbound_link") return;
|
|
172
|
+
this.pageview({ trigger: "session" });
|
|
151
173
|
this.send(eventName, properties);
|
|
152
174
|
}
|
|
153
175
|
async identify(userOrExternalId, email, options = {}) {
|
|
154
|
-
if (!this.
|
|
176
|
+
if (!this.isActive() || this.isCookieless()) return false;
|
|
155
177
|
const user = typeof userOrExternalId === "string" ? {
|
|
156
178
|
id: userOrExternalId,
|
|
157
179
|
email,
|
|
@@ -165,7 +187,7 @@ var WebAnalytics = class {
|
|
|
165
187
|
"phone",
|
|
166
188
|
"avatarUrl"
|
|
167
189
|
].filter((field) => user[field] === null);
|
|
168
|
-
return
|
|
190
|
+
return sendData({
|
|
169
191
|
url: `${this.baseUrl}${URLS.identify}`,
|
|
170
192
|
data: JSON.stringify({
|
|
171
193
|
token: this.options.siteKey,
|
|
@@ -188,9 +210,11 @@ var WebAnalytics = class {
|
|
|
188
210
|
});
|
|
189
211
|
}
|
|
190
212
|
logout(resetAnonymousIdentity = true) {
|
|
191
|
-
if (!this.
|
|
213
|
+
if (!this.isActive()) return;
|
|
214
|
+
this.leavePage();
|
|
192
215
|
if (resetAnonymousIdentity) resetAnonymousId(this.options.siteKey, this.isCookieless());
|
|
193
216
|
resetSession(this.options.siteKey, this.isCookieless());
|
|
217
|
+
this.pageview({ trigger: "logout" });
|
|
194
218
|
}
|
|
195
219
|
setConsentMode(mode) {
|
|
196
220
|
if (mode === this.consentMode) return;
|
|
@@ -201,26 +225,21 @@ var WebAnalytics = class {
|
|
|
201
225
|
if (wasTracking && willTrack && wasCookieless !== willBeCookieless) this.leavePage();
|
|
202
226
|
this.consentMode = mode;
|
|
203
227
|
if (!willTrack) {
|
|
204
|
-
this.
|
|
205
|
-
this.
|
|
206
|
-
this.pendingPageviewTrigger = null;
|
|
228
|
+
this.leavePage();
|
|
229
|
+
this.pendingPageview = null;
|
|
207
230
|
this.stopExtensions(true);
|
|
208
231
|
return;
|
|
209
232
|
}
|
|
210
233
|
if (wasTracking) {
|
|
211
234
|
this.extensions?.emit("consentChange", { cookieless: willBeCookieless });
|
|
212
|
-
if (wasCookieless !== willBeCookieless) {
|
|
213
|
-
this.enterPage();
|
|
214
|
-
this.pageKey = "";
|
|
215
|
-
this.pageview({ trigger: "consent" });
|
|
216
|
-
}
|
|
235
|
+
if (wasCookieless !== willBeCookieless) this.pageview({ trigger: "consent" });
|
|
217
236
|
} else if (this.started) this.activate("consent");
|
|
218
237
|
}
|
|
219
238
|
getAnonymousId() {
|
|
220
239
|
return this.canTrack() ? getAnonymousId(this.options.siteKey, this.isCookieless()) : "";
|
|
221
240
|
}
|
|
222
241
|
reportError(error) {
|
|
223
|
-
if (!this.
|
|
242
|
+
if (!this.isActive()) return;
|
|
224
243
|
this.extensions?.emit("error", { error });
|
|
225
244
|
}
|
|
226
245
|
canTrack() {
|
|
@@ -229,17 +248,12 @@ var WebAnalytics = class {
|
|
|
229
248
|
isCookieless() {
|
|
230
249
|
return this.options.cookieless === true || this.consentMode === "anonymous";
|
|
231
250
|
}
|
|
232
|
-
|
|
233
|
-
return this.started === true &&
|
|
251
|
+
isActive() {
|
|
252
|
+
return this.started === true && this.canTrack();
|
|
234
253
|
}
|
|
235
254
|
activate(trigger) {
|
|
236
|
-
this.touch();
|
|
237
255
|
this.startExtensions();
|
|
238
|
-
this.
|
|
239
|
-
this.pageKey = "";
|
|
240
|
-
this.startHeartbeat();
|
|
241
|
-
if (document.visibilityState === "visible") this.pageview({ trigger });
|
|
242
|
-
else this.pendingPageviewTrigger = trigger;
|
|
256
|
+
this.pageview({ trigger });
|
|
243
257
|
}
|
|
244
258
|
startExtensions() {
|
|
245
259
|
if (this.extensions || !this.options.extensions?.length) return;
|
|
@@ -268,32 +282,28 @@ var WebAnalytics = class {
|
|
|
268
282
|
this.bind(document, "visibilitychange", this.onVisibilityChange);
|
|
269
283
|
this.bind(window, "pagehide", this.onPageHide);
|
|
270
284
|
this.bind(window, "pageshow", this.onPageShow);
|
|
285
|
+
this.bind(window, "focus", this.onResume);
|
|
286
|
+
this.bind(window, "blur", () => this.pauseVisit());
|
|
271
287
|
this.bind(window, "popstate", this.onNavigate);
|
|
272
288
|
if (this.options.trackHash) this.bind(window, "hashchange", this.onNavigate);
|
|
273
|
-
this.cleanup.push(observeHistory(
|
|
289
|
+
this.cleanup.push(observeHistory(this.onNavigate));
|
|
274
290
|
}
|
|
275
|
-
bind(target, type, listener
|
|
276
|
-
target.addEventListener(type, listener
|
|
277
|
-
this.cleanup.push(() => target.removeEventListener(type, listener
|
|
291
|
+
bind(target, type, listener) {
|
|
292
|
+
target.addEventListener(type, listener);
|
|
293
|
+
this.cleanup.push(() => target.removeEventListener(type, listener));
|
|
278
294
|
}
|
|
279
|
-
onNavigate = () => this.
|
|
295
|
+
onNavigate = () => this.pageview({ trigger: "navigation" });
|
|
280
296
|
onVisibilityChange = () => {
|
|
281
297
|
if (!this.canTrack()) return;
|
|
282
|
-
this.touch();
|
|
283
298
|
if (document.visibilityState === "hidden") {
|
|
284
299
|
this.pauseVisit();
|
|
285
300
|
this.extensions?.emit("pageHide", {
|
|
286
301
|
persisted: false,
|
|
287
302
|
terminal: false
|
|
288
303
|
});
|
|
289
|
-
this.stopHeartbeat();
|
|
290
304
|
return;
|
|
291
305
|
}
|
|
292
|
-
this.
|
|
293
|
-
this.resumeVisit();
|
|
294
|
-
const trigger = this.pendingPageviewTrigger;
|
|
295
|
-
this.pendingPageviewTrigger = null;
|
|
296
|
-
if (trigger) this.pageview({ trigger });
|
|
306
|
+
this.onResume();
|
|
297
307
|
};
|
|
298
308
|
onPageHide = (event) => {
|
|
299
309
|
if (!this.canTrack()) return;
|
|
@@ -307,82 +317,69 @@ var WebAnalytics = class {
|
|
|
307
317
|
};
|
|
308
318
|
onPageShow = (event) => {
|
|
309
319
|
if (!this.canTrack()) return;
|
|
310
|
-
|
|
320
|
+
this.onResume();
|
|
311
321
|
this.extensions?.emit("pageShow", { persisted: event.persisted });
|
|
312
322
|
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const url = sanitizeUrl(location.href, includeHash);
|
|
318
|
-
this.extensions?.emit("pageChange", { url });
|
|
319
|
-
this.leavePage();
|
|
320
|
-
this.enterPage();
|
|
321
|
-
this.pageview({ trigger: "navigation" });
|
|
322
|
-
}
|
|
323
|
-
enterPage() {
|
|
324
|
-
const includeHash = this.options.trackHash ?? false;
|
|
325
|
-
const hash = includeHash ? location.hash : "";
|
|
326
|
-
this.entry = {
|
|
327
|
-
path: `${location.pathname}${hash}`,
|
|
328
|
-
url: sanitizeUrl(location.href, includeHash),
|
|
329
|
-
search: location.search
|
|
330
|
-
};
|
|
331
|
-
this.left = false;
|
|
332
|
-
this.visitDuration = 0;
|
|
333
|
-
this.visitStartedAt = document.visibilityState === "visible" ? Date.now() : null;
|
|
334
|
-
}
|
|
323
|
+
onResume = () => {
|
|
324
|
+
this.pageview(this.pendingPageview ?? { trigger: "session" });
|
|
325
|
+
this.resumeVisit();
|
|
326
|
+
};
|
|
335
327
|
leavePage() {
|
|
336
|
-
if (this.started !== true || this.left || !this.canTrack()) return;
|
|
337
328
|
this.pauseVisit();
|
|
338
|
-
this.
|
|
339
|
-
|
|
329
|
+
this.visit = null;
|
|
330
|
+
}
|
|
331
|
+
checkpoint() {
|
|
332
|
+
const visit = this.visit;
|
|
333
|
+
if (!visit || !this.isActive()) return;
|
|
334
|
+
if (visit.startedAt !== null) {
|
|
335
|
+
const now = performance.now();
|
|
336
|
+
visit.duration += now - visit.startedAt;
|
|
337
|
+
visit.startedAt = now;
|
|
338
|
+
}
|
|
339
|
+
const duration = Math.round(visit.duration);
|
|
340
|
+
if (duration === visit.reportedDuration) return;
|
|
341
|
+
visit.reportedDuration = duration;
|
|
340
342
|
this.send("page_leave", {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
url: this.entry.url
|
|
346
|
-
});
|
|
343
|
+
visit_id: visit.id,
|
|
344
|
+
time_on_page: duration,
|
|
345
|
+
session_duration: Math.max(0, Date.now() - visit.session.sessionStart)
|
|
346
|
+
}, visit.context);
|
|
347
347
|
}
|
|
348
348
|
pauseVisit() {
|
|
349
|
-
if (this.
|
|
350
|
-
this.
|
|
351
|
-
this.
|
|
349
|
+
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
350
|
+
this.heartbeatTimer = null;
|
|
351
|
+
this.checkpoint();
|
|
352
|
+
if (this.visit) this.visit.startedAt = null;
|
|
352
353
|
}
|
|
353
354
|
resumeVisit() {
|
|
354
|
-
if (!this.
|
|
355
|
-
|
|
356
|
-
touch() {
|
|
357
|
-
if (this.canTrack()) touchActivity(this.options.siteKey, this.isCookieless());
|
|
358
|
-
}
|
|
359
|
-
startHeartbeat() {
|
|
360
|
-
this.stopHeartbeat();
|
|
355
|
+
if (!this.isActive() || !this.visit || this.visit.startedAt !== null || document.visibilityState !== "visible" || !document.hasFocus()) return;
|
|
356
|
+
this.visit.startedAt = performance.now();
|
|
361
357
|
this.heartbeatTimer = setInterval(() => {
|
|
362
|
-
if (document.visibilityState
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
this.heartbeatTimer = null;
|
|
358
|
+
if (document.visibilityState !== "visible" || !document.hasFocus()) {
|
|
359
|
+
this.pauseVisit();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.pageview({ trigger: "session" });
|
|
363
|
+
this.checkpoint();
|
|
364
|
+
}, 15e3);
|
|
370
365
|
}
|
|
371
|
-
send(event, properties = {},
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
366
|
+
send(event, properties = {}, context) {
|
|
367
|
+
if (!this.isActive()) return;
|
|
368
|
+
if (!context) {
|
|
369
|
+
const session = getSessionContext(this.options.siteKey, this.isCookieless());
|
|
370
|
+
context = {
|
|
371
|
+
...getPageContext(this.options.trackHash),
|
|
372
|
+
userId: this.getAnonymousId() || void 0,
|
|
373
|
+
sessionId: session.sessionId,
|
|
374
|
+
windowId: session.windowId
|
|
375
|
+
};
|
|
376
|
+
}
|
|
376
377
|
let data;
|
|
377
378
|
try {
|
|
378
379
|
data = JSON.stringify({
|
|
379
380
|
token: this.options.siteKey,
|
|
380
|
-
...userId ? { userId } : {},
|
|
381
|
-
sessionId: session.sessionId,
|
|
382
|
-
windowId: session.windowId,
|
|
383
381
|
event,
|
|
384
|
-
...
|
|
385
|
-
...dimensions,
|
|
382
|
+
...context,
|
|
386
383
|
properties
|
|
387
384
|
});
|
|
388
385
|
} catch (error) {
|
package/dist/outbound-links.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
2
|
//#region src/outbound-links.d.ts
|
|
3
|
-
declare function outboundLinks(): AnalyticsExtension;
|
|
4
|
-
declare class OutboundLinkTracker {
|
|
3
|
+
export declare function outboundLinks(): AnalyticsExtension;
|
|
4
|
+
export declare class OutboundLinkTracker {
|
|
5
5
|
private lastActivation;
|
|
6
6
|
getHref(event: Event): string | null;
|
|
7
7
|
private isDuplicate;
|
|
8
8
|
}
|
|
9
|
-
//#endregion
|
|
10
|
-
export { OutboundLinkTracker, outboundLinks };
|
|
9
|
+
//#endregion
|
package/dist/replay.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
2
|
import { record } from "@rrweb/record";
|
|
3
3
|
import { LogLevel } from "@rrweb/rrweb-plugin-console-record";
|
|
4
|
-
//#region ../../node_modules/.bun/@rrweb+types@2.1.
|
|
4
|
+
//#region ../../node_modules/.bun/@rrweb+types@2.1.4/node_modules/@rrweb/types/dist/index.d.ts
|
|
5
5
|
declare global {
|
|
6
6
|
interface Window {
|
|
7
7
|
FontFace: typeof FontFace;
|
|
@@ -17,7 +17,7 @@ type ReplayMutationThrottleOptions = {
|
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/replay.d.ts
|
|
19
19
|
type RecordOptions = NonNullable<Parameters<typeof record>[0]>;
|
|
20
|
-
interface ReplayTrackerOptions {
|
|
20
|
+
export interface ReplayTrackerOptions {
|
|
21
21
|
siteKey: string;
|
|
22
22
|
baseUrl?: string;
|
|
23
23
|
debug?: boolean;
|
|
@@ -47,9 +47,9 @@ interface ReplayTrackerOptions {
|
|
|
47
47
|
consoleStringLengthLimit?: number;
|
|
48
48
|
mutationThrottle?: ReplayMutationThrottleOptions;
|
|
49
49
|
}
|
|
50
|
-
type SessionReplayExtensionOptions = Omit<ReplayTrackerOptions, "siteKey" | "baseUrl" | "debug" | "trackHash" | "cookieless">;
|
|
51
|
-
declare function sessionReplay(options?: SessionReplayExtensionOptions): AnalyticsExtension;
|
|
52
|
-
|
|
50
|
+
export type SessionReplayExtensionOptions = Omit<ReplayTrackerOptions, "siteKey" | "baseUrl" | "debug" | "trackHash" | "cookieless">;
|
|
51
|
+
export declare function sessionReplay(options?: SessionReplayExtensionOptions): AnalyticsExtension;
|
|
52
|
+
export default class ReplayTracker {
|
|
53
53
|
private readonly options;
|
|
54
54
|
private readonly endpoint;
|
|
55
55
|
private readonly compressionSupported;
|
|
@@ -85,6 +85,7 @@ declare class ReplayTracker {
|
|
|
85
85
|
private unsubscribeRotation?;
|
|
86
86
|
private lastCookielessMode;
|
|
87
87
|
private terminalFlushRequested;
|
|
88
|
+
private initialFlushPending;
|
|
88
89
|
private overflowed;
|
|
89
90
|
private replayEventSequence;
|
|
90
91
|
private readonly mutationThrottler;
|
|
@@ -126,5 +127,4 @@ declare class ReplayTracker {
|
|
|
126
127
|
private retryDelay;
|
|
127
128
|
private sendBatch;
|
|
128
129
|
}
|
|
129
|
-
//#endregion
|
|
130
|
-
export { ReplayTrackerOptions, SessionReplayExtensionOptions, ReplayTracker as default, sessionReplay };
|
|
130
|
+
//#endregion
|
package/dist/replay.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { i as onSessionRotated, l as setStorageItem, n as createId, o as touchActivity, r as getSessionContext, s as getStorageItem, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
2
|
+
import { i as onSessionRotated, l as setStorageItem, n as createId, o as touchActivity, r as getSessionContext, s as getStorageItem, t as sendData } from "./chunks/send-data-Dplz7m4c.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-xX7o7oZz.js";
|
|
4
4
|
import { record } from "@rrweb/record";
|
|
5
5
|
import { getRecordConsolePlugin } from "@rrweb/rrweb-plugin-console-record";
|
|
6
6
|
//#region src/replay-mutation-throttler.ts
|
|
@@ -122,6 +122,7 @@ var ReplayTracker = class {
|
|
|
122
122
|
unsubscribeRotation;
|
|
123
123
|
lastCookielessMode;
|
|
124
124
|
terminalFlushRequested = false;
|
|
125
|
+
initialFlushPending = true;
|
|
125
126
|
overflowed = false;
|
|
126
127
|
replayEventSequence = 0;
|
|
127
128
|
mutationThrottler;
|
|
@@ -204,6 +205,7 @@ var ReplayTracker = class {
|
|
|
204
205
|
this.started = true;
|
|
205
206
|
this.disposed = false;
|
|
206
207
|
this.terminalFlushRequested = false;
|
|
208
|
+
this.initialFlushPending = true;
|
|
207
209
|
this.overflowed = false;
|
|
208
210
|
this.replayEventSequence = 0;
|
|
209
211
|
this.viewId = createId();
|
|
@@ -371,6 +373,7 @@ var ReplayTracker = class {
|
|
|
371
373
|
this.minLengthFlushTask = null;
|
|
372
374
|
this.startTime = next.sessionStart;
|
|
373
375
|
this.chunkStartedAt = Date.now();
|
|
376
|
+
this.initialFlushPending = true;
|
|
374
377
|
}
|
|
375
378
|
onEvent = (event, _isCheckout) => {
|
|
376
379
|
if (this.overflowed) return;
|
|
@@ -429,10 +432,17 @@ var ReplayTracker = class {
|
|
|
429
432
|
}, 0);
|
|
430
433
|
};
|
|
431
434
|
scheduleMinLengthFlush() {
|
|
432
|
-
if (this.
|
|
435
|
+
if (!this.initialFlushPending || this.events.length === 0 || this.minReplayLengthMs <= 0) return;
|
|
436
|
+
if (this.hasReachedMinLength()) {
|
|
437
|
+
this.initialFlushPending = false;
|
|
438
|
+
this.requestFlush("minLength");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (this.minLengthFlushTask) return;
|
|
433
442
|
const remainingMs = Math.max(0, this.minReplayLengthMs - (Date.now() - this.startTime));
|
|
434
443
|
this.minLengthFlushTask = setTimeout(() => {
|
|
435
444
|
this.minLengthFlushTask = null;
|
|
445
|
+
this.initialFlushPending = false;
|
|
436
446
|
this.requestFlush("minLength");
|
|
437
447
|
}, Math.min(remainingMs, MAX_TIMEOUT_MS));
|
|
438
448
|
}
|
|
@@ -600,6 +610,8 @@ var ReplayTracker = class {
|
|
|
600
610
|
const json = JSON.stringify(batch);
|
|
601
611
|
const lowLatencySafe = lowLatency && textEncoder.encode(json).byteLength <= 61440;
|
|
602
612
|
const sendOptions = {
|
|
613
|
+
debug: this.options.debug,
|
|
614
|
+
debugPrefix: "[Replay]",
|
|
603
615
|
useBeacon: lowLatencySafe,
|
|
604
616
|
keepalive: lowLatencySafe
|
|
605
617
|
};
|
package/dist/web-vitals.d.ts
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
2
|
//#region src/web-vitals.d.ts
|
|
3
|
-
interface WebVitalsOptions {
|
|
3
|
+
export interface WebVitalsOptions {
|
|
4
4
|
siteKey: string;
|
|
5
5
|
baseUrl?: string;
|
|
6
6
|
debug?: boolean;
|
|
7
|
-
attribution?: boolean;
|
|
8
7
|
trackHash?: boolean;
|
|
9
8
|
cookieless?: boolean;
|
|
10
9
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
declare class WebVitalsTracker {
|
|
10
|
+
export declare function webVitals(): AnalyticsExtension;
|
|
11
|
+
export default class WebVitalsTracker {
|
|
14
12
|
private readonly options;
|
|
15
13
|
private readonly endpoint;
|
|
16
14
|
private metrics;
|
|
17
15
|
private started;
|
|
18
|
-
private
|
|
16
|
+
private sending?;
|
|
17
|
+
private generation;
|
|
18
|
+
private unsubscribe?;
|
|
19
|
+
private readonly sentMetrics;
|
|
19
20
|
private initialUrl;
|
|
20
21
|
private session;
|
|
21
22
|
private cookieless;
|
|
22
23
|
constructor(options: WebVitalsOptions);
|
|
23
24
|
setCookielessMode(cookieless: boolean): void;
|
|
24
|
-
start():
|
|
25
|
+
start(): void;
|
|
25
26
|
stop(discard?: boolean): void;
|
|
26
27
|
onPageHidden(persisted?: boolean): void;
|
|
27
28
|
private onMetric;
|
|
29
|
+
private discard;
|
|
28
30
|
private flush;
|
|
29
31
|
}
|
|
30
|
-
//#endregion
|
|
31
|
-
export { WebVitalsExtensionOptions, WebVitalsOptions, WebVitalsTracker as default, webVitals };
|
|
32
|
+
//#endregion
|
package/dist/web-vitals.js
CHANGED
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
2
|
+
import { r as getSessionContext, t as sendData } from "./chunks/send-data-Dplz7m4c.js";
|
|
3
|
+
import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals/attribution";
|
|
4
|
+
//#region src/utils/web-vitals-collector.ts
|
|
5
|
+
const collectors = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function subscribeToVitals(subscriber) {
|
|
7
|
+
let collector = collectors.get(document);
|
|
8
|
+
if (!collector) {
|
|
9
|
+
collector = {
|
|
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
|
+
return () => {
|
|
31
|
+
collector.subscribers.delete(subscriber);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
3
35
|
//#region src/web-vitals.ts
|
|
4
|
-
function webVitals(
|
|
36
|
+
function webVitals() {
|
|
5
37
|
return {
|
|
6
38
|
name: "web-vitals",
|
|
7
|
-
|
|
39
|
+
setup(context) {
|
|
8
40
|
const tracker = new WebVitalsTracker({
|
|
9
|
-
...options,
|
|
10
41
|
siteKey: context.siteKey,
|
|
11
42
|
baseUrl: context.baseUrl,
|
|
12
43
|
debug: context.debug,
|
|
@@ -15,7 +46,7 @@ function webVitals(options = {}) {
|
|
|
15
46
|
});
|
|
16
47
|
context.on("pageHide", ({ persisted }) => tracker.onPageHidden(persisted));
|
|
17
48
|
context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
|
|
18
|
-
|
|
49
|
+
tracker.start();
|
|
19
50
|
return ({ discard }) => tracker.stop(discard);
|
|
20
51
|
}
|
|
21
52
|
};
|
|
@@ -32,7 +63,10 @@ var WebVitalsTracker = class {
|
|
|
32
63
|
endpoint;
|
|
33
64
|
metrics = /* @__PURE__ */ new Map();
|
|
34
65
|
started = false;
|
|
35
|
-
|
|
66
|
+
sending;
|
|
67
|
+
generation = 0;
|
|
68
|
+
unsubscribe;
|
|
69
|
+
sentMetrics = /* @__PURE__ */ new Map();
|
|
36
70
|
initialUrl = "";
|
|
37
71
|
session = null;
|
|
38
72
|
cookieless;
|
|
@@ -44,77 +78,83 @@ var WebVitalsTracker = class {
|
|
|
44
78
|
setCookielessMode(cookieless) {
|
|
45
79
|
if (this.cookieless === cookieless) return;
|
|
46
80
|
this.cookieless = cookieless;
|
|
47
|
-
this.
|
|
81
|
+
this.discard();
|
|
48
82
|
this.session = this.started ? getSessionContext(this.options.siteKey, cookieless) : null;
|
|
49
83
|
}
|
|
50
|
-
|
|
84
|
+
start() {
|
|
51
85
|
if (this.started || typeof window === "undefined") return;
|
|
52
86
|
this.started = true;
|
|
53
|
-
this.initialUrl = sanitizeUrl(window.location.href, this.options.trackHash ?? false);
|
|
87
|
+
this.initialUrl = sanitizeUrl(window.performance?.getEntriesByType("navigation")[0]?.name || window.location.href, this.options.trackHash ?? false);
|
|
54
88
|
this.session = getSessionContext(this.options.siteKey, this.cookieless);
|
|
55
|
-
|
|
56
|
-
if (!this.started) return;
|
|
57
|
-
const changes = { reportAllChanges: true };
|
|
58
|
-
vitals.onCLS(this.onMetric, changes);
|
|
59
|
-
vitals.onINP(this.onMetric, changes);
|
|
60
|
-
vitals.onLCP(this.onMetric, changes);
|
|
61
|
-
vitals.onFCP(this.onMetric);
|
|
62
|
-
vitals.onTTFB(this.onMetric);
|
|
89
|
+
this.unsubscribe = subscribeToVitals(this.onMetric);
|
|
63
90
|
}
|
|
64
91
|
stop(discard = false) {
|
|
65
92
|
if (!this.started) return;
|
|
66
93
|
this.started = false;
|
|
67
|
-
|
|
94
|
+
this.unsubscribe?.();
|
|
95
|
+
this.unsubscribe = void 0;
|
|
96
|
+
if (discard) this.discard();
|
|
68
97
|
else this.flush();
|
|
69
98
|
}
|
|
70
99
|
onPageHidden(persisted = false) {
|
|
71
100
|
if (persisted) return;
|
|
72
|
-
|
|
101
|
+
const generation = this.generation;
|
|
102
|
+
queueMicrotask(() => {
|
|
103
|
+
if (generation === this.generation) this.flush();
|
|
104
|
+
});
|
|
73
105
|
}
|
|
74
106
|
onMetric = (metric) => {
|
|
75
107
|
if (!this.started) return;
|
|
76
108
|
const name = metric.name;
|
|
77
109
|
if (!METRIC_NAMES.has(name) || !Number.isFinite(metric.value) || metric.value < 0) return;
|
|
78
|
-
|
|
79
|
-
const attribution = this.options.attribution && "attribution" in metric && metric.attribution ? metric.attribution : void 0;
|
|
80
|
-
this.metrics.set(name, {
|
|
81
|
-
metric: name,
|
|
82
|
-
value: metric.value,
|
|
83
|
-
attributes: {
|
|
84
|
-
id,
|
|
85
|
-
rating,
|
|
86
|
-
delta,
|
|
87
|
-
navigationType,
|
|
88
|
-
...attribution
|
|
89
|
-
}
|
|
90
|
-
});
|
|
110
|
+
this.metrics.set(name, metric);
|
|
91
111
|
};
|
|
112
|
+
discard() {
|
|
113
|
+
this.generation++;
|
|
114
|
+
this.metrics.clear();
|
|
115
|
+
this.sentMetrics.clear();
|
|
116
|
+
}
|
|
92
117
|
async flush() {
|
|
93
|
-
|
|
94
|
-
this.
|
|
118
|
+
const generation = this.generation;
|
|
119
|
+
while (this.sending) await this.sending;
|
|
120
|
+
if (generation !== this.generation || !this.metrics.size) return;
|
|
95
121
|
const batch = this.metrics;
|
|
96
|
-
this.metrics = /* @__PURE__ */ new Map();
|
|
97
122
|
const session = this.session ?? getSessionContext(this.options.siteKey, this.cookieless);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
this.
|
|
117
|
-
}
|
|
123
|
+
const data = JSON.stringify({
|
|
124
|
+
token: this.options.siteKey,
|
|
125
|
+
sessionId: session.sessionId,
|
|
126
|
+
windowId: session.windowId,
|
|
127
|
+
vitals: [...batch.values()].map(({ name, value, id, rating, navigationType, attribution }) => {
|
|
128
|
+
const previous = this.sentMetrics.get(name);
|
|
129
|
+
return {
|
|
130
|
+
metric: name,
|
|
131
|
+
value,
|
|
132
|
+
attributes: {
|
|
133
|
+
...attribution,
|
|
134
|
+
id,
|
|
135
|
+
rating,
|
|
136
|
+
navigationType,
|
|
137
|
+
delta: value - (previous?.id === id ? previous.value : 0)
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}),
|
|
141
|
+
metadata: { url: this.initialUrl }
|
|
142
|
+
});
|
|
143
|
+
this.metrics = /* @__PURE__ */ new Map();
|
|
144
|
+
this.sending = sendData({
|
|
145
|
+
url: this.endpoint,
|
|
146
|
+
data,
|
|
147
|
+
debug: this.options.debug,
|
|
148
|
+
debugPrefix: "[WebVitals]"
|
|
149
|
+
});
|
|
150
|
+
const sent = await this.sending;
|
|
151
|
+
this.sending = void 0;
|
|
152
|
+
if (generation !== this.generation) return;
|
|
153
|
+
for (const [name, vital] of batch) if (sent) this.sentMetrics.set(name, {
|
|
154
|
+
id: vital.id,
|
|
155
|
+
value: vital.value
|
|
156
|
+
});
|
|
157
|
+
else if (!this.metrics.has(name)) this.metrics.set(name, vital);
|
|
118
158
|
}
|
|
119
159
|
};
|
|
120
160
|
//#endregion
|
package/package.json
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.
|
|
45
|
+
"version": "0.9.0",
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown && bun run check-size",
|
|
48
48
|
"dev": "bun run build",
|
|
@@ -51,14 +51,14 @@
|
|
|
51
51
|
"check-size": "node scripts/check-bundle-size.mjs"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@rrweb/types": "^2.1.
|
|
55
|
-
"@types/bun": "^1.4.
|
|
56
|
-
"tsdown": "^0.
|
|
57
|
-
"typescript": "^
|
|
54
|
+
"@rrweb/types": "^2.1.4",
|
|
55
|
+
"@types/bun": "^1.4.2",
|
|
56
|
+
"tsdown": "^0.23.0",
|
|
57
|
+
"typescript": "^7.0.2"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@rrweb/record": "^2.1.
|
|
61
|
-
"@rrweb/rrweb-plugin-console-record": "^2.1.
|
|
60
|
+
"@rrweb/record": "^2.1.4",
|
|
61
|
+
"@rrweb/rrweb-plugin-console-record": "^2.1.4",
|
|
62
62
|
"web-vitals": "^6.2.1"
|
|
63
63
|
}
|
|
64
64
|
}
|