@faststats/web 0.7.0 → 0.8.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/README.md +67 -0
- package/dist/chunks/{api-urls-Dp2XNjRF.js → api-urls-DWk43fu6.js} +12 -5
- package/dist/chunks/extensions-DW7tJY0T.d.ts +51 -0
- package/dist/chunks/{identifiers-BvRNbiwA.js → identifiers-KgQDX74Q.js} +3 -3
- package/dist/chunks/{send-data-DR1JUXka.js → send-data-DFZnsaL2.js} +63 -45
- package/dist/error.d.ts +7 -2
- package/dist/error.js +36 -14
- package/dist/index.d.ts +54 -72
- package/dist/index.js +341 -363
- package/dist/outbound-links.d.ts +10 -0
- package/dist/outbound-links.js +62 -0
- package/dist/replay.d.ts +130 -2
- package/dist/replay.js +81 -32
- package/dist/web-vitals.d.ts +7 -1
- package/dist/web-vitals.js +39 -12
- package/package.json +11 -12
- package/dist/chunks/replay-BeC2XA4N.d.ts +0 -124
- package/dist/feature-flags.d.ts +0 -18
- package/dist/feature-flags.js +0 -32
package/README.md
CHANGED
|
@@ -2,3 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
> [!IMPORTANT]
|
|
4
4
|
> This repository is in early development and not intended for production use.
|
|
5
|
+
|
|
6
|
+
The base SDK only handles analytics, identity, sessions, navigation, and consent.
|
|
7
|
+
Error tracking, Web Vitals, and session replay are independent extensions, so
|
|
8
|
+
applications only download the products they use.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { init, track } from "@faststats/web";
|
|
12
|
+
import { errorTracking } from "@faststats/web/error";
|
|
13
|
+
import { outboundLinks } from "@faststats/web/outbound-links";
|
|
14
|
+
import { sessionReplay } from "@faststats/web/replay";
|
|
15
|
+
import { webVitals } from "@faststats/web/web-vitals";
|
|
16
|
+
|
|
17
|
+
init({
|
|
18
|
+
siteKey: "site_123",
|
|
19
|
+
extensions: [
|
|
20
|
+
outboundLinks(),
|
|
21
|
+
errorTracking(),
|
|
22
|
+
webVitals({ attribution: true }),
|
|
23
|
+
sessionReplay({ recordConsole: true }),
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
track("checkout_completed", { total: 42 });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`init()` starts one shared client and is idempotent, making it safe to call from
|
|
31
|
+
application bootstrap code. Calls made before initialization are ignored rather
|
|
32
|
+
than buffered with stale context. Use `shutdown()` before initializing with new
|
|
33
|
+
options.
|
|
34
|
+
|
|
35
|
+
For multiple sites, tests, or framework integrations, create an independent
|
|
36
|
+
client with explicit lifecycle control:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createClient } from "@faststats/web";
|
|
40
|
+
|
|
41
|
+
const analytics = createClient({ siteKey: "site_123" });
|
|
42
|
+
analytics.start();
|
|
43
|
+
analytics.track("checkout_completed", { total: 42 });
|
|
44
|
+
analytics.destroy();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Consent remains client-local: use `"anonymous"` for cookieless collection,
|
|
48
|
+
`"granted"` for normal collection, or `"denied"` to keep the client dormant.
|
|
49
|
+
|
|
50
|
+
### Custom extensions
|
|
51
|
+
|
|
52
|
+
An extension registers only the lifecycle hooks it needs. Hook failures are
|
|
53
|
+
isolated from analytics collection, asynchronous setup is safe during teardown,
|
|
54
|
+
and cleanup receives `discard: true` when consent is revoked.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import type { AnalyticsExtension } from "@faststats/web";
|
|
58
|
+
|
|
59
|
+
export function diagnostics(): AnalyticsExtension {
|
|
60
|
+
return {
|
|
61
|
+
name: "diagnostics",
|
|
62
|
+
setup(context) {
|
|
63
|
+
const off = context.on("pageChange", ({ url }) => {
|
|
64
|
+
console.debug("page changed", url, context.getSession());
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return () => off();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
```
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
//#region src/utils/api-urls.ts
|
|
2
2
|
const ANALYTICS_BASE = "https://metrics.faststats.dev";
|
|
3
|
-
const FEATURE_FLAGS_BASE = "https://flags.faststats.dev";
|
|
4
3
|
function resolveBaseUrl(input, fallback) {
|
|
5
4
|
return input?.replace(/\/+$/, "") || fallback;
|
|
6
5
|
}
|
|
@@ -8,8 +7,7 @@ const URLS = {
|
|
|
8
7
|
events: "/v1/web",
|
|
9
8
|
identify: "/v1/identify",
|
|
10
9
|
replay: "/v1/replay",
|
|
11
|
-
vitals: "/v1/vitals"
|
|
12
|
-
flags: "/v1/check"
|
|
10
|
+
vitals: "/v1/vitals"
|
|
13
11
|
};
|
|
14
12
|
function sanitizeUrl(url, includeHash = false) {
|
|
15
13
|
try {
|
|
@@ -26,12 +24,21 @@ const UTM_KEYS = [
|
|
|
26
24
|
"utm_term",
|
|
27
25
|
"utm_content"
|
|
28
26
|
];
|
|
27
|
+
function getReferrer() {
|
|
28
|
+
const raw = document.referrer;
|
|
29
|
+
if (!raw) return null;
|
|
30
|
+
try {
|
|
31
|
+
const pageOrigin = new URL(location.href).origin;
|
|
32
|
+
if (new URL(raw, location.href).origin === pageOrigin) return null;
|
|
33
|
+
} catch {}
|
|
34
|
+
return sanitizeUrl(raw);
|
|
35
|
+
}
|
|
29
36
|
function getPageContext(includeHash = false) {
|
|
30
37
|
const hash = includeHash ? location.hash : "";
|
|
31
38
|
const ctx = {
|
|
32
39
|
page: `${location.pathname}${hash}`,
|
|
33
40
|
url: sanitizeUrl(location.href, includeHash),
|
|
34
|
-
referrer:
|
|
41
|
+
referrer: getReferrer(),
|
|
35
42
|
title: document.title || ""
|
|
36
43
|
};
|
|
37
44
|
if (!location.search) return ctx;
|
|
@@ -43,4 +50,4 @@ function getPageContext(includeHash = false) {
|
|
|
43
50
|
return ctx;
|
|
44
51
|
}
|
|
45
52
|
//#endregion
|
|
46
|
-
export {
|
|
53
|
+
export { sanitizeUrl as a, resolveBaseUrl as i, URLS as n, getPageContext as r, ANALYTICS_BASE as t };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/utils/session-manager.d.ts
|
|
2
|
+
type SessionContext = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
windowId: string;
|
|
5
|
+
sessionStart: number;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/extensions.d.ts
|
|
9
|
+
type ExtensionHooks = {
|
|
10
|
+
pageChange: {
|
|
11
|
+
url: string;
|
|
12
|
+
};
|
|
13
|
+
pageHide: {
|
|
14
|
+
persisted: boolean;
|
|
15
|
+
terminal: boolean;
|
|
16
|
+
};
|
|
17
|
+
pageShow: {
|
|
18
|
+
persisted: boolean;
|
|
19
|
+
};
|
|
20
|
+
consentChange: {
|
|
21
|
+
cookieless: boolean;
|
|
22
|
+
};
|
|
23
|
+
error: {
|
|
24
|
+
error: Error;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
type ExtensionHook = keyof ExtensionHooks;
|
|
28
|
+
type ExtensionHandler<K extends ExtensionHook> = (event: ExtensionHooks[K]) => void | Promise<void>;
|
|
29
|
+
interface AnalyticsExtensionContext {
|
|
30
|
+
readonly siteKey: string;
|
|
31
|
+
readonly baseUrl: string;
|
|
32
|
+
readonly debug: boolean;
|
|
33
|
+
readonly trackHash: boolean;
|
|
34
|
+
readonly sdkName?: string;
|
|
35
|
+
readonly sdkVersion?: string;
|
|
36
|
+
isCookieless(): boolean;
|
|
37
|
+
getAnonymousId(): string;
|
|
38
|
+
getSession(): SessionContext;
|
|
39
|
+
capture(event: string, properties?: Record<string, unknown>): void;
|
|
40
|
+
on<K extends ExtensionHook>(hook: K, handler: ExtensionHandler<K>): () => void;
|
|
41
|
+
log(message: string, ...details: unknown[]): void;
|
|
42
|
+
}
|
|
43
|
+
interface AnalyticsExtension {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
setup(context: AnalyticsExtensionContext): ExtensionCleanup | undefined | Promise<ExtensionCleanup | undefined>;
|
|
46
|
+
}
|
|
47
|
+
type ExtensionCleanup = (options: {
|
|
48
|
+
discard: boolean;
|
|
49
|
+
}) => void;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { ExtensionHook as a, ExtensionHandler as i, AnalyticsExtensionContext as n, ExtensionHooks as o, ExtensionCleanup as r, AnalyticsExtension as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as removeStorageItem, l as setStorageItem, n as createId, s as getStorageItem } from "./send-data-DFZnsaL2.js";
|
|
2
2
|
//#region src/utils/identifiers.ts
|
|
3
3
|
const ANONYMOUS_ID_KEY = "faststats_anon_id_";
|
|
4
4
|
function getOrCreateAnonymousId(siteKey) {
|
|
@@ -8,8 +8,8 @@ function getOrCreateAnonymousId(siteKey) {
|
|
|
8
8
|
const newId = createId();
|
|
9
9
|
return setStorageItem("localStorage", key, newId) ? newId : "";
|
|
10
10
|
}
|
|
11
|
-
function getAnonymousId(siteKey, cookieless) {
|
|
12
|
-
if (cookieless
|
|
11
|
+
function getAnonymousId(siteKey, cookieless = false) {
|
|
12
|
+
if (cookieless) return "";
|
|
13
13
|
return getOrCreateAnonymousId(siteKey);
|
|
14
14
|
}
|
|
15
15
|
function resetAnonymousId(siteKey, cookieless) {
|
|
@@ -28,18 +28,38 @@ function removeStorageItem(name, key) {
|
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
30
|
//#region src/utils/session-manager.ts
|
|
31
|
-
const IDLE_MS =
|
|
32
|
-
const MAX_AGE_MS =
|
|
31
|
+
const IDLE_MS = 18e5;
|
|
32
|
+
const MAX_AGE_MS = 864e5;
|
|
33
33
|
const SESSION_KEY = "faststats_session_";
|
|
34
|
-
|
|
34
|
+
const NO_STORAGE = {};
|
|
35
35
|
const memorySessions = /* @__PURE__ */ new Map();
|
|
36
|
-
const rotationListeners = /* @__PURE__ */ new
|
|
36
|
+
const rotationListeners = /* @__PURE__ */ new Map();
|
|
37
|
+
function memoryKey(siteKey, cookieless) {
|
|
38
|
+
return `${siteKey}:${cookieless ? "memory" : "persistent"}`;
|
|
39
|
+
}
|
|
40
|
+
function storageScope() {
|
|
41
|
+
try {
|
|
42
|
+
return globalThis.localStorage ?? NO_STORAGE;
|
|
43
|
+
} catch {
|
|
44
|
+
return NO_STORAGE;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readMemory(siteKey, cookieless) {
|
|
48
|
+
const entry = memorySessions.get(memoryKey(siteKey, cookieless));
|
|
49
|
+
return entry?.scope === storageScope() ? entry.session : null;
|
|
50
|
+
}
|
|
51
|
+
function writeMemory(siteKey, cookieless, session) {
|
|
52
|
+
memorySessions.set(memoryKey(siteKey, cookieless), {
|
|
53
|
+
scope: storageScope(),
|
|
54
|
+
session
|
|
55
|
+
});
|
|
56
|
+
}
|
|
37
57
|
function createId() {
|
|
38
58
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
39
59
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
40
60
|
}
|
|
41
61
|
function readSession(siteKey) {
|
|
42
|
-
const memory =
|
|
62
|
+
const memory = readMemory(siteKey, false);
|
|
43
63
|
const raw = getStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
44
64
|
if (!raw) return memory;
|
|
45
65
|
try {
|
|
@@ -49,29 +69,29 @@ function readSession(siteKey) {
|
|
|
49
69
|
return memory;
|
|
50
70
|
}
|
|
51
71
|
function writeSession(siteKey, session) {
|
|
52
|
-
if (!setStorageItem("localStorage", `${SESSION_KEY}${siteKey}`, JSON.stringify(session)))
|
|
72
|
+
if (!setStorageItem("localStorage", `${SESSION_KEY}${siteKey}`, JSON.stringify(session))) writeMemory(siteKey, false, session);
|
|
53
73
|
}
|
|
54
|
-
function clearSession(siteKey) {
|
|
55
|
-
memorySessions.delete(siteKey);
|
|
56
|
-
removeStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
74
|
+
function clearSession(siteKey, cookieless) {
|
|
75
|
+
memorySessions.delete(memoryKey(siteKey, cookieless));
|
|
76
|
+
if (!cookieless) removeStorageItem("localStorage", `${SESSION_KEY}${siteKey}`);
|
|
57
77
|
}
|
|
58
78
|
function expired(session, now) {
|
|
59
79
|
return now - session.activity >= IDLE_MS || now - session.start >= MAX_AGE_MS;
|
|
60
80
|
}
|
|
61
|
-
function emitRotation(prev, next) {
|
|
62
|
-
for (const listener of rotationListeners) listener(prev, next);
|
|
81
|
+
function emitRotation(siteKey, cookieless, prev, next) {
|
|
82
|
+
for (const listener of rotationListeners.get(memoryKey(siteKey, cookieless)) ?? []) listener(prev, next);
|
|
63
83
|
}
|
|
64
84
|
function resolveSession(siteKey, cookieless, touch) {
|
|
65
85
|
const now = Date.now();
|
|
66
86
|
if (cookieless) {
|
|
67
|
-
let session =
|
|
87
|
+
let session = readMemory(siteKey, true) ?? void 0;
|
|
68
88
|
if (!session) {
|
|
69
89
|
session = {
|
|
70
90
|
id: createId(),
|
|
71
91
|
start: now,
|
|
72
92
|
activity: now
|
|
73
93
|
};
|
|
74
|
-
|
|
94
|
+
writeMemory(siteKey, true, session);
|
|
75
95
|
} else if (touch) session.activity = now;
|
|
76
96
|
return {
|
|
77
97
|
session,
|
|
@@ -115,52 +135,50 @@ function toContext(siteKey, session, cookieless, windowIdOverride) {
|
|
|
115
135
|
sessionStart: session.start
|
|
116
136
|
};
|
|
117
137
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
function isCookielessMode() {
|
|
124
|
-
return cookielessMode;
|
|
125
|
-
}
|
|
126
|
-
function getSessionContext(siteKey, cookieless) {
|
|
127
|
-
const useCookieless = cookieless ?? cookielessMode;
|
|
128
|
-
const { session, rotatedFrom } = resolveSession(siteKey, useCookieless, false);
|
|
129
|
-
const next = toContext(siteKey, session, useCookieless);
|
|
130
|
-
if (rotatedFrom) emitRotation(toContext(siteKey, rotatedFrom, useCookieless, next.windowId), next);
|
|
138
|
+
function getSessionContext(siteKey, cookieless = false) {
|
|
139
|
+
const { session, rotatedFrom } = resolveSession(siteKey, cookieless, false);
|
|
140
|
+
const next = toContext(siteKey, session, cookieless);
|
|
141
|
+
if (rotatedFrom) emitRotation(siteKey, cookieless, toContext(siteKey, rotatedFrom, cookieless, next.windowId), next);
|
|
131
142
|
return next;
|
|
132
143
|
}
|
|
133
|
-
function touchActivity(siteKey, cookieless) {
|
|
134
|
-
const
|
|
135
|
-
const { session, rotatedFrom } = resolveSession(siteKey, useCookieless, true);
|
|
144
|
+
function touchActivity(siteKey, cookieless = false) {
|
|
145
|
+
const { session, rotatedFrom } = resolveSession(siteKey, cookieless, true);
|
|
136
146
|
if (!rotatedFrom) return null;
|
|
137
|
-
const wid = windowId(siteKey,
|
|
147
|
+
const wid = windowId(siteKey, cookieless, session.id);
|
|
138
148
|
const rotation = {
|
|
139
|
-
prev: toContext(siteKey, rotatedFrom,
|
|
140
|
-
next: toContext(siteKey, session,
|
|
149
|
+
prev: toContext(siteKey, rotatedFrom, cookieless, wid),
|
|
150
|
+
next: toContext(siteKey, session, cookieless, wid)
|
|
141
151
|
};
|
|
142
|
-
emitRotation(rotation.prev, rotation.next);
|
|
152
|
+
emitRotation(siteKey, cookieless, rotation.prev, rotation.next);
|
|
143
153
|
return rotation;
|
|
144
154
|
}
|
|
145
|
-
function resetSession(siteKey) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (useCookieless) {
|
|
155
|
+
function resetSession(siteKey, cookieless = false) {
|
|
156
|
+
const previous = getSessionContext(siteKey, cookieless);
|
|
157
|
+
clearSession(siteKey, cookieless);
|
|
158
|
+
if (cookieless) {
|
|
150
159
|
const now = Date.now();
|
|
151
|
-
|
|
160
|
+
writeMemory(siteKey, true, {
|
|
152
161
|
id: createId(),
|
|
153
162
|
start: now,
|
|
154
163
|
activity: now
|
|
155
164
|
});
|
|
156
165
|
} else removeStorageItem("sessionStorage", `faststats_window_id_${siteKey}`);
|
|
157
|
-
const next = getSessionContext(siteKey,
|
|
158
|
-
if (previous.sessionId !== next.sessionId) emitRotation(previous, next);
|
|
166
|
+
const next = getSessionContext(siteKey, cookieless);
|
|
167
|
+
if (previous.sessionId !== next.sessionId) emitRotation(siteKey, cookieless, previous, next);
|
|
159
168
|
return next;
|
|
160
169
|
}
|
|
161
|
-
function onSessionRotated(listener) {
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
function onSessionRotated(siteKey, cookieless, listener) {
|
|
171
|
+
const key = memoryKey(siteKey, cookieless);
|
|
172
|
+
let listeners = rotationListeners.get(key);
|
|
173
|
+
if (!listeners) {
|
|
174
|
+
listeners = /* @__PURE__ */ new Set();
|
|
175
|
+
rotationListeners.set(key, listeners);
|
|
176
|
+
}
|
|
177
|
+
listeners.add(listener);
|
|
178
|
+
return () => {
|
|
179
|
+
listeners?.delete(listener);
|
|
180
|
+
if (listeners?.size === 0) rotationListeners.delete(key);
|
|
181
|
+
};
|
|
164
182
|
}
|
|
165
183
|
//#endregion
|
|
166
184
|
//#region src/utils/send-data.ts
|
|
@@ -198,4 +216,4 @@ async function sendData(options) {
|
|
|
198
216
|
}
|
|
199
217
|
}
|
|
200
218
|
//#endregion
|
|
201
|
-
export {
|
|
219
|
+
export { resetSession as a, removeStorageItem as c, onSessionRotated as i, setStorageItem as l, createId as n, touchActivity as o, getSessionContext as r, getStorageItem as s, sendData as t };
|
package/dist/error.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
1
2
|
//#region src/error.d.ts
|
|
2
3
|
type ErrorEntry = {
|
|
3
4
|
error: string;
|
|
@@ -10,11 +11,14 @@ interface ErrorTrackingOptions {
|
|
|
10
11
|
baseUrl?: string;
|
|
11
12
|
debug?: boolean;
|
|
12
13
|
trackHash?: boolean;
|
|
14
|
+
cookieless?: boolean;
|
|
13
15
|
flushInterval?: number;
|
|
14
16
|
maxQueueSize?: number;
|
|
15
17
|
sdkName?: string;
|
|
16
18
|
sdkVersion?: string;
|
|
17
19
|
}
|
|
20
|
+
type ErrorTrackingExtensionOptions = Omit<ErrorTrackingOptions, "siteKey" | "baseUrl" | "debug" | "trackHash" | "cookieless" | "sdkName" | "sdkVersion">;
|
|
21
|
+
declare function errorTracking(options?: ErrorTrackingExtensionOptions): AnalyticsExtension;
|
|
18
22
|
declare class ErrorTracker {
|
|
19
23
|
private readonly options;
|
|
20
24
|
private readonly endpoint;
|
|
@@ -22,8 +26,9 @@ declare class ErrorTracker {
|
|
|
22
26
|
private timer;
|
|
23
27
|
private started;
|
|
24
28
|
private flushing;
|
|
29
|
+
private cookieless;
|
|
25
30
|
constructor(options: ErrorTrackingOptions);
|
|
26
|
-
|
|
31
|
+
setCookielessMode(cookieless: boolean): void;
|
|
27
32
|
start(): void;
|
|
28
33
|
stop(discard?: boolean): void;
|
|
29
34
|
onPageHidden(persisted?: boolean): void;
|
|
@@ -34,4 +39,4 @@ declare class ErrorTracker {
|
|
|
34
39
|
private flush;
|
|
35
40
|
}
|
|
36
41
|
//#endregion
|
|
37
|
-
export { ErrorEntry, ErrorTrackingOptions, ErrorTracker as default };
|
|
42
|
+
export { ErrorEntry, ErrorTrackingExtensionOptions, ErrorTrackingOptions, ErrorTracker as default, errorTracking };
|
package/dist/error.js
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { 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 { r as getSessionContext, t as sendData } from "./chunks/send-data-DFZnsaL2.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-KgQDX74Q.js";
|
|
4
4
|
//#region src/error.ts
|
|
5
5
|
const SDK_NAME = "@faststats/web";
|
|
6
|
-
const SDK_VERSION = "0.
|
|
7
|
-
|
|
6
|
+
const SDK_VERSION = "0.8.1";
|
|
7
|
+
function errorTracking(options = {}) {
|
|
8
|
+
return {
|
|
9
|
+
name: "error-tracking",
|
|
10
|
+
setup(context) {
|
|
11
|
+
const tracker = new ErrorTracker({
|
|
12
|
+
...options,
|
|
13
|
+
siteKey: context.siteKey,
|
|
14
|
+
baseUrl: context.baseUrl,
|
|
15
|
+
debug: context.debug,
|
|
16
|
+
trackHash: context.trackHash,
|
|
17
|
+
cookieless: context.isCookieless(),
|
|
18
|
+
sdkName: context.sdkName,
|
|
19
|
+
sdkVersion: context.sdkVersion
|
|
20
|
+
});
|
|
21
|
+
context.on("pageHide", ({ persisted }) => tracker.onPageHidden(persisted));
|
|
22
|
+
context.on("error", ({ error }) => tracker.captureError(error));
|
|
23
|
+
context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
|
|
24
|
+
tracker.start();
|
|
25
|
+
return ({ discard }) => tracker.stop(discard);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const EXTENSION_URL = /(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//;
|
|
8
30
|
function toEntry(kind, source, handled, fallback) {
|
|
9
31
|
if (source instanceof Error) return {
|
|
10
32
|
error: kind === "UnhandledRejection" ? kind : source.name.trim() || kind,
|
|
@@ -20,7 +42,7 @@ function toEntry(kind, source, handled, fallback) {
|
|
|
20
42
|
}
|
|
21
43
|
function isExtensionError(filename, source) {
|
|
22
44
|
const stack = source instanceof Error ? source.stack ?? "" : "";
|
|
23
|
-
return
|
|
45
|
+
return EXTENSION_URL.test(filename) || EXTENSION_URL.test(stack);
|
|
24
46
|
}
|
|
25
47
|
var ErrorTracker = class {
|
|
26
48
|
options;
|
|
@@ -29,12 +51,15 @@ var ErrorTracker = class {
|
|
|
29
51
|
timer = null;
|
|
30
52
|
started = false;
|
|
31
53
|
flushing = false;
|
|
54
|
+
cookieless;
|
|
32
55
|
constructor(options) {
|
|
33
56
|
this.options = options;
|
|
34
57
|
this.endpoint = `${resolveBaseUrl(options.baseUrl, ANALYTICS_BASE)}${URLS.events}`;
|
|
58
|
+
this.cookieless = options.cookieless ?? false;
|
|
35
59
|
}
|
|
36
|
-
|
|
37
|
-
if (this.
|
|
60
|
+
setCookielessMode(cookieless) {
|
|
61
|
+
if (this.cookieless !== cookieless) this.queue = [];
|
|
62
|
+
this.cookieless = cookieless;
|
|
38
63
|
}
|
|
39
64
|
start() {
|
|
40
65
|
if (this.started || typeof window === "undefined") return;
|
|
@@ -42,7 +67,6 @@ var ErrorTracker = class {
|
|
|
42
67
|
window.addEventListener("error", this.onError);
|
|
43
68
|
window.addEventListener("unhandledrejection", this.onRejection);
|
|
44
69
|
this.timer = setInterval(() => void this.flush(), this.options.flushInterval ?? 5e3);
|
|
45
|
-
this.log("Started");
|
|
46
70
|
}
|
|
47
71
|
stop(discard = false) {
|
|
48
72
|
if (!this.started || typeof window === "undefined") return;
|
|
@@ -53,7 +77,6 @@ var ErrorTracker = class {
|
|
|
53
77
|
this.timer = null;
|
|
54
78
|
if (discard) this.queue = [];
|
|
55
79
|
else this.flush();
|
|
56
|
-
this.log("Stopped");
|
|
57
80
|
}
|
|
58
81
|
onPageHidden(persisted = false) {
|
|
59
82
|
if (!persisted) this.flush();
|
|
@@ -71,9 +94,8 @@ var ErrorTracker = class {
|
|
|
71
94
|
};
|
|
72
95
|
enqueue(error) {
|
|
73
96
|
const buildId = globalThis.__SOURCEMAPS_BUILD__?.buildId;
|
|
74
|
-
const userId = getAnonymousId(this.options.siteKey);
|
|
75
|
-
const ctx = getSessionContext(this.options.siteKey);
|
|
76
|
-
this.log("Captured:", error);
|
|
97
|
+
const userId = getAnonymousId(this.options.siteKey, this.cookieless);
|
|
98
|
+
const ctx = getSessionContext(this.options.siteKey, this.cookieless);
|
|
77
99
|
this.queue.push({
|
|
78
100
|
error,
|
|
79
101
|
context: {
|
|
@@ -132,4 +154,4 @@ var ErrorTracker = class {
|
|
|
132
154
|
}
|
|
133
155
|
};
|
|
134
156
|
//#endregion
|
|
135
|
-
export { ErrorTracker as default };
|
|
157
|
+
export { ErrorTracker as default, errorTracking };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,60 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ReplayTrackerOptions } from "./chunks/replay-BeC2XA4N.js";
|
|
3
|
-
|
|
1
|
+
import { a as ExtensionHook, i as ExtensionHandler, n as AnalyticsExtensionContext, o as ExtensionHooks, r as ExtensionCleanup, t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
4
2
|
//#region src/analytics.d.ts
|
|
5
|
-
type ConsentMode = "
|
|
6
|
-
type PendingBehavior = "anonymous" | "disabled";
|
|
3
|
+
type ConsentMode = "anonymous" | "granted" | "denied";
|
|
7
4
|
interface IdentifyOptions {
|
|
8
|
-
name?: string;
|
|
9
|
-
phone?: string;
|
|
10
|
-
avatarUrl?: string;
|
|
5
|
+
name?: string | null;
|
|
6
|
+
phone?: string | null;
|
|
7
|
+
avatarUrl?: string | null;
|
|
11
8
|
traits?: Record<string, unknown>;
|
|
9
|
+
traitMode?: "merge" | "replace";
|
|
10
|
+
unsetTraits?: string[];
|
|
11
|
+
aliases?: string[];
|
|
12
|
+
}
|
|
13
|
+
interface IdentifyUser extends IdentifyOptions {
|
|
14
|
+
id: string;
|
|
15
|
+
email?: string | null;
|
|
12
16
|
}
|
|
13
17
|
interface WebAnalyticsOptions {
|
|
14
18
|
siteKey: string;
|
|
15
19
|
baseUrl?: string;
|
|
16
|
-
featureFlagsBaseUrl?: string;
|
|
17
20
|
debug?: boolean;
|
|
18
|
-
autoTrack?: boolean;
|
|
19
21
|
trackHash?: boolean;
|
|
20
22
|
cookieless?: boolean;
|
|
21
|
-
consent?:
|
|
22
|
-
|
|
23
|
-
pendingBehavior?: PendingBehavior;
|
|
24
|
-
};
|
|
25
|
-
webVitals?: {
|
|
26
|
-
enabled?: boolean;
|
|
27
|
-
attribution?: boolean;
|
|
28
|
-
};
|
|
29
|
-
sessionReplays?: {
|
|
30
|
-
enabled?: boolean;
|
|
31
|
-
};
|
|
32
|
-
errorTracking?: {
|
|
33
|
-
enabled?: boolean;
|
|
34
|
-
};
|
|
35
|
-
replayOptions?: Partial<ReplayTrackerOptions>;
|
|
23
|
+
consent?: ConsentMode;
|
|
24
|
+
extensions?: readonly AnalyticsExtension[];
|
|
36
25
|
/** @internal */
|
|
37
26
|
sdkName?: string;
|
|
38
27
|
/** @internal */
|
|
39
28
|
sdkVersion?: string;
|
|
40
29
|
}
|
|
41
|
-
declare function getInstance(): WebAnalytics | null;
|
|
42
|
-
declare function trackEvent(eventName: string, properties?: Record<string, unknown>): void;
|
|
43
|
-
declare function identify(externalId: string, email: string, options?: IdentifyOptions): Promise<boolean>;
|
|
44
|
-
declare function logout(resetAnonymousIdentity?: boolean): void;
|
|
45
|
-
declare function setConsentMode(mode: ConsentMode): void;
|
|
46
|
-
declare function optIn(): void;
|
|
47
|
-
declare function optOut(): void;
|
|
48
|
-
declare function reportError(error: Error): void;
|
|
49
|
-
declare function isTrackingDisabled(): boolean;
|
|
50
30
|
declare class WebAnalytics {
|
|
51
31
|
private readonly options;
|
|
52
32
|
private readonly baseUrl;
|
|
53
33
|
private readonly debug;
|
|
34
|
+
private readonly cleanup;
|
|
54
35
|
private started;
|
|
55
|
-
private destroyed;
|
|
56
|
-
private epoch;
|
|
57
36
|
private heartbeatTimer;
|
|
37
|
+
private extensions;
|
|
58
38
|
private pageKey;
|
|
59
39
|
private entry;
|
|
60
40
|
private left;
|
|
@@ -62,51 +42,53 @@ declare class WebAnalytics {
|
|
|
62
42
|
private visitStartedAt;
|
|
63
43
|
private pendingPageviewTrigger;
|
|
64
44
|
private consentMode;
|
|
65
|
-
private readonly pendingBehavior;
|
|
66
|
-
private readonly cleanup;
|
|
67
|
-
private readonly childTrackers;
|
|
68
|
-
private readonly outboundLinks;
|
|
69
45
|
constructor(options: WebAnalyticsOptions);
|
|
70
|
-
|
|
71
|
-
private isTrackingBlocked;
|
|
72
|
-
private ensureActive;
|
|
73
|
-
private canTrack;
|
|
74
|
-
private canSendEvents;
|
|
75
|
-
private cookieless;
|
|
76
|
-
private touch;
|
|
77
|
-
private bind;
|
|
78
|
-
private stopHeartbeat;
|
|
79
|
-
private loadChild;
|
|
80
|
-
private loadOptionalTrackers;
|
|
81
|
-
private enterPage;
|
|
82
|
-
private beginTracking;
|
|
83
|
-
private readonly onLinkClick;
|
|
84
|
-
private readonly onVisibilityChange;
|
|
85
|
-
private readonly onPageHide;
|
|
86
|
-
private readonly onPageShow;
|
|
87
|
-
start(): Promise<void>;
|
|
46
|
+
start(): void;
|
|
88
47
|
destroy(): void;
|
|
89
|
-
pageview(
|
|
90
|
-
track(name: string,
|
|
91
|
-
identify(
|
|
48
|
+
pageview(properties?: Record<string, unknown>): void;
|
|
49
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
50
|
+
identify(user: IdentifyUser): Promise<boolean>;
|
|
51
|
+
identify(externalId: string, email?: string, options?: IdentifyOptions): Promise<boolean>;
|
|
92
52
|
logout(resetAnonymousIdentity?: boolean): void;
|
|
93
53
|
setConsentMode(mode: ConsentMode): void;
|
|
94
|
-
getConsentMode(): ConsentMode;
|
|
95
54
|
getAnonymousId(): string;
|
|
96
|
-
getSessionId(): string;
|
|
97
|
-
getWindowId(): string;
|
|
98
|
-
checkFeatureFlag(key: string, attributes?: Record<string, unknown>, opts?: {
|
|
99
|
-
externalId?: string;
|
|
100
|
-
signal?: AbortSignal;
|
|
101
|
-
}): Promise<FeatureFlagEvaluation>;
|
|
102
55
|
reportError(error: Error): void;
|
|
103
|
-
private
|
|
56
|
+
private canTrack;
|
|
57
|
+
private isCookieless;
|
|
58
|
+
private ensureActive;
|
|
59
|
+
private activate;
|
|
60
|
+
private startExtensions;
|
|
61
|
+
private stopExtensions;
|
|
62
|
+
private installBrowserLifecycle;
|
|
63
|
+
private bind;
|
|
64
|
+
private readonly onNavigate;
|
|
65
|
+
private readonly onVisibilityChange;
|
|
66
|
+
private readonly onPageHide;
|
|
67
|
+
private readonly onPageShow;
|
|
68
|
+
private navigate;
|
|
69
|
+
private enterPage;
|
|
104
70
|
private leavePage;
|
|
105
|
-
private startVisit;
|
|
106
71
|
private pauseVisit;
|
|
107
72
|
private resumeVisit;
|
|
73
|
+
private touch;
|
|
108
74
|
private startHeartbeat;
|
|
109
|
-
private
|
|
75
|
+
private stopHeartbeat;
|
|
76
|
+
private send;
|
|
110
77
|
}
|
|
111
78
|
//#endregion
|
|
112
|
-
|
|
79
|
+
//#region src/client.d.ts
|
|
80
|
+
/** Creates an independent client. Call `start()` when it should begin tracking. */
|
|
81
|
+
declare function createClient(options: WebAnalyticsOptions): WebAnalytics;
|
|
82
|
+
/** Initializes and starts the shared client used by the convenience functions. */
|
|
83
|
+
declare function init(options: WebAnalyticsOptions): WebAnalytics;
|
|
84
|
+
/** 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;
|
|
93
|
+
//#endregion
|
|
94
|
+
export { type AnalyticsExtension, type AnalyticsExtensionContext, type ConsentMode, type ExtensionCleanup, type ExtensionHandler, type ExtensionHook, type ExtensionHooks, type IdentifyOptions, type IdentifyUser, WebAnalytics, type WebAnalyticsOptions, createClient, identify, init, logout, pageview, reportError, setConsentMode, shutdown, track };
|