5xx-sdk 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/browser.d.mts +35 -1
- package/dist/browser.d.ts +35 -1
- package/dist/browser.js +160 -0
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +2 -2
- package/dist/{chunk-JCBAF7DF.mjs → chunk-762E5RVI.mjs} +2 -2
- package/dist/{chunk-5YY5E33R.mjs → chunk-G4LKAXG5.mjs} +9 -1
- package/dist/chunk-G4LKAXG5.mjs.map +1 -0
- package/dist/chunk-IAF5CKNL.mjs +236 -0
- package/dist/chunk-IAF5CKNL.mjs.map +1 -0
- package/dist/{client-BhKj28Zi.d.mts → client-t5SsLKrz.d.mts} +17 -1
- package/dist/{client-BhKj28Zi.d.ts → client-t5SsLKrz.d.ts} +17 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +160 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/node.d.mts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +8 -0
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-5YY5E33R.mjs.map +0 -1
- package/dist/chunk-LD3XNYST.mjs +0 -84
- package/dist/chunk-LD3XNYST.mjs.map +0 -1
- /package/dist/{chunk-JCBAF7DF.mjs.map → chunk-762E5RVI.mjs.map} +0 -0
package/README.md
CHANGED
|
@@ -53,6 +53,13 @@ const fivexx = new FiveXXNode({
|
|
|
53
53
|
// Enable automatic capture of uncaught exceptions
|
|
54
54
|
fivexx.enableAutoCapture();
|
|
55
55
|
|
|
56
|
+
// Wrap route handlers for automatic error capture (recommended)
|
|
57
|
+
// Works with Next.js, Express, Fastify, and any framework
|
|
58
|
+
export const GET = fivexx.wrapHandler(async (req: NextRequest) => {
|
|
59
|
+
const data = await db.user.findMany();
|
|
60
|
+
return NextResponse.json(data);
|
|
61
|
+
});
|
|
62
|
+
|
|
56
63
|
// Wrap async functions
|
|
57
64
|
const wrappedFn = fivexx.wrapAsync(async () => {
|
|
58
65
|
await someAsyncOperation();
|
|
@@ -118,7 +125,8 @@ Extends FiveXX with Node.js-specific features.
|
|
|
118
125
|
|
|
119
126
|
**Additional Methods:**
|
|
120
127
|
- `enableAutoCapture()` - Auto-capture uncaughtException and unhandledRejection
|
|
121
|
-
- `
|
|
128
|
+
- `wrapHandler(fn)` - Wrap HTTP route handlers to automatically capture errors (recommended for Next.js, Express, Fastify)
|
|
129
|
+
- `wrapAsync(fn, metadata?)` - Wrap any async function to capture errors
|
|
122
130
|
|
|
123
131
|
## Metadata
|
|
124
132
|
|
package/dist/browser.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FiveXX,
|
|
1
|
+
import { F as FiveXX, b as FiveXXOptions } from './client-t5SsLKrz.mjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Browser-specific FiveXX client with automatic error capture
|
|
@@ -7,6 +7,15 @@ declare class FiveXXBrowser extends FiveXX {
|
|
|
7
7
|
private autoCapture;
|
|
8
8
|
private originalOnError;
|
|
9
9
|
private originalOnUnhandledRejection;
|
|
10
|
+
private analyticsEnabled;
|
|
11
|
+
private sessionId;
|
|
12
|
+
private analyticsBuffer;
|
|
13
|
+
private flushInterval;
|
|
14
|
+
private originalPushState;
|
|
15
|
+
private originalReplaceState;
|
|
16
|
+
private boundPopstateHandler;
|
|
17
|
+
private boundVisibilityHandler;
|
|
18
|
+
private boundBeforeUnloadHandler;
|
|
10
19
|
constructor(options: FiveXXOptions);
|
|
11
20
|
/**
|
|
12
21
|
* Enable automatic capture of unhandled errors and promise rejections
|
|
@@ -16,6 +25,31 @@ declare class FiveXXBrowser extends FiveXX {
|
|
|
16
25
|
* Disable automatic capture
|
|
17
26
|
*/
|
|
18
27
|
disableAutoCapture(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Enable opt-in analytics tracking.
|
|
30
|
+
* Tracks page views automatically, including SPA navigations.
|
|
31
|
+
*/
|
|
32
|
+
enableAnalytics(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Track a custom event (stored for future use, logs for now)
|
|
35
|
+
*/
|
|
36
|
+
trackEvent(name: string, metadata?: Record<string, unknown>): void;
|
|
37
|
+
/**
|
|
38
|
+
* Capture a page view and add it to the buffer
|
|
39
|
+
*/
|
|
40
|
+
private trackPageView;
|
|
41
|
+
/**
|
|
42
|
+
* Flush the analytics buffer by POSTing to the server
|
|
43
|
+
*/
|
|
44
|
+
private flushAnalytics;
|
|
45
|
+
/**
|
|
46
|
+
* Flush analytics using navigator.sendBeacon (for beforeunload / visibilitychange)
|
|
47
|
+
*/
|
|
48
|
+
private flushAnalyticsBeacon;
|
|
49
|
+
/**
|
|
50
|
+
* Clean up analytics listeners and intervals
|
|
51
|
+
*/
|
|
52
|
+
cleanupAnalytics(): void;
|
|
19
53
|
}
|
|
20
54
|
|
|
21
55
|
export { FiveXXBrowser };
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FiveXX,
|
|
1
|
+
import { F as FiveXX, b as FiveXXOptions } from './client-t5SsLKrz.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Browser-specific FiveXX client with automatic error capture
|
|
@@ -7,6 +7,15 @@ declare class FiveXXBrowser extends FiveXX {
|
|
|
7
7
|
private autoCapture;
|
|
8
8
|
private originalOnError;
|
|
9
9
|
private originalOnUnhandledRejection;
|
|
10
|
+
private analyticsEnabled;
|
|
11
|
+
private sessionId;
|
|
12
|
+
private analyticsBuffer;
|
|
13
|
+
private flushInterval;
|
|
14
|
+
private originalPushState;
|
|
15
|
+
private originalReplaceState;
|
|
16
|
+
private boundPopstateHandler;
|
|
17
|
+
private boundVisibilityHandler;
|
|
18
|
+
private boundBeforeUnloadHandler;
|
|
10
19
|
constructor(options: FiveXXOptions);
|
|
11
20
|
/**
|
|
12
21
|
* Enable automatic capture of unhandled errors and promise rejections
|
|
@@ -16,6 +25,31 @@ declare class FiveXXBrowser extends FiveXX {
|
|
|
16
25
|
* Disable automatic capture
|
|
17
26
|
*/
|
|
18
27
|
disableAutoCapture(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Enable opt-in analytics tracking.
|
|
30
|
+
* Tracks page views automatically, including SPA navigations.
|
|
31
|
+
*/
|
|
32
|
+
enableAnalytics(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Track a custom event (stored for future use, logs for now)
|
|
35
|
+
*/
|
|
36
|
+
trackEvent(name: string, metadata?: Record<string, unknown>): void;
|
|
37
|
+
/**
|
|
38
|
+
* Capture a page view and add it to the buffer
|
|
39
|
+
*/
|
|
40
|
+
private trackPageView;
|
|
41
|
+
/**
|
|
42
|
+
* Flush the analytics buffer by POSTing to the server
|
|
43
|
+
*/
|
|
44
|
+
private flushAnalytics;
|
|
45
|
+
/**
|
|
46
|
+
* Flush analytics using navigator.sendBeacon (for beforeunload / visibilitychange)
|
|
47
|
+
*/
|
|
48
|
+
private flushAnalyticsBeacon;
|
|
49
|
+
/**
|
|
50
|
+
* Clean up analytics listeners and intervals
|
|
51
|
+
*/
|
|
52
|
+
cleanupAnalytics(): void;
|
|
19
53
|
}
|
|
20
54
|
|
|
21
55
|
export { FiveXXBrowser };
|
package/dist/browser.js
CHANGED
|
@@ -32,6 +32,14 @@ var FiveXX = class {
|
|
|
32
32
|
this.endpoint = options.endpoint.replace(/\/$/, "");
|
|
33
33
|
this.environment = options.environment || "production";
|
|
34
34
|
}
|
|
35
|
+
/** @internal */
|
|
36
|
+
getApiKey() {
|
|
37
|
+
return this.apiKey;
|
|
38
|
+
}
|
|
39
|
+
/** @internal */
|
|
40
|
+
getEndpoint() {
|
|
41
|
+
return this.endpoint;
|
|
42
|
+
}
|
|
35
43
|
/**
|
|
36
44
|
* Set the environment (e.g., "production", "development", "staging")
|
|
37
45
|
*/
|
|
@@ -106,6 +114,20 @@ var FiveXXBrowser = class extends FiveXX {
|
|
|
106
114
|
this.autoCapture = false;
|
|
107
115
|
this.originalOnError = null;
|
|
108
116
|
this.originalOnUnhandledRejection = null;
|
|
117
|
+
// Analytics properties
|
|
118
|
+
this.analyticsEnabled = false;
|
|
119
|
+
this.analyticsBuffer = [];
|
|
120
|
+
this.flushInterval = null;
|
|
121
|
+
this.originalPushState = null;
|
|
122
|
+
this.originalReplaceState = null;
|
|
123
|
+
this.boundPopstateHandler = null;
|
|
124
|
+
this.boundVisibilityHandler = null;
|
|
125
|
+
this.boundBeforeUnloadHandler = null;
|
|
126
|
+
this.sessionId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
127
|
+
const r = Math.random() * 16 | 0;
|
|
128
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
129
|
+
return v.toString(16);
|
|
130
|
+
});
|
|
109
131
|
if (typeof window !== "undefined" && !options.environment) {
|
|
110
132
|
const hostname = window.location.hostname;
|
|
111
133
|
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
@@ -173,6 +195,144 @@ var FiveXXBrowser = class extends FiveXX {
|
|
|
173
195
|
window.onerror = this.originalOnError;
|
|
174
196
|
window.onunhandledrejection = this.originalOnUnhandledRejection;
|
|
175
197
|
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Analytics
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Enable opt-in analytics tracking.
|
|
203
|
+
* Tracks page views automatically, including SPA navigations.
|
|
204
|
+
*/
|
|
205
|
+
enableAnalytics() {
|
|
206
|
+
if (this.analyticsEnabled || typeof window === "undefined") return;
|
|
207
|
+
this.analyticsEnabled = true;
|
|
208
|
+
this.trackPageView();
|
|
209
|
+
this.originalPushState = history.pushState.bind(history);
|
|
210
|
+
this.originalReplaceState = history.replaceState.bind(history);
|
|
211
|
+
const self = this;
|
|
212
|
+
history.pushState = function(...args) {
|
|
213
|
+
self.originalPushState(...args);
|
|
214
|
+
self.trackPageView();
|
|
215
|
+
};
|
|
216
|
+
history.replaceState = function(...args) {
|
|
217
|
+
self.originalReplaceState(...args);
|
|
218
|
+
self.trackPageView();
|
|
219
|
+
};
|
|
220
|
+
this.boundPopstateHandler = () => this.trackPageView();
|
|
221
|
+
window.addEventListener("popstate", this.boundPopstateHandler);
|
|
222
|
+
this.flushInterval = setInterval(() => this.flushAnalytics(), 5e3);
|
|
223
|
+
this.boundVisibilityHandler = () => {
|
|
224
|
+
if (document.visibilityState === "hidden") {
|
|
225
|
+
this.flushAnalyticsBeacon();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
229
|
+
this.boundBeforeUnloadHandler = () => this.flushAnalyticsBeacon();
|
|
230
|
+
window.addEventListener("beforeunload", this.boundBeforeUnloadHandler);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Track a custom event (stored for future use, logs for now)
|
|
234
|
+
*/
|
|
235
|
+
trackEvent(name, metadata) {
|
|
236
|
+
if (!this.analyticsEnabled) return;
|
|
237
|
+
console.debug("[5xx] trackEvent:", name, metadata);
|
|
238
|
+
}
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Private analytics helpers
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
/**
|
|
243
|
+
* Capture a page view and add it to the buffer
|
|
244
|
+
*/
|
|
245
|
+
trackPageView() {
|
|
246
|
+
if (typeof window === "undefined") return;
|
|
247
|
+
const url = new URL(window.location.href);
|
|
248
|
+
const beacon = {
|
|
249
|
+
path: url.pathname,
|
|
250
|
+
referrer: document.referrer || void 0,
|
|
251
|
+
screenWidth: window.innerWidth
|
|
252
|
+
};
|
|
253
|
+
const utmSource = url.searchParams.get("utm_source");
|
|
254
|
+
const utmMedium = url.searchParams.get("utm_medium");
|
|
255
|
+
const utmCampaign = url.searchParams.get("utm_campaign");
|
|
256
|
+
if (utmSource) beacon.utmSource = utmSource;
|
|
257
|
+
if (utmMedium) beacon.utmMedium = utmMedium;
|
|
258
|
+
if (utmCampaign) beacon.utmCampaign = utmCampaign;
|
|
259
|
+
this.analyticsBuffer.push(beacon);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Flush the analytics buffer by POSTing to the server
|
|
263
|
+
*/
|
|
264
|
+
async flushAnalytics() {
|
|
265
|
+
if (this.analyticsBuffer.length === 0) return;
|
|
266
|
+
const payload = {
|
|
267
|
+
events: [...this.analyticsBuffer],
|
|
268
|
+
sessionId: this.sessionId
|
|
269
|
+
};
|
|
270
|
+
const snapshot = this.analyticsBuffer;
|
|
271
|
+
this.analyticsBuffer = [];
|
|
272
|
+
try {
|
|
273
|
+
const response = await fetch(`${this.getEndpoint()}/api/analytics`, {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: {
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
"X-API-Key": this.getApiKey()
|
|
278
|
+
},
|
|
279
|
+
body: JSON.stringify(payload)
|
|
280
|
+
});
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Flush analytics using navigator.sendBeacon (for beforeunload / visibilitychange)
|
|
290
|
+
*/
|
|
291
|
+
flushAnalyticsBeacon() {
|
|
292
|
+
if (this.analyticsBuffer.length === 0) return;
|
|
293
|
+
const payload = {
|
|
294
|
+
events: [...this.analyticsBuffer],
|
|
295
|
+
sessionId: this.sessionId
|
|
296
|
+
};
|
|
297
|
+
const blob = new Blob([JSON.stringify(payload)], {
|
|
298
|
+
type: "application/json"
|
|
299
|
+
});
|
|
300
|
+
const sent = navigator.sendBeacon(`${this.getEndpoint()}/api/analytics`, blob);
|
|
301
|
+
if (sent) {
|
|
302
|
+
this.analyticsBuffer = [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Clean up analytics listeners and intervals
|
|
307
|
+
*/
|
|
308
|
+
cleanupAnalytics() {
|
|
309
|
+
if (!this.analyticsEnabled) return;
|
|
310
|
+
this.analyticsEnabled = false;
|
|
311
|
+
if (this.flushInterval !== null) {
|
|
312
|
+
clearInterval(this.flushInterval);
|
|
313
|
+
this.flushInterval = null;
|
|
314
|
+
}
|
|
315
|
+
if (this.originalPushState) {
|
|
316
|
+
history.pushState = this.originalPushState;
|
|
317
|
+
this.originalPushState = null;
|
|
318
|
+
}
|
|
319
|
+
if (this.originalReplaceState) {
|
|
320
|
+
history.replaceState = this.originalReplaceState;
|
|
321
|
+
this.originalReplaceState = null;
|
|
322
|
+
}
|
|
323
|
+
if (this.boundPopstateHandler) {
|
|
324
|
+
window.removeEventListener("popstate", this.boundPopstateHandler);
|
|
325
|
+
this.boundPopstateHandler = null;
|
|
326
|
+
}
|
|
327
|
+
if (this.boundVisibilityHandler) {
|
|
328
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
329
|
+
this.boundVisibilityHandler = null;
|
|
330
|
+
}
|
|
331
|
+
if (this.boundBeforeUnloadHandler) {
|
|
332
|
+
window.removeEventListener("beforeunload", this.boundBeforeUnloadHandler);
|
|
333
|
+
this.boundBeforeUnloadHandler = null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
176
336
|
};
|
|
177
337
|
// Annotate the CommonJS export names for ESM import in node:
|
|
178
338
|
0 && (module.exports = {
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/client.ts"],"sourcesContent":["import { FiveXX } from \"./client\";\nimport type { FiveXXOptions } from \"./types\";\n\n/**\n * Browser-specific FiveXX client with automatic error capture\n */\nexport class FiveXXBrowser extends FiveXX {\n private autoCapture = false;\n private originalOnError: OnErrorEventHandler | null = null;\n private originalOnUnhandledRejection: ((event: PromiseRejectionEvent) => void) | null = null;\n\n constructor(options: FiveXXOptions) {\n super(options);\n\n // Auto-detect environment from URL\n if (typeof window !== \"undefined\" && !options.environment) {\n const hostname = window.location.hostname;\n if (hostname === \"localhost\" || hostname === \"127.0.0.1\") {\n this.setEnvironment(\"development\");\n }\n }\n }\n\n /**\n * Enable automatic capture of unhandled errors and promise rejections\n */\n enableAutoCapture(): void {\n if (this.autoCapture || typeof window === \"undefined\") return;\n\n this.autoCapture = true;\n\n // Capture unhandled errors\n this.originalOnError = window.onerror;\n window.onerror = (message, source, lineno, colno, error) => {\n if (error) {\n this.captureError(error, {\n source,\n lineno,\n colno,\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n } else {\n this.captureMessage(String(message), {\n source,\n lineno,\n colno,\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n }\n\n // Call original handler if it exists\n if (this.originalOnError) {\n return this.originalOnError(message, source, lineno, colno, error);\n }\n return false;\n };\n\n // Capture unhandled promise rejections\n this.originalOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = (event) => {\n const error = event.reason;\n if (error instanceof Error) {\n this.captureError(error, {\n type: \"unhandledRejection\",\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n } else {\n this.captureMessage(String(error), {\n type: \"unhandledRejection\",\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n }\n\n if (this.originalOnUnhandledRejection) {\n this.originalOnUnhandledRejection(event);\n }\n };\n }\n\n /**\n * Disable automatic capture\n */\n disableAutoCapture(): void {\n if (!this.autoCapture || typeof window === \"undefined\") return;\n\n this.autoCapture = false;\n window.onerror = this.originalOnError;\n window.onunhandledrejection = this.originalOnUnhandledRejection;\n }\n}\n","import type { FiveXXOptions, ErrorPayload, User } from \"./types\";\n\nexport class FiveXX {\n private apiKey: string;\n private endpoint: string;\n private environment: string;\n private user: User | null = null;\n\n constructor(options: FiveXXOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint.replace(/\\/$/, \"\"); // Remove trailing slash\n this.environment = options.environment || \"production\";\n }\n\n /**\n * Set the environment (e.g., \"production\", \"development\", \"staging\")\n */\n setEnvironment(env: string): void {\n this.environment = env;\n }\n\n /**\n * Set user context for errors\n */\n setUser(user: User | null): void {\n this.user = user;\n }\n\n /**\n * Capture an error and send it to the 5xx server\n */\n async captureError(\n error: Error,\n metadata?: Record<string, unknown>\n ): Promise<string | null> {\n const payload: ErrorPayload = {\n message: error.message || String(error),\n stack: error.stack,\n environment: this.environment,\n metadata: {\n ...metadata,\n ...(this.user && { user: this.user }),\n },\n };\n\n return this.send(payload);\n }\n\n /**\n * Capture a message as an error\n */\n async captureMessage(\n message: string,\n metadata?: Record<string, unknown>\n ): Promise<string | null> {\n const payload: ErrorPayload = {\n message,\n environment: this.environment,\n metadata: {\n ...metadata,\n ...(this.user && { user: this.user }),\n },\n };\n\n return this.send(payload);\n }\n\n /**\n * Send error payload to the server\n */\n private async send(payload: ErrorPayload): Promise<string | null> {\n try {\n const response = await fetch(`${this.endpoint}/api/errors`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n console.error(`5xx: Failed to send error (${response.status})`);\n return null;\n }\n\n const data = await response.json();\n return data.id;\n } catch (err) {\n console.error(\"5xx: Failed to send error\", err);\n return null;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/client.ts"],"sourcesContent":["import { FiveXX } from \"./client\";\nimport type { FiveXXOptions, AnalyticsBeacon, AnalyticsBatchPayload } from \"./types\";\n\n/** UTM and tracking params we keep when sanitizing URLs */\nconst ALLOWED_QUERY_PARAMS = new Set([\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_term\",\n \"utm_content\",\n \"ref\",\n]);\n\n/**\n * Browser-specific FiveXX client with automatic error capture\n */\nexport class FiveXXBrowser extends FiveXX {\n private autoCapture = false;\n private originalOnError: OnErrorEventHandler | null = null;\n private originalOnUnhandledRejection: ((event: PromiseRejectionEvent) => void) | null = null;\n\n // Analytics properties\n private analyticsEnabled = false;\n private sessionId: string;\n private analyticsBuffer: AnalyticsBeacon[] = [];\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n private boundPopstateHandler: (() => void) | null = null;\n private boundVisibilityHandler: (() => void) | null = null;\n private boundBeforeUnloadHandler: (() => void) | null = null;\n\n constructor(options: FiveXXOptions) {\n super(options);\n\n // Generate session ID\n this.sessionId =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n\n // Auto-detect environment from URL\n if (typeof window !== \"undefined\" && !options.environment) {\n const hostname = window.location.hostname;\n if (hostname === \"localhost\" || hostname === \"127.0.0.1\") {\n this.setEnvironment(\"development\");\n }\n }\n }\n\n /**\n * Enable automatic capture of unhandled errors and promise rejections\n */\n enableAutoCapture(): void {\n if (this.autoCapture || typeof window === \"undefined\") return;\n\n this.autoCapture = true;\n\n // Capture unhandled errors\n this.originalOnError = window.onerror;\n window.onerror = (message, source, lineno, colno, error) => {\n if (error) {\n this.captureError(error, {\n source,\n lineno,\n colno,\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n } else {\n this.captureMessage(String(message), {\n source,\n lineno,\n colno,\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n }\n\n // Call original handler if it exists\n if (this.originalOnError) {\n return this.originalOnError(message, source, lineno, colno, error);\n }\n return false;\n };\n\n // Capture unhandled promise rejections\n this.originalOnUnhandledRejection = window.onunhandledrejection;\n window.onunhandledrejection = (event) => {\n const error = event.reason;\n if (error instanceof Error) {\n this.captureError(error, {\n type: \"unhandledRejection\",\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n } else {\n this.captureMessage(String(error), {\n type: \"unhandledRejection\",\n url: window.location.href,\n userAgent: navigator.userAgent,\n });\n }\n\n if (this.originalOnUnhandledRejection) {\n this.originalOnUnhandledRejection(event);\n }\n };\n }\n\n /**\n * Disable automatic capture\n */\n disableAutoCapture(): void {\n if (!this.autoCapture || typeof window === \"undefined\") return;\n\n this.autoCapture = false;\n window.onerror = this.originalOnError;\n window.onunhandledrejection = this.originalOnUnhandledRejection;\n }\n\n // ---------------------------------------------------------------------------\n // Analytics\n // ---------------------------------------------------------------------------\n\n /**\n * Enable opt-in analytics tracking.\n * Tracks page views automatically, including SPA navigations.\n */\n enableAnalytics(): void {\n if (this.analyticsEnabled || typeof window === \"undefined\") return;\n\n this.analyticsEnabled = true;\n\n // Track the current page immediately\n this.trackPageView();\n\n // Monkey-patch history methods for SPA navigation tracking\n this.originalPushState = history.pushState.bind(history);\n this.originalReplaceState = history.replaceState.bind(history);\n\n const self = this;\n\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\n self.originalPushState!(...args);\n self.trackPageView();\n };\n\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\n self.originalReplaceState!(...args);\n self.trackPageView();\n };\n\n // Listen for popstate (browser back/forward)\n this.boundPopstateHandler = () => this.trackPageView();\n window.addEventListener(\"popstate\", this.boundPopstateHandler);\n\n // Start flush interval (every 5 seconds)\n this.flushInterval = setInterval(() => this.flushAnalytics(), 5000);\n\n // Flush on visibility change (tab hidden) and before unload\n this.boundVisibilityHandler = () => {\n if (document.visibilityState === \"hidden\") {\n this.flushAnalyticsBeacon();\n }\n };\n document.addEventListener(\"visibilitychange\", this.boundVisibilityHandler);\n\n this.boundBeforeUnloadHandler = () => this.flushAnalyticsBeacon();\n window.addEventListener(\"beforeunload\", this.boundBeforeUnloadHandler);\n }\n\n /**\n * Track a custom event (stored for future use, logs for now)\n */\n trackEvent(name: string, metadata?: Record<string, unknown>): void {\n if (!this.analyticsEnabled) return;\n console.debug(\"[5xx] trackEvent:\", name, metadata);\n }\n\n // ---------------------------------------------------------------------------\n // Private analytics helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Capture a page view and add it to the buffer\n */\n private trackPageView(): void {\n if (typeof window === \"undefined\") return;\n\n const url = new URL(window.location.href);\n\n const beacon: AnalyticsBeacon = {\n path: url.pathname,\n referrer: document.referrer || undefined,\n screenWidth: window.innerWidth,\n };\n\n // Extract UTM params\n const utmSource = url.searchParams.get(\"utm_source\");\n const utmMedium = url.searchParams.get(\"utm_medium\");\n const utmCampaign = url.searchParams.get(\"utm_campaign\");\n\n if (utmSource) beacon.utmSource = utmSource;\n if (utmMedium) beacon.utmMedium = utmMedium;\n if (utmCampaign) beacon.utmCampaign = utmCampaign;\n\n this.analyticsBuffer.push(beacon);\n }\n\n /**\n * Flush the analytics buffer by POSTing to the server\n */\n private async flushAnalytics(): Promise<void> {\n if (this.analyticsBuffer.length === 0) return;\n\n const payload: AnalyticsBatchPayload = {\n events: [...this.analyticsBuffer],\n sessionId: this.sessionId,\n };\n\n // Optimistically clear the buffer; we will restore on failure\n const snapshot = this.analyticsBuffer;\n this.analyticsBuffer = [];\n\n try {\n const response = await fetch(`${this.getEndpoint()}/api/analytics`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.getApiKey(),\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n // Put events back for retry\n this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);\n }\n } catch {\n // Network error - put events back for retry\n this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);\n }\n }\n\n /**\n * Flush analytics using navigator.sendBeacon (for beforeunload / visibilitychange)\n */\n private flushAnalyticsBeacon(): void {\n if (this.analyticsBuffer.length === 0) return;\n\n const payload: AnalyticsBatchPayload = {\n events: [...this.analyticsBuffer],\n sessionId: this.sessionId,\n };\n\n const blob = new Blob([JSON.stringify(payload)], {\n type: \"application/json\",\n });\n\n const sent = navigator.sendBeacon(`${this.getEndpoint()}/api/analytics`, blob);\n\n if (sent) {\n this.analyticsBuffer = [];\n }\n // If sendBeacon fails, keep buffer for next flush attempt\n }\n\n /**\n * Clean up analytics listeners and intervals\n */\n cleanupAnalytics(): void {\n if (!this.analyticsEnabled) return;\n\n this.analyticsEnabled = false;\n\n // Clear flush interval\n if (this.flushInterval !== null) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n // Restore original pushState / replaceState\n if (this.originalPushState) {\n history.pushState = this.originalPushState;\n this.originalPushState = null;\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState;\n this.originalReplaceState = null;\n }\n\n // Remove popstate listener\n if (this.boundPopstateHandler) {\n window.removeEventListener(\"popstate\", this.boundPopstateHandler);\n this.boundPopstateHandler = null;\n }\n\n // Remove visibility change listener\n if (this.boundVisibilityHandler) {\n document.removeEventListener(\"visibilitychange\", this.boundVisibilityHandler);\n this.boundVisibilityHandler = null;\n }\n\n // Remove beforeunload listener\n if (this.boundBeforeUnloadHandler) {\n window.removeEventListener(\"beforeunload\", this.boundBeforeUnloadHandler);\n this.boundBeforeUnloadHandler = null;\n }\n }\n}\n","import type { FiveXXOptions, ErrorPayload, User } from \"./types\";\n\nexport class FiveXX {\n private apiKey: string;\n private endpoint: string;\n private environment: string;\n private user: User | null = null;\n\n /** @internal */\n protected getApiKey(): string {\n return this.apiKey;\n }\n\n /** @internal */\n protected getEndpoint(): string {\n return this.endpoint;\n }\n\n constructor(options: FiveXXOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint.replace(/\\/$/, \"\"); // Remove trailing slash\n this.environment = options.environment || \"production\";\n }\n\n /**\n * Set the environment (e.g., \"production\", \"development\", \"staging\")\n */\n setEnvironment(env: string): void {\n this.environment = env;\n }\n\n /**\n * Set user context for errors\n */\n setUser(user: User | null): void {\n this.user = user;\n }\n\n /**\n * Capture an error and send it to the 5xx server\n */\n async captureError(\n error: Error,\n metadata?: Record<string, unknown>\n ): Promise<string | null> {\n const payload: ErrorPayload = {\n message: error.message || String(error),\n stack: error.stack,\n environment: this.environment,\n metadata: {\n ...metadata,\n ...(this.user && { user: this.user }),\n },\n };\n\n return this.send(payload);\n }\n\n /**\n * Capture a message as an error\n */\n async captureMessage(\n message: string,\n metadata?: Record<string, unknown>\n ): Promise<string | null> {\n const payload: ErrorPayload = {\n message,\n environment: this.environment,\n metadata: {\n ...metadata,\n ...(this.user && { user: this.user }),\n },\n };\n\n return this.send(payload);\n }\n\n /**\n * Send error payload to the server\n */\n private async send(payload: ErrorPayload): Promise<string | null> {\n try {\n const response = await fetch(`${this.endpoint}/api/errors`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n console.error(`5xx: Failed to send error (${response.status})`);\n return null;\n }\n\n const data = await response.json();\n return data.id;\n } catch (err) {\n console.error(\"5xx: Failed to send error\", err);\n return null;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/browser.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FiveXX
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-G4LKAXG5.mjs";
|
|
4
4
|
|
|
5
5
|
// src/node.ts
|
|
6
6
|
var FiveXXNode = class extends FiveXX {
|
|
@@ -112,4 +112,4 @@ var FiveXXNode = class extends FiveXX {
|
|
|
112
112
|
export {
|
|
113
113
|
FiveXXNode
|
|
114
114
|
};
|
|
115
|
-
//# sourceMappingURL=chunk-
|
|
115
|
+
//# sourceMappingURL=chunk-762E5RVI.mjs.map
|
|
@@ -6,6 +6,14 @@ var FiveXX = class {
|
|
|
6
6
|
this.endpoint = options.endpoint.replace(/\/$/, "");
|
|
7
7
|
this.environment = options.environment || "production";
|
|
8
8
|
}
|
|
9
|
+
/** @internal */
|
|
10
|
+
getApiKey() {
|
|
11
|
+
return this.apiKey;
|
|
12
|
+
}
|
|
13
|
+
/** @internal */
|
|
14
|
+
getEndpoint() {
|
|
15
|
+
return this.endpoint;
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
18
|
* Set the environment (e.g., "production", "development", "staging")
|
|
11
19
|
*/
|
|
@@ -76,4 +84,4 @@ var FiveXX = class {
|
|
|
76
84
|
export {
|
|
77
85
|
FiveXX
|
|
78
86
|
};
|
|
79
|
-
//# sourceMappingURL=chunk-
|
|
87
|
+
//# sourceMappingURL=chunk-G4LKAXG5.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { FiveXXOptions, ErrorPayload, User } from \"./types\";\n\nexport class FiveXX {\n private apiKey: string;\n private endpoint: string;\n private environment: string;\n private user: User | null = null;\n\n /** @internal */\n protected getApiKey(): string {\n return this.apiKey;\n }\n\n /** @internal */\n protected getEndpoint(): string {\n return this.endpoint;\n }\n\n constructor(options: FiveXXOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint.replace(/\\/$/, \"\"); // Remove trailing slash\n this.environment = options.environment || \"production\";\n }\n\n /**\n * Set the environment (e.g., \"production\", \"development\", \"staging\")\n */\n setEnvironment(env: string): void {\n this.environment = env;\n }\n\n /**\n * Set user context for errors\n */\n setUser(user: User | null): void {\n this.user = user;\n }\n\n /**\n * Capture an error and send it to the 5xx server\n */\n async captureError(\n error: Error,\n metadata?: Record<string, unknown>\n ): Promise<string | null> {\n const payload: ErrorPayload = {\n message: error.message || String(error),\n stack: error.stack,\n environment: this.environment,\n metadata: {\n ...metadata,\n ...(this.user && { user: this.user }),\n },\n };\n\n return this.send(payload);\n }\n\n /**\n * Capture a message as an error\n */\n async captureMessage(\n message: string,\n metadata?: Record<string, unknown>\n ): Promise<string | null> {\n const payload: ErrorPayload = {\n message,\n environment: this.environment,\n metadata: {\n ...metadata,\n ...(this.user && { user: this.user }),\n },\n };\n\n return this.send(payload);\n }\n\n /**\n * Send error payload to the server\n */\n private async send(payload: ErrorPayload): Promise<string | null> {\n try {\n const response = await fetch(`${this.endpoint}/api/errors`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n console.error(`5xx: Failed to send error (${response.status})`);\n return null;\n }\n\n const data = await response.json();\n return data.id;\n } catch (err) {\n console.error(\"5xx: Failed to send error\", err);\n return null;\n }\n }\n}\n"],"mappings":";AAEO,IAAM,SAAN,MAAa;AAAA,EAgBlB,YAAY,SAAwB;AAZpC,SAAQ,OAAoB;AAa1B,SAAK,SAAS,QAAQ;AACtB,SAAK,WAAW,QAAQ,SAAS,QAAQ,OAAO,EAAE;AAClD,SAAK,cAAc,QAAQ,eAAe;AAAA,EAC5C;AAAA;AAAA,EAbU,YAAoB;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGU,cAAsB;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAWA,eAAe,KAAmB;AAChC,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,MAAyB;AAC/B,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aACJ,OACA,UACwB;AACxB,UAAM,UAAwB;AAAA,MAC5B,SAAS,MAAM,WAAW,OAAO,KAAK;AAAA,MACtC,OAAO,MAAM;AAAA,MACb,aAAa,KAAK;AAAA,MAClB,UAAU;AAAA,QACR,GAAG;AAAA,QACH,GAAI,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAK;AAAA,MACrC;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eACJ,SACA,UACwB;AACxB,UAAM,UAAwB;AAAA,MAC5B;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,UAAU;AAAA,QACR,GAAG;AAAA,QACH,GAAI,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAK;AAAA,MACrC;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,KAAK,SAA+C;AAChE,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,QAAQ,eAAe;AAAA,QAC1D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC9B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,gBAAQ,MAAM,8BAA8B,SAAS,MAAM,GAAG;AAC9D,eAAO;AAAA,MACT;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,aAAO,KAAK;AAAA,IACd,SAAS,KAAK;AACZ,cAAQ,MAAM,6BAA6B,GAAG;AAC9C,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FiveXX
|
|
3
|
+
} from "./chunk-G4LKAXG5.mjs";
|
|
4
|
+
|
|
5
|
+
// src/browser.ts
|
|
6
|
+
var FiveXXBrowser = class extends FiveXX {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super(options);
|
|
9
|
+
this.autoCapture = false;
|
|
10
|
+
this.originalOnError = null;
|
|
11
|
+
this.originalOnUnhandledRejection = null;
|
|
12
|
+
// Analytics properties
|
|
13
|
+
this.analyticsEnabled = false;
|
|
14
|
+
this.analyticsBuffer = [];
|
|
15
|
+
this.flushInterval = null;
|
|
16
|
+
this.originalPushState = null;
|
|
17
|
+
this.originalReplaceState = null;
|
|
18
|
+
this.boundPopstateHandler = null;
|
|
19
|
+
this.boundVisibilityHandler = null;
|
|
20
|
+
this.boundBeforeUnloadHandler = null;
|
|
21
|
+
this.sessionId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
22
|
+
const r = Math.random() * 16 | 0;
|
|
23
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
24
|
+
return v.toString(16);
|
|
25
|
+
});
|
|
26
|
+
if (typeof window !== "undefined" && !options.environment) {
|
|
27
|
+
const hostname = window.location.hostname;
|
|
28
|
+
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
29
|
+
this.setEnvironment("development");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Enable automatic capture of unhandled errors and promise rejections
|
|
35
|
+
*/
|
|
36
|
+
enableAutoCapture() {
|
|
37
|
+
if (this.autoCapture || typeof window === "undefined") return;
|
|
38
|
+
this.autoCapture = true;
|
|
39
|
+
this.originalOnError = window.onerror;
|
|
40
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
41
|
+
if (error) {
|
|
42
|
+
this.captureError(error, {
|
|
43
|
+
source,
|
|
44
|
+
lineno,
|
|
45
|
+
colno,
|
|
46
|
+
url: window.location.href,
|
|
47
|
+
userAgent: navigator.userAgent
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
this.captureMessage(String(message), {
|
|
51
|
+
source,
|
|
52
|
+
lineno,
|
|
53
|
+
colno,
|
|
54
|
+
url: window.location.href,
|
|
55
|
+
userAgent: navigator.userAgent
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (this.originalOnError) {
|
|
59
|
+
return this.originalOnError(message, source, lineno, colno, error);
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
63
|
+
this.originalOnUnhandledRejection = window.onunhandledrejection;
|
|
64
|
+
window.onunhandledrejection = (event) => {
|
|
65
|
+
const error = event.reason;
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
this.captureError(error, {
|
|
68
|
+
type: "unhandledRejection",
|
|
69
|
+
url: window.location.href,
|
|
70
|
+
userAgent: navigator.userAgent
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
this.captureMessage(String(error), {
|
|
74
|
+
type: "unhandledRejection",
|
|
75
|
+
url: window.location.href,
|
|
76
|
+
userAgent: navigator.userAgent
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (this.originalOnUnhandledRejection) {
|
|
80
|
+
this.originalOnUnhandledRejection(event);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Disable automatic capture
|
|
86
|
+
*/
|
|
87
|
+
disableAutoCapture() {
|
|
88
|
+
if (!this.autoCapture || typeof window === "undefined") return;
|
|
89
|
+
this.autoCapture = false;
|
|
90
|
+
window.onerror = this.originalOnError;
|
|
91
|
+
window.onunhandledrejection = this.originalOnUnhandledRejection;
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Analytics
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/**
|
|
97
|
+
* Enable opt-in analytics tracking.
|
|
98
|
+
* Tracks page views automatically, including SPA navigations.
|
|
99
|
+
*/
|
|
100
|
+
enableAnalytics() {
|
|
101
|
+
if (this.analyticsEnabled || typeof window === "undefined") return;
|
|
102
|
+
this.analyticsEnabled = true;
|
|
103
|
+
this.trackPageView();
|
|
104
|
+
this.originalPushState = history.pushState.bind(history);
|
|
105
|
+
this.originalReplaceState = history.replaceState.bind(history);
|
|
106
|
+
const self = this;
|
|
107
|
+
history.pushState = function(...args) {
|
|
108
|
+
self.originalPushState(...args);
|
|
109
|
+
self.trackPageView();
|
|
110
|
+
};
|
|
111
|
+
history.replaceState = function(...args) {
|
|
112
|
+
self.originalReplaceState(...args);
|
|
113
|
+
self.trackPageView();
|
|
114
|
+
};
|
|
115
|
+
this.boundPopstateHandler = () => this.trackPageView();
|
|
116
|
+
window.addEventListener("popstate", this.boundPopstateHandler);
|
|
117
|
+
this.flushInterval = setInterval(() => this.flushAnalytics(), 5e3);
|
|
118
|
+
this.boundVisibilityHandler = () => {
|
|
119
|
+
if (document.visibilityState === "hidden") {
|
|
120
|
+
this.flushAnalyticsBeacon();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
document.addEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
124
|
+
this.boundBeforeUnloadHandler = () => this.flushAnalyticsBeacon();
|
|
125
|
+
window.addEventListener("beforeunload", this.boundBeforeUnloadHandler);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Track a custom event (stored for future use, logs for now)
|
|
129
|
+
*/
|
|
130
|
+
trackEvent(name, metadata) {
|
|
131
|
+
if (!this.analyticsEnabled) return;
|
|
132
|
+
console.debug("[5xx] trackEvent:", name, metadata);
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Private analytics helpers
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
/**
|
|
138
|
+
* Capture a page view and add it to the buffer
|
|
139
|
+
*/
|
|
140
|
+
trackPageView() {
|
|
141
|
+
if (typeof window === "undefined") return;
|
|
142
|
+
const url = new URL(window.location.href);
|
|
143
|
+
const beacon = {
|
|
144
|
+
path: url.pathname,
|
|
145
|
+
referrer: document.referrer || void 0,
|
|
146
|
+
screenWidth: window.innerWidth
|
|
147
|
+
};
|
|
148
|
+
const utmSource = url.searchParams.get("utm_source");
|
|
149
|
+
const utmMedium = url.searchParams.get("utm_medium");
|
|
150
|
+
const utmCampaign = url.searchParams.get("utm_campaign");
|
|
151
|
+
if (utmSource) beacon.utmSource = utmSource;
|
|
152
|
+
if (utmMedium) beacon.utmMedium = utmMedium;
|
|
153
|
+
if (utmCampaign) beacon.utmCampaign = utmCampaign;
|
|
154
|
+
this.analyticsBuffer.push(beacon);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Flush the analytics buffer by POSTing to the server
|
|
158
|
+
*/
|
|
159
|
+
async flushAnalytics() {
|
|
160
|
+
if (this.analyticsBuffer.length === 0) return;
|
|
161
|
+
const payload = {
|
|
162
|
+
events: [...this.analyticsBuffer],
|
|
163
|
+
sessionId: this.sessionId
|
|
164
|
+
};
|
|
165
|
+
const snapshot = this.analyticsBuffer;
|
|
166
|
+
this.analyticsBuffer = [];
|
|
167
|
+
try {
|
|
168
|
+
const response = await fetch(`${this.getEndpoint()}/api/analytics`, {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: {
|
|
171
|
+
"Content-Type": "application/json",
|
|
172
|
+
"X-API-Key": this.getApiKey()
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify(payload)
|
|
175
|
+
});
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Flush analytics using navigator.sendBeacon (for beforeunload / visibilitychange)
|
|
185
|
+
*/
|
|
186
|
+
flushAnalyticsBeacon() {
|
|
187
|
+
if (this.analyticsBuffer.length === 0) return;
|
|
188
|
+
const payload = {
|
|
189
|
+
events: [...this.analyticsBuffer],
|
|
190
|
+
sessionId: this.sessionId
|
|
191
|
+
};
|
|
192
|
+
const blob = new Blob([JSON.stringify(payload)], {
|
|
193
|
+
type: "application/json"
|
|
194
|
+
});
|
|
195
|
+
const sent = navigator.sendBeacon(`${this.getEndpoint()}/api/analytics`, blob);
|
|
196
|
+
if (sent) {
|
|
197
|
+
this.analyticsBuffer = [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Clean up analytics listeners and intervals
|
|
202
|
+
*/
|
|
203
|
+
cleanupAnalytics() {
|
|
204
|
+
if (!this.analyticsEnabled) return;
|
|
205
|
+
this.analyticsEnabled = false;
|
|
206
|
+
if (this.flushInterval !== null) {
|
|
207
|
+
clearInterval(this.flushInterval);
|
|
208
|
+
this.flushInterval = null;
|
|
209
|
+
}
|
|
210
|
+
if (this.originalPushState) {
|
|
211
|
+
history.pushState = this.originalPushState;
|
|
212
|
+
this.originalPushState = null;
|
|
213
|
+
}
|
|
214
|
+
if (this.originalReplaceState) {
|
|
215
|
+
history.replaceState = this.originalReplaceState;
|
|
216
|
+
this.originalReplaceState = null;
|
|
217
|
+
}
|
|
218
|
+
if (this.boundPopstateHandler) {
|
|
219
|
+
window.removeEventListener("popstate", this.boundPopstateHandler);
|
|
220
|
+
this.boundPopstateHandler = null;
|
|
221
|
+
}
|
|
222
|
+
if (this.boundVisibilityHandler) {
|
|
223
|
+
document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
|
|
224
|
+
this.boundVisibilityHandler = null;
|
|
225
|
+
}
|
|
226
|
+
if (this.boundBeforeUnloadHandler) {
|
|
227
|
+
window.removeEventListener("beforeunload", this.boundBeforeUnloadHandler);
|
|
228
|
+
this.boundBeforeUnloadHandler = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
FiveXXBrowser
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=chunk-IAF5CKNL.mjs.map
|