5xx-sdk 1.0.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.
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/browser.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"],"mappings":";;;;;AAgBO,IAAM,gBAAN,cAA4B,OAAO;AAAA,EAgBxC,YAAY,SAAwB;AAClC,UAAM,OAAO;AAhBf,SAAQ,cAAc;AACtB,SAAQ,kBAA8C;AACtD,SAAQ,+BAAgF;AAGxF;AAAA,SAAQ,mBAAmB;AAE3B,SAAQ,kBAAqC,CAAC;AAC9C,SAAQ,gBAAuD;AAC/D,SAAQ,oBAAqD;AAC7D,SAAQ,uBAA2D;AACnE,SAAQ,uBAA4C;AACpD,SAAQ,yBAA8C;AACtD,SAAQ,2BAAgD;AAMtD,SAAK,YACH,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,aAC1D,OAAO,WAAW,IAClB,uCAAuC,QAAQ,SAAS,CAAC,MAAM;AAC7D,YAAM,IAAK,KAAK,OAAO,IAAI,KAAM;AACjC,YAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAO;AACtC,aAAO,EAAE,SAAS,EAAE;AAAA,IACtB,CAAC;AAGP,QAAI,OAAO,WAAW,eAAe,CAAC,QAAQ,aAAa;AACzD,YAAM,WAAW,OAAO,SAAS;AACjC,UAAI,aAAa,eAAe,aAAa,aAAa;AACxD,aAAK,eAAe,aAAa;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACxB,QAAI,KAAK,eAAe,OAAO,WAAW,YAAa;AAEvD,SAAK,cAAc;AAGnB,SAAK,kBAAkB,OAAO;AAC9B,WAAO,UAAU,CAAC,SAAS,QAAQ,QAAQ,OAAO,UAAU;AAC1D,UAAI,OAAO;AACT,aAAK,aAAa,OAAO;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,OAAO,SAAS;AAAA,UACrB,WAAW,UAAU;AAAA,QACvB,CAAC;AAAA,MACH,OAAO;AACL,aAAK,eAAe,OAAO,OAAO,GAAG;AAAA,UACnC;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,OAAO,SAAS;AAAA,UACrB,WAAW,UAAU;AAAA,QACvB,CAAC;AAAA,MACH;AAGA,UAAI,KAAK,iBAAiB;AACxB,eAAO,KAAK,gBAAgB,SAAS,QAAQ,QAAQ,OAAO,KAAK;AAAA,MACnE;AACA,aAAO;AAAA,IACT;AAGA,SAAK,+BAA+B,OAAO;AAC3C,WAAO,uBAAuB,CAAC,UAAU;AACvC,YAAM,QAAQ,MAAM;AACpB,UAAI,iBAAiB,OAAO;AAC1B,aAAK,aAAa,OAAO;AAAA,UACvB,MAAM;AAAA,UACN,KAAK,OAAO,SAAS;AAAA,UACrB,WAAW,UAAU;AAAA,QACvB,CAAC;AAAA,MACH,OAAO;AACL,aAAK,eAAe,OAAO,KAAK,GAAG;AAAA,UACjC,MAAM;AAAA,UACN,KAAK,OAAO,SAAS;AAAA,UACrB,WAAW,UAAU;AAAA,QACvB,CAAC;AAAA,MACH;AAEA,UAAI,KAAK,8BAA8B;AACrC,aAAK,6BAA6B,KAAK;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA2B;AACzB,QAAI,CAAC,KAAK,eAAe,OAAO,WAAW,YAAa;AAExD,SAAK,cAAc;AACnB,WAAO,UAAU,KAAK;AACtB,WAAO,uBAAuB,KAAK;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAAwB;AACtB,QAAI,KAAK,oBAAoB,OAAO,WAAW,YAAa;AAE5D,SAAK,mBAAmB;AAGxB,SAAK,cAAc;AAGnB,SAAK,oBAAoB,QAAQ,UAAU,KAAK,OAAO;AACvD,SAAK,uBAAuB,QAAQ,aAAa,KAAK,OAAO;AAE7D,UAAM,OAAO;AAEb,YAAQ,YAAY,YAAa,MAA4C;AAC3E,WAAK,kBAAmB,GAAG,IAAI;AAC/B,WAAK,cAAc;AAAA,IACrB;AAEA,YAAQ,eAAe,YAAa,MAA+C;AACjF,WAAK,qBAAsB,GAAG,IAAI;AAClC,WAAK,cAAc;AAAA,IACrB;AAGA,SAAK,uBAAuB,MAAM,KAAK,cAAc;AACrD,WAAO,iBAAiB,YAAY,KAAK,oBAAoB;AAG7D,SAAK,gBAAgB,YAAY,MAAM,KAAK,eAAe,GAAG,GAAI;AAGlE,SAAK,yBAAyB,MAAM;AAClC,UAAI,SAAS,oBAAoB,UAAU;AACzC,aAAK,qBAAqB;AAAA,MAC5B;AAAA,IACF;AACA,aAAS,iBAAiB,oBAAoB,KAAK,sBAAsB;AAEzE,SAAK,2BAA2B,MAAM,KAAK,qBAAqB;AAChE,WAAO,iBAAiB,gBAAgB,KAAK,wBAAwB;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAc,UAA0C;AACjE,QAAI,CAAC,KAAK,iBAAkB;AAC5B,YAAQ,MAAM,qBAAqB,MAAM,QAAQ;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,gBAAsB;AAC5B,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AAExC,UAAM,SAA0B;AAAA,MAC9B,MAAM,IAAI;AAAA,MACV,UAAU,SAAS,YAAY;AAAA,MAC/B,aAAa,OAAO;AAAA,IACtB;AAGA,UAAM,YAAY,IAAI,aAAa,IAAI,YAAY;AACnD,UAAM,YAAY,IAAI,aAAa,IAAI,YAAY;AACnD,UAAM,cAAc,IAAI,aAAa,IAAI,cAAc;AAEvD,QAAI,UAAW,QAAO,YAAY;AAClC,QAAI,UAAW,QAAO,YAAY;AAClC,QAAI,YAAa,QAAO,cAAc;AAEtC,SAAK,gBAAgB,KAAK,MAAM;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAgC;AAC5C,QAAI,KAAK,gBAAgB,WAAW,EAAG;AAEvC,UAAM,UAAiC;AAAA,MACrC,QAAQ,CAAC,GAAG,KAAK,eAAe;AAAA,MAChC,WAAW,KAAK;AAAA,IAClB;AAGA,UAAM,WAAW,KAAK;AACtB,SAAK,kBAAkB,CAAC;AAExB,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,YAAY,CAAC,kBAAkB;AAAA,QAClE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK,UAAU;AAAA,QAC9B;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC9B,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAEhB,aAAK,kBAAkB,SAAS,OAAO,KAAK,eAAe;AAAA,MAC7D;AAAA,IACF,QAAQ;AAEN,WAAK,kBAAkB,SAAS,OAAO,KAAK,eAAe;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAA6B;AACnC,QAAI,KAAK,gBAAgB,WAAW,EAAG;AAEvC,UAAM,UAAiC;AAAA,MACrC,QAAQ,CAAC,GAAG,KAAK,eAAe;AAAA,MAChC,WAAW,KAAK;AAAA,IAClB;AAEA,UAAM,OAAO,IAAI,KAAK,CAAC,KAAK,UAAU,OAAO,CAAC,GAAG;AAAA,MAC/C,MAAM;AAAA,IACR,CAAC;AAED,UAAM,OAAO,UAAU,WAAW,GAAG,KAAK,YAAY,CAAC,kBAAkB,IAAI;AAE7E,QAAI,MAAM;AACR,WAAK,kBAAkB,CAAC;AAAA,IAC1B;AAAA,EAEF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAyB;AACvB,QAAI,CAAC,KAAK,iBAAkB;AAE5B,SAAK,mBAAmB;AAGxB,QAAI,KAAK,kBAAkB,MAAM;AAC/B,oBAAc,KAAK,aAAa;AAChC,WAAK,gBAAgB;AAAA,IACvB;AAGA,QAAI,KAAK,mBAAmB;AAC1B,cAAQ,YAAY,KAAK;AACzB,WAAK,oBAAoB;AAAA,IAC3B;AACA,QAAI,KAAK,sBAAsB;AAC7B,cAAQ,eAAe,KAAK;AAC5B,WAAK,uBAAuB;AAAA,IAC9B;AAGA,QAAI,KAAK,sBAAsB;AAC7B,aAAO,oBAAoB,YAAY,KAAK,oBAAoB;AAChE,WAAK,uBAAuB;AAAA,IAC9B;AAGA,QAAI,KAAK,wBAAwB;AAC/B,eAAS,oBAAoB,oBAAoB,KAAK,sBAAsB;AAC5E,WAAK,yBAAyB;AAAA,IAChC;AAGA,QAAI,KAAK,0BAA0B;AACjC,aAAO,oBAAoB,gBAAgB,KAAK,wBAAwB;AACxE,WAAK,2BAA2B;AAAA,IAClC;AAAA,EACF;AACF;","names":[]}
@@ -14,12 +14,28 @@ interface User {
14
14
  email?: string;
15
15
  name?: string;
16
16
  }
