@faststats/web 0.13.0 → 0.13.1
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/dist/chunks/identifiers-3X8XI1pD.js +16 -0
- package/dist/chunks/send-data-B7P3f5PT.js +177 -0
- package/dist/chunks/session-manager-VM5RZj9H.js +116 -0
- package/dist/error.d.ts +2 -0
- package/dist/error.js +47 -59
- package/dist/flags.js +2 -2
- package/dist/index.d.ts +1 -4
- package/dist/index.js +51 -121
- package/dist/replay.d.ts +37 -64
- package/dist/replay.js +423 -439
- package/dist/web-vitals.js +3 -18
- package/package.json +3 -3
- package/dist/chunks/identifiers-DOtve3E5.js +0 -18
- package/dist/chunks/send-data-C76HbZ8d.js +0 -96
- package/dist/chunks/session-manager-DUpF3mi3.js +0 -149
package/dist/web-vitals.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as resolveBaseUrl, n as URLS, o as sanitizeUrl, t as sendData } from "./chunks/send-data-
|
|
2
|
-
import { n as getSessionContext, r as onSessionRotated } from "./chunks/session-manager-
|
|
1
|
+
import { a as resolveBaseUrl, c as trackerExtension, n as URLS, o as sanitizeUrl, t as sendData } from "./chunks/send-data-B7P3f5PT.js";
|
|
2
|
+
import { n as getSessionContext, r as onSessionRotated } from "./chunks/session-manager-VM5RZj9H.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();
|
|
@@ -47,22 +47,7 @@ function sentVitals(destination) {
|
|
|
47
47
|
//#endregion
|
|
48
48
|
//#region src/web-vitals.ts
|
|
49
49
|
function webVitals() {
|
|
50
|
-
return
|
|
51
|
-
name: "web-vitals",
|
|
52
|
-
setup(context) {
|
|
53
|
-
const tracker = new WebVitalsTracker({
|
|
54
|
-
siteKey: context.siteKey,
|
|
55
|
-
baseUrl: context.baseUrl,
|
|
56
|
-
debug: context.debug,
|
|
57
|
-
trackHash: context.trackHash,
|
|
58
|
-
cookieless: context.isCookieless()
|
|
59
|
-
});
|
|
60
|
-
context.on("pageHide", ({ persisted }) => tracker.onPageHidden(persisted));
|
|
61
|
-
context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
|
|
62
|
-
tracker.start();
|
|
63
|
-
return ({ discard }) => tracker.stop(discard);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
50
|
+
return trackerExtension("web-vitals", (context) => new WebVitalsTracker(context));
|
|
66
51
|
}
|
|
67
52
|
var WebVitalsTracker = class {
|
|
68
53
|
options;
|
package/package.json
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.13.
|
|
45
|
+
"version": "0.13.1",
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown && bun run check-size",
|
|
48
48
|
"dev": "bun run build",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@rrweb/types": "^2.1.7",
|
|
55
|
-
"@types/bun": "^1.4.
|
|
56
|
-
"tsdown": "^0.23.
|
|
55
|
+
"@types/bun": "^1.4.3",
|
|
56
|
+
"tsdown": "^0.23.1",
|
|
57
57
|
"typescript": "^7.0.2"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { c as setStorageItem, o as getStorageItem, s as removeStorageItem, t as createId } from "./session-manager-DUpF3mi3.js";
|
|
2
|
-
//#region src/utils/identifiers.ts
|
|
3
|
-
const ANONYMOUS_ID_KEY = "faststats_anon_id_";
|
|
4
|
-
function getAnonymousId(siteKey, cookieless = false) {
|
|
5
|
-
if (cookieless) return "";
|
|
6
|
-
const key = `${ANONYMOUS_ID_KEY}${siteKey}`;
|
|
7
|
-
const existingId = getStorageItem("localStorage", key);
|
|
8
|
-
if (existingId) return existingId;
|
|
9
|
-
const newId = createId();
|
|
10
|
-
return setStorageItem("localStorage", key, newId) ? newId : "";
|
|
11
|
-
}
|
|
12
|
-
function resetAnonymousId(siteKey, cookieless) {
|
|
13
|
-
if (cookieless) return "";
|
|
14
|
-
if (!removeStorageItem("localStorage", `${ANONYMOUS_ID_KEY}${siteKey}`)) return "";
|
|
15
|
-
return getAnonymousId(siteKey);
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { resetAnonymousId as n, getAnonymousId as t };
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
//#region src/utils/api-urls.ts
|
|
2
|
-
function resolveBaseUrl(baseUrl) {
|
|
3
|
-
return baseUrl?.replace(/\/+$/, "") || "https://metrics.faststats.dev";
|
|
4
|
-
}
|
|
5
|
-
const URLS = {
|
|
6
|
-
events: "/v1/web",
|
|
7
|
-
identify: "/v1/identify",
|
|
8
|
-
replay: "/v1/replay",
|
|
9
|
-
vitals: "/v1/vitals"
|
|
10
|
-
};
|
|
11
|
-
function sanitizeUrl(url, includeHash = false) {
|
|
12
|
-
try {
|
|
13
|
-
const parsed = new URL(url, location.href);
|
|
14
|
-
return `${parsed.origin}${parsed.pathname}${includeHash ? parsed.hash : ""}`;
|
|
15
|
-
} catch {
|
|
16
|
-
return url.split(includeHash ? "?" : /[?#]/, 1)[0] ?? "";
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
const UTM_KEYS = [
|
|
20
|
-
"utm_source",
|
|
21
|
-
"utm_medium",
|
|
22
|
-
"utm_campaign",
|
|
23
|
-
"utm_term",
|
|
24
|
-
"utm_content"
|
|
25
|
-
];
|
|
26
|
-
function getReferrer() {
|
|
27
|
-
const raw = document.referrer;
|
|
28
|
-
if (!raw) return null;
|
|
29
|
-
try {
|
|
30
|
-
if (new URL(raw, location.href).origin === location.origin) return null;
|
|
31
|
-
} catch {}
|
|
32
|
-
return sanitizeUrl(raw);
|
|
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
|
-
}
|
|
47
|
-
function getPageContext(includeHash = false) {
|
|
48
|
-
const hash = includeHash ? location.hash : "";
|
|
49
|
-
const ctx = {
|
|
50
|
-
page: `${location.pathname}${hash}`,
|
|
51
|
-
url: sanitizeUrl(location.href, includeHash),
|
|
52
|
-
referrer: getReferrer(),
|
|
53
|
-
title: document.title
|
|
54
|
-
};
|
|
55
|
-
if (!location.search) return ctx;
|
|
56
|
-
const sp = new URLSearchParams(location.search);
|
|
57
|
-
for (const key of UTM_KEYS) {
|
|
58
|
-
const value = sp.get(key);
|
|
59
|
-
if (value) ctx[key] = value;
|
|
60
|
-
}
|
|
61
|
-
return ctx;
|
|
62
|
-
}
|
|
63
|
-
//#endregion
|
|
64
|
-
//#region src/utils/send-data.ts
|
|
65
|
-
async function sendData(options) {
|
|
66
|
-
const { url, data, contentType = "application/json", debug = false, debugPrefix = "[Analytics]", useBeacon = true, keepalive = true, settleRejected } = options;
|
|
67
|
-
if (useBeacon && typeof globalThis.navigator?.sendBeacon === "function") try {
|
|
68
|
-
const body = data instanceof Blob ? data : new Blob([data], { type: contentType });
|
|
69
|
-
if (globalThis.navigator.sendBeacon(url, body)) {
|
|
70
|
-
if (debug) console.log(`${debugPrefix} Sent via beacon`);
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
} catch {}
|
|
74
|
-
const size = typeof data === "string" ? data.length : data instanceof Blob ? data.size : data.byteLength;
|
|
75
|
-
const post = (keepalive) => fetch(url, {
|
|
76
|
-
method: "POST",
|
|
77
|
-
body: data,
|
|
78
|
-
headers: { "Content-Type": contentType },
|
|
79
|
-
keepalive,
|
|
80
|
-
credentials: "omit"
|
|
81
|
-
});
|
|
82
|
-
try {
|
|
83
|
-
const useKeepalive = keepalive && size <= 61440;
|
|
84
|
-
const { ok, status } = await post(useKeepalive).catch((error) => {
|
|
85
|
-
if (!useKeepalive) throw error;
|
|
86
|
-
return post(false);
|
|
87
|
-
});
|
|
88
|
-
if (debug) console.log(debugPrefix, ok ? "Sent via fetch" : status);
|
|
89
|
-
return ok || !!settleRejected && status < 500 && status !== 429;
|
|
90
|
-
} catch {
|
|
91
|
-
if (debug) console.log(debugPrefix, "Failed to send");
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
//#endregion
|
|
96
|
-
export { resolveBaseUrl as a, getPageContext as i, URLS as n, sanitizeUrl as o, getClientContext as r, sendData as t };
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
//#region src/utils/storage.ts
|
|
2
|
-
function getStorageItem(name, key) {
|
|
3
|
-
try {
|
|
4
|
-
return globalThis[name]?.getItem(key) ?? null;
|
|
5
|
-
} catch {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
function setStorageItem(name, key, value) {
|
|
10
|
-
try {
|
|
11
|
-
const storage = globalThis[name];
|
|
12
|
-
if (!storage) return false;
|
|
13
|
-
storage.setItem(key, value);
|
|
14
|
-
return true;
|
|
15
|
-
} catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function removeStorageItem(name, key) {
|
|
20
|
-
try {
|
|
21
|
-
const storage = globalThis[name];
|
|
22
|
-
if (!storage) return false;
|
|
23
|
-
storage.removeItem(key);
|
|
24
|
-
return true;
|
|
25
|
-
} catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
//#region src/utils/session-manager.ts
|
|
31
|
-
const IDLE_MS = 18e5;
|
|
32
|
-
const MAX_AGE_MS = 864e5;
|
|
33
|
-
const SESSION_KEY = "faststats_session_";
|
|
34
|
-
const WINDOW_KEY = "faststats_window_id_";
|
|
35
|
-
const NO_STORAGE = {};
|
|
36
|
-
const memorySessions = /* @__PURE__ */ new Map();
|
|
37
|
-
const rotationListeners = /* @__PURE__ */ new Map();
|
|
38
|
-
function memoryKey(siteKey, cookieless) {
|
|
39
|
-
return `${siteKey}:${cookieless ? "memory" : "persistent"}`;
|
|
40
|
-
}
|
|
41
|
-
function storageScope() {
|
|
42
|
-
try {
|
|
43
|
-
return globalThis.localStorage ?? NO_STORAGE;
|
|
44
|
-
} catch {
|
|
45
|
-
return NO_STORAGE;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function readMemory(siteKey, cookieless) {
|
|
49
|
-
const entry = memorySessions.get(memoryKey(siteKey, cookieless));
|
|
50
|
-
return entry?.scope === storageScope() ? entry.session : null;
|
|
51
|
-
}
|
|
52
|
-
function writeMemory(siteKey, cookieless, session) {
|
|
53
|
-
memorySessions.set(memoryKey(siteKey, cookieless), {
|
|
54
|
-
scope: storageScope(),
|
|
55
|
-
session
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
function createId() {
|
|
59
|
-
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
60
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
61
|
-
}
|
|
62
|
-
function readSession(siteKey) {
|
|
63
|
-
const memory = readMemory(siteKey, false);
|
|
64
|
-
const raw = getStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
65
|
-
if (!raw) return memory;
|
|
66
|
-
try {
|
|
67
|
-
const session = JSON.parse(raw);
|
|
68
|
-
if (typeof session?.id === "string" && session.id && Number.isFinite(session.start) && Number.isFinite(session.activity)) return session;
|
|
69
|
-
} catch {}
|
|
70
|
-
return memory;
|
|
71
|
-
}
|
|
72
|
-
function writeSession(siteKey, session) {
|
|
73
|
-
if (!setStorageItem("localStorage", `${SESSION_KEY}${siteKey}`, JSON.stringify(session))) writeMemory(siteKey, false, session);
|
|
74
|
-
}
|
|
75
|
-
function expired(session, now) {
|
|
76
|
-
return now - session.activity >= IDLE_MS || now - session.start >= MAX_AGE_MS;
|
|
77
|
-
}
|
|
78
|
-
function emitRotation(siteKey, cookieless, prev, next) {
|
|
79
|
-
for (const listener of rotationListeners.get(memoryKey(siteKey, cookieless)) ?? []) listener(prev, next);
|
|
80
|
-
}
|
|
81
|
-
function resolveSession(siteKey, cookieless, touch) {
|
|
82
|
-
const now = Date.now();
|
|
83
|
-
const existing = cookieless ? readMemory(siteKey, true) : readSession(siteKey);
|
|
84
|
-
const rotatedFrom = existing && expired(existing, now) ? existing : null;
|
|
85
|
-
const session = existing && !rotatedFrom ? existing : {
|
|
86
|
-
id: createId(),
|
|
87
|
-
start: now,
|
|
88
|
-
activity: now
|
|
89
|
-
};
|
|
90
|
-
if (touch) session.activity = now;
|
|
91
|
-
if (touch || session !== existing) {
|
|
92
|
-
if (cookieless) writeMemory(siteKey, true, session);
|
|
93
|
-
else writeSession(siteKey, session);
|
|
94
|
-
}
|
|
95
|
-
const context = toContext(siteKey, session, cookieless);
|
|
96
|
-
const rotation = rotatedFrom ? {
|
|
97
|
-
prev: toContext(siteKey, rotatedFrom, cookieless),
|
|
98
|
-
next: context
|
|
99
|
-
} : null;
|
|
100
|
-
if (rotation) emitRotation(siteKey, cookieless, rotation.prev, rotation.next);
|
|
101
|
-
return {
|
|
102
|
-
context,
|
|
103
|
-
rotation
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function windowId(siteKey, cookieless, sessionId) {
|
|
107
|
-
if (cookieless) return sessionId;
|
|
108
|
-
const key = `${WINDOW_KEY}${siteKey}`;
|
|
109
|
-
const existing = getStorageItem("sessionStorage", key);
|
|
110
|
-
if (existing) return existing;
|
|
111
|
-
const id = createId();
|
|
112
|
-
return setStorageItem("sessionStorage", key, id) ? id : sessionId;
|
|
113
|
-
}
|
|
114
|
-
function toContext(siteKey, session, cookieless) {
|
|
115
|
-
return {
|
|
116
|
-
sessionId: session.id,
|
|
117
|
-
windowId: windowId(siteKey, cookieless, session.id),
|
|
118
|
-
sessionStart: session.start
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function getSessionContext(siteKey, cookieless = false) {
|
|
122
|
-
return resolveSession(siteKey, cookieless, false).context;
|
|
123
|
-
}
|
|
124
|
-
function touchActivity(siteKey, cookieless = false) {
|
|
125
|
-
return resolveSession(siteKey, cookieless, true).rotation;
|
|
126
|
-
}
|
|
127
|
-
function resetSession(siteKey, cookieless = false) {
|
|
128
|
-
const previous = getSessionContext(siteKey, cookieless);
|
|
129
|
-
memorySessions.delete(memoryKey(siteKey, cookieless));
|
|
130
|
-
if (!cookieless) {
|
|
131
|
-
removeStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
132
|
-
removeStorageItem("sessionStorage", `${WINDOW_KEY}${siteKey}`);
|
|
133
|
-
}
|
|
134
|
-
const next = getSessionContext(siteKey, cookieless);
|
|
135
|
-
if (previous.sessionId !== next.sessionId) emitRotation(siteKey, cookieless, previous, next);
|
|
136
|
-
return next;
|
|
137
|
-
}
|
|
138
|
-
function onSessionRotated(siteKey, cookieless, listener) {
|
|
139
|
-
const key = memoryKey(siteKey, cookieless);
|
|
140
|
-
const listeners = rotationListeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
141
|
-
rotationListeners.set(key, listeners);
|
|
142
|
-
listeners.add(listener);
|
|
143
|
-
return () => {
|
|
144
|
-
listeners.delete(listener);
|
|
145
|
-
if (listeners.size === 0) rotationListeners.delete(key);
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
//#endregion
|
|
149
|
-
export { touchActivity as a, setStorageItem as c, resetSession as i, getSessionContext as n, getStorageItem as o, onSessionRotated as r, removeStorageItem as s, createId as t };
|