@faststats/web 0.2.14 → 0.3.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.
Files changed (53) hide show
  1. package/dist/chunks/api-urls-nKmJnyjD.js +1 -0
  2. package/dist/chunks/identifiers-wylBcW5K.js +1 -0
  3. package/dist/chunks/{replay-DvJYurEC.d.ts → replay-Dpl1P6cw.d.ts} +11 -14
  4. package/dist/chunks/send-data-D6WdryL7.js +1 -0
  5. package/dist/error.d.ts +4 -12
  6. package/dist/error.js +2 -1
  7. package/dist/feature-flags.d.ts +19 -2
  8. package/dist/feature-flags.js +1 -1
  9. package/dist/index.d.ts +14 -37
  10. package/dist/index.js +1 -1
  11. package/dist/replay.d.ts +2 -2
  12. package/dist/replay.js +1 -1
  13. package/dist/web-vitals.d.ts +4 -7
  14. package/dist/web-vitals.js +1 -1
  15. package/package.json +7 -6
  16. package/CHANGELOG.md +0 -184
  17. package/REPLAY_PAYLOAD.md +0 -64
  18. package/dist/chunks/api-urls-DaeYkG0_.js +0 -1
  19. package/dist/chunks/error-Cd9PTS5v.js +0 -2
  20. package/dist/chunks/feature-flags-BClx56v5.d.ts +0 -19
  21. package/dist/chunks/feature-flags-DSOCIZHK.js +0 -1
  22. package/dist/chunks/replay-rTqcjOo2.js +0 -1
  23. package/dist/chunks/rolldown-runtime-MP-BAFHD.js +0 -1
  24. package/dist/chunks/send-data-B2fYGj6v.js +0 -1
  25. package/dist/chunks/session-manager-Cy63ptPF.js +0 -1
  26. package/dist/chunks/types-CYzR5xtT.js +0 -1
  27. package/dist/chunks/web-vitals-Be-Cg4Po.js +0 -1
  28. package/scripts/check-bundle-size.mjs +0 -96
  29. package/src/analytics.ts +0 -787
  30. package/src/entries/error.ts +0 -6
  31. package/src/entries/feature-flags.ts +0 -5
  32. package/src/entries/main.ts +0 -21
  33. package/src/entries/replay.ts +0 -1
  34. package/src/entries/web-vitals.ts +0 -1
  35. package/src/env.d.ts +0 -2
  36. package/src/error.ts +0 -257
  37. package/src/feature-flags.ts +0 -84
  38. package/src/replay.ts +0 -596
  39. package/src/sdk.ts +0 -8
  40. package/src/utils/api-urls.ts +0 -24
  41. package/src/utils/identifiers.ts +0 -47
  42. package/src/utils/send-data.ts +0 -52
  43. package/src/utils/session-manager.ts +0 -416
  44. package/src/utils/types.ts +0 -15
  45. package/src/web-vitals.ts +0 -159
  46. package/tests/analytics.test.ts +0 -453
  47. package/tests/identifiers.test.ts +0 -78
  48. package/tests/replay.test.ts +0 -582
  49. package/tests/session-manager.test.ts +0 -161
  50. package/tests/web-vitals.test.ts +0 -208
  51. package/tsconfig.json +0 -30
  52. package/tsdown.config.ts +0 -27
  53. package/worker/index.ts +0 -22
