@watchupltd/browser 0.1.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.
@@ -0,0 +1,121 @@
1
+ interface WatchupOptions {
2
+ /**
3
+ * Your project's **public** API key from the Watchup dashboard.
4
+ * Format: `wup_pub_xxxxxxxxxxxx`
5
+ * This key is safe to include in client-side code.
6
+ */
7
+ apiKey: string;
8
+ /** Defaults to `https://api.watchup.site`. Override for self-hosted. */
9
+ baseUrl?: string;
10
+ /** Flush interval in ms. Default: `5000`. */
11
+ flushInterval?: number;
12
+ /** Max items before forcing a flush. Default: `100`. */
13
+ maxBatchSize?: number;
14
+ /** Log SDK warnings to `console.warn`. Default: `false`. */
15
+ debug?: boolean;
16
+ /** Environment label on every payload. Default: `'production'`. */
17
+ environment?: string;
18
+ /** Git SHA / release tag for deploy correlation. */
19
+ release?: string;
20
+ /**
21
+ * Fraction of navigations/fetches captured as traces (0–1).
22
+ * Default: `1` (100 %).
23
+ */
24
+ sampleRate?: number;
25
+ /** Fine-grained control over what gets captured automatically. */
26
+ autoCapture?: {
27
+ /** Capture `window.onerror` and `unhandledrejection`. Default: `true`. */
28
+ errors?: boolean;
29
+ /** Capture FCP, LCP, and page-load timing. Default: `true`. */
30
+ performance?: boolean;
31
+ /**
32
+ * Track the initial page view and SPA navigations (History API).
33
+ * Default: `true`.
34
+ * Set to `false` when a framework SDK (React, Next.js, Svelte) handles
35
+ * page view tracking via the router.
36
+ */
37
+ pageViews?: boolean;
38
+ };
39
+ }
40
+ interface TracePayload {
41
+ span: string;
42
+ ms: number;
43
+ status_code: number;
44
+ status: 'ok' | 'warn' | 'err';
45
+ timestamp: string;
46
+ environment?: string;
47
+ release?: string;
48
+ meta?: Record<string, unknown>;
49
+ }
50
+ interface ErrorPayload {
51
+ message: string;
52
+ level: 'debug' | 'info' | 'warning' | 'error' | 'fatal';
53
+ route?: string;
54
+ stack?: string;
55
+ context?: Record<string, unknown>;
56
+ timestamp: string;
57
+ environment?: string;
58
+ release?: string;
59
+ }
60
+ interface EventPayload {
61
+ name: string;
62
+ properties?: Record<string, unknown>;
63
+ occurred_at: string;
64
+ }
65
+ interface IngestBatch {
66
+ traces?: TracePayload[];
67
+ errors?: ErrorPayload[];
68
+ events?: EventPayload[];
69
+ }
70
+
71
+ declare class Watchup {
72
+ private readonly cfg;
73
+ private readonly batcher;
74
+ private readonly cleanup;
75
+ /**
76
+ * A random UUID generated on init. Stable for the lifetime of the page —
77
+ * useful for correlating all events from one user session.
78
+ */
79
+ readonly sessionId: string;
80
+ constructor(options: WatchupOptions);
81
+ /**
82
+ * Track a custom analytics event.
83
+ *
84
+ * @example
85
+ * watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
86
+ */
87
+ track(name: string, properties?: Record<string, unknown>): void;
88
+ /**
89
+ * Manually capture an error.
90
+ *
91
+ * @example
92
+ * try { ... } catch (err) {
93
+ * watchup.captureError(err, { component: 'CheckoutForm' });
94
+ * }
95
+ */
96
+ captureError(error: Error | string | unknown, context?: Record<string, unknown> & {
97
+ route?: string;
98
+ level?: ErrorPayload['level'];
99
+ }): void;
100
+ /**
101
+ * Time any async operation and record it as a trace.
102
+ * Returns an `end()` function — call it when the operation finishes.
103
+ *
104
+ * @example
105
+ * const end = watchup.startTrace('fetch /api/cart');
106
+ * const cart = await fetch('/api/cart');
107
+ * end({ status: cart.ok ? 'ok' : 'err' });
108
+ */
109
+ startTrace(span: string): (opts?: {
110
+ status?: TracePayload['status'];
111
+ meta?: Record<string, unknown>;
112
+ }) => void;
113
+ /** Immediately flush all queued items. */
114
+ flush(): void;
115
+ /** Stop the flush timer and release all listeners. */
116
+ shutdown(): void;
117
+ private _setupAutoCapture;
118
+ private _setupPageViewTracking;
119
+ }
120
+
121
+ export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions };
@@ -0,0 +1,121 @@
1
+ interface WatchupOptions {
2
+ /**
3
+ * Your project's **public** API key from the Watchup dashboard.
4
+ * Format: `wup_pub_xxxxxxxxxxxx`
5
+ * This key is safe to include in client-side code.
6
+ */
7
+ apiKey: string;
8
+ /** Defaults to `https://api.watchup.site`. Override for self-hosted. */
9
+ baseUrl?: string;
10
+ /** Flush interval in ms. Default: `5000`. */
11
+ flushInterval?: number;
12
+ /** Max items before forcing a flush. Default: `100`. */
13
+ maxBatchSize?: number;
14
+ /** Log SDK warnings to `console.warn`. Default: `false`. */
15
+ debug?: boolean;
16
+ /** Environment label on every payload. Default: `'production'`. */
17
+ environment?: string;
18
+ /** Git SHA / release tag for deploy correlation. */
19
+ release?: string;
20
+ /**
21
+ * Fraction of navigations/fetches captured as traces (0–1).
22
+ * Default: `1` (100 %).
23
+ */
24
+ sampleRate?: number;
25
+ /** Fine-grained control over what gets captured automatically. */
26
+ autoCapture?: {
27
+ /** Capture `window.onerror` and `unhandledrejection`. Default: `true`. */
28
+ errors?: boolean;
29
+ /** Capture FCP, LCP, and page-load timing. Default: `true`. */
30
+ performance?: boolean;
31
+ /**
32
+ * Track the initial page view and SPA navigations (History API).
33
+ * Default: `true`.
34
+ * Set to `false` when a framework SDK (React, Next.js, Svelte) handles
35
+ * page view tracking via the router.
36
+ */
37
+ pageViews?: boolean;
38
+ };
39
+ }
40
+ interface TracePayload {
41
+ span: string;
42
+ ms: number;
43
+ status_code: number;
44
+ status: 'ok' | 'warn' | 'err';
45
+ timestamp: string;
46
+ environment?: string;
47
+ release?: string;
48
+ meta?: Record<string, unknown>;
49
+ }
50
+ interface ErrorPayload {
51
+ message: string;
52
+ level: 'debug' | 'info' | 'warning' | 'error' | 'fatal';
53
+ route?: string;
54
+ stack?: string;
55
+ context?: Record<string, unknown>;
56
+ timestamp: string;
57
+ environment?: string;
58
+ release?: string;
59
+ }
60
+ interface EventPayload {
61
+ name: string;
62
+ properties?: Record<string, unknown>;
63
+ occurred_at: string;
64
+ }
65
+ interface IngestBatch {
66
+ traces?: TracePayload[];
67
+ errors?: ErrorPayload[];
68
+ events?: EventPayload[];
69
+ }
70
+
71
+ declare class Watchup {
72
+ private readonly cfg;
73
+ private readonly batcher;
74
+ private readonly cleanup;
75
+ /**
76
+ * A random UUID generated on init. Stable for the lifetime of the page —
77
+ * useful for correlating all events from one user session.
78
+ */
79
+ readonly sessionId: string;
80
+ constructor(options: WatchupOptions);
81
+ /**
82
+ * Track a custom analytics event.
83
+ *
84
+ * @example
85
+ * watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
86
+ */
87
+ track(name: string, properties?: Record<string, unknown>): void;
88
+ /**
89
+ * Manually capture an error.
90
+ *
91
+ * @example
92
+ * try { ... } catch (err) {
93
+ * watchup.captureError(err, { component: 'CheckoutForm' });
94
+ * }
95
+ */
96
+ captureError(error: Error | string | unknown, context?: Record<string, unknown> & {
97
+ route?: string;
98
+ level?: ErrorPayload['level'];
99
+ }): void;
100
+ /**
101
+ * Time any async operation and record it as a trace.
102
+ * Returns an `end()` function — call it when the operation finishes.
103
+ *
104
+ * @example
105
+ * const end = watchup.startTrace('fetch /api/cart');
106
+ * const cart = await fetch('/api/cart');
107
+ * end({ status: cart.ok ? 'ok' : 'err' });
108
+ */
109
+ startTrace(span: string): (opts?: {
110
+ status?: TracePayload['status'];
111
+ meta?: Record<string, unknown>;
112
+ }) => void;
113
+ /** Immediately flush all queued items. */
114
+ flush(): void;
115
+ /** Stop the flush timer and release all listeners. */
116
+ shutdown(): void;
117
+ private _setupAutoCapture;
118
+ private _setupPageViewTracking;
119
+ }
120
+
121
+ export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions };
package/dist/index.js ADDED
@@ -0,0 +1,424 @@
1
+ 'use strict';
2
+
3
+ // src/transport.ts
4
+ var Transport = class {
5
+ constructor(baseUrl, apiKey, debug = false) {
6
+ this.url = `${baseUrl.replace(/\/$/, "")}/api/v1/ingest/batch`;
7
+ this.headers = {
8
+ "Content-Type": "application/json",
9
+ "X-Api-Key": apiKey
10
+ };
11
+ this.debug = debug;
12
+ }
13
+ /**
14
+ * Send via `fetch` with `keepalive: true`.
15
+ * `keepalive` lets the request outlive the current page — it's the
16
+ * browser equivalent of a "fire and forget" POST.
17
+ * Never rejects.
18
+ */
19
+ async send(batch) {
20
+ try {
21
+ const body = JSON.stringify(batch);
22
+ if (body.length > 6e4) {
23
+ this.beacon(batch);
24
+ return;
25
+ }
26
+ const res = await fetch(this.url, {
27
+ method: "POST",
28
+ headers: this.headers,
29
+ body,
30
+ keepalive: true
31
+ });
32
+ if (this.debug && !res.ok) {
33
+ const text = await res.text().catch(() => "");
34
+ console.warn(`[watchup] ingest ${res.status}: ${text}`);
35
+ }
36
+ } catch (err) {
37
+ if (this.debug) console.warn("[watchup] send failed:", err);
38
+ }
39
+ }
40
+ /**
41
+ * Send via `navigator.sendBeacon`.
42
+ * Returns `true` if the browser accepted the request (doesn't guarantee delivery).
43
+ * The server must accept `application/json` from sendBeacon via a Blob.
44
+ */
45
+ beacon(batch) {
46
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
47
+ try {
48
+ const blob = new Blob([JSON.stringify(batch)], { type: "application/json" });
49
+ return navigator.sendBeacon(this.url, blob);
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ };
55
+
56
+ // src/batcher.ts
57
+ var Batcher = class {
58
+ constructor(transport, flushInterval, maxBatchSize) {
59
+ this.traces = [];
60
+ this.errors = [];
61
+ this.events = [];
62
+ this.timer = null;
63
+ this.flushing = false;
64
+ this.transport = transport;
65
+ this.flushInterval = flushInterval;
66
+ this.maxBatchSize = maxBatchSize;
67
+ }
68
+ start() {
69
+ if (this.timer) return;
70
+ this.timer = setInterval(() => this.flush(), this.flushInterval);
71
+ document.addEventListener("visibilitychange", () => {
72
+ if (document.visibilityState === "hidden") this.beaconFlush();
73
+ });
74
+ window.addEventListener("pagehide", () => this.beaconFlush(), { once: true });
75
+ }
76
+ stop() {
77
+ if (this.timer) {
78
+ clearInterval(this.timer);
79
+ this.timer = null;
80
+ }
81
+ }
82
+ addTrace(t) {
83
+ this.traces.push(t);
84
+ if (this.traces.length >= this.maxBatchSize) this.flush();
85
+ }
86
+ addError(e) {
87
+ this.errors.push(e);
88
+ if (this.errors.length >= Math.ceil(this.maxBatchSize / 2)) this.flush();
89
+ }
90
+ addEvent(e) {
91
+ this.events.push(e);
92
+ if (this.events.length >= this.maxBatchSize) this.flush();
93
+ }
94
+ drain() {
95
+ const traces = this.traces.splice(0);
96
+ const errors = this.errors.splice(0);
97
+ const events = this.events.splice(0);
98
+ if (!traces.length && !errors.length && !events.length) return null;
99
+ return { traces, errors, events };
100
+ }
101
+ flush() {
102
+ if (this.flushing) return;
103
+ const batch = this.drain();
104
+ if (!batch) return;
105
+ this.flushing = true;
106
+ this.transport.send(batch).finally(() => {
107
+ this.flushing = false;
108
+ });
109
+ }
110
+ beaconFlush() {
111
+ const batch = this.drain();
112
+ if (!batch) return;
113
+ if (!this.transport.beacon(batch)) {
114
+ this.transport.send(batch);
115
+ }
116
+ }
117
+ };
118
+
119
+ // src/error-capture.ts
120
+ function captureGlobalErrors(onError, env) {
121
+ const handleError = (event) => {
122
+ var _a, _b, _c, _d;
123
+ onError({
124
+ message: event.message || "Unknown error",
125
+ level: "error",
126
+ route: window.location.pathname,
127
+ stack: (_a = event.error) == null ? void 0 : _a.stack,
128
+ context: {
129
+ url: window.location.href,
130
+ source: (_b = event.filename) != null ? _b : void 0,
131
+ line: (_c = event.lineno) != null ? _c : void 0,
132
+ col: (_d = event.colno) != null ? _d : void 0
133
+ },
134
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
135
+ ...env && { environment: env }
136
+ });
137
+ };
138
+ const handleRejection = (event) => {
139
+ const reason = event.reason;
140
+ const isErr = reason instanceof Error;
141
+ onError({
142
+ message: isErr ? reason.message : String(reason != null ? reason : "Unhandled Promise rejection"),
143
+ level: "error",
144
+ route: window.location.pathname,
145
+ stack: isErr ? reason.stack : void 0,
146
+ context: {
147
+ url: window.location.href,
148
+ type: "unhandledrejection"
149
+ },
150
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
151
+ ...env && { environment: env }
152
+ });
153
+ };
154
+ window.addEventListener("error", handleError);
155
+ window.addEventListener("unhandledrejection", handleRejection);
156
+ return () => {
157
+ window.removeEventListener("error", handleError);
158
+ window.removeEventListener("unhandledrejection", handleRejection);
159
+ };
160
+ }
161
+
162
+ // src/perf.ts
163
+ function rating(ms, good, needsImprovement) {
164
+ if (ms <= good) return "ok";
165
+ if (ms <= needsImprovement) return "warn";
166
+ return "err";
167
+ }
168
+ function statusCode(status) {
169
+ return status === "err" ? 500 : status === "warn" ? 400 : 200;
170
+ }
171
+ function captureFCP(onTrace, env) {
172
+ if (typeof PerformanceObserver === "undefined") return;
173
+ try {
174
+ const po = new PerformanceObserver((list) => {
175
+ for (const entry of list.getEntries()) {
176
+ if (entry.name !== "first-contentful-paint") continue;
177
+ const ms = Math.round(entry.startTime);
178
+ const status = rating(ms, 1800, 3e3);
179
+ onTrace({
180
+ span: "web-vital fcp",
181
+ ms,
182
+ status_code: statusCode(status),
183
+ status,
184
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
185
+ ...env && { environment: env }
186
+ });
187
+ po.disconnect();
188
+ }
189
+ });
190
+ po.observe({ type: "paint", buffered: true });
191
+ } catch {
192
+ }
193
+ }
194
+ function captureLCP(onTrace, env) {
195
+ if (typeof PerformanceObserver === "undefined") return;
196
+ let last = null;
197
+ let reported = false;
198
+ const report = () => {
199
+ if (reported || !last) return;
200
+ reported = true;
201
+ try {
202
+ po.disconnect();
203
+ } catch {
204
+ }
205
+ const ms = Math.round(last.startTime);
206
+ const status = rating(ms, 2500, 4e3);
207
+ onTrace({
208
+ span: "web-vital lcp",
209
+ ms,
210
+ status_code: statusCode(status),
211
+ status,
212
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
213
+ ...env && { environment: env }
214
+ });
215
+ };
216
+ let po;
217
+ try {
218
+ po = new PerformanceObserver((list) => {
219
+ var _a;
220
+ const entries = list.getEntries();
221
+ if (entries.length) last = (_a = entries[entries.length - 1]) != null ? _a : null;
222
+ });
223
+ po.observe({ type: "largest-contentful-paint", buffered: true });
224
+ } catch {
225
+ return;
226
+ }
227
+ document.addEventListener("visibilitychange", report, { once: true });
228
+ document.addEventListener("keydown", report, { once: true, capture: true });
229
+ document.addEventListener("pointerdown", report, { once: true, capture: true });
230
+ }
231
+ function capturePageLoad(onTrace, env) {
232
+ const report = () => {
233
+ const nav = performance.getEntriesByType("navigation")[0];
234
+ if (!nav || nav.loadEventEnd <= 0) return;
235
+ const ms = Math.round(nav.loadEventEnd - nav.startTime);
236
+ const ttfb = Math.round(nav.responseStart - nav.requestStart);
237
+ const status = rating(ms, 2e3, 4e3);
238
+ onTrace({
239
+ span: "pageload",
240
+ ms,
241
+ status_code: statusCode(status),
242
+ status,
243
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
244
+ meta: { ttfb },
245
+ ...env && { environment: env }
246
+ });
247
+ };
248
+ if (document.readyState === "complete") {
249
+ setTimeout(report, 0);
250
+ } else {
251
+ window.addEventListener("load", () => setTimeout(report, 100), { once: true });
252
+ }
253
+ }
254
+
255
+ // src/watchup.ts
256
+ var DEFAULTS = {
257
+ baseUrl: "https://api.watchup.site",
258
+ flushInterval: 5e3,
259
+ maxBatchSize: 100,
260
+ debug: false,
261
+ environment: "production",
262
+ release: "",
263
+ sampleRate: 1,
264
+ autoCapture: {
265
+ errors: true,
266
+ performance: true,
267
+ pageViews: true
268
+ }
269
+ };
270
+ var Watchup = class {
271
+ constructor(options) {
272
+ this.cleanup = [];
273
+ /**
274
+ * A random UUID generated on init. Stable for the lifetime of the page —
275
+ * useful for correlating all events from one user session.
276
+ */
277
+ this.sessionId = crypto.randomUUID();
278
+ if (!options.apiKey) {
279
+ throw new Error("[watchup] apiKey is required.");
280
+ }
281
+ this.cfg = {
282
+ ...DEFAULTS,
283
+ autoCapture: { ...DEFAULTS.autoCapture, ...options.autoCapture },
284
+ ...options
285
+ };
286
+ const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);
287
+ this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);
288
+ this.batcher.start();
289
+ this._setupAutoCapture();
290
+ }
291
+ // ── Public API ────────────────────────────────────────────────────────────
292
+ /**
293
+ * Track a custom analytics event.
294
+ *
295
+ * @example
296
+ * watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
297
+ */
298
+ track(name, properties) {
299
+ if (!name) return;
300
+ const event = {
301
+ name,
302
+ ...properties && Object.keys(properties).length && { properties },
303
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString()
304
+ };
305
+ this.batcher.addEvent(event);
306
+ }
307
+ /**
308
+ * Manually capture an error.
309
+ *
310
+ * @example
311
+ * try { ... } catch (err) {
312
+ * watchup.captureError(err, { component: 'CheckoutForm' });
313
+ * }
314
+ */
315
+ captureError(error, context) {
316
+ const { route, level = "error", ...rest } = context != null ? context : {};
317
+ const err = error instanceof Error ? error : new Error(String(error));
318
+ const payload = {
319
+ message: err.message,
320
+ level,
321
+ ...err.stack !== void 0 && { stack: err.stack },
322
+ route: route != null ? route : window.location.pathname,
323
+ ...Object.keys(rest).length && {
324
+ context: { ...rest, url: window.location.href }
325
+ },
326
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
327
+ environment: this.cfg.environment,
328
+ ...this.cfg.release && { release: this.cfg.release }
329
+ };
330
+ this.batcher.addError(payload);
331
+ }
332
+ /**
333
+ * Time any async operation and record it as a trace.
334
+ * Returns an `end()` function — call it when the operation finishes.
335
+ *
336
+ * @example
337
+ * const end = watchup.startTrace('fetch /api/cart');
338
+ * const cart = await fetch('/api/cart');
339
+ * end({ status: cart.ok ? 'ok' : 'err' });
340
+ */
341
+ startTrace(span) {
342
+ const start = Date.now();
343
+ return (opts = {}) => {
344
+ var _a;
345
+ const status = (_a = opts.status) != null ? _a : "ok";
346
+ this.batcher.addTrace({
347
+ span,
348
+ ms: Date.now() - start,
349
+ status_code: status === "err" ? 500 : status === "warn" ? 400 : 200,
350
+ status,
351
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
352
+ environment: this.cfg.environment,
353
+ ...this.cfg.release && { release: this.cfg.release },
354
+ ...opts.meta && { meta: opts.meta }
355
+ });
356
+ };
357
+ }
358
+ /** Immediately flush all queued items. */
359
+ flush() {
360
+ this.batcher.flush();
361
+ }
362
+ /** Stop the flush timer and release all listeners. */
363
+ shutdown() {
364
+ this.batcher.stop();
365
+ this.batcher.flush();
366
+ this.cleanup.forEach((fn) => fn());
367
+ }
368
+ // ── Auto-capture setup ────────────────────────────────────────────────────
369
+ _setupAutoCapture() {
370
+ const { autoCapture, environment } = this.cfg;
371
+ if (autoCapture.errors) {
372
+ this.cleanup.push(
373
+ captureGlobalErrors((e) => this.batcher.addError(e), environment)
374
+ );
375
+ }
376
+ if (autoCapture.performance) {
377
+ captureFCP((t) => this.batcher.addTrace(t), environment);
378
+ captureLCP((t) => this.batcher.addTrace(t), environment);
379
+ capturePageLoad((t) => this.batcher.addTrace(t), environment);
380
+ }
381
+ if (autoCapture.pageViews) {
382
+ this._setupPageViewTracking();
383
+ }
384
+ }
385
+ _setupPageViewTracking() {
386
+ const track = () => {
387
+ this.batcher.addEvent({
388
+ name: "pageview",
389
+ properties: {
390
+ path: window.location.pathname,
391
+ ...window.location.search && { search: window.location.search },
392
+ ...document.referrer && { referrer: document.referrer },
393
+ title: document.title
394
+ },
395
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString()
396
+ });
397
+ };
398
+ if (document.readyState === "loading") {
399
+ document.addEventListener("DOMContentLoaded", track, { once: true });
400
+ } else {
401
+ setTimeout(track, 0);
402
+ }
403
+ const origPush = history.pushState.bind(history);
404
+ const origReplace = history.replaceState.bind(history);
405
+ history.pushState = (...args) => {
406
+ origPush(...args);
407
+ setTimeout(track, 0);
408
+ };
409
+ history.replaceState = (...args) => {
410
+ origReplace(...args);
411
+ };
412
+ const onPopState = () => setTimeout(track, 0);
413
+ window.addEventListener("popstate", onPopState);
414
+ this.cleanup.push(() => {
415
+ history.pushState = origPush;
416
+ history.replaceState = origReplace;
417
+ window.removeEventListener("popstate", onPopState);
418
+ });
419
+ }
420
+ };
421
+
422
+ exports.Watchup = Watchup;
423
+ //# sourceMappingURL=index.js.map
424
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport.ts","../src/batcher.ts","../src/error-capture.ts","../src/perf.ts","../src/watchup.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · transport\n//\n// Two delivery strategies:\n// 1. fetch(keepalive: true) — for regular periodic flushes.\n// 2. navigator.sendBeacon — for page-hide/unload; survives tab close.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { IngestBatch } from './types.js';\n\nexport class Transport {\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly debug: boolean;\n\n constructor(baseUrl: string, apiKey: string, debug = false) {\n this.url = `${baseUrl.replace(/\\/$/, '')}/api/v1/ingest/batch`;\n this.headers = {\n 'Content-Type': 'application/json',\n 'X-Api-Key': apiKey,\n };\n this.debug = debug;\n }\n\n /**\n * Send via `fetch` with `keepalive: true`.\n * `keepalive` lets the request outlive the current page — it's the\n * browser equivalent of a \"fire and forget\" POST.\n * Never rejects.\n */\n async send(batch: IngestBatch): Promise<void> {\n try {\n const body = JSON.stringify(batch);\n\n // keepalive has a 64 KiB payload limit; fall back to beacon for large batches\n if (body.length > 60_000) {\n this.beacon(batch);\n return;\n }\n\n const res = await fetch(this.url, {\n method: 'POST',\n headers: this.headers,\n body,\n keepalive: true,\n });\n\n if (this.debug && !res.ok) {\n const text = await res.text().catch(() => '');\n console.warn(`[watchup] ingest ${res.status}: ${text}`);\n }\n } catch (err) {\n if (this.debug) console.warn('[watchup] send failed:', err);\n }\n }\n\n /**\n * Send via `navigator.sendBeacon`.\n * Returns `true` if the browser accepted the request (doesn't guarantee delivery).\n * The server must accept `application/json` from sendBeacon via a Blob.\n */\n beacon(batch: IngestBatch): boolean {\n if (typeof navigator === 'undefined' || !navigator.sendBeacon) return false;\n try {\n const blob = new Blob([JSON.stringify(batch)], { type: 'application/json' });\n return navigator.sendBeacon(this.url, blob);\n } catch {\n return false;\n }\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · batcher\n//\n// Browser-specific flush strategy:\n// - Periodic interval flush via fetch(keepalive)\n// - visibilitychange 'hidden' + pagehide → sendBeacon for reliable exit delivery\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { TracePayload, ErrorPayload, EventPayload, IngestBatch } from './types.js';\nimport { Transport } from './transport.js';\n\nexport class Batcher {\n private traces: TracePayload[] = [];\n private errors: ErrorPayload[] = [];\n private events: EventPayload[] = [];\n\n private readonly transport: Transport;\n private readonly flushInterval: number;\n private readonly maxBatchSize: number;\n\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n\n constructor(transport: Transport, flushInterval: number, maxBatchSize: number) {\n this.transport = transport;\n this.flushInterval = flushInterval;\n this.maxBatchSize = maxBatchSize;\n }\n\n start(): void {\n if (this.timer) return;\n\n // Periodic flush\n this.timer = setInterval(() => this.flush(), this.flushInterval);\n\n // Reliable delivery on tab hide — visibilitychange fires before the page\n // is destroyed, giving sendBeacon the best chance of succeeding.\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') this.beaconFlush();\n });\n\n // Belt-and-suspenders for browsers/environments that skip visibilitychange\n window.addEventListener('pagehide', () => this.beaconFlush(), { once: true });\n }\n\n stop(): void {\n if (this.timer) { clearInterval(this.timer); this.timer = null; }\n }\n\n addTrace(t: TracePayload): void {\n this.traces.push(t);\n if (this.traces.length >= this.maxBatchSize) this.flush();\n }\n\n addError(e: ErrorPayload): void {\n this.errors.push(e);\n // Errors are high-priority — flush at half capacity\n if (this.errors.length >= Math.ceil(this.maxBatchSize / 2)) this.flush();\n }\n\n addEvent(e: EventPayload): void {\n this.events.push(e);\n if (this.events.length >= this.maxBatchSize) this.flush();\n }\n\n private drain(): IngestBatch | null {\n const traces = this.traces.splice(0);\n const errors = this.errors.splice(0);\n const events = this.events.splice(0);\n if (!traces.length && !errors.length && !events.length) return null;\n return { traces, errors, events };\n }\n\n flush(): void {\n if (this.flushing) return;\n const batch = this.drain();\n if (!batch) return;\n this.flushing = true;\n this.transport.send(batch).finally(() => { this.flushing = false; });\n }\n\n beaconFlush(): void {\n const batch = this.drain();\n if (!batch) return;\n // Try beacon first; fall back to fetch if unsupported\n if (!this.transport.beacon(batch)) {\n this.transport.send(batch);\n }\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · global error capture\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { ErrorPayload } from './types.js';\n\ntype ErrorCallback = (error: ErrorPayload) => void;\n\n/**\n * Attaches `window.onerror` and `window.addEventListener('unhandledrejection')`\n * listeners that forward caught errors to `onError`.\n *\n * Returns a cleanup function that removes both listeners.\n */\nexport function captureGlobalErrors(onError: ErrorCallback, env?: string): () => void {\n const handleError = (event: ErrorEvent) => {\n onError({\n message: event.message || 'Unknown error',\n level: 'error',\n route: window.location.pathname,\n stack: event.error?.stack,\n context: {\n url: window.location.href,\n source: event.filename ?? undefined,\n line: event.lineno ?? undefined,\n col: event.colno ?? undefined,\n },\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n };\n\n const handleRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const isErr = reason instanceof Error;\n onError({\n message: isErr ? reason.message : String(reason ?? 'Unhandled Promise rejection'),\n level: 'error',\n route: window.location.pathname,\n stack: isErr ? reason.stack : undefined,\n context: {\n url: window.location.href,\n type: 'unhandledrejection',\n },\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n };\n\n window.addEventListener('error', handleError);\n window.addEventListener('unhandledrejection', handleRejection);\n\n return () => {\n window.removeEventListener('error', handleError);\n window.removeEventListener('unhandledrejection', handleRejection);\n };\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · Web Vitals capture\n//\n// Captures FCP, LCP, and overall page-load time via PerformanceObserver and\n// PerformanceNavigationTiming. Forwarded as traces so they appear in the\n// Watchup dashboard alongside request spans.\n//\n// Thresholds come from Google's Core Web Vitals 2024 targets:\n// FCP: good ≤ 1800 ms, needs improvement ≤ 3000 ms, poor > 3000 ms\n// LCP: good ≤ 2500 ms, needs improvement ≤ 4000 ms, poor > 4000 ms\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { TracePayload } from './types.js';\n\ntype TraceCallback = (trace: TracePayload) => void;\n\nfunction rating(\n ms: number,\n good: number,\n needsImprovement: number,\n): TracePayload['status'] {\n if (ms <= good) return 'ok';\n if (ms <= needsImprovement) return 'warn';\n return 'err';\n}\n\nfunction statusCode(status: TracePayload['status']): number {\n return status === 'err' ? 500 : status === 'warn' ? 400 : 200;\n}\n\n// ── FCP — First Contentful Paint ─────────────────────────────────────────────\n\nexport function captureFCP(onTrace: TraceCallback, env?: string): void {\n if (typeof PerformanceObserver === 'undefined') return;\n try {\n const po = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name !== 'first-contentful-paint') continue;\n const ms = Math.round(entry.startTime);\n const status = rating(ms, 1800, 3000);\n onTrace({\n span: 'web-vital fcp',\n ms,\n status_code: statusCode(status),\n status,\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n po.disconnect();\n }\n });\n po.observe({ type: 'paint', buffered: true });\n } catch { /* PerformanceObserver 'paint' not supported */ }\n}\n\n// ── LCP — Largest Contentful Paint ───────────────────────────────────────────\n\nexport function captureLCP(onTrace: TraceCallback, env?: string): void {\n if (typeof PerformanceObserver === 'undefined') return;\n\n let last: PerformanceEntry | null = null;\n let reported = false;\n\n const report = () => {\n if (reported || !last) return;\n reported = true;\n try { po.disconnect(); } catch {}\n const ms = Math.round(last.startTime);\n const status = rating(ms, 2500, 4000);\n onTrace({\n span: 'web-vital lcp',\n ms,\n status_code: statusCode(status),\n status,\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n };\n\n let po: PerformanceObserver;\n try {\n po = new PerformanceObserver((list) => {\n const entries = list.getEntries();\n if (entries.length) last = entries[entries.length - 1] ?? null;\n });\n po.observe({ type: 'largest-contentful-paint', buffered: true });\n } catch {\n return; // 'largest-contentful-paint' not supported\n }\n\n // LCP is only finalised once the user interacts or the tab hides.\n document.addEventListener('visibilitychange', report, { once: true });\n document.addEventListener('keydown', report, { once: true, capture: true });\n document.addEventListener('pointerdown', report, { once: true, capture: true });\n}\n\n// ── Page load (overall) ───────────────────────────────────────────────────────\n\nexport function capturePageLoad(onTrace: TraceCallback, env?: string): void {\n const report = () => {\n const nav = performance.getEntriesByType('navigation')[0] as\n PerformanceNavigationTiming | undefined;\n if (!nav || nav.loadEventEnd <= 0) return;\n\n const ms = Math.round(nav.loadEventEnd - nav.startTime);\n const ttfb = Math.round(nav.responseStart - nav.requestStart);\n const status = rating(ms, 2000, 4000);\n\n onTrace({\n span: 'pageload',\n ms,\n status_code: statusCode(status),\n status,\n timestamp: new Date().toISOString(),\n meta: { ttfb },\n ...(env && { environment: env }),\n });\n };\n\n if (document.readyState === 'complete') {\n // PerformanceNavigationTiming might not be fully populated yet\n setTimeout(report, 0);\n } else {\n window.addEventListener('load', () => setTimeout(report, 100), { once: true });\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · Watchup client\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { WatchupOptions, TracePayload, ErrorPayload, EventPayload } from './types.js';\nimport { Transport } from './transport.js';\nimport { Batcher } from './batcher.js';\nimport { captureGlobalErrors } from './error-capture.js';\nimport { captureFCP, captureLCP, capturePageLoad } from './perf.js';\n\nconst DEFAULTS = {\n baseUrl: 'https://api.watchup.site',\n flushInterval: 5_000,\n maxBatchSize: 100,\n debug: false,\n environment: 'production',\n release: '',\n sampleRate: 1,\n autoCapture: {\n errors: true,\n performance: true,\n pageViews: true,\n },\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class Watchup {\n private readonly cfg: Required<WatchupOptions>;\n private readonly batcher: Batcher;\n private readonly cleanup: Array<() => void> = [];\n\n /**\n * A random UUID generated on init. Stable for the lifetime of the page —\n * useful for correlating all events from one user session.\n */\n readonly sessionId: string = crypto.randomUUID();\n\n constructor(options: WatchupOptions) {\n if (!options.apiKey) {\n throw new Error('[watchup] apiKey is required.');\n }\n\n this.cfg = {\n ...DEFAULTS,\n autoCapture: { ...DEFAULTS.autoCapture, ...options.autoCapture },\n ...options,\n } as Required<WatchupOptions>;\n\n const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);\n this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);\n this.batcher.start();\n\n this._setupAutoCapture();\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Track a custom analytics event.\n *\n * @example\n * watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });\n */\n track(name: string, properties?: Record<string, unknown>): void {\n if (!name) return;\n const event: EventPayload = {\n name,\n ...(properties && Object.keys(properties).length && { properties }),\n occurred_at: new Date().toISOString(),\n };\n this.batcher.addEvent(event);\n }\n\n /**\n * Manually capture an error.\n *\n * @example\n * try { ... } catch (err) {\n * watchup.captureError(err, { component: 'CheckoutForm' });\n * }\n */\n captureError(\n error: Error | string | unknown,\n context?: Record<string, unknown> & { route?: string; level?: ErrorPayload['level'] },\n ): void {\n const { route, level = 'error', ...rest } = context ?? {};\n const err = error instanceof Error ? error : new Error(String(error));\n\n const payload: ErrorPayload = {\n message: err.message,\n level,\n ...(err.stack !== undefined && { stack: err.stack }),\n route: route ?? window.location.pathname,\n ...(Object.keys(rest).length && {\n context: { ...rest, url: window.location.href },\n }),\n timestamp: new Date().toISOString(),\n environment: this.cfg.environment,\n ...(this.cfg.release && { release: this.cfg.release }),\n };\n\n this.batcher.addError(payload);\n }\n\n /**\n * Time any async operation and record it as a trace.\n * Returns an `end()` function — call it when the operation finishes.\n *\n * @example\n * const end = watchup.startTrace('fetch /api/cart');\n * const cart = await fetch('/api/cart');\n * end({ status: cart.ok ? 'ok' : 'err' });\n */\n startTrace(\n span: string,\n ): (opts?: { status?: TracePayload['status']; meta?: Record<string, unknown> }) => void {\n const start = Date.now();\n return (opts = {}) => {\n const status = opts.status ?? 'ok';\n this.batcher.addTrace({\n span,\n ms: Date.now() - start,\n status_code: status === 'err' ? 500 : status === 'warn' ? 400 : 200,\n status,\n timestamp: new Date().toISOString(),\n environment: this.cfg.environment,\n ...(this.cfg.release && { release: this.cfg.release }),\n ...(opts.meta && { meta: opts.meta }),\n });\n };\n }\n\n /** Immediately flush all queued items. */\n flush(): void { this.batcher.flush(); }\n\n /** Stop the flush timer and release all listeners. */\n shutdown(): void {\n this.batcher.stop();\n this.batcher.flush();\n this.cleanup.forEach((fn) => fn());\n }\n\n // ── Auto-capture setup ────────────────────────────────────────────────────\n\n private _setupAutoCapture(): void {\n const { autoCapture, environment } = this.cfg;\n\n if (autoCapture.errors) {\n this.cleanup.push(\n captureGlobalErrors((e) => this.batcher.addError(e), environment),\n );\n }\n\n if (autoCapture.performance) {\n captureFCP((t) => this.batcher.addTrace(t), environment);\n captureLCP((t) => this.batcher.addTrace(t), environment);\n capturePageLoad((t) => this.batcher.addTrace(t), environment);\n }\n\n if (autoCapture.pageViews) {\n this._setupPageViewTracking();\n }\n }\n\n private _setupPageViewTracking(): void {\n const track = () => {\n this.batcher.addEvent({\n name: 'pageview',\n properties: {\n path: window.location.pathname,\n ...(window.location.search && { search: window.location.search }),\n ...(document.referrer && { referrer: document.referrer }),\n title: document.title,\n },\n occurred_at: new Date().toISOString(),\n });\n };\n\n // Initial view\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', track, { once: true });\n } else {\n // Small timeout so the page title has settled\n setTimeout(track, 0);\n }\n\n // SPA navigation — patch History API\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n origPush(...args);\n setTimeout(track, 0); // title settles asynchronously\n };\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n origReplace(...args);\n // replaceState often doesn't mean a new \"page\" (it's used for URL\n // canonicalisation etc.) — only track if the pathname actually changed.\n };\n\n const onPopState = () => setTimeout(track, 0);\n window.addEventListener('popstate', onPopState);\n\n this.cleanup.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener('popstate', onPopState);\n });\n }\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,422 @@
1
+ // src/transport.ts
2
+ var Transport = class {
3
+ constructor(baseUrl, apiKey, debug = false) {
4
+ this.url = `${baseUrl.replace(/\/$/, "")}/api/v1/ingest/batch`;
5
+ this.headers = {
6
+ "Content-Type": "application/json",
7
+ "X-Api-Key": apiKey
8
+ };
9
+ this.debug = debug;
10
+ }
11
+ /**
12
+ * Send via `fetch` with `keepalive: true`.
13
+ * `keepalive` lets the request outlive the current page — it's the
14
+ * browser equivalent of a "fire and forget" POST.
15
+ * Never rejects.
16
+ */
17
+ async send(batch) {
18
+ try {
19
+ const body = JSON.stringify(batch);
20
+ if (body.length > 6e4) {
21
+ this.beacon(batch);
22
+ return;
23
+ }
24
+ const res = await fetch(this.url, {
25
+ method: "POST",
26
+ headers: this.headers,
27
+ body,
28
+ keepalive: true
29
+ });
30
+ if (this.debug && !res.ok) {
31
+ const text = await res.text().catch(() => "");
32
+ console.warn(`[watchup] ingest ${res.status}: ${text}`);
33
+ }
34
+ } catch (err) {
35
+ if (this.debug) console.warn("[watchup] send failed:", err);
36
+ }
37
+ }
38
+ /**
39
+ * Send via `navigator.sendBeacon`.
40
+ * Returns `true` if the browser accepted the request (doesn't guarantee delivery).
41
+ * The server must accept `application/json` from sendBeacon via a Blob.
42
+ */
43
+ beacon(batch) {
44
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
45
+ try {
46
+ const blob = new Blob([JSON.stringify(batch)], { type: "application/json" });
47
+ return navigator.sendBeacon(this.url, blob);
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ };
53
+
54
+ // src/batcher.ts
55
+ var Batcher = class {
56
+ constructor(transport, flushInterval, maxBatchSize) {
57
+ this.traces = [];
58
+ this.errors = [];
59
+ this.events = [];
60
+ this.timer = null;
61
+ this.flushing = false;
62
+ this.transport = transport;
63
+ this.flushInterval = flushInterval;
64
+ this.maxBatchSize = maxBatchSize;
65
+ }
66
+ start() {
67
+ if (this.timer) return;
68
+ this.timer = setInterval(() => this.flush(), this.flushInterval);
69
+ document.addEventListener("visibilitychange", () => {
70
+ if (document.visibilityState === "hidden") this.beaconFlush();
71
+ });
72
+ window.addEventListener("pagehide", () => this.beaconFlush(), { once: true });
73
+ }
74
+ stop() {
75
+ if (this.timer) {
76
+ clearInterval(this.timer);
77
+ this.timer = null;
78
+ }
79
+ }
80
+ addTrace(t) {
81
+ this.traces.push(t);
82
+ if (this.traces.length >= this.maxBatchSize) this.flush();
83
+ }
84
+ addError(e) {
85
+ this.errors.push(e);
86
+ if (this.errors.length >= Math.ceil(this.maxBatchSize / 2)) this.flush();
87
+ }
88
+ addEvent(e) {
89
+ this.events.push(e);
90
+ if (this.events.length >= this.maxBatchSize) this.flush();
91
+ }
92
+ drain() {
93
+ const traces = this.traces.splice(0);
94
+ const errors = this.errors.splice(0);
95
+ const events = this.events.splice(0);
96
+ if (!traces.length && !errors.length && !events.length) return null;
97
+ return { traces, errors, events };
98
+ }
99
+ flush() {
100
+ if (this.flushing) return;
101
+ const batch = this.drain();
102
+ if (!batch) return;
103
+ this.flushing = true;
104
+ this.transport.send(batch).finally(() => {
105
+ this.flushing = false;
106
+ });
107
+ }
108
+ beaconFlush() {
109
+ const batch = this.drain();
110
+ if (!batch) return;
111
+ if (!this.transport.beacon(batch)) {
112
+ this.transport.send(batch);
113
+ }
114
+ }
115
+ };
116
+
117
+ // src/error-capture.ts
118
+ function captureGlobalErrors(onError, env) {
119
+ const handleError = (event) => {
120
+ var _a, _b, _c, _d;
121
+ onError({
122
+ message: event.message || "Unknown error",
123
+ level: "error",
124
+ route: window.location.pathname,
125
+ stack: (_a = event.error) == null ? void 0 : _a.stack,
126
+ context: {
127
+ url: window.location.href,
128
+ source: (_b = event.filename) != null ? _b : void 0,
129
+ line: (_c = event.lineno) != null ? _c : void 0,
130
+ col: (_d = event.colno) != null ? _d : void 0
131
+ },
132
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
133
+ ...env && { environment: env }
134
+ });
135
+ };
136
+ const handleRejection = (event) => {
137
+ const reason = event.reason;
138
+ const isErr = reason instanceof Error;
139
+ onError({
140
+ message: isErr ? reason.message : String(reason != null ? reason : "Unhandled Promise rejection"),
141
+ level: "error",
142
+ route: window.location.pathname,
143
+ stack: isErr ? reason.stack : void 0,
144
+ context: {
145
+ url: window.location.href,
146
+ type: "unhandledrejection"
147
+ },
148
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
149
+ ...env && { environment: env }
150
+ });
151
+ };
152
+ window.addEventListener("error", handleError);
153
+ window.addEventListener("unhandledrejection", handleRejection);
154
+ return () => {
155
+ window.removeEventListener("error", handleError);
156
+ window.removeEventListener("unhandledrejection", handleRejection);
157
+ };
158
+ }
159
+
160
+ // src/perf.ts
161
+ function rating(ms, good, needsImprovement) {
162
+ if (ms <= good) return "ok";
163
+ if (ms <= needsImprovement) return "warn";
164
+ return "err";
165
+ }
166
+ function statusCode(status) {
167
+ return status === "err" ? 500 : status === "warn" ? 400 : 200;
168
+ }
169
+ function captureFCP(onTrace, env) {
170
+ if (typeof PerformanceObserver === "undefined") return;
171
+ try {
172
+ const po = new PerformanceObserver((list) => {
173
+ for (const entry of list.getEntries()) {
174
+ if (entry.name !== "first-contentful-paint") continue;
175
+ const ms = Math.round(entry.startTime);
176
+ const status = rating(ms, 1800, 3e3);
177
+ onTrace({
178
+ span: "web-vital fcp",
179
+ ms,
180
+ status_code: statusCode(status),
181
+ status,
182
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
183
+ ...env && { environment: env }
184
+ });
185
+ po.disconnect();
186
+ }
187
+ });
188
+ po.observe({ type: "paint", buffered: true });
189
+ } catch {
190
+ }
191
+ }
192
+ function captureLCP(onTrace, env) {
193
+ if (typeof PerformanceObserver === "undefined") return;
194
+ let last = null;
195
+ let reported = false;
196
+ const report = () => {
197
+ if (reported || !last) return;
198
+ reported = true;
199
+ try {
200
+ po.disconnect();
201
+ } catch {
202
+ }
203
+ const ms = Math.round(last.startTime);
204
+ const status = rating(ms, 2500, 4e3);
205
+ onTrace({
206
+ span: "web-vital lcp",
207
+ ms,
208
+ status_code: statusCode(status),
209
+ status,
210
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
211
+ ...env && { environment: env }
212
+ });
213
+ };
214
+ let po;
215
+ try {
216
+ po = new PerformanceObserver((list) => {
217
+ var _a;
218
+ const entries = list.getEntries();
219
+ if (entries.length) last = (_a = entries[entries.length - 1]) != null ? _a : null;
220
+ });
221
+ po.observe({ type: "largest-contentful-paint", buffered: true });
222
+ } catch {
223
+ return;
224
+ }
225
+ document.addEventListener("visibilitychange", report, { once: true });
226
+ document.addEventListener("keydown", report, { once: true, capture: true });
227
+ document.addEventListener("pointerdown", report, { once: true, capture: true });
228
+ }
229
+ function capturePageLoad(onTrace, env) {
230
+ const report = () => {
231
+ const nav = performance.getEntriesByType("navigation")[0];
232
+ if (!nav || nav.loadEventEnd <= 0) return;
233
+ const ms = Math.round(nav.loadEventEnd - nav.startTime);
234
+ const ttfb = Math.round(nav.responseStart - nav.requestStart);
235
+ const status = rating(ms, 2e3, 4e3);
236
+ onTrace({
237
+ span: "pageload",
238
+ ms,
239
+ status_code: statusCode(status),
240
+ status,
241
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
242
+ meta: { ttfb },
243
+ ...env && { environment: env }
244
+ });
245
+ };
246
+ if (document.readyState === "complete") {
247
+ setTimeout(report, 0);
248
+ } else {
249
+ window.addEventListener("load", () => setTimeout(report, 100), { once: true });
250
+ }
251
+ }
252
+
253
+ // src/watchup.ts
254
+ var DEFAULTS = {
255
+ baseUrl: "https://api.watchup.site",
256
+ flushInterval: 5e3,
257
+ maxBatchSize: 100,
258
+ debug: false,
259
+ environment: "production",
260
+ release: "",
261
+ sampleRate: 1,
262
+ autoCapture: {
263
+ errors: true,
264
+ performance: true,
265
+ pageViews: true
266
+ }
267
+ };
268
+ var Watchup = class {
269
+ constructor(options) {
270
+ this.cleanup = [];
271
+ /**
272
+ * A random UUID generated on init. Stable for the lifetime of the page —
273
+ * useful for correlating all events from one user session.
274
+ */
275
+ this.sessionId = crypto.randomUUID();
276
+ if (!options.apiKey) {
277
+ throw new Error("[watchup] apiKey is required.");
278
+ }
279
+ this.cfg = {
280
+ ...DEFAULTS,
281
+ autoCapture: { ...DEFAULTS.autoCapture, ...options.autoCapture },
282
+ ...options
283
+ };
284
+ const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);
285
+ this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);
286
+ this.batcher.start();
287
+ this._setupAutoCapture();
288
+ }
289
+ // ── Public API ────────────────────────────────────────────────────────────
290
+ /**
291
+ * Track a custom analytics event.
292
+ *
293
+ * @example
294
+ * watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
295
+ */
296
+ track(name, properties) {
297
+ if (!name) return;
298
+ const event = {
299
+ name,
300
+ ...properties && Object.keys(properties).length && { properties },
301
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString()
302
+ };
303
+ this.batcher.addEvent(event);
304
+ }
305
+ /**
306
+ * Manually capture an error.
307
+ *
308
+ * @example
309
+ * try { ... } catch (err) {
310
+ * watchup.captureError(err, { component: 'CheckoutForm' });
311
+ * }
312
+ */
313
+ captureError(error, context) {
314
+ const { route, level = "error", ...rest } = context != null ? context : {};
315
+ const err = error instanceof Error ? error : new Error(String(error));
316
+ const payload = {
317
+ message: err.message,
318
+ level,
319
+ ...err.stack !== void 0 && { stack: err.stack },
320
+ route: route != null ? route : window.location.pathname,
321
+ ...Object.keys(rest).length && {
322
+ context: { ...rest, url: window.location.href }
323
+ },
324
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
325
+ environment: this.cfg.environment,
326
+ ...this.cfg.release && { release: this.cfg.release }
327
+ };
328
+ this.batcher.addError(payload);
329
+ }
330
+ /**
331
+ * Time any async operation and record it as a trace.
332
+ * Returns an `end()` function — call it when the operation finishes.
333
+ *
334
+ * @example
335
+ * const end = watchup.startTrace('fetch /api/cart');
336
+ * const cart = await fetch('/api/cart');
337
+ * end({ status: cart.ok ? 'ok' : 'err' });
338
+ */
339
+ startTrace(span) {
340
+ const start = Date.now();
341
+ return (opts = {}) => {
342
+ var _a;
343
+ const status = (_a = opts.status) != null ? _a : "ok";
344
+ this.batcher.addTrace({
345
+ span,
346
+ ms: Date.now() - start,
347
+ status_code: status === "err" ? 500 : status === "warn" ? 400 : 200,
348
+ status,
349
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
350
+ environment: this.cfg.environment,
351
+ ...this.cfg.release && { release: this.cfg.release },
352
+ ...opts.meta && { meta: opts.meta }
353
+ });
354
+ };
355
+ }
356
+ /** Immediately flush all queued items. */
357
+ flush() {
358
+ this.batcher.flush();
359
+ }
360
+ /** Stop the flush timer and release all listeners. */
361
+ shutdown() {
362
+ this.batcher.stop();
363
+ this.batcher.flush();
364
+ this.cleanup.forEach((fn) => fn());
365
+ }
366
+ // ── Auto-capture setup ────────────────────────────────────────────────────
367
+ _setupAutoCapture() {
368
+ const { autoCapture, environment } = this.cfg;
369
+ if (autoCapture.errors) {
370
+ this.cleanup.push(
371
+ captureGlobalErrors((e) => this.batcher.addError(e), environment)
372
+ );
373
+ }
374
+ if (autoCapture.performance) {
375
+ captureFCP((t) => this.batcher.addTrace(t), environment);
376
+ captureLCP((t) => this.batcher.addTrace(t), environment);
377
+ capturePageLoad((t) => this.batcher.addTrace(t), environment);
378
+ }
379
+ if (autoCapture.pageViews) {
380
+ this._setupPageViewTracking();
381
+ }
382
+ }
383
+ _setupPageViewTracking() {
384
+ const track = () => {
385
+ this.batcher.addEvent({
386
+ name: "pageview",
387
+ properties: {
388
+ path: window.location.pathname,
389
+ ...window.location.search && { search: window.location.search },
390
+ ...document.referrer && { referrer: document.referrer },
391
+ title: document.title
392
+ },
393
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString()
394
+ });
395
+ };
396
+ if (document.readyState === "loading") {
397
+ document.addEventListener("DOMContentLoaded", track, { once: true });
398
+ } else {
399
+ setTimeout(track, 0);
400
+ }
401
+ const origPush = history.pushState.bind(history);
402
+ const origReplace = history.replaceState.bind(history);
403
+ history.pushState = (...args) => {
404
+ origPush(...args);
405
+ setTimeout(track, 0);
406
+ };
407
+ history.replaceState = (...args) => {
408
+ origReplace(...args);
409
+ };
410
+ const onPopState = () => setTimeout(track, 0);
411
+ window.addEventListener("popstate", onPopState);
412
+ this.cleanup.push(() => {
413
+ history.pushState = origPush;
414
+ history.replaceState = origReplace;
415
+ window.removeEventListener("popstate", onPopState);
416
+ });
417
+ }
418
+ };
419
+
420
+ export { Watchup };
421
+ //# sourceMappingURL=index.mjs.map
422
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport.ts","../src/batcher.ts","../src/error-capture.ts","../src/perf.ts","../src/watchup.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · transport\n//\n// Two delivery strategies:\n// 1. fetch(keepalive: true) — for regular periodic flushes.\n// 2. navigator.sendBeacon — for page-hide/unload; survives tab close.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { IngestBatch } from './types.js';\n\nexport class Transport {\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly debug: boolean;\n\n constructor(baseUrl: string, apiKey: string, debug = false) {\n this.url = `${baseUrl.replace(/\\/$/, '')}/api/v1/ingest/batch`;\n this.headers = {\n 'Content-Type': 'application/json',\n 'X-Api-Key': apiKey,\n };\n this.debug = debug;\n }\n\n /**\n * Send via `fetch` with `keepalive: true`.\n * `keepalive` lets the request outlive the current page — it's the\n * browser equivalent of a \"fire and forget\" POST.\n * Never rejects.\n */\n async send(batch: IngestBatch): Promise<void> {\n try {\n const body = JSON.stringify(batch);\n\n // keepalive has a 64 KiB payload limit; fall back to beacon for large batches\n if (body.length > 60_000) {\n this.beacon(batch);\n return;\n }\n\n const res = await fetch(this.url, {\n method: 'POST',\n headers: this.headers,\n body,\n keepalive: true,\n });\n\n if (this.debug && !res.ok) {\n const text = await res.text().catch(() => '');\n console.warn(`[watchup] ingest ${res.status}: ${text}`);\n }\n } catch (err) {\n if (this.debug) console.warn('[watchup] send failed:', err);\n }\n }\n\n /**\n * Send via `navigator.sendBeacon`.\n * Returns `true` if the browser accepted the request (doesn't guarantee delivery).\n * The server must accept `application/json` from sendBeacon via a Blob.\n */\n beacon(batch: IngestBatch): boolean {\n if (typeof navigator === 'undefined' || !navigator.sendBeacon) return false;\n try {\n const blob = new Blob([JSON.stringify(batch)], { type: 'application/json' });\n return navigator.sendBeacon(this.url, blob);\n } catch {\n return false;\n }\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · batcher\n//\n// Browser-specific flush strategy:\n// - Periodic interval flush via fetch(keepalive)\n// - visibilitychange 'hidden' + pagehide → sendBeacon for reliable exit delivery\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { TracePayload, ErrorPayload, EventPayload, IngestBatch } from './types.js';\nimport { Transport } from './transport.js';\n\nexport class Batcher {\n private traces: TracePayload[] = [];\n private errors: ErrorPayload[] = [];\n private events: EventPayload[] = [];\n\n private readonly transport: Transport;\n private readonly flushInterval: number;\n private readonly maxBatchSize: number;\n\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n\n constructor(transport: Transport, flushInterval: number, maxBatchSize: number) {\n this.transport = transport;\n this.flushInterval = flushInterval;\n this.maxBatchSize = maxBatchSize;\n }\n\n start(): void {\n if (this.timer) return;\n\n // Periodic flush\n this.timer = setInterval(() => this.flush(), this.flushInterval);\n\n // Reliable delivery on tab hide — visibilitychange fires before the page\n // is destroyed, giving sendBeacon the best chance of succeeding.\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') this.beaconFlush();\n });\n\n // Belt-and-suspenders for browsers/environments that skip visibilitychange\n window.addEventListener('pagehide', () => this.beaconFlush(), { once: true });\n }\n\n stop(): void {\n if (this.timer) { clearInterval(this.timer); this.timer = null; }\n }\n\n addTrace(t: TracePayload): void {\n this.traces.push(t);\n if (this.traces.length >= this.maxBatchSize) this.flush();\n }\n\n addError(e: ErrorPayload): void {\n this.errors.push(e);\n // Errors are high-priority — flush at half capacity\n if (this.errors.length >= Math.ceil(this.maxBatchSize / 2)) this.flush();\n }\n\n addEvent(e: EventPayload): void {\n this.events.push(e);\n if (this.events.length >= this.maxBatchSize) this.flush();\n }\n\n private drain(): IngestBatch | null {\n const traces = this.traces.splice(0);\n const errors = this.errors.splice(0);\n const events = this.events.splice(0);\n if (!traces.length && !errors.length && !events.length) return null;\n return { traces, errors, events };\n }\n\n flush(): void {\n if (this.flushing) return;\n const batch = this.drain();\n if (!batch) return;\n this.flushing = true;\n this.transport.send(batch).finally(() => { this.flushing = false; });\n }\n\n beaconFlush(): void {\n const batch = this.drain();\n if (!batch) return;\n // Try beacon first; fall back to fetch if unsupported\n if (!this.transport.beacon(batch)) {\n this.transport.send(batch);\n }\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · global error capture\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { ErrorPayload } from './types.js';\n\ntype ErrorCallback = (error: ErrorPayload) => void;\n\n/**\n * Attaches `window.onerror` and `window.addEventListener('unhandledrejection')`\n * listeners that forward caught errors to `onError`.\n *\n * Returns a cleanup function that removes both listeners.\n */\nexport function captureGlobalErrors(onError: ErrorCallback, env?: string): () => void {\n const handleError = (event: ErrorEvent) => {\n onError({\n message: event.message || 'Unknown error',\n level: 'error',\n route: window.location.pathname,\n stack: event.error?.stack,\n context: {\n url: window.location.href,\n source: event.filename ?? undefined,\n line: event.lineno ?? undefined,\n col: event.colno ?? undefined,\n },\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n };\n\n const handleRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const isErr = reason instanceof Error;\n onError({\n message: isErr ? reason.message : String(reason ?? 'Unhandled Promise rejection'),\n level: 'error',\n route: window.location.pathname,\n stack: isErr ? reason.stack : undefined,\n context: {\n url: window.location.href,\n type: 'unhandledrejection',\n },\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n };\n\n window.addEventListener('error', handleError);\n window.addEventListener('unhandledrejection', handleRejection);\n\n return () => {\n window.removeEventListener('error', handleError);\n window.removeEventListener('unhandledrejection', handleRejection);\n };\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · Web Vitals capture\n//\n// Captures FCP, LCP, and overall page-load time via PerformanceObserver and\n// PerformanceNavigationTiming. Forwarded as traces so they appear in the\n// Watchup dashboard alongside request spans.\n//\n// Thresholds come from Google's Core Web Vitals 2024 targets:\n// FCP: good ≤ 1800 ms, needs improvement ≤ 3000 ms, poor > 3000 ms\n// LCP: good ≤ 2500 ms, needs improvement ≤ 4000 ms, poor > 4000 ms\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { TracePayload } from './types.js';\n\ntype TraceCallback = (trace: TracePayload) => void;\n\nfunction rating(\n ms: number,\n good: number,\n needsImprovement: number,\n): TracePayload['status'] {\n if (ms <= good) return 'ok';\n if (ms <= needsImprovement) return 'warn';\n return 'err';\n}\n\nfunction statusCode(status: TracePayload['status']): number {\n return status === 'err' ? 500 : status === 'warn' ? 400 : 200;\n}\n\n// ── FCP — First Contentful Paint ─────────────────────────────────────────────\n\nexport function captureFCP(onTrace: TraceCallback, env?: string): void {\n if (typeof PerformanceObserver === 'undefined') return;\n try {\n const po = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name !== 'first-contentful-paint') continue;\n const ms = Math.round(entry.startTime);\n const status = rating(ms, 1800, 3000);\n onTrace({\n span: 'web-vital fcp',\n ms,\n status_code: statusCode(status),\n status,\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n po.disconnect();\n }\n });\n po.observe({ type: 'paint', buffered: true });\n } catch { /* PerformanceObserver 'paint' not supported */ }\n}\n\n// ── LCP — Largest Contentful Paint ───────────────────────────────────────────\n\nexport function captureLCP(onTrace: TraceCallback, env?: string): void {\n if (typeof PerformanceObserver === 'undefined') return;\n\n let last: PerformanceEntry | null = null;\n let reported = false;\n\n const report = () => {\n if (reported || !last) return;\n reported = true;\n try { po.disconnect(); } catch {}\n const ms = Math.round(last.startTime);\n const status = rating(ms, 2500, 4000);\n onTrace({\n span: 'web-vital lcp',\n ms,\n status_code: statusCode(status),\n status,\n timestamp: new Date().toISOString(),\n ...(env && { environment: env }),\n });\n };\n\n let po: PerformanceObserver;\n try {\n po = new PerformanceObserver((list) => {\n const entries = list.getEntries();\n if (entries.length) last = entries[entries.length - 1] ?? null;\n });\n po.observe({ type: 'largest-contentful-paint', buffered: true });\n } catch {\n return; // 'largest-contentful-paint' not supported\n }\n\n // LCP is only finalised once the user interacts or the tab hides.\n document.addEventListener('visibilitychange', report, { once: true });\n document.addEventListener('keydown', report, { once: true, capture: true });\n document.addEventListener('pointerdown', report, { once: true, capture: true });\n}\n\n// ── Page load (overall) ───────────────────────────────────────────────────────\n\nexport function capturePageLoad(onTrace: TraceCallback, env?: string): void {\n const report = () => {\n const nav = performance.getEntriesByType('navigation')[0] as\n PerformanceNavigationTiming | undefined;\n if (!nav || nav.loadEventEnd <= 0) return;\n\n const ms = Math.round(nav.loadEventEnd - nav.startTime);\n const ttfb = Math.round(nav.responseStart - nav.requestStart);\n const status = rating(ms, 2000, 4000);\n\n onTrace({\n span: 'pageload',\n ms,\n status_code: statusCode(status),\n status,\n timestamp: new Date().toISOString(),\n meta: { ttfb },\n ...(env && { environment: env }),\n });\n };\n\n if (document.readyState === 'complete') {\n // PerformanceNavigationTiming might not be fully populated yet\n setTimeout(report, 0);\n } else {\n window.addEventListener('load', () => setTimeout(report, 100), { once: true });\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// @watchupltd/browser · Watchup client\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { WatchupOptions, TracePayload, ErrorPayload, EventPayload } from './types.js';\nimport { Transport } from './transport.js';\nimport { Batcher } from './batcher.js';\nimport { captureGlobalErrors } from './error-capture.js';\nimport { captureFCP, captureLCP, capturePageLoad } from './perf.js';\n\nconst DEFAULTS = {\n baseUrl: 'https://api.watchup.site',\n flushInterval: 5_000,\n maxBatchSize: 100,\n debug: false,\n environment: 'production',\n release: '',\n sampleRate: 1,\n autoCapture: {\n errors: true,\n performance: true,\n pageViews: true,\n },\n} as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class Watchup {\n private readonly cfg: Required<WatchupOptions>;\n private readonly batcher: Batcher;\n private readonly cleanup: Array<() => void> = [];\n\n /**\n * A random UUID generated on init. Stable for the lifetime of the page —\n * useful for correlating all events from one user session.\n */\n readonly sessionId: string = crypto.randomUUID();\n\n constructor(options: WatchupOptions) {\n if (!options.apiKey) {\n throw new Error('[watchup] apiKey is required.');\n }\n\n this.cfg = {\n ...DEFAULTS,\n autoCapture: { ...DEFAULTS.autoCapture, ...options.autoCapture },\n ...options,\n } as Required<WatchupOptions>;\n\n const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);\n this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);\n this.batcher.start();\n\n this._setupAutoCapture();\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Track a custom analytics event.\n *\n * @example\n * watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });\n */\n track(name: string, properties?: Record<string, unknown>): void {\n if (!name) return;\n const event: EventPayload = {\n name,\n ...(properties && Object.keys(properties).length && { properties }),\n occurred_at: new Date().toISOString(),\n };\n this.batcher.addEvent(event);\n }\n\n /**\n * Manually capture an error.\n *\n * @example\n * try { ... } catch (err) {\n * watchup.captureError(err, { component: 'CheckoutForm' });\n * }\n */\n captureError(\n error: Error | string | unknown,\n context?: Record<string, unknown> & { route?: string; level?: ErrorPayload['level'] },\n ): void {\n const { route, level = 'error', ...rest } = context ?? {};\n const err = error instanceof Error ? error : new Error(String(error));\n\n const payload: ErrorPayload = {\n message: err.message,\n level,\n ...(err.stack !== undefined && { stack: err.stack }),\n route: route ?? window.location.pathname,\n ...(Object.keys(rest).length && {\n context: { ...rest, url: window.location.href },\n }),\n timestamp: new Date().toISOString(),\n environment: this.cfg.environment,\n ...(this.cfg.release && { release: this.cfg.release }),\n };\n\n this.batcher.addError(payload);\n }\n\n /**\n * Time any async operation and record it as a trace.\n * Returns an `end()` function — call it when the operation finishes.\n *\n * @example\n * const end = watchup.startTrace('fetch /api/cart');\n * const cart = await fetch('/api/cart');\n * end({ status: cart.ok ? 'ok' : 'err' });\n */\n startTrace(\n span: string,\n ): (opts?: { status?: TracePayload['status']; meta?: Record<string, unknown> }) => void {\n const start = Date.now();\n return (opts = {}) => {\n const status = opts.status ?? 'ok';\n this.batcher.addTrace({\n span,\n ms: Date.now() - start,\n status_code: status === 'err' ? 500 : status === 'warn' ? 400 : 200,\n status,\n timestamp: new Date().toISOString(),\n environment: this.cfg.environment,\n ...(this.cfg.release && { release: this.cfg.release }),\n ...(opts.meta && { meta: opts.meta }),\n });\n };\n }\n\n /** Immediately flush all queued items. */\n flush(): void { this.batcher.flush(); }\n\n /** Stop the flush timer and release all listeners. */\n shutdown(): void {\n this.batcher.stop();\n this.batcher.flush();\n this.cleanup.forEach((fn) => fn());\n }\n\n // ── Auto-capture setup ────────────────────────────────────────────────────\n\n private _setupAutoCapture(): void {\n const { autoCapture, environment } = this.cfg;\n\n if (autoCapture.errors) {\n this.cleanup.push(\n captureGlobalErrors((e) => this.batcher.addError(e), environment),\n );\n }\n\n if (autoCapture.performance) {\n captureFCP((t) => this.batcher.addTrace(t), environment);\n captureLCP((t) => this.batcher.addTrace(t), environment);\n capturePageLoad((t) => this.batcher.addTrace(t), environment);\n }\n\n if (autoCapture.pageViews) {\n this._setupPageViewTracking();\n }\n }\n\n private _setupPageViewTracking(): void {\n const track = () => {\n this.batcher.addEvent({\n name: 'pageview',\n properties: {\n path: window.location.pathname,\n ...(window.location.search && { search: window.location.search }),\n ...(document.referrer && { referrer: document.referrer }),\n title: document.title,\n },\n occurred_at: new Date().toISOString(),\n });\n };\n\n // Initial view\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', track, { once: true });\n } else {\n // Small timeout so the page title has settled\n setTimeout(track, 0);\n }\n\n // SPA navigation — patch History API\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n origPush(...args);\n setTimeout(track, 0); // title settles asynchronously\n };\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n origReplace(...args);\n // replaceState often doesn't mean a new \"page\" (it's used for URL\n // canonicalisation etc.) — only track if the pathname actually changed.\n };\n\n const onPopState = () => setTimeout(track, 0);\n window.addEventListener('popstate', onPopState);\n\n this.cleanup.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener('popstate', onPopState);\n });\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@watchupltd/browser",
3
+ "version": "0.1.0",
4
+ "description": "Official Watchup SDK for browsers — analytics, errors, and performance",
5
+ "license": "MIT",
6
+ "author": "Watchup Ltd",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": ["dist", "README.md"],
18
+ "sideEffects": false,
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "typecheck": "tsc --noEmit",
22
+ "dev": "tsup --watch"
23
+ },
24
+ "devDependencies": {
25
+ "tsup": "^8.0.2",
26
+ "typescript": "^5.4.5"
27
+ }
28
+ }