@faststats/web 0.12.1 → 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.
@@ -1,5 +1,5 @@
1
- import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as sendData } from "./chunks/send-data-gBIF6DTb.js";
2
- import { n as getSessionContext, r as onSessionRotated } from "./chunks/session-manager-DUpF3mi3.js";
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.12.1",
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.2",
56
- "tsdown": "^0.23.0",
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,83 +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
- function getPageContext(includeHash = false) {
35
- const hash = includeHash ? location.hash : "";
36
- const ctx = {
37
- page: `${location.pathname}${hash}`,
38
- url: sanitizeUrl(location.href, includeHash),
39
- referrer: getReferrer(),
40
- title: document.title
41
- };
42
- if (!location.search) return ctx;
43
- const sp = new URLSearchParams(location.search);
44
- for (const key of UTM_KEYS) {
45
- const value = sp.get(key);
46
- if (value) ctx[key] = value;
47
- }
48
- return ctx;
49
- }
50
- //#endregion
51
- //#region src/utils/send-data.ts
52
- async function sendData(options) {
53
- const { url, data, contentType = "application/json", debug = false, debugPrefix = "[Analytics]", useBeacon = true, keepalive = true, settleRejected } = options;
54
- if (useBeacon && typeof globalThis.navigator?.sendBeacon === "function") try {
55
- const body = data instanceof Blob ? data : new Blob([data], { type: contentType });
56
- if (globalThis.navigator.sendBeacon(url, body)) {
57
- if (debug) console.log(`${debugPrefix} Sent via beacon`);
58
- return true;
59
- }
60
- } catch {}
61
- const size = typeof data === "string" ? data.length : data instanceof Blob ? data.size : data.byteLength;
62
- const post = (keepalive) => fetch(url, {
63
- method: "POST",
64
- body: data,
65
- headers: { "Content-Type": contentType },
66
- keepalive,
67
- credentials: "omit"
68
- });
69
- try {
70
- const useKeepalive = keepalive && size <= 61440;
71
- const { ok, status } = await post(useKeepalive).catch((error) => {
72
- if (!useKeepalive) throw error;
73
- return post(false);
74
- });
75
- if (debug) console.log(debugPrefix, ok ? "Sent via fetch" : status);
76
- return ok || !!settleRejected && status < 500 && status !== 429;
77
- } catch {
78
- if (debug) console.log(debugPrefix, "Failed to send");
79
- return false;
80
- }
81
- }
82
- //#endregion
83
- export { sanitizeUrl as a, resolveBaseUrl as i, URLS as n, getPageContext 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 };