@@ -1,6 +0,0 @@
1
- export {
2
- default,
3
- type ErrorEntry,
4
- type ErrorTracking,
5
- type ErrorTrackingOptions,
6
- } from "../error";
@@ -1,5 +0,0 @@
1
- export type {
2
- FeatureFlagCheckContext,
3
- FeatureFlagEvaluation,
4
- } from "../feature-flags";
5
- export { fetchFeatureFlagEvaluation } from "../feature-flags";
@@ -1,21 +0,0 @@
1
- export {
2
- type ConsentMode,
3
- getInstance,
4
- type IdentifyOptions,
5
- identify,
6
- isTrackingDisabled,
7
- logout,
8
- optIn,
9
- optOut,
10
- reportError,
11
- sendData,
12
- setConsentMode,
13
- trackEvent,
14
- WebAnalytics,
15
- type WebAnalyticsOptions,
16
- } from "../analytics";
17
- export type {
18
- FeatureFlagCheckContext,
19
- FeatureFlagEvaluation,
20
- } from "../feature-flags";
21
- export { fetchFeatureFlagEvaluation } from "../feature-flags";
@@ -1 +0,0 @@
1
- export { default, type ReplayTrackerOptions } from "../replay";
@@ -1 +0,0 @@
1
- export { default, type WebVitalsOptions } from "../web-vitals";
package/src/env.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare const __FASTSTATS_SDK_NAME__: string;
2
- declare const __FASTSTATS_SDK_VERSION__: string;
package/src/error.ts DELETED
@@ -1,257 +0,0 @@
1
- import { SDK_NAME, SDK_VERSION } from "./sdk";
2
- import { normalizeAnalyticsBaseUrl, URLS } from "./utils/api-urls";
3
- import { getAnonymousId } from "./utils/identifiers";
4
- import { sendData } from "./utils/send-data";
5
- import {
6
- getOrCreateSessionId,
7
- getOrCreateWindowId,
8
- } from "./utils/session-manager";
9
-
10
- export type ErrorEntry = {
11
- error: string;
12
- message?: string;
13
- stack?: string[];
14
- handled?: boolean;
15
- cause?: ErrorEntry;
16
- };
17
-
18
- export type ErrorTracking = ErrorEntry & {
19
- hash: string;
20
- count: number;
21
- };
22
-
23
- export interface ErrorTrackingOptions {
24
- siteKey: string;
25
- baseUrl?: string;
26
- debug?: boolean;
27
- flushInterval?: number;
28
- maxQueueSize?: number;
29
- sdkName?: string;
30
- sdkVersion?: string;
31
- }
32
-
33
- type Kind = "error" | "unhandledrejection";
34
-
35
- const EXTENSION_RE =
36
- /(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//;
37
-
38
- const parseStack = (stack?: string): string[] | undefined =>
39
- stack
40
- ?.split("\n")
41
- .map((l) => l.trim())
42
- .filter(Boolean);
43
-
44
- function serializeCause(value: unknown): ErrorEntry | undefined {
45
- if (value instanceof Error) {
46
- return {
47
- error: value.name?.trim() || "Error",
48
- message: value.message,
49
- stack: parseStack(value.stack),
50
- cause: serializeCause(value.cause),
51
- };
52
- }
53
- if (typeof value === "string") return { error: "Error", message: value };
54
- }
55
-
56
- function fingerprintCause(cause?: ErrorEntry): string {
57
- let s = "";
58
- for (let c = cause; c; c = c.cause) {
59
- s += `${c.error}\0${c.message ?? ""}\0`;
60
- }
61
- return s;
62
- }
63
-
64
- function hashKey(key: string): string {
65
- let a = 2166136261;
66
- let b = 3598710387;
67
- for (let i = 0; i < key.length; i++) {
68
- const c = key.charCodeAt(i);
69
- a = Math.imul(a ^ c, 16777619);
70
- b = Math.imul(b ^ c, 2246822519);
71
- }
72
- return `err_${(a >>> 0).toString(16).padStart(8, "0")}${(b >>> 0).toString(16).padStart(8, "0")}`;
73
- }
74
-
75
- function readBuildId(): string | undefined {
76
- const v = (globalThis as { __SOURCEMAPS_BUILD__?: { buildId?: unknown } })
77
- .__SOURCEMAPS_BUILD__?.buildId;
78
- return typeof v === "string" && v.trim() ? v : undefined;
79
- }
80
-
81
- export default class ErrorTracker {
82
- private readonly endpoint: string;
83
- private readonly seen = new WeakSet<object>();
84
- private readonly queue = new Map<string, ErrorTracking>();
85
- private timer: ReturnType<typeof setInterval> | null = null;
86
- private started = false;
87
- private flushing = false;
88
-
89
- constructor(private readonly options: ErrorTrackingOptions) {
90
- this.endpoint = `${normalizeAnalyticsBaseUrl(options.baseUrl)}${URLS.events}`;
91
- }
92
-
93
- private log(...args: unknown[]): void {
94
- if (this.options.debug) console.log("[ErrorTracker]", ...args);
95
- }
96
-
97
- start(): void {
98
- if (this.started || typeof window === "undefined") return;
99
- this.started = true;
100
- window.addEventListener("error", this.onError);
101
- window.addEventListener("unhandledrejection", this.onRejection);
102
- window.addEventListener("pagehide", this.scheduleFlush);
103
- document.addEventListener("visibilitychange", this.onVisibility);
104
- this.timer = setInterval(
105
- this.scheduleFlush,
106
- this.options.flushInterval ?? 5000,
107
- );
108
- this.log("Started");
109
- }
110
-
111
- stop(): void {
112
- if (!this.started || typeof window === "undefined") return;
113
- this.started = false;
114
- window.removeEventListener("error", this.onError);
115
- window.removeEventListener("unhandledrejection", this.onRejection);
116
- window.removeEventListener("pagehide", this.scheduleFlush);
117
- document.removeEventListener("visibilitychange", this.onVisibility);
118
- if (this.timer) clearInterval(this.timer);
119
- this.timer = null;
120
- this.scheduleFlush();
121
- this.log("Stopped");
122
- }
123
-
124
- captureError(error: Error): void {
125
- this.capture("error", error.message, true, error);
126
- }
127
-
128
- private onError = (event: ErrorEvent): void => {
129
- this.capture(
130
- "error",
131
- event.message || "Unknown error",
132
- false,
133
- event.error,
134
- event.filename || "",
135
- event.lineno,
136
- );
137
- };
138
-
139
- private onRejection = (event: PromiseRejectionEvent): void => {
140
- const reason = event.reason;
141
- const message =
142
- reason instanceof Error
143
- ? reason.message
144
- : typeof reason === "string"
145
- ? reason
146
- : "Unhandled promise rejection";
147
- this.capture("unhandledrejection", message, false, reason);
148
- };
149
-
150
- private onVisibility = (): void => {
151
- if (document.visibilityState === "hidden") this.scheduleFlush();
152
- };
153
-
154
- private scheduleFlush = (): void => {
155
- void this.flush();
156
- };
157
-
158
- private capture(
159
- kind: Kind,
160
- message: string,
161
- handled: boolean,
162
- source: unknown,
163
- filename = "",
164
- lineno: number | "" = "",
165
- ): void {
166
- const stackText = source instanceof Error ? (source.stack ?? "") : "";
167
- if (EXTENSION_RE.test(`${filename}\n${stackText}`)) return;
168
-
169
- if (typeof source === "object" && source !== null) {
170
- if (this.seen.has(source)) {
171
- this.log("Skipping duplicate:", message);
172
- return;
173
- }
174
- this.seen.add(source);
175
- }
176
-
177
- const entry: ErrorEntry = {
178
- error: kind === "unhandledrejection" ? "UnhandledRejection" : "Error",
179
- message,
180
- handled,
181
- };
182
- if (source instanceof Error) {
183
- entry.stack = parseStack(source.stack);
184
- const cause = serializeCause(source.cause);
185
- if (cause) entry.cause = cause;
186
- }
187
-
188
- const key = hashKey(
189
- `${kind}\0${handled ? "handled" : "unhandled"}\0${message}\0${filename}\0${lineno}\0${fingerprintCause(entry.cause)}`,
190
- );
191
-
192
- const existing = this.queue.get(key);
193
- if (existing) {
194
- existing.count++;
195
- } else {
196
- this.queue.set(key, { hash: key, count: 1, ...entry });
197
- }
198
-
199
- this.log("Captured:", entry);
200
-
201
- if (this.queue.size >= (this.options.maxQueueSize ?? 50)) {
202
- this.scheduleFlush();
203
- }
204
- }
205
-
206
- private async flush(): Promise<void> {
207
- if (this.flushing || this.queue.size === 0) return;
208
-
209
- const errors = [...this.queue.values()];
210
- this.queue.clear();
211
- this.flushing = true;
212
-
213
- const userId = getAnonymousId();
214
- const buildId = readBuildId();
215
-
216
- const payload = JSON.stringify({
217
- token: this.options.siteKey,
218
- ...(userId ? { userId } : {}),
219
- sessionId: getOrCreateSessionId(),
220
- windowId: getOrCreateWindowId(this.options.siteKey),
221
- ...(buildId ? { buildId } : {}),
222
- sdkName: this.options.sdkName ?? SDK_NAME,
223
- sdkVersion: this.options.sdkVersion ?? SDK_VERSION,
224
- data: {
225
- url: location.href,
226
- page: location.pathname,
227
- referrer: document.referrer || null,
228
- title: document.title,
229
- },
230
- errors,
231
- });
232
-
233
- this.log("Flushing:", errors);
234
-
235
- try {
236
- const sent = await sendData({
237
- url: this.endpoint,
238
- data: payload,
239
- debug: this.options.debug,
240
- debugPrefix: "[ErrorTracker]",
241
- });
242
- if (!sent) this.requeue(errors);
243
- } catch {
244
- this.requeue(errors);
245
- } finally {
246
- this.flushing = false;
247
- }
248
- }
249
-
250
- private requeue(errors: ErrorTracking[]): void {
251
- for (const e of errors) {
252
- const existing = this.queue.get(e.hash);
253
- if (existing) existing.count += e.count;
254
- else this.queue.set(e.hash, e);
255
- }
256
- }
257
- }
@@ -1,84 +0,0 @@
1
- import { normalizeFeatureFlagsBaseUrl, URLS } from "./utils/api-urls";
2
-
3
- export type FeatureFlagEvaluation = {
4
- value: string;
5
- variantKey?: string;
6
- variantValue?: string;
7
- };
8
-
9
- export type FeatureFlagCheckContext = {
10
- baseUrl?: string;
11
- projectToken?: string;
12
- projectId?: string;
13
- identifier?: string;
14
- externalId?: string;
15
- sessionId?: string;
16
- attributes?: Record<string, unknown>;
17
- signal?: AbortSignal;
18
- };
19
-
20
- export async function fetchFeatureFlagEvaluation(
21
- flagKey: string,
22
- context: FeatureFlagCheckContext,
23
- ): Promise<FeatureFlagEvaluation> {
24
- if (context.projectToken && context.projectId) {
25
- throw new Error("provide either projectToken or projectId, not both");
26
- }
27
-
28
- const projectToken = context.projectToken;
29
- const projectId = context.projectId;
30
- const identifier = context.identifier;
31
- const externalId = context.externalId;
32
- const sessionId = context.sessionId;
33
-
34
- if (!projectToken && !projectId) {
35
- throw new Error("feature flag check requires projectToken or projectId");
36
- }
37
- if (!identifier && !externalId) {
38
- throw new Error("feature flag check requires serverId or externalId");
39
- }
40
-
41
- const body: Record<string, unknown> = {
42
- key: flagKey,
43
- ...(projectId ? { projectId } : {}),
44
- ...(identifier ? { identifier } : {}),
45
- ...(externalId ? { externalId } : {}),
46
- ...(sessionId ? { sessionId } : {}),
47
- };
48
-
49
- if (context.attributes && Object.keys(context.attributes).length > 0) {
50
- body.attributes = context.attributes;
51
- }
52
-
53
- const headers: Record<string, string> = {
54
- "Content-Type": "application/json",
55
- };
56
- if (projectToken) {
57
- headers.Authorization = `Bearer ${projectToken}`;
58
- }
59
-
60
- const url = `${normalizeFeatureFlagsBaseUrl(context.baseUrl)}${URLS.flags}`;
61
- const res = await fetch(url, {
62
- method: "POST",
63
- headers,
64
- body: JSON.stringify(body),
65
- credentials: "omit",
66
- signal: context.signal,
67
- });
68
-
69
- if (!res.ok) {
70
- let detail = "";
71
- try {
72
- const err = (await res.json()) as { error?: string };
73
- if (typeof err.error === "string" && err.error.length > 0) {
74
- detail = ` — ${err.error}`;
75
- }
76
- } catch {
77
- /* ignore */
78
- }
79
- throw new Error(`feature flag check failed: HTTP ${res.status}${detail}`);
80
- }
81
-
82
- const data = (await res.json()) as FeatureFlagEvaluation;
83
- return data;
84
- }