17
+ interface AnalyticsBeacon {
18
+ path: string;
19
+ referrer?: string;
20
+ utmSource?: string;
21
+ utmMedium?: string;
22
+ utmCampaign?: string;
23
+ screenWidth?: number;
24
+ }
25
+ interface AnalyticsBatchPayload {
26
+ events: AnalyticsBeacon[];
27
+ sessionId?: string;
28
+ }
17
29
 
18
30
  declare class FiveXX {
19
31
  private apiKey;
20
32
  private endpoint;
21
33
  private environment;
22
34
  private user;
35
+ /** @internal */
36
+ protected getApiKey(): string;
37
+ /** @internal */
38
+ protected getEndpoint(): string;
23
39
  constructor(options: FiveXXOptions);
24
40
  /**
25
41
  * Set the environment (e.g., "production", "development", "staging")
@@ -43,4 +59,4 @@ declare class FiveXX {
43
59
  private send;
44
60
  }
45
61
 
46
- export { type ErrorPayload as E, FiveXX as F, type User as U, type FiveXXOptions as a };
62
+ export { type AnalyticsBatchPayload as A, type ErrorPayload as E, FiveXX as F, type User as U, type AnalyticsBeacon as a, type FiveXXOptions as b };
@@ -14,12 +14,28 @@ interface User {
14
14
  email?: string;
15
15
  name?: string;
16
16
  }
17
+ interface AnalyticsBeacon {
18
+ path: string;
19
+ referrer?: string;
20
+ utmSource?: string;
21
+ utmMedium?: string;
22
+ utmCampaign?: string;
23
+ screenWidth?: number;
24
+ }
25
+ interface AnalyticsBatchPayload {
26
+ events: AnalyticsBeacon[];
27
+ sessionId?: string;
28
+ }
17
29
 
18
30
  declare class FiveXX {
19
31
  private apiKey;
20
32
  private endpoint;
21
33
  private environment;
22
34
  private user;
35
+ /** @internal */
36
+ protected getApiKey(): string;
37
+ /** @internal */
38
+ protected getEndpoint(): string;
23
39
  constructor(options: FiveXXOptions);
24
40
  /**
25
41
  * Set the environment (e.g., "production", "development", "staging")
@@ -43,4 +59,4 @@ declare class FiveXX {
43
59
  private send;
44
60
  }
45
61
 
46
- export { type ErrorPayload as E, FiveXX as F, type User as U, type FiveXXOptions as a };
62
+ export { type AnalyticsBatchPayload as A, type ErrorPayload as E, FiveXX as F, type User as U, type AnalyticsBeacon as a, type FiveXXOptions as b };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- export { E as ErrorPayload, F as FiveXX, a as FiveXXOptions, U as User } from './client-BhKj28Zi.mjs';
1
+ export { A as AnalyticsBatchPayload, a as AnalyticsBeacon, E as ErrorPayload, F as FiveXX, b as FiveXXOptions, U as User } from './client-t5SsLKrz.mjs';
2
2
  export { FiveXXBrowser } from './browser.mjs';
3
3
  export { FiveXXNode } from './node.mjs';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { E as ErrorPayload, F as FiveXX, a as FiveXXOptions, U as User } from './client-BhKj28Zi.js';
1
+ export { A as AnalyticsBatchPayload, a as AnalyticsBeacon, E as ErrorPayload, F as FiveXX, b as FiveXXOptions, U as User } from './client-t5SsLKrz.js';
2
2
  export { FiveXXBrowser } from './browser.js';
3
3
  export { FiveXXNode } from './node.js';
package/dist/index.js CHANGED
@@ -34,6 +34,14 @@ var FiveXX = class {
34
34
  this.endpoint = options.endpoint.replace(/\/$/, "");
35
35
  this.environment = options.environment || "production";
36
36
  }
37
+ /** @internal */
38
+ getApiKey() {
39
+ return this.apiKey;
40
+ }
41
+ /** @internal */
42
+ getEndpoint() {
43
+ return this.endpoint;
44
+ }
37
45
  /**
38
46
  * Set the environment (e.g., "production", "development", "staging")
39
47
  */
@@ -108,6 +116,20 @@ var FiveXXBrowser = class extends FiveXX {
108
116
  this.autoCapture = false;
109
117
  this.originalOnError = null;
110
118
  this.originalOnUnhandledRejection = null;
119
+ // Analytics properties
120
+ this.analyticsEnabled = false;
121
+ this.analyticsBuffer = [];
122
+ this.flushInterval = null;
123
+ this.originalPushState = null;
124
+ this.originalReplaceState = null;
125
+ this.boundPopstateHandler = null;
126
+ this.boundVisibilityHandler = null;
127
+ this.boundBeforeUnloadHandler = null;
128
+ this.sessionId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
129
+ const r = Math.random() * 16 | 0;
130
+ const v = c === "x" ? r : r & 3 | 8;
131
+ return v.toString(16);
132
+ });
111
133
  if (typeof window !== "undefined" && !options.environment) {
112
134
  const hostname = window.location.hostname;
113
135
  if (hostname === "localhost" || hostname === "127.0.0.1") {
@@ -175,6 +197,144 @@ var FiveXXBrowser = class extends FiveXX {
175
197
  window.onerror = this.originalOnError;
176
198
  window.onunhandledrejection = this.originalOnUnhandledRejection;
177
199
  }
200
+ // ---------------------------------------------------------------------------
201
+ // Analytics
202
+ // ---------------------------------------------------------------------------
203
+ /**
204
+ * Enable opt-in analytics tracking.
205
+ * Tracks page views automatically, including SPA navigations.
206
+ */
207
+ enableAnalytics() {
208
+ if (this.analyticsEnabled || typeof window === "undefined") return;
209
+ this.analyticsEnabled = true;
210
+ this.trackPageView();
211
+ this.originalPushState = history.pushState.bind(history);
212
+ this.originalReplaceState = history.replaceState.bind(history);
213
+ const self = this;
214
+ history.pushState = function(...args) {
215
+ self.originalPushState(...args);
216
+ self.trackPageView();
217
+ };
218
+ history.replaceState = function(...args) {
219
+ self.originalReplaceState(...args);
220
+ self.trackPageView();
221
+ };
222
+ this.boundPopstateHandler = () => this.trackPageView();
223
+ window.addEventListener("popstate", this.boundPopstateHandler);
224
+ this.flushInterval = setInterval(() => this.flushAnalytics(), 5e3);
225
+ this.boundVisibilityHandler = () => {
226
+ if (document.visibilityState === "hidden") {
227
+ this.flushAnalyticsBeacon();
228
+ }
229
+ };
230
+ document.addEventListener("visibilitychange", this.boundVisibilityHandler);
231
+ this.boundBeforeUnloadHandler = () => this.flushAnalyticsBeacon();
232
+ window.addEventListener("beforeunload", this.boundBeforeUnloadHandler);
233
+ }
234
+ /**
235
+ * Track a custom event (stored for future use, logs for now)
236
+ */
237
+ trackEvent(name, metadata) {
238
+ if (!this.analyticsEnabled) return;
239
+ console.debug("[5xx] trackEvent:", name, metadata);
240
+ }
241
+ // ---------------------------------------------------------------------------
242
+ // Private analytics helpers
243
+ // ---------------------------------------------------------------------------
244
+ /**
245
+ * Capture a page view and add it to the buffer
246
+ */
247
+ trackPageView() {
248
+ if (typeof window === "undefined") return;
249
+ const url = new URL(window.location.href);
250
+ const beacon = {
251
+ path: url.pathname,
252
+ referrer: document.referrer || void 0,
253
+ screenWidth: window.innerWidth
254
+ };
255
+ const utmSource = url.searchParams.get("utm_source");
256
+ const utmMedium = url.searchParams.get("utm_medium");
257
+ const utmCampaign = url.searchParams.get("utm_campaign");
258
+ if (utmSource) beacon.utmSource = utmSource;
259
+ if (utmMedium) beacon.utmMedium = utmMedium;
260
+ if (utmCampaign) beacon.utmCampaign = utmCampaign;
261
+ this.analyticsBuffer.push(beacon);
262
+ }
263
+ /**
264
+ * Flush the analytics buffer by POSTing to the server
265
+ */
266
+ async flushAnalytics() {
267
+ if (this.analyticsBuffer.length === 0) return;
268
+ const payload = {
269
+ events: [...this.analyticsBuffer],
270
+ sessionId: this.sessionId
271
+ };
272
+ const snapshot = this.analyticsBuffer;
273
+ this.analyticsBuffer = [];
274
+ try {
275
+ const response = await fetch(`${this.getEndpoint()}/api/analytics`, {
276
+ method: "POST",
277
+ headers: {
278
+ "Content-Type": "application/json",
279
+ "X-API-Key": this.getApiKey()
280
+ },
281
+ body: JSON.stringify(payload)
282
+ });
283
+ if (!response.ok) {
284
+ this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);
285
+ }
286
+ } catch {
287
+ this.analyticsBuffer = snapshot.concat(this.analyticsBuffer);
288
+ }
289
+ }
290
+ /**
291
+ * Flush analytics using navigator.sendBeacon (for beforeunload / visibilitychange)
292
+ */
293
+ flushAnalyticsBeacon() {
294
+ if (this.analyticsBuffer.length === 0) return;
295
+ const payload = {
296
+ events: [...this.analyticsBuffer],
297
+ sessionId: this.sessionId
298
+ };
299
+ const blob = new Blob([JSON.stringify(payload)], {
300
+ type: "application/json"
301
+ });
302
+ const sent = navigator.sendBeacon(`${this.getEndpoint()}/api/analytics`, blob);
303
+ if (sent) {
304
+ this.analyticsBuffer = [];
305
+ }
306
+ }
307
+ /**
308
+ * Clean up analytics listeners and intervals
309
+ */
310
+ cleanupAnalytics() {
311
+ if (!this.analyticsEnabled) return;
312
+ this.analyticsEnabled = false;
313
+ if (this.flushInterval !== null) {
314
+ clearInterval(this.flushInterval);
315
+ this.flushInterval = null;
316
+ }
317
+ if (this.originalPushState) {
318
+ history.pushState = this.originalPushState;
319
+ this.originalPushState = null;
320
+ }
321
+ if (this.originalReplaceState) {
322
+ history.replaceState = this.originalReplaceState;
323
+ this.originalReplaceState = null;
324
+ }
325
+ if (this.boundPopstateHandler) {
326
+ window.removeEventListener("popstate", this.boundPopstateHandler);
327
+ this.boundPopstateHandler = null;
328
+ }
329
+ if (this.boundVisibilityHandler) {
330
+ document.removeEventListener("visibilitychange", this.boundVisibilityHandler);
331
+ this.boundVisibilityHandler = null;
332
+ }
333
+ if (this.boundBeforeUnloadHandler) {
334
+ window.removeEventListener("beforeunload", this.boundBeforeUnloadHandler);
335
+ this.boundBeforeUnloadHandler = null;
336
+ }
337
+ }
178
338
  };
