@watchupltd/browser 0.1.0 → 0.1.2
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 +87 -2
- package/dist/index.d.ts +87 -2
- package/dist/index.js +180 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +180 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -7
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
interface WatchupUser {
|
|
2
|
+
/** Your app's internal user ID — the only required field. */
|
|
3
|
+
id: string | number;
|
|
4
|
+
email?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
/** Any extra key/value pairs you want attached to events. */
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
1
9
|
interface WatchupOptions {
|
|
2
10
|
/**
|
|
3
11
|
* Your project's **public** API key from the Watchup dashboard.
|
|
@@ -46,6 +54,7 @@ interface TracePayload {
|
|
|
46
54
|
environment?: string;
|
|
47
55
|
release?: string;
|
|
48
56
|
meta?: Record<string, unknown>;
|
|
57
|
+
user?: WatchupUser;
|
|
49
58
|
}
|
|
50
59
|
interface ErrorPayload {
|
|
51
60
|
message: string;
|
|
@@ -56,6 +65,7 @@ interface ErrorPayload {
|
|
|
56
65
|
timestamp: string;
|
|
57
66
|
environment?: string;
|
|
58
67
|
release?: string;
|
|
68
|
+
user?: WatchupUser;
|
|
59
69
|
}
|
|
60
70
|
interface EventPayload {
|
|
61
71
|
name: string;
|
|
@@ -67,17 +77,81 @@ interface IngestBatch {
|
|
|
67
77
|
errors?: ErrorPayload[];
|
|
68
78
|
events?: EventPayload[];
|
|
69
79
|
}
|
|
80
|
+
interface WebAnalyticsPayload {
|
|
81
|
+
/** URL path, e.g. "/pricing". */
|
|
82
|
+
path: string;
|
|
83
|
+
/** Hostname of the tracked site, e.g. "example.com". */
|
|
84
|
+
hostname: string;
|
|
85
|
+
/** Full referrer URL (document.referrer). */
|
|
86
|
+
referrer?: string;
|
|
87
|
+
/** document.title at tracking time. */
|
|
88
|
+
title?: string;
|
|
89
|
+
/** Screen width in CSS pixels. */
|
|
90
|
+
screen_w?: number;
|
|
91
|
+
/** Screen height in CSS pixels. */
|
|
92
|
+
screen_h?: number;
|
|
93
|
+
/** navigator.language, e.g. "en-GB". */
|
|
94
|
+
lang?: string;
|
|
95
|
+
/** Intl.DateTimeFormat().resolvedOptions().timeZone */
|
|
96
|
+
timezone?: string;
|
|
97
|
+
/** UTM parameters parsed from the current URL's query string. */
|
|
98
|
+
utm_source?: string;
|
|
99
|
+
utm_medium?: string;
|
|
100
|
+
utm_campaign?: string;
|
|
101
|
+
utm_term?: string;
|
|
102
|
+
utm_content?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Persistent visitor UUID (stored in localStorage).
|
|
105
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
106
|
+
*/
|
|
107
|
+
visitor_id: string;
|
|
108
|
+
/**
|
|
109
|
+
* Per-session UUID (stored in sessionStorage).
|
|
110
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
111
|
+
*/
|
|
112
|
+
session_id: string;
|
|
113
|
+
/** Event type — defaults to "pageview". Use for custom web events. */
|
|
114
|
+
event_name?: string;
|
|
115
|
+
/** ISO-8601 timestamp when the event occurred. */
|
|
116
|
+
occurred_at: string;
|
|
117
|
+
}
|
|
70
118
|
|
|
71
119
|
declare class Watchup {
|
|
72
120
|
private readonly cfg;
|
|
73
121
|
private readonly batcher;
|
|
74
122
|
private readonly cleanup;
|
|
123
|
+
private _user;
|
|
75
124
|
/**
|
|
76
125
|
* A random UUID generated on init. Stable for the lifetime of the page —
|
|
77
126
|
* useful for correlating all events from one user session.
|
|
78
127
|
*/
|
|
79
128
|
readonly sessionId: string;
|
|
129
|
+
/**
|
|
130
|
+
* Persistent visitor ID. Stored in localStorage so it survives browser
|
|
131
|
+
* sessions. Falls back to a per-session UUID when localStorage is blocked.
|
|
132
|
+
* The server hashes this value with SHA-256 before persisting.
|
|
133
|
+
*/
|
|
134
|
+
private readonly visitorId;
|
|
135
|
+
/**
|
|
136
|
+
* Per-session ID stored in sessionStorage. Resets on tab close.
|
|
137
|
+
* The server hashes this value before persisting.
|
|
138
|
+
*/
|
|
139
|
+
private readonly webSessionId;
|
|
80
140
|
constructor(options: WatchupOptions);
|
|
141
|
+
private _getOrCreateVisitorId;
|
|
142
|
+
private _getOrCreateSessionId;
|
|
143
|
+
/**
|
|
144
|
+
* Attach a user to all subsequent errors, traces, and events.
|
|
145
|
+
* Call this after login; the context persists until `clearUser()` or page reload.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* watchup.setUser({ id: '42', email: 'ada@example.com', name: 'Ada Lovelace' });
|
|
149
|
+
*/
|
|
150
|
+
setUser(user: WatchupUser): void;
|
|
151
|
+
/**
|
|
152
|
+
* Remove the current user context (e.g. after logout).
|
|
153
|
+
*/
|
|
154
|
+
clearUser(): void;
|
|
81
155
|
/**
|
|
82
156
|
* Track a custom analytics event.
|
|
83
157
|
*
|
|
@@ -85,6 +159,16 @@ declare class Watchup {
|
|
|
85
159
|
* watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
|
|
86
160
|
*/
|
|
87
161
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
162
|
+
/**
|
|
163
|
+
* Track a web analytics page view (or custom web event).
|
|
164
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
165
|
+
*
|
|
166
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
170
|
+
*/
|
|
171
|
+
trackWebView(overrides?: Partial<WebAnalyticsPayload>): void;
|
|
88
172
|
/**
|
|
89
173
|
* Manually capture an error.
|
|
90
174
|
*
|
|
@@ -110,12 +194,13 @@ declare class Watchup {
|
|
|
110
194
|
status?: TracePayload['status'];
|
|
111
195
|
meta?: Record<string, unknown>;
|
|
112
196
|
}) => void;
|
|
113
|
-
/** Immediately flush all queued items. */
|
|
197
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
114
198
|
flush(): void;
|
|
115
199
|
/** Stop the flush timer and release all listeners. */
|
|
116
200
|
shutdown(): void;
|
|
117
201
|
private _setupAutoCapture;
|
|
118
202
|
private _setupPageViewTracking;
|
|
203
|
+
private _timezone;
|
|
119
204
|
}
|
|
120
205
|
|
|
121
|
-
export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions };
|
|
206
|
+
export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions, type WatchupUser };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
interface WatchupUser {
|
|
2
|
+
/** Your app's internal user ID — the only required field. */
|
|
3
|
+
id: string | number;
|
|
4
|
+
email?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
/** Any extra key/value pairs you want attached to events. */
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
1
9
|
interface WatchupOptions {
|
|
2
10
|
/**
|
|
3
11
|
* Your project's **public** API key from the Watchup dashboard.
|
|
@@ -46,6 +54,7 @@ interface TracePayload {
|
|
|
46
54
|
environment?: string;
|
|
47
55
|
release?: string;
|
|
48
56
|
meta?: Record<string, unknown>;
|
|
57
|
+
user?: WatchupUser;
|
|
49
58
|
}
|
|
50
59
|
interface ErrorPayload {
|
|
51
60
|
message: string;
|
|
@@ -56,6 +65,7 @@ interface ErrorPayload {
|
|
|
56
65
|
timestamp: string;
|
|
57
66
|
environment?: string;
|
|
58
67
|
release?: string;
|
|
68
|
+
user?: WatchupUser;
|
|
59
69
|
}
|
|
60
70
|
interface EventPayload {
|
|
61
71
|
name: string;
|
|
@@ -67,17 +77,81 @@ interface IngestBatch {
|
|
|
67
77
|
errors?: ErrorPayload[];
|
|
68
78
|
events?: EventPayload[];
|
|
69
79
|
}
|
|
80
|
+
interface WebAnalyticsPayload {
|
|
81
|
+
/** URL path, e.g. "/pricing". */
|
|
82
|
+
path: string;
|
|
83
|
+
/** Hostname of the tracked site, e.g. "example.com". */
|
|
84
|
+
hostname: string;
|
|
85
|
+
/** Full referrer URL (document.referrer). */
|
|
86
|
+
referrer?: string;
|
|
87
|
+
/** document.title at tracking time. */
|
|
88
|
+
title?: string;
|
|
89
|
+
/** Screen width in CSS pixels. */
|
|
90
|
+
screen_w?: number;
|
|
91
|
+
/** Screen height in CSS pixels. */
|
|
92
|
+
screen_h?: number;
|
|
93
|
+
/** navigator.language, e.g. "en-GB". */
|
|
94
|
+
lang?: string;
|
|
95
|
+
/** Intl.DateTimeFormat().resolvedOptions().timeZone */
|
|
96
|
+
timezone?: string;
|
|
97
|
+
/** UTM parameters parsed from the current URL's query string. */
|
|
98
|
+
utm_source?: string;
|
|
99
|
+
utm_medium?: string;
|
|
100
|
+
utm_campaign?: string;
|
|
101
|
+
utm_term?: string;
|
|
102
|
+
utm_content?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Persistent visitor UUID (stored in localStorage).
|
|
105
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
106
|
+
*/
|
|
107
|
+
visitor_id: string;
|
|
108
|
+
/**
|
|
109
|
+
* Per-session UUID (stored in sessionStorage).
|
|
110
|
+
* The server hashes this before storing — the raw value is never persisted.
|
|
111
|
+
*/
|
|
112
|
+
session_id: string;
|
|
113
|
+
/** Event type — defaults to "pageview". Use for custom web events. */
|
|
114
|
+
event_name?: string;
|
|
115
|
+
/** ISO-8601 timestamp when the event occurred. */
|
|
116
|
+
occurred_at: string;
|
|
117
|
+
}
|
|
70
118
|
|
|
71
119
|
declare class Watchup {
|
|
72
120
|
private readonly cfg;
|
|
73
121
|
private readonly batcher;
|
|
74
122
|
private readonly cleanup;
|
|
123
|
+
private _user;
|
|
75
124
|
/**
|
|
76
125
|
* A random UUID generated on init. Stable for the lifetime of the page —
|
|
77
126
|
* useful for correlating all events from one user session.
|
|
78
127
|
*/
|
|
79
128
|
readonly sessionId: string;
|
|
129
|
+
/**
|
|
130
|
+
* Persistent visitor ID. Stored in localStorage so it survives browser
|
|
131
|
+
* sessions. Falls back to a per-session UUID when localStorage is blocked.
|
|
132
|
+
* The server hashes this value with SHA-256 before persisting.
|
|
133
|
+
*/
|
|
134
|
+
private readonly visitorId;
|
|
135
|
+
/**
|
|
136
|
+
* Per-session ID stored in sessionStorage. Resets on tab close.
|
|
137
|
+
* The server hashes this value before persisting.
|
|
138
|
+
*/
|
|
139
|
+
private readonly webSessionId;
|
|
80
140
|
constructor(options: WatchupOptions);
|
|
141
|
+
private _getOrCreateVisitorId;
|
|
142
|
+
private _getOrCreateSessionId;
|
|
143
|
+
/**
|
|
144
|
+
* Attach a user to all subsequent errors, traces, and events.
|
|
145
|
+
* Call this after login; the context persists until `clearUser()` or page reload.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* watchup.setUser({ id: '42', email: 'ada@example.com', name: 'Ada Lovelace' });
|
|
149
|
+
*/
|
|
150
|
+
setUser(user: WatchupUser): void;
|
|
151
|
+
/**
|
|
152
|
+
* Remove the current user context (e.g. after logout).
|
|
153
|
+
*/
|
|
154
|
+
clearUser(): void;
|
|
81
155
|
/**
|
|
82
156
|
* Track a custom analytics event.
|
|
83
157
|
*
|
|
@@ -85,6 +159,16 @@ declare class Watchup {
|
|
|
85
159
|
* watchup.track('button.clicked', { label: 'Sign Up', variant: 'A' });
|
|
86
160
|
*/
|
|
87
161
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
162
|
+
/**
|
|
163
|
+
* Track a web analytics page view (or custom web event).
|
|
164
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
165
|
+
*
|
|
166
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
170
|
+
*/
|
|
171
|
+
trackWebView(overrides?: Partial<WebAnalyticsPayload>): void;
|
|
88
172
|
/**
|
|
89
173
|
* Manually capture an error.
|
|
90
174
|
*
|
|
@@ -110,12 +194,13 @@ declare class Watchup {
|
|
|
110
194
|
status?: TracePayload['status'];
|
|
111
195
|
meta?: Record<string, unknown>;
|
|
112
196
|
}) => void;
|
|
113
|
-
/** Immediately flush all queued items. */
|
|
197
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
114
198
|
flush(): void;
|
|
115
199
|
/** Stop the flush timer and release all listeners. */
|
|
116
200
|
shutdown(): void;
|
|
117
201
|
private _setupAutoCapture;
|
|
118
202
|
private _setupPageViewTracking;
|
|
203
|
+
private _timezone;
|
|
119
204
|
}
|
|
120
205
|
|
|
121
|
-
export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions };
|
|
206
|
+
export { type ErrorPayload, type EventPayload, type IngestBatch, type TracePayload, Watchup, type WatchupOptions, type WatchupUser };
|
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,9 +330,12 @@ 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 = [];
|
|
338
|
+
this._user = null;
|
|
273
339
|
/**
|
|
274
340
|
* A random UUID generated on init. Stable for the lifetime of the page —
|
|
275
341
|
* useful for correlating all events from one user session.
|
|
@@ -286,8 +352,52 @@ var Watchup = class {
|
|
|
286
352
|
const transport = new Transport(this.cfg.baseUrl, this.cfg.apiKey, this.cfg.debug);
|
|
287
353
|
this.batcher = new Batcher(transport, this.cfg.flushInterval, this.cfg.maxBatchSize);
|
|
288
354
|
this.batcher.start();
|
|
355
|
+
this.visitorId = this._getOrCreateVisitorId();
|
|
356
|
+
this.webSessionId = this._getOrCreateSessionId();
|
|
289
357
|
this._setupAutoCapture();
|
|
290
358
|
}
|
|
359
|
+
// ── Visitor / session identity helpers ─────────────────────────────────────
|
|
360
|
+
_getOrCreateVisitorId() {
|
|
361
|
+
try {
|
|
362
|
+
let id = localStorage.getItem(VISITOR_KEY);
|
|
363
|
+
if (!id) {
|
|
364
|
+
id = crypto.randomUUID();
|
|
365
|
+
localStorage.setItem(VISITOR_KEY, id);
|
|
366
|
+
}
|
|
367
|
+
return id;
|
|
368
|
+
} catch {
|
|
369
|
+
return crypto.randomUUID();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
_getOrCreateSessionId() {
|
|
373
|
+
try {
|
|
374
|
+
let id = sessionStorage.getItem(SESSION_KEY);
|
|
375
|
+
if (!id) {
|
|
376
|
+
id = crypto.randomUUID();
|
|
377
|
+
sessionStorage.setItem(SESSION_KEY, id);
|
|
378
|
+
}
|
|
379
|
+
return id;
|
|
380
|
+
} catch {
|
|
381
|
+
return this.sessionId;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// ── User identification ───────────────────────────────────────────────────
|
|
385
|
+
/**
|
|
386
|
+
* Attach a user to all subsequent errors, traces, and events.
|
|
387
|
+
* Call this after login; the context persists until `clearUser()` or page reload.
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* watchup.setUser({ id: '42', email: 'ada@example.com', name: 'Ada Lovelace' });
|
|
391
|
+
*/
|
|
392
|
+
setUser(user) {
|
|
393
|
+
this._user = { ...user };
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Remove the current user context (e.g. after logout).
|
|
397
|
+
*/
|
|
398
|
+
clearUser() {
|
|
399
|
+
this._user = null;
|
|
400
|
+
}
|
|
291
401
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
292
402
|
/**
|
|
293
403
|
* Track a custom analytics event.
|
|
@@ -304,6 +414,44 @@ var Watchup = class {
|
|
|
304
414
|
};
|
|
305
415
|
this.batcher.addEvent(event);
|
|
306
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Track a web analytics page view (or custom web event).
|
|
419
|
+
* Enriches the payload with visitor context, UTM params, and device info.
|
|
420
|
+
*
|
|
421
|
+
* Normally called automatically. Call manually when you need custom event_name.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* watchup.trackWebView({ event_name: 'conversion', path: '/checkout/success' });
|
|
425
|
+
*/
|
|
426
|
+
trackWebView(overrides = {}) {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
const url = new URL(window.location.href);
|
|
429
|
+
const params = url.searchParams;
|
|
430
|
+
const payload = {
|
|
431
|
+
path: url.pathname + (url.search || ""),
|
|
432
|
+
hostname: url.hostname,
|
|
433
|
+
referrer: document.referrer || void 0,
|
|
434
|
+
title: document.title || void 0,
|
|
435
|
+
screen_w: (_a = window.screen) == null ? void 0 : _a.width,
|
|
436
|
+
screen_h: (_b = window.screen) == null ? void 0 : _b.height,
|
|
437
|
+
lang: navigator.language || void 0,
|
|
438
|
+
timezone: this._timezone(),
|
|
439
|
+
// UTM parameters
|
|
440
|
+
utm_source: params.get("utm_source") || void 0,
|
|
441
|
+
utm_medium: params.get("utm_medium") || void 0,
|
|
442
|
+
utm_campaign: params.get("utm_campaign") || void 0,
|
|
443
|
+
utm_term: params.get("utm_term") || void 0,
|
|
444
|
+
utm_content: params.get("utm_content") || void 0,
|
|
445
|
+
// Identity (raw; the server hashes before storing)
|
|
446
|
+
visitor_id: this.visitorId,
|
|
447
|
+
session_id: this.webSessionId,
|
|
448
|
+
event_name: "pageview",
|
|
449
|
+
occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
450
|
+
// Apply caller overrides last
|
|
451
|
+
...overrides
|
|
452
|
+
};
|
|
453
|
+
this.batcher.addWebView(payload);
|
|
454
|
+
}
|
|
307
455
|
/**
|
|
308
456
|
* Manually capture an error.
|
|
309
457
|
*
|
|
@@ -325,7 +473,8 @@ var Watchup = class {
|
|
|
325
473
|
},
|
|
326
474
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
327
475
|
environment: this.cfg.environment,
|
|
328
|
-
...this.cfg.release && { release: this.cfg.release }
|
|
476
|
+
...this.cfg.release && { release: this.cfg.release },
|
|
477
|
+
...this._user && { user: this._user }
|
|
329
478
|
};
|
|
330
479
|
this.batcher.addError(payload);
|
|
331
480
|
}
|
|
@@ -351,11 +500,12 @@ var Watchup = class {
|
|
|
351
500
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
352
501
|
environment: this.cfg.environment,
|
|
353
502
|
...this.cfg.release && { release: this.cfg.release },
|
|
354
|
-
...opts.meta && { meta: opts.meta }
|
|
503
|
+
...opts.meta && { meta: opts.meta },
|
|
504
|
+
...this._user && { user: this._user }
|
|
355
505
|
});
|
|
356
506
|
};
|
|
357
507
|
}
|
|
358
|
-
/** Immediately flush all queued items. */
|
|
508
|
+
/** Immediately flush all queued items (both telemetry and web analytics). */
|
|
359
509
|
flush() {
|
|
360
510
|
this.batcher.flush();
|
|
361
511
|
}
|
|
@@ -383,33 +533,24 @@ var Watchup = class {
|
|
|
383
533
|
}
|
|
384
534
|
}
|
|
385
535
|
_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
|
-
});
|
|
536
|
+
const trackView = () => {
|
|
537
|
+
setTimeout(() => this.trackWebView(), 0);
|
|
397
538
|
};
|
|
398
539
|
if (document.readyState === "loading") {
|
|
399
|
-
document.addEventListener("DOMContentLoaded",
|
|
540
|
+
document.addEventListener("DOMContentLoaded", trackView, { once: true });
|
|
400
541
|
} else {
|
|
401
|
-
setTimeout(
|
|
542
|
+
setTimeout(() => this.trackWebView(), 0);
|
|
402
543
|
}
|
|
403
544
|
const origPush = history.pushState.bind(history);
|
|
404
545
|
const origReplace = history.replaceState.bind(history);
|
|
405
546
|
history.pushState = (...args) => {
|
|
406
547
|
origPush(...args);
|
|
407
|
-
|
|
548
|
+
trackView();
|
|
408
549
|
};
|
|
409
550
|
history.replaceState = (...args) => {
|
|
410
551
|
origReplace(...args);
|
|
411
552
|
};
|
|
412
|
-
const onPopState = () =>
|
|
553
|
+
const onPopState = () => trackView();
|
|
413
554
|
window.addEventListener("popstate", onPopState);
|
|
414
555
|
this.cleanup.push(() => {
|
|
415
556
|
history.pushState = origPush;
|
|
@@ -417,6 +558,14 @@ var Watchup = class {
|
|
|
417
558
|
window.removeEventListener("popstate", onPopState);
|
|
418
559
|
});
|
|
419
560
|
}
|
|
561
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
562
|
+
_timezone() {
|
|
563
|
+
try {
|
|
564
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || void 0;
|
|
565
|
+
} catch {
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
420
569
|
};
|
|
421
570
|
|
|
422
571
|
exports.Watchup = Watchup;
|