@watchupltd/browser 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +158 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +158 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -7
package/dist/index.d.mts
CHANGED
|
@@ -67,6 +67,44 @@ interface IngestBatch {
|
|
|
67
67
|
errors?: ErrorPayload[];
|
|
68
68
|
events?: EventPayload[];
|
|
69
69
|
}
|
|
70
|
+
interface WebAnalyticsPayload {
|
|
71
|
+
/** URL path, e.g. "/pricing". */
|
|
72
|
+
path: string;
|
|
73
|
+
/** Hostname of the tracked site, e.g. "example.com". */
|
|
74
|
+
hostname: string;
|
|
75
|
+
/** Full referrer URL (document.referrer). */
|
|
76
|
+
referrer?: string;
|
|
77
|
+
/** document.title at tracking time. */
|
|
78
|
+
title?: string;
|
|
79
|
+
/** Screen width in CSS pixels. */
|
|
80
|
+
screen_w?: number;
|
|
81
|
+
/** Screen height in CSS pixels. */
|
|
82
|
+
screen_h?: number;
|
|
83
|
+
/** navigator.language, e.g. "en-GB". */
|
|
84
|
+
lang?: string;
|
|
85
|
+
/** Intl.DateTimeFormat().resolvedOptions().timeZone */
|
|
86
|
+
timezone?: string;
|
|
87
|
+
/** UTM parameters parsed from the current URL's query string. */
|
|
88
|
+
utm_source?: string;
|
|
89
|
+
utm_medium?: string;
|
|
90
|
+
utm_campaign?: string;
|
|
91
|
+
utm_term?: string;
|
|
92
|
+
utm_content?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Persistent visitor UUID (stored in localStorage).
|
|
95
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
96
|
+
*/
|
|
97
|
+
visitor_id: string;
|
|
98
|
+
/**
|
|
99
|
+
* Per-session UUID (stored in sessionStorage).
|
|
100
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
101
|
+
*/
|
|
102
|
+
session_id: string;
|
|
103
|
+
/** Event type — defaults to "pageview". Use for custom web events. */
|
|
104
|
+
event_name?: string;
|
|
105
|
+
/** ISO-8601 timestamp when the event occurred. */
|
|
106
|
+
occurred_at: string;
|
|
107
|
+
}
|
|
70
108
|
|
|
71
109
|
declare class Watchup {
|
|
72
110
|
private readonly cfg;
|
|
@@ -77,7 +115,20 @@ declare class Watchup {
|
|
|
77
115
|
* useful for correlating all events from one user session.
|
|
78
116
|
*/
|
|
79
117
|
readonly sessionId: string;
|
|
118
|
+
/**
|
|
119
|
+
* Persistent visitor ID. Stored in localStorage so it survives browser
|
|
120
|
+
* sessions. Falls back to a per-session UUID when localStorage is blocked.
|
|
121
|
+
* The server hashes this value with SHA-256 before persisting.
|
|
122
|
+
*/
|
|
123
|
+
private readonly visitorId;
|
|
124
|
+
/**
|
|
125
|
+
* Per-session ID stored in sessionStorage. Resets on tab close.
|
|
126
|
+
* The server hashes this value before persisting.
|
|
127
|
+
*/
|
|
128
|
+
private readonly webSessionId;
|
|
80
129
|
constructor(options: WatchupOptions);
|
|
130
|
+
private _getOrCreateVisitorId;
|
|
131
|
+
private _getOrCreateSessionId;
|
|
81
132
|
/**
|
|
82
133
|
* Track a custom analytics event.
|
|
83
134
|
*
|
|
@@ -85,6 +136,16 @@ declare class Watchup {
|
|
|
85
136
|
* watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
|
|
86
137
|
*/
|
|
87
138
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
139
|
+
/**
|
|
140
|
+
* Track a web analytics page view (or custom web event).
|
|
141
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
142
|
+
*
|
|
143
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
147
|
+
*/
|
|
148
|
+
trackWebView(overrides?: Partial<WebAnalyticsPayload>): void;
|
|
88
149
|
/**
|
|
89
150
|
* Manually capture an error.
|
|
90
151
|
*
|
|
@@ -110,12 +171,13 @@ declare class Watchup {
|
|
|
110
171
|
status?: TracePayload['status'];
|
|
111
172
|
meta?: Record<string, unknown>;
|
|
112
173
|
}) => void;
|
|
113
|
-
/** Immediately flush all queued items. */
|
|
174
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
114
175
|
flush(): void;
|
|
115
176
|
/** Stop the flush timer and release all listeners. */
|
|
116
177
|
shutdown(): void;
|
|
117
178
|
private _setupAutoCapture;
|
|
118
179
|
private _setupPageViewTracking;
|
|
180
|
+
private _timezone;
|
|
119
181
|
}
|
|
120
182
|
|
|
121
183
|
export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,44 @@ interface IngestBatch {
|
|
|
67
67
|
errors?: ErrorPayload[];
|
|
68
68
|
events?: EventPayload[];
|
|
69
69
|
}
|
|
70
|
+
interface WebAnalyticsPayload {
|
|
71
|
+
/** URL path, e.g. "/pricing". */
|
|
72
|
+
path: string;
|
|
73
|
+
/** Hostname of the tracked site, e.g. "example.com". */
|
|
74
|
+
hostname: string;
|
|
75
|
+
/** Full referrer URL (document.referrer). */
|
|
76
|
+
referrer?: string;
|
|
77
|
+
/** document.title at tracking time. */
|
|
78
|
+
title?: string;
|
|
79
|
+
/** Screen width in CSS pixels. */
|
|
80
|
+
screen_w?: number;
|
|
81
|
+
/** Screen height in CSS pixels. */
|
|
82
|
+
screen_h?: number;
|
|
83
|
+
/** navigator.language, e.g. "en-GB". */
|
|
84
|
+
lang?: string;
|
|
85
|
+
/** Intl.DateTimeFormat().resolvedOptions().timeZone */
|
|
86
|
+
timezone?: string;
|
|
87
|
+
/** UTM parameters parsed from the current URL's query string. */
|
|
88
|
+
utm_source?: string;
|
|
89
|
+
utm_medium?: string;
|
|
90
|
+
utm_campaign?: string;
|
|
91
|
+
utm_term?: string;
|
|
92
|
+
utm_content?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Persistent visitor UUID (stored in localStorage).
|
|
95
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
96
|
+
*/
|
|
97
|
+
visitor_id: string;
|
|
98
|
+
/**
|
|
99
|
+
* Per-session UUID (stored in sessionStorage).
|
|
100
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
101
|
+
*/
|
|
102
|
+
session_id: string;
|
|
103
|
+
/** Event type — defaults to "pageview". Use for custom web events. */
|
|
104
|
+
event_name?: string;
|
|
105
|
+
/** ISO-8601 timestamp when the event occurred. */
|
|
106
|
+
occurred_at: string;
|
|
107
|
+
}
|
|
70
108
|
|
|
71
109
|
declare class Watchup {
|
|
72
110
|
private readonly cfg;
|
|
@@ -77,7 +115,20 @@ declare class Watchup {
|
|
|
77
115
|
* useful for correlating all events from one user session.
|
|
78
116
|
*/
|
|
79
117
|
readonly sessionId: string;
|
|
118
|
+
/**
|
|
119
|
+
* Persistent visitor ID. Stored in localStorage so it survives browser
|
|
120
|
+
* sessions. Falls back to a per-session UUID when localStorage is blocked.
|
|
121
|
+
* The server hashes this value with SHA-256 before persisting.
|
|
122
|
+
*/
|
|
123
|
+
private readonly visitorId;
|
|
124
|
+
/**
|
|
125
|
+
* Per-session ID stored in sessionStorage. Resets on tab close.
|
|
126
|
+
* The server hashes this value before persisting.
|
|
127
|
+
*/
|
|
128
|
+
private readonly webSessionId;
|
|
80
129
|
constructor(options: WatchupOptions);
|
|
130
|
+
private _getOrCreateVisitorId;
|
|
131
|
+
private _getOrCreateSessionId;
|
|
81
132
|
/**
|
|
82
133
|
* Track a custom analytics event.
|
|
83
134
|
*
|
|
@@ -85,6 +136,16 @@ declare class Watchup {
|
|
|
85
136
|
* watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
|
|
86
137
|
*/
|
|
87
138
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
139
|
+
/**
|
|
140
|
+
* Track a web analytics page view (or custom web event).
|
|
141
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
142
|
+
*
|
|
143
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
147
|
+
*/
|
|
148
|
+
trackWebView(overrides?: Partial<WebAnalyticsPayload>): void;
|
|
88
149
|
/**
|
|
89
150
|
* Manually capture an error.
|
|
90
151
|
*
|
|
@@ -110,12 +171,13 @@ declare class Watchup {
|
|
|
110
171
|
status?: TracePayload['status'];
|
|
111
172
|
meta?: Record<string, unknown>;
|
|
112
173
|
}) => void;
|
|
113
|
-
/** Immediately flush all queued items. */
|
|
174
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
114
175
|
flush(): void;
|
|
115
176
|
/** Stop the flush timer and release all listeners. */
|
|
116
177
|
shutdown(): void;
|
|
117
178
|
private _setupAutoCapture;
|
|
118
179
|
private _setupPageViewTracking;
|
|
180
|
+
private _timezone;
|
|
119
181
|
}
|
|
120
182
|
|
|
121
183
|
export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// src/transport.ts
|
|
4
4
|
var Transport = class {
|
|
5
5
|
constructor(baseUrl, apiKey, debug = false) {
|
|
6
|
-
|
|
6
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
7
|
+
this.url = `${base}/api/v1/ingest/batch`;
|
|
8
|
+
this.webUrl = `${base}/api/v1/ingest/web-batch`;
|
|
7
9
|
this.headers = {
|
|
8
10
|
"Content-Type": "application/json",
|
|
9
11
|
"X-Api-Key": apiKey
|
|
@@ -37,6 +39,31 @@ var Transport = class {
|
|
|
37
39
|
if (this.debug) console.warn("[watchup] send failed:", err);
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Send web analytics batch to the dedicated /web-batch endpoint.
|
|
44
|
+
* Never rejects.
|
|
45
|
+
*/
|
|
46
|
+
async sendWeb(batch) {
|
|
47
|
+
try {
|
|
48
|
+
const body = JSON.stringify(batch);
|
|
49
|
+
if (body.length > 6e4) {
|
|
50
|
+
this.beaconWeb(batch);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const res = await fetch(this.webUrl, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: this.headers,
|
|
56
|
+
body,
|
|
57
|
+
keepalive: true
|
|
58
|
+
});
|
|
59
|
+
if (this.debug && !res.ok) {
|
|
60
|
+
const text = await res.text().catch(() => "");
|
|
61
|
+
console.warn(`[watchup] web-batch ${res.status}: ${text}`);
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (this.debug) console.warn("[watchup] sendWeb failed:", err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
40
67
|
/**
|
|
41
68
|
* Send via `navigator.sendBeacon`.
|
|
42
69
|
* Returns `true` if the browser accepted the request (doesn't guarantee delivery).
|
|
@@ -51,6 +78,18 @@ var Transport = class {
|
|
|
51
78
|
return false;
|
|
52
79
|
}
|
|
53
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* sendBeacon variant for web analytics events.
|
|
83
|
+
*/
|
|
84
|
+
beaconWeb(batch) {
|
|
85
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
|
|
86
|
+
try {
|
|
87
|
+
const blob = new Blob([JSON.stringify(batch)], { type: "application/json" });
|
|
88
|
+
return navigator.sendBeacon(this.webUrl, blob);
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
54
93
|
};
|
|
55
94
|
|
|
56
95
|
// src/batcher.ts
|
|
@@ -59,6 +98,7 @@ var Batcher = class {
|
|
|
59
98
|
this.traces = [];
|
|
60
99
|
this.errors = [];
|
|
61
100
|
this.events = [];
|
|
101
|
+
this.webViews = [];
|
|
62
102
|
this.timer = null;
|
|
63
103
|
this.flushing = false;
|
|
64
104
|
this.transport = transport;
|
|
@@ -79,6 +119,7 @@ var Batcher = class {
|
|
|
79
119
|
this.timer = null;
|
|
80
120
|
}
|
|
81
121
|
}
|
|
122
|
+
// ── Telemetry queue ───────────────────────────────────────────────────────
|
|
82
123
|
addTrace(t) {
|
|
83
124
|
this.traces.push(t);
|
|
84
125
|
if (this.traces.length >= this.maxBatchSize) this.flush();
|
|
@@ -91,27 +132,49 @@ var Batcher = class {
|
|
|
91
132
|
this.events.push(e);
|
|
92
133
|
if (this.events.length >= this.maxBatchSize) this.flush();
|
|
93
134
|
}
|
|
94
|
-
|
|
135
|
+
// ── Web analytics queue ───────────────────────────────────────────────────
|
|
136
|
+
addWebView(payload) {
|
|
137
|
+
this.webViews.push(payload);
|
|
138
|
+
if (this.webViews.length >= this.maxBatchSize) this.flushWeb();
|
|
139
|
+
}
|
|
140
|
+
// ── Drain helpers ─────────────────────────────────────────────────────────
|
|
141
|
+
drainTelemetry() {
|
|
95
142
|
const traces = this.traces.splice(0);
|
|
96
143
|
const errors = this.errors.splice(0);
|
|
97
144
|
const events = this.events.splice(0);
|
|
98
145
|
if (!traces.length && !errors.length && !events.length) return null;
|
|
99
146
|
return { traces, errors, events };
|
|
100
147
|
}
|
|
148
|
+
drainWeb() {
|
|
149
|
+
const web = this.webViews.splice(0);
|
|
150
|
+
if (!web.length) return null;
|
|
151
|
+
return { web };
|
|
152
|
+
}
|
|
153
|
+
// ── Flush ─────────────────────────────────────────────────────────────────
|
|
101
154
|
flush() {
|
|
102
|
-
if (this.flushing)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
155
|
+
if (!this.flushing) {
|
|
156
|
+
const batch = this.drainTelemetry();
|
|
157
|
+
if (batch) {
|
|
158
|
+
this.flushing = true;
|
|
159
|
+
this.transport.send(batch).finally(() => {
|
|
160
|
+
this.flushing = false;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
this.flushWeb();
|
|
165
|
+
}
|
|
166
|
+
flushWeb() {
|
|
167
|
+
const batch = this.drainWeb();
|
|
168
|
+
if (batch) this.transport.sendWeb(batch);
|
|
109
169
|
}
|
|
110
170
|
beaconFlush() {
|
|
111
|
-
const batch = this.
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
171
|
+
const batch = this.drainTelemetry();
|
|
172
|
+
if (batch) {
|
|
173
|
+
if (!this.transport.beacon(batch)) this.transport.send(batch);
|
|
174
|
+
}
|
|
175
|
+
const webBatch = this.drainWeb();
|
|
176
|
+
if (webBatch) {
|
|
177
|
+
if (!this.transport.beaconWeb(webBatch)) this.transport.sendWeb(webBatch);
|
|
115
178
|
}
|
|
116
179
|
}
|
|
117
180
|
};
|
|
@@ -267,6 +330,8 @@ var DEFAULTS = {
|
|
|
267
330
|
pageViews: true
|
|
268
331
|
}
|
|
269
332
|
};
|
|
333
|
+
var VISITOR_KEY = "__wup_vid";
|
|
334
|
+
var SESSION_KEY = "__wup_sid";
|
|
270
335
|
var Watchup = class {
|
|
271
336
|
constructor(options) {
|
|
272
337
|
this.cleanup = [];
|
|
@@ -286,8 +351,35 @@ var Watchup = class {
|
|
|
286
351
|
const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);
|
|
287
352
|
this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);
|
|
288
353
|
this.batcher.start();
|
|
354
|
+
this.visitorId = this._getOrCreateVisitorId();
|
|
355
|
+
this.webSessionId = this._getOrCreateSessionId();
|
|
289
356
|
this._setupAutoCapture();
|
|
290
357
|
}
|
|
358
|
+
// ── Visitor / session identity helpers ─────────────────────────────────────
|
|
359
|
+
_getOrCreateVisitorId() {
|
|
360
|
+
try {
|
|
361
|
+
let id = localStorage.getItem(VISITOR_KEY);
|
|
362
|
+
if (!id) {
|
|
363
|
+
id = crypto.randomUUID();
|
|
364
|
+
localStorage.setItem(VISITOR_KEY, id);
|
|
365
|
+
}
|
|
366
|
+
return id;
|
|
367
|
+
} catch {
|
|
368
|
+
return crypto.randomUUID();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
_getOrCreateSessionId() {
|
|
372
|
+
try {
|
|
373
|
+
let id = sessionStorage.getItem(SESSION_KEY);
|
|
374
|
+
if (!id) {
|
|
375
|
+
id = crypto.randomUUID();
|
|
376
|
+
sessionStorage.setItem(SESSION_KEY, id);
|
|
377
|
+
}
|
|
378
|
+
return id;
|
|
379
|
+
} catch {
|
|
380
|
+
return this.sessionId;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
291
383
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
292
384
|
/**
|
|
293
385
|
* Track a custom analytics event.
|
|
@@ -304,6 +396,44 @@ var Watchup = class {
|
|
|
304
396
|
};
|
|
305
397
|
this.batcher.addEvent(event);
|
|
306
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Track a web analytics page view (or custom web event).
|
|
401
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
402
|
+
*
|
|
403
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
407
|
+
*/
|
|
408
|
+
trackWebView(overrides = {}) {
|
|
409
|
+
var _a, _b;
|
|
410
|
+
const url = new URL(window.location.href);
|
|
411
|
+
const params = url.searchParams;
|
|
412
|
+
const payload = {
|
|
413
|
+
path: url.pathname + (url.search || ""),
|
|
414
|
+
hostname: url.hostname,
|
|
415
|
+
referrer: document.referrer || void 0,
|
|
416
|
+
title: document.title || void 0,
|
|
417
|
+
screen_w: (_a = window.screen) == null ? void 0 : _a.width,
|
|
418
|
+
screen_h: (_b = window.screen) == null ? void 0 : _b.height,
|
|
419
|
+
lang: navigator.language || void 0,
|
|
420
|
+
timezone: this._timezone(),
|
|
421
|
+
// UTM parameters
|
|
422
|
+
utm_source: params.get("utm_source") || void 0,
|
|
423
|
+
utm_medium: params.get("utm_medium") || void 0,
|
|
424
|
+
utm_campaign: params.get("utm_campaign") || void 0,
|
|
425
|
+
utm_term: params.get("utm_term") || void 0,
|
|
426
|
+
utm_content: params.get("utm_content") || void 0,
|
|
427
|
+
// Identity (raw; the server hashes before storing)
|
|
428
|
+
visitor_id: this.visitorId,
|
|
429
|
+
session_id: this.webSessionId,
|
|
430
|
+
event_name: "pageview",
|
|
431
|
+
occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
432
|
+
// Apply caller overrides last
|
|
433
|
+
...overrides
|
|
434
|
+
};
|
|
435
|
+
this.batcher.addWebView(payload);
|
|
436
|
+
}
|
|
307
437
|
/**
|
|
308
438
|
* Manually capture an error.
|
|
309
439
|
*
|
|
@@ -355,7 +485,7 @@ var Watchup = class {
|
|
|
355
485
|
});
|
|
356
486
|
};
|
|
357
487
|
}
|
|
358
|
-
/** Immediately flush all queued items. */
|
|
488
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
359
489
|
flush() {
|
|
360
490
|
this.batcher.flush();
|
|
361
491
|
}
|
|
@@ -383,33 +513,24 @@ var Watchup = class {
|
|
|
383
513
|
}
|
|
384
514
|
}
|
|
385
515
|
_setupPageViewTracking() {
|
|
386
|
-
const
|
|
387
|
-
this.
|
|
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
|
-
});
|
|
516
|
+
const trackView = () => {
|
|
517
|
+
setTimeout(() => this.trackWebView(), 0);
|
|
397
518
|
};
|
|
398
519
|
if (document.readyState === "loading") {
|
|
399
|
-
document.addEventListener("DOMContentLoaded",
|
|
520
|
+
document.addEventListener("DOMContentLoaded", trackView, { once: true });
|
|
400
521
|
} else {
|
|
401
|
-
setTimeout(
|
|
522
|
+
setTimeout(() => this.trackWebView(), 0);
|
|
402
523
|
}
|
|
403
524
|
const origPush = history.pushState.bind(history);
|
|
404
525
|
const origReplace = history.replaceState.bind(history);
|
|
405
526
|
history.pushState = (...args) => {
|
|
406
527
|
origPush(...args);
|
|
407
|
-
|
|
528
|
+
trackView();
|
|
408
529
|
};
|
|
409
530
|
history.replaceState = (...args) => {
|
|
410
531
|
origReplace(...args);
|
|
411
532
|
};
|
|
412
|
-
const onPopState = () =>
|
|
533
|
+
const onPopState = () => trackView();
|
|
413
534
|
window.addEventListener("popstate", onPopState);
|
|
414
535
|
this.cleanup.push(() => {
|
|
415
536
|
history.pushState = origPush;
|
|
@@ -417,6 +538,14 @@ var Watchup = class {
|
|
|
417
538
|
window.removeEventListener("popstate", onPopState);
|
|
418
539
|
});
|
|
419
540
|
}
|
|
541
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
542
|
+
_timezone() {
|
|
543
|
+
try {
|
|
544
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || void 0;
|
|
545
|
+
} catch {
|
|
546
|
+
return void 0;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
420
549
|
};
|
|
421
550
|
|
|
422
551
|
exports.Watchup = Watchup;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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// Web analytics events are sent to a separate endpoint (/web-batch) that is\n// optimised for high-volume, low-latency browser hits.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { IngestBatch, WebAnalyticsBatch } from './types.js';\n\nexport class Transport {\n private readonly url: string;\n private readonly webUrl: string;\n private readonly headers: Record<string, string>;\n private readonly debug: boolean;\n\n constructor(baseUrl: string, apiKey: string, debug = false) {\n const base = baseUrl.replace(/\\/$/, '');\n this.url = `${base}/api/v1/ingest/batch`;\n this.webUrl = `${base}/api/v1/ingest/web-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 web analytics batch to the dedicated /web-batch endpoint.\n * Never rejects.\n */\n async sendWeb(batch: WebAnalyticsBatch): Promise<void> {\n try {\n const body = JSON.stringify(batch);\n\n if (body.length > 60_000) {\n this.beaconWeb(batch);\n return;\n }\n\n const res = await fetch(this.webUrl, {\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] web-batch ${res.status}: ${text}`);\n }\n } catch (err) {\n if (this.debug) console.warn('[watchup] sendWeb 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 * sendBeacon variant for web analytics events.\n */\n beaconWeb(batch: WebAnalyticsBatch): 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.webUrl, 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// Two independent queues:\n// - telemetry queue (traces, errors, events) → /ingest/batch\n// - web queue (web page views) → /ingest/web-batch\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type {\n TracePayload,\n ErrorPayload,\n EventPayload,\n IngestBatch,\n WebAnalyticsPayload,\n WebAnalyticsBatch,\n} 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 private webViews: WebAnalyticsPayload[] = [];\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 // ── Telemetry queue ───────────────────────────────────────────────────────\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 // ── Web analytics queue ───────────────────────────────────────────────────\n\n addWebView(payload: WebAnalyticsPayload): void {\n this.webViews.push(payload);\n if (this.webViews.length >= this.maxBatchSize) this.flushWeb();\n }\n\n // ── Drain helpers ─────────────────────────────────────────────────────────\n\n private drainTelemetry(): 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 private drainWeb(): WebAnalyticsBatch | null {\n const web = this.webViews.splice(0);\n if (!web.length) return null;\n return { web };\n }\n\n // ── Flush ─────────────────────────────────────────────────────────────────\n\n flush(): void {\n if (!this.flushing) {\n const batch = this.drainTelemetry();\n if (batch) {\n this.flushing = true;\n this.transport.send(batch).finally(() => { this.flushing = false; });\n }\n }\n this.flushWeb();\n }\n\n flushWeb(): void {\n const batch = this.drainWeb();\n if (batch) this.transport.sendWeb(batch);\n }\n\n beaconFlush(): void {\n // Telemetry\n const batch = this.drainTelemetry();\n if (batch) {\n if (!this.transport.beacon(batch)) this.transport.send(batch);\n }\n // Web analytics\n const webBatch = this.drainWeb();\n if (webBatch) {\n if (!this.transport.beaconWeb(webBatch)) this.transport.sendWeb(webBatch);\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 {\n WatchupOptions,\n TracePayload,\n ErrorPayload,\n EventPayload,\n WebAnalyticsPayload,\n} 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// ── Storage keys ──────────────────────────────────────────────────────────────\n\nconst VISITOR_KEY = '__wup_vid';\nconst SESSION_KEY = '__wup_sid';\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 // ── Visitor / session identity ─────────────────────────────────────────────\n\n /**\n * Persistent visitor ID. Stored in localStorage so it survives browser\n * sessions. Falls back to a per-session UUID when localStorage is blocked.\n * The server hashes this value with SHA-256 before persisting.\n */\n private readonly visitorId: string;\n\n /**\n * Per-session ID stored in sessionStorage. Resets on tab close.\n * The server hashes this value before persisting.\n */\n private readonly webSessionId: string;\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 // Initialise visitor & session IDs\n this.visitorId = this._getOrCreateVisitorId();\n this.webSessionId = this._getOrCreateSessionId();\n\n this._setupAutoCapture();\n }\n\n // ── Visitor / session identity helpers ─────────────────────────────────────\n\n private _getOrCreateVisitorId(): string {\n try {\n let id = localStorage.getItem(VISITOR_KEY);\n if (!id) {\n id = crypto.randomUUID();\n localStorage.setItem(VISITOR_KEY, id);\n }\n return id;\n } catch {\n // localStorage blocked (private mode, etc.) — fall back to session scope\n return crypto.randomUUID();\n }\n }\n\n private _getOrCreateSessionId(): string {\n try {\n let id = sessionStorage.getItem(SESSION_KEY);\n if (!id) {\n id = crypto.randomUUID();\n sessionStorage.setItem(SESSION_KEY, id);\n }\n return id;\n } catch {\n return this.sessionId; // fallback: correlate with SDK sessionId\n }\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 * Track a web analytics page view (or custom web event).\n * Enriches the payload with visitor context, UTM params, and device info.\n *\n * Normally called automatically. Call manually when you need custom event_name.\n *\n * @example\n * watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });\n */\n trackWebView(overrides: Partial<WebAnalyticsPayload> = {}): void {\n const url = new URL(window.location.href);\n const params = url.searchParams;\n\n const payload: WebAnalyticsPayload = {\n path: url.pathname + (url.search || ''),\n hostname: url.hostname,\n referrer: document.referrer || undefined,\n title: document.title || undefined,\n screen_w: window.screen?.width,\n screen_h: window.screen?.height,\n lang: navigator.language || undefined,\n timezone: this._timezone(),\n // UTM parameters\n utm_source: params.get('utm_source') || undefined,\n utm_medium: params.get('utm_medium') || undefined,\n utm_campaign: params.get('utm_campaign') || undefined,\n utm_term: params.get('utm_term') || undefined,\n utm_content: params.get('utm_content') || undefined,\n // Identity (raw; the server hashes before storing)\n visitor_id: this.visitorId,\n session_id: this.webSessionId,\n event_name: 'pageview',\n occurred_at: new Date().toISOString(),\n // Apply caller overrides last\n ...overrides,\n };\n\n this.batcher.addWebView(payload);\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 (both telemetry and web analytics). */\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 trackView = () => {\n // Small delay so the page title has settled after navigation\n setTimeout(() => this.trackWebView(), 0);\n };\n\n // Initial view\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', trackView, { once: true });\n } else {\n setTimeout(() => this.trackWebView(), 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 trackView();\n };\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n origReplace(...args);\n // replaceState is often used for URL canonicalisation — don't track.\n };\n\n const onPopState = () => trackView();\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 // ── Helpers ───────────────────────────────────────────────────────────────\n\n private _timezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;\n } catch {\n return undefined;\n }\n }\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/transport.ts
|
|
2
2
|
var Transport = class {
|
|
3
3
|
constructor(baseUrl, apiKey, debug = false) {
|
|
4
|
-
|
|
4
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
5
|
+
this.url = `${base}/api/v1/ingest/batch`;
|
|
6
|
+
this.webUrl = `${base}/api/v1/ingest/web-batch`;
|
|
5
7
|
this.headers = {
|
|
6
8
|
"Content-Type": "application/json",
|
|
7
9
|
"X-Api-Key": apiKey
|
|
@@ -35,6 +37,31 @@ var Transport = class {
|
|
|
35
37
|
if (this.debug) console.warn("[watchup] send failed:", err);
|
|
36
38
|
}
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Send web analytics batch to the dedicated /web-batch endpoint.
|
|
42
|
+
* Never rejects.
|
|
43
|
+
*/
|
|
44
|
+
async sendWeb(batch) {
|
|
45
|
+
try {
|
|
46
|
+
const body = JSON.stringify(batch);
|
|
47
|
+
if (body.length > 6e4) {
|
|
48
|
+
this.beaconWeb(batch);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const res = await fetch(this.webUrl, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: this.headers,
|
|
54
|
+
body,
|
|
55
|
+
keepalive: true
|
|
56
|
+
});
|
|
57
|
+
if (this.debug && !res.ok) {
|
|
58
|
+
const text = await res.text().catch(() => "");
|
|
59
|
+
console.warn(`[watchup] web-batch ${res.status}: ${text}`);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (this.debug) console.warn("[watchup] sendWeb failed:", err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
38
65
|
/**
|
|
39
66
|
* Send via `navigator.sendBeacon`.
|
|
40
67
|
* Returns `true` if the browser accepted the request (doesn't guarantee delivery).
|
|
@@ -49,6 +76,18 @@ var Transport = class {
|
|
|
49
76
|
return false;
|
|
50
77
|
}
|
|
51
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* sendBeacon variant for web analytics events.
|
|
81
|
+
*/
|
|
82
|
+
beaconWeb(batch) {
|
|
83
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
|
|
84
|
+
try {
|
|
85
|
+
const blob = new Blob([JSON.stringify(batch)], { type: "application/json" });
|
|
86
|
+
return navigator.sendBeacon(this.webUrl, blob);
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
52
91
|
};
|
|
53
92
|
|
|
54
93
|
// src/batcher.ts
|
|
@@ -57,6 +96,7 @@ var Batcher = class {
|
|
|
57
96
|
this.traces = [];
|
|
58
97
|
this.errors = [];
|
|
59
98
|
this.events = [];
|
|
99
|
+
this.webViews = [];
|
|
60
100
|
this.timer = null;
|
|
61
101
|
this.flushing = false;
|
|
62
102
|
this.transport = transport;
|
|
@@ -77,6 +117,7 @@ var Batcher = class {
|
|
|
77
117
|
this.timer = null;
|
|
78
118
|
}
|
|
79
119
|
}
|
|
120
|
+
// ── Telemetry queue ───────────────────────────────────────────────────────
|
|
80
121
|
addTrace(t) {
|
|
81
122
|
this.traces.push(t);
|
|
82
123
|
if (this.traces.length >= this.maxBatchSize) this.flush();
|
|
@@ -89,27 +130,49 @@ var Batcher = class {
|
|
|
89
130
|
this.events.push(e);
|
|
90
131
|
if (this.events.length >= this.maxBatchSize) this.flush();
|
|
91
132
|
}
|
|
92
|
-
|
|
133
|
+
// ── Web analytics queue ───────────────────────────────────────────────────
|
|
134
|
+
addWebView(payload) {
|
|
135
|
+
this.webViews.push(payload);
|
|
136
|
+
if (this.webViews.length >= this.maxBatchSize) this.flushWeb();
|
|
137
|
+
}
|
|
138
|
+
// ── Drain helpers ─────────────────────────────────────────────────────────
|
|
139
|
+
drainTelemetry() {
|
|
93
140
|
const traces = this.traces.splice(0);
|
|
94
141
|
const errors = this.errors.splice(0);
|
|
95
142
|
const events = this.events.splice(0);
|
|
96
143
|
if (!traces.length && !errors.length && !events.length) return null;
|
|
97
144
|
return { traces, errors, events };
|
|
98
145
|
}
|
|
146
|
+
drainWeb() {
|
|
147
|
+
const web = this.webViews.splice(0);
|
|
148
|
+
if (!web.length) return null;
|
|
149
|
+
return { web };
|
|
150
|
+
}
|
|
151
|
+
// ── Flush ─────────────────────────────────────────────────────────────────
|
|
99
152
|
flush() {
|
|
100
|
-
if (this.flushing)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
153
|
+
if (!this.flushing) {
|
|
154
|
+
const batch = this.drainTelemetry();
|
|
155
|
+
if (batch) {
|
|
156
|
+
this.flushing = true;
|
|
157
|
+
this.transport.send(batch).finally(() => {
|
|
158
|
+
this.flushing = false;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
this.flushWeb();
|
|
163
|
+
}
|
|
164
|
+
flushWeb() {
|
|
165
|
+
const batch = this.drainWeb();
|
|
166
|
+
if (batch) this.transport.sendWeb(batch);
|
|
107
167
|
}
|
|
108
168
|
beaconFlush() {
|
|
109
|
-
const batch = this.
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
169
|
+
const batch = this.drainTelemetry();
|
|
170
|
+
if (batch) {
|
|
171
|
+
if (!this.transport.beacon(batch)) this.transport.send(batch);
|
|
172
|
+
}
|
|
173
|
+
const webBatch = this.drainWeb();
|
|
174
|
+
if (webBatch) {
|
|
175
|
+
if (!this.transport.beaconWeb(webBatch)) this.transport.sendWeb(webBatch);
|
|
113
176
|
}
|
|
114
177
|
}
|
|
115
178
|
};
|
|
@@ -265,6 +328,8 @@ var DEFAULTS = {
|
|
|
265
328
|
pageViews: true
|
|
266
329
|
}
|
|
267
330
|
};
|
|
331
|
+
var VISITOR_KEY = "__wup_vid";
|
|
332
|
+
var SESSION_KEY = "__wup_sid";
|
|
268
333
|
var Watchup = class {
|
|
269
334
|
constructor(options) {
|
|
270
335
|
this.cleanup = [];
|
|
@@ -284,8 +349,35 @@ var Watchup = class {
|
|
|
284
349
|
const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);
|
|
285
350
|
this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);
|
|
286
351
|
this.batcher.start();
|
|
352
|
+
this.visitorId = this._getOrCreateVisitorId();
|
|
353
|
+
this.webSessionId = this._getOrCreateSessionId();
|
|
287
354
|
this._setupAutoCapture();
|
|
288
355
|
}
|
|
356
|
+
// ── Visitor / session identity helpers ─────────────────────────────────────
|
|
357
|
+
_getOrCreateVisitorId() {
|
|
358
|
+
try {
|
|
359
|
+
let id = localStorage.getItem(VISITOR_KEY);
|
|
360
|
+
if (!id) {
|
|
361
|
+
id = crypto.randomUUID();
|
|
362
|
+
localStorage.setItem(VISITOR_KEY, id);
|
|
363
|
+
}
|
|
364
|
+
return id;
|
|
365
|
+
} catch {
|
|
366
|
+
return crypto.randomUUID();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
_getOrCreateSessionId() {
|
|
370
|
+
try {
|
|
371
|
+
let id = sessionStorage.getItem(SESSION_KEY);
|
|
372
|
+
if (!id) {
|
|
373
|
+
id = crypto.randomUUID();
|
|
374
|
+
sessionStorage.setItem(SESSION_KEY, id);
|
|
375
|
+
}
|
|
376
|
+
return id;
|
|
377
|
+
} catch {
|
|
378
|
+
return this.sessionId;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
289
381
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
290
382
|
/**
|
|
291
383
|
* Track a custom analytics event.
|
|
@@ -302,6 +394,44 @@ var Watchup = class {
|
|
|
302
394
|
};
|
|
303
395
|
this.batcher.addEvent(event);
|
|
304
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Track a web analytics page view (or custom web event).
|
|
399
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
400
|
+
*
|
|
401
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
405
|
+
*/
|
|
406
|
+
trackWebView(overrides = {}) {
|
|
407
|
+
var _a, _b;
|
|
408
|
+
const url = new URL(window.location.href);
|
|
409
|
+
const params = url.searchParams;
|
|
410
|
+
const payload = {
|
|
411
|
+
path: url.pathname + (url.search || ""),
|
|
412
|
+
hostname: url.hostname,
|
|
413
|
+
referrer: document.referrer || void 0,
|
|
414
|
+
title: document.title || void 0,
|
|
415
|
+
screen_w: (_a = window.screen) == null ? void 0 : _a.width,
|
|
416
|
+
screen_h: (_b = window.screen) == null ? void 0 : _b.height,
|
|
417
|
+
lang: navigator.language || void 0,
|
|
418
|
+
timezone: this._timezone(),
|
|
419
|
+
// UTM parameters
|
|
420
|
+
utm_source: params.get("utm_source") || void 0,
|
|
421
|
+
utm_medium: params.get("utm_medium") || void 0,
|
|
422
|
+
utm_campaign: params.get("utm_campaign") || void 0,
|
|
423
|
+
utm_term: params.get("utm_term") || void 0,
|
|
424
|
+
utm_content: params.get("utm_content") || void 0,
|
|
425
|
+
// Identity (raw; the server hashes before storing)
|
|
426
|
+
visitor_id: this.visitorId,
|
|
427
|
+
session_id: this.webSessionId,
|
|
428
|
+
event_name: "pageview",
|
|
429
|
+
occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
430
|
+
// Apply caller overrides last
|
|
431
|
+
...overrides
|
|
432
|
+
};
|
|
433
|
+
this.batcher.addWebView(payload);
|
|
434
|
+
}
|
|
305
435
|
/**
|
|
306
436
|
* Manually capture an error.
|
|
307
437
|
*
|
|
@@ -353,7 +483,7 @@ var Watchup = class {
|
|
|
353
483
|
});
|
|
354
484
|
};
|
|
355
485
|
}
|
|
356
|
-
/** Immediately flush all queued items. */
|
|
486
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
357
487
|
flush() {
|
|
358
488
|
this.batcher.flush();
|
|
359
489
|
}
|
|
@@ -381,33 +511,24 @@ var Watchup = class {
|
|
|
381
511
|
}
|
|
382
512
|
}
|
|
383
513
|
_setupPageViewTracking() {
|
|
384
|
-
const
|
|
385
|
-
this.
|
|
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
|
-
});
|
|
514
|
+
const trackView = () => {
|
|
515
|
+
setTimeout(() => this.trackWebView(), 0);
|
|
395
516
|
};
|
|
396
517
|
if (document.readyState === "loading") {
|
|
397
|
-
document.addEventListener("DOMContentLoaded",
|
|
518
|
+
document.addEventListener("DOMContentLoaded", trackView, { once: true });
|
|
398
519
|
} else {
|
|
399
|
-
setTimeout(
|
|
520
|
+
setTimeout(() => this.trackWebView(), 0);
|
|
400
521
|
}
|
|
401
522
|
const origPush = history.pushState.bind(history);
|
|
402
523
|
const origReplace = history.replaceState.bind(history);
|
|
403
524
|
history.pushState = (...args) => {
|
|
404
525
|
origPush(...args);
|
|
405
|
-
|
|
526
|
+
trackView();
|
|
406
527
|
};
|
|
407
528
|
history.replaceState = (...args) => {
|
|
408
529
|
origReplace(...args);
|
|
409
530
|
};
|
|
410
|
-
const onPopState = () =>
|
|
531
|
+
const onPopState = () => trackView();
|
|
411
532
|
window.addEventListener("popstate", onPopState);
|
|
412
533
|
this.cleanup.push(() => {
|
|
413
534
|
history.pushState = origPush;
|
|
@@ -415,6 +536,14 @@ var Watchup = class {
|
|
|
415
536
|
window.removeEventListener("popstate", onPopState);
|
|
416
537
|
});
|
|
417
538
|
}
|
|
539
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
540
|
+
_timezone() {
|
|
541
|
+
try {
|
|
542
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || void 0;
|
|
543
|
+
} catch {
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
418
547
|
};
|
|
419
548
|
|
|
420
549
|
export { Watchup };
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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// Web analytics events are sent to a separate endpoint (/web-batch) that is\n// optimised for high-volume, low-latency browser hits.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { IngestBatch, WebAnalyticsBatch } from './types.js';\n\nexport class Transport {\n private readonly url: string;\n private readonly webUrl: string;\n private readonly headers: Record<string, string>;\n private readonly debug: boolean;\n\n constructor(baseUrl: string, apiKey: string, debug = false) {\n const base = baseUrl.replace(/\\/$/, '');\n this.url = `${base}/api/v1/ingest/batch`;\n this.webUrl = `${base}/api/v1/ingest/web-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 web analytics batch to the dedicated /web-batch endpoint.\n * Never rejects.\n */\n async sendWeb(batch: WebAnalyticsBatch): Promise<void> {\n try {\n const body = JSON.stringify(batch);\n\n if (body.length > 60_000) {\n this.beaconWeb(batch);\n return;\n }\n\n const res = await fetch(this.webUrl, {\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] web-batch ${res.status}: ${text}`);\n }\n } catch (err) {\n if (this.debug) console.warn('[watchup] sendWeb 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 * sendBeacon variant for web analytics events.\n */\n beaconWeb(batch: WebAnalyticsBatch): 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.webUrl, 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// Two independent queues:\n// - telemetry queue (traces, errors, events) → /ingest/batch\n// - web queue (web page views) → /ingest/web-batch\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type {\n TracePayload,\n ErrorPayload,\n EventPayload,\n IngestBatch,\n WebAnalyticsPayload,\n WebAnalyticsBatch,\n} 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 private webViews: WebAnalyticsPayload[] = [];\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 // ── Telemetry queue ───────────────────────────────────────────────────────\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 // ── Web analytics queue ───────────────────────────────────────────────────\n\n addWebView(payload: WebAnalyticsPayload): void {\n this.webViews.push(payload);\n if (this.webViews.length >= this.maxBatchSize) this.flushWeb();\n }\n\n // ── Drain helpers ─────────────────────────────────────────────────────────\n\n private drainTelemetry(): 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 private drainWeb(): WebAnalyticsBatch | null {\n const web = this.webViews.splice(0);\n if (!web.length) return null;\n return { web };\n }\n\n // ── Flush ─────────────────────────────────────────────────────────────────\n\n flush(): void {\n if (!this.flushing) {\n const batch = this.drainTelemetry();\n if (batch) {\n this.flushing = true;\n this.transport.send(batch).finally(() => { this.flushing = false; });\n }\n }\n this.flushWeb();\n }\n\n flushWeb(): void {\n const batch = this.drainWeb();\n if (batch) this.transport.sendWeb(batch);\n }\n\n beaconFlush(): void {\n // Telemetry\n const batch = this.drainTelemetry();\n if (batch) {\n if (!this.transport.beacon(batch)) this.transport.send(batch);\n }\n // Web analytics\n const webBatch = this.drainWeb();\n if (webBatch) {\n if (!this.transport.beaconWeb(webBatch)) this.transport.sendWeb(webBatch);\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 {\n WatchupOptions,\n TracePayload,\n ErrorPayload,\n EventPayload,\n WebAnalyticsPayload,\n} 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// ── Storage keys ──────────────────────────────────────────────────────────────\n\nconst VISITOR_KEY = '__wup_vid';\nconst SESSION_KEY = '__wup_sid';\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 // ── Visitor / session identity ─────────────────────────────────────────────\n\n /**\n * Persistent visitor ID. Stored in localStorage so it survives browser\n * sessions. Falls back to a per-session UUID when localStorage is blocked.\n * The server hashes this value with SHA-256 before persisting.\n */\n private readonly visitorId: string;\n\n /**\n * Per-session ID stored in sessionStorage. Resets on tab close.\n * The server hashes this value before persisting.\n */\n private readonly webSessionId: string;\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 // Initialise visitor & session IDs\n this.visitorId = this._getOrCreateVisitorId();\n this.webSessionId = this._getOrCreateSessionId();\n\n this._setupAutoCapture();\n }\n\n // ── Visitor / session identity helpers ─────────────────────────────────────\n\n private _getOrCreateVisitorId(): string {\n try {\n let id = localStorage.getItem(VISITOR_KEY);\n if (!id) {\n id = crypto.randomUUID();\n localStorage.setItem(VISITOR_KEY, id);\n }\n return id;\n } catch {\n // localStorage blocked (private mode, etc.) — fall back to session scope\n return crypto.randomUUID();\n }\n }\n\n private _getOrCreateSessionId(): string {\n try {\n let id = sessionStorage.getItem(SESSION_KEY);\n if (!id) {\n id = crypto.randomUUID();\n sessionStorage.setItem(SESSION_KEY, id);\n }\n return id;\n } catch {\n return this.sessionId; // fallback: correlate with SDK sessionId\n }\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 * Track a web analytics page view (or custom web event).\n * Enriches the payload with visitor context, UTM params, and device info.\n *\n * Normally called automatically. Call manually when you need custom event_name.\n *\n * @example\n * watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });\n */\n trackWebView(overrides: Partial<WebAnalyticsPayload> = {}): void {\n const url = new URL(window.location.href);\n const params = url.searchParams;\n\n const payload: WebAnalyticsPayload = {\n path: url.pathname + (url.search || ''),\n hostname: url.hostname,\n referrer: document.referrer || undefined,\n title: document.title || undefined,\n screen_w: window.screen?.width,\n screen_h: window.screen?.height,\n lang: navigator.language || undefined,\n timezone: this._timezone(),\n // UTM parameters\n utm_source: params.get('utm_source') || undefined,\n utm_medium: params.get('utm_medium') || undefined,\n utm_campaign: params.get('utm_campaign') || undefined,\n utm_term: params.get('utm_term') || undefined,\n utm_content: params.get('utm_content') || undefined,\n // Identity (raw; the server hashes before storing)\n visitor_id: this.visitorId,\n session_id: this.webSessionId,\n event_name: 'pageview',\n occurred_at: new Date().toISOString(),\n // Apply caller overrides last\n ...overrides,\n };\n\n this.batcher.addWebView(payload);\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 (both telemetry and web analytics). */\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 trackView = () => {\n // Small delay so the page title has settled after navigation\n setTimeout(() => this.trackWebView(), 0);\n };\n\n // Initial view\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', trackView, { once: true });\n } else {\n setTimeout(() => this.trackWebView(), 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 trackView();\n };\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n origReplace(...args);\n // replaceState is often used for URL canonicalisation — don't track.\n };\n\n const onPopState = () => trackView();\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 // ── Helpers ───────────────────────────────────────────────────────────────\n\n private _timezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;\n } catch {\n return undefined;\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watchupltd/browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Official Watchup SDK for browsers — analytics, errors, and performance",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Watchup Ltd",
|
|
@@ -9,20 +9,23 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types":
|
|
13
|
-
"import":
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
14
|
"require": "./dist/index.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"files": [
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
18
21
|
"sideEffects": false,
|
|
19
22
|
"scripts": {
|
|
20
|
-
"build":
|
|
23
|
+
"build": "tsup",
|
|
21
24
|
"typecheck": "tsc --noEmit",
|
|
22
|
-
"dev":
|
|
25
|
+
"dev": "tsup --watch"
|
|
23
26
|
},
|
|
24
27
|
"devDependencies": {
|
|
25
|
-
"tsup":
|
|
28
|
+
"tsup": "^8.0.2",
|
|
26
29
|
"typescript": "^5.4.5"
|
|
27
30
|
}
|
|
28
31
|
}
|