179
339
 
180
340
  // src/node.ts
@@ -239,6 +399,49 @@ var FiveXXNode = class extends FiveXX {
239
399
  }
240
400
  });
241
401
  }
402
+ /**
403
+ * Wrap an HTTP route handler to automatically capture errors.
404
+ * Works with Next.js, Express, Fastify, and any framework.
405
+ * Captures the error with request metadata, then re-throws
406
+ * so the framework's own error handling still works.
407
+ *
408
+ * Usage (Next.js):
409
+ * export const GET = fivexx.wrapHandler(async (req) => {
410
+ * return NextResponse.json(data);
411
+ * });
412
+ *
413
+ * Usage (Express):
414
+ * app.get('/api', fivexx.wrapHandler(async (req, res) => {
415
+ * res.json(data);
416
+ * }));
417
+ */
418
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
419
+ wrapHandler(fn) {
420
+ const self = this;
421
+ return (async function(...args) {
422
+ try {
423
+ return await fn(...args);
424
+ } catch (error) {
425
+ const metadata = {
426
+ type: "handlerError",
427
+ nodeVersion: process.version,
428
+ platform: process.platform,
429
+ pid: process.pid
430
+ };
431
+ const req = args[0];
432
+ if (req && typeof req === "object") {
433
+ if ("url" in req) metadata.url = String(req.url);
434
+ if ("method" in req) metadata.method = String(req.method);
435
+ }
436
+ if (error instanceof Error) {
437
+ await self.captureError(error, metadata);
438
+ } else {
439
+ await self.captureMessage(String(error), metadata);
440
+ }
441
+ throw error;
442
+ }
443
+ });
444
+ }
242
445
  };
243
446
  // Annotate the CommonJS export names for ESM import in node:
244
447
  0 && (module.exports = {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/browser.ts","../src/node.ts"],"sourcesContent":["export { FiveXX } from \"./client\";\nexport { FiveXXBrowser } from \"./browser\";\nexport { FiveXXNode } from \"./node\";\nexport type { FiveXXOptions, ErrorPayload, User } from \"./types\";\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","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 { FiveXX } from \"./client\";\nimport type { FiveXXOptions } from \"./types\";\n\ninterface NodeOptions extends FiveXXOptions {\n exitOnFatalError?: boolean;\n}\n\n/**\n * Node.js-specific FiveXX client with automatic error capture\n */\nexport class FiveXXNode extends FiveXX {\n private autoCapture = false;\n private exitOnFatalError: boolean;\n\n constructor(options: NodeOptions) {\n super(options);\n this.exitOnFatalError = options.exitOnFatalError ?? true;\n\n // Auto-detect environment from NODE_ENV\n if (!options.environment && process.env.NODE_ENV) {\n this.setEnvironment(process.env.NODE_ENV);\n }\n }\n\n /**\n * Enable automatic capture of uncaught exceptions and unhandled rejections\n */\n enableAutoCapture(): void {\n if (this.autoCapture) return;\n\n this.autoCapture = true;\n\n // Capture uncaught exceptions\n process.on(\"uncaughtException\", async (error) => {\n console.error(\"5xx: Uncaught exception:\", error);\n await this.captureError(error, {\n type: \"uncaughtException\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n });\n\n if (this.exitOnFatalError) {\n process.exit(1);\n }\n });\n\n // Capture unhandled promise rejections\n process.on(\"unhandledRejection\", async (reason) => {\n console.error(\"5xx: Unhandled rejection:\", reason);\n if (reason instanceof Error) {\n await this.captureError(reason, {\n type: \"unhandledRejection\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n });\n } else {\n await this.captureMessage(String(reason), {\n type: \"unhandledRejection\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n });\n }\n });\n }\n\n /**\n * Wrap an async function to capture errors\n */\n wrapAsync<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n metadata?: Record<string, unknown>\n ): T {\n return (async (...args: Parameters<T>) => {\n try {\n return await fn(...args);\n } catch (error) {\n if (error instanceof Error) {\n await this.captureError(error, metadata);\n }\n throw error;\n }\n }) as T;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/browser.ts","../src/node.ts"],"sourcesContent":["export { FiveXX } from \"./client\";\nexport { FiveXXBrowser } from \"./browser\";\nexport { FiveXXNode } from \"./node\";\nexport type {\n FiveXXOptions,\n ErrorPayload,\n User,\n AnalyticsBeacon,\n AnalyticsBatchPayload,\n} from \"./types\";\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","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 { FiveXX } from \"./client\";\nimport type { FiveXXOptions } from \"./types\";\n\ninterface NodeOptions extends FiveXXOptions {\n exitOnFatalError?: boolean;\n}\n\n/**\n * Node.js-specific FiveXX client with automatic error capture\n */\nexport class FiveXXNode extends FiveXX {\n private autoCapture = false;\n private exitOnFatalError: boolean;\n\n constructor(options: NodeOptions) {\n super(options);\n this.exitOnFatalError = options.exitOnFatalError ?? true;\n\n // Auto-detect environment from NODE_ENV\n if (!options.environment && process.env.NODE_ENV) {\n this.setEnvironment(process.env.NODE_ENV);\n }\n }\n\n /**\n * Enable automatic capture of uncaught exceptions and unhandled rejections\n */\n enableAutoCapture(): void {\n if (this.autoCapture) return;\n\n this.autoCapture = true;\n\n // Capture uncaught exceptions\n process.on(\"uncaughtException\", async (error) => {\n console.error(\"5xx: Uncaught exception:\", error);\n await this.captureError(error, {\n type: \"uncaughtException\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n });\n\n if (this.exitOnFatalError) {\n process.exit(1);\n }\n });\n\n // Capture unhandled promise rejections\n process.on(\"unhandledRejection\", async (reason) => {\n console.error(\"5xx: Unhandled rejection:\", reason);\n if (reason instanceof Error) {\n await this.captureError(reason, {\n type: \"unhandledRejection\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n });\n } else {\n await this.captureMessage(String(reason), {\n type: \"unhandledRejection\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n });\n }\n });\n }\n\n /**\n * Wrap an async function to capture errors\n */\n wrapAsync<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n metadata?: Record<string, unknown>\n ): T {\n return (async (...args: Parameters<T>) => {\n try {\n return await fn(...args);\n } catch (error) {\n if (error instanceof Error) {\n await this.captureError(error, metadata);\n }\n throw error;\n }\n }) as T;\n }\n\n /**\n * Wrap an HTTP route handler to automatically capture errors.\n * Works with Next.js, Express, Fastify, and any framework.\n * Captures the error with request metadata, then re-throws\n * so the framework's own error handling still works.\n *\n * Usage (Next.js):\n * export const GET = fivexx.wrapHandler(async (req) => {\n * return NextResponse.json(data);\n * });\n *\n * Usage (Express):\n * app.get('/api', fivexx.wrapHandler(async (req, res) => {\n * res.json(data);\n * }));\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wrapHandler<T extends (...args: any[]) => Promise<any>>(fn: T): T {\n const self = this;\n return (async function (...args: Parameters<T>) {\n try {\n return await fn(...args);\n } catch (error) {\n const metadata: Record<string, unknown> = {\n type: \"handlerError\",\n nodeVersion: process.version,\n platform: process.platform,\n pid: process.pid,\n };\n\n // Extract request info from first argument (works with Next.js Request, Express req, etc.)\n const req = args[0];\n if (req && typeof req === \"object\") {\n if (\"url\" in req) metadata.url = String(req.url);\n if (\"method\" in req) metadata.method = String(req.method);\n }\n\n if (error instanceof Error) {\n await self.captureError(error, metadata);\n } else {\n await self.captureMessage(String(error), metadata);\n }\n throw error;\n }\n }) as T;\n }\n}\n"],"mappings":"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","names":[]}