reportli 1.0.3 → 1.0.4

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/src/index.ts CHANGED
@@ -1,425 +1,794 @@
1
- // src/index.ts — Reportli SDK v1.0.3
1
+ /**
2
+ * Reportli SDK
3
+ * Enterprise-grade real-time exception tracking and AI diagnostic companion.
4
+ */
2
5
 
3
- const ENDPOINT =
4
- "https://fahikyfmgdyzejdfftox.supabase.co/functions/v1/rapid-processor";
5
-
6
- // ─── Types ────────────────────────────────────────────────────────────────────
7
-
8
- type Config = {
6
+ export interface ReportliConfig {
9
7
  apiKey: string;
8
+ projectId?: string;
9
+ projectName?: string;
10
10
  environment?: string;
11
- };
11
+ framework?: string;
12
+ disableHmrLogging?: boolean;
13
+ captureUnhandledRejections?: boolean;
14
+ autoCreateGitHubIssues?: boolean;
15
+ userEmail?: string;
16
+ endpoint?: string;
17
+ }
12
18
 
13
- type QueueItem = Record<string, unknown>;
19
+ export interface ExceptionPayload {
20
+ apiKey: string;
21
+ projectId?: string;
22
+ projectName?: string;
23
+ errorType: string;
24
+ errorMessage: string;
25
+ errorStack: string;
26
+ framework?: string;
27
+ user_email?: string;
28
+ severity?: "low" | "medium" | "high" | "critical";
29
+ status?: string;
30
+ }
14
31
 
15
- // ─── State ────────────────────────────────────────────────────────────────────
32
+ const isBrowser = typeof window !== "undefined";
16
33
 
17
- let initialized = false;
18
- let _config: Config;
19
- const queue: QueueItem[] = [];
20
- let flushTimer: ReturnType<typeof setTimeout> | null = null;
21
- let isFlushing = false;
34
+ const REPORTLI_BASE_URL = "https://ais-dev-ohcojup63zslqou25sisle-541405064838.asia-east1.run.app";
22
35
 
23
- // ─── Queue & Batch Send ───────────────────────────────────────────────────────
36
+ class ReportliTracker {
37
+ private config: ReportliConfig | null = null;
38
+ private isInitialized = false;
24
39
 
25
- function scheduleFlush() {
26
- if (flushTimer) return;
27
- flushTimer = setTimeout(() => {
28
- flushTimer = null;
29
- flush();
30
- }, 2000); // batch every 2 seconds
31
- }
40
+ /**
41
+ * Initializes the Reportli tracker with your secure project credentials.
42
+ */
43
+ public init(config: ReportliConfig): void {
44
+ if (!config || !config.apiKey) {
45
+ console.error("[Reportli SDK] Initialization failed: 'apiKey' is required.");
46
+ return;
47
+ }
32
48
 
33
- async function flush() {
34
- if (isFlushing || queue.length === 0) return;
35
- isFlushing = true;
49
+ if (this.isInitialized) {
50
+ return; // Already active in thread
51
+ }
52
+
53
+ this.config = {
54
+ environment: "production",
55
+ captureUnhandledRejections: true,
56
+ autoCreateGitHubIssues: true,
57
+ ...config,
58
+ };
59
+ this.isInitialized = true;
36
60
 
37
- const batch = queue.splice(0, 10); // send max 10 at a time
61
+ const isBrowser = typeof window !== "undefined";
62
+ const isNode = typeof process !== "undefined" && process.versions && process.versions.node;
38
63
 
39
- for (const payload of batch) {
40
- await sendNow(payload);
64
+ // A static flag to prevent multiple registration sends per runtime instance
65
+ if ((ReportliTracker as any)._registrationSent) {
66
+ return;
67
+ }
68
+
69
+ // 1. Send the installation/setup success message on initial setup block
70
+ this.sendRegistrationWebhook(isBrowser);
71
+ (ReportliTracker as any)._registrationSent = true;
72
+
73
+ // 2. Setup standard listeners
74
+ if (isBrowser) {
75
+ this.setupBrowserListeners();
76
+ } else if (isNode) {
77
+ this.setupNodeListeners();
78
+ }
79
+
80
+ console.log("[Reportli SDK] Exception listener successfully initialized.");
41
81
  }
42
82
 
43
- isFlushing = false;
83
+ /**
84
+ * Manually captures and files a custom application exception with custom severity.
85
+ */
86
+ public captureException(error: Error | any, severityInput?: "low" | "medium" | "high" | "critical"): void {
87
+ if (!this.isInitialized || !this.config) {
88
+ console.warn("[Reportli SDK] Capture failed: SDK is not initialized yet.");
89
+ return;
90
+ }
44
91
 
45
- if (queue.length > 0) flush(); // flush remaining
46
- }
92
+ const errorObject = this.normalizeError(error);
93
+ const { errorType, severity } = this.classifyError(errorObject);
94
+
95
+ const payload: ExceptionPayload = {
96
+ apiKey: this.config.apiKey,
97
+ projectId: this.config.projectId || "proj-sandbox",
98
+ projectName: this.config.projectName || "SaaS App",
99
+ errorType: errorType,
100
+ errorMessage: errorObject.message || "Manual trace reported",
101
+ errorStack: errorObject.stack || "",
102
+ framework: this.config.framework || (typeof window !== "undefined" ? "React/Next.js Client" : "Node.js Server"),
103
+ user_email: this.config.userEmail || "anonymous",
104
+ severity: severityInput || severity,
105
+ status: "open"
106
+ };
107
+
108
+ this.sendCrashReport(payload);
109
+ }
110
+
111
+ /**
112
+ * Express error handler middleware
113
+ */
114
+ public expressErrorHandler = (err: any, req: any, res: any, next: any): void => {
115
+ if (this.isInitialized && this.config) {
116
+ try {
117
+ const errorObject = this.normalizeError(err);
118
+ const { errorType, severity } = this.classifyError(errorObject, "express");
119
+
120
+ const payload: ExceptionPayload = {
121
+ apiKey: this.config.apiKey,
122
+ projectId: this.config.projectId || "express-server",
123
+ projectName: this.config.projectName || "Express App",
124
+ errorType: errorType || "Route handler error",
125
+ errorMessage: `${req.method} ${req.url} - ${errorObject.message}`,
126
+ errorStack: errorObject.stack || "",
127
+ framework: this.config.framework || "Express",
128
+ user_email: this.config.userEmail || req.user?.email || "anonymous",
129
+ severity: severity || "high",
130
+ status: "open"
131
+ };
132
+
133
+ this.sendCrashReport(payload);
134
+ } catch (e) {
135
+ console.error("[Reportli SDK] Failed to process express exception:", e);
136
+ }
137
+ }
138
+ next(err);
139
+ };
140
+
141
+ /**
142
+ * Registers global window listeners for DOM-based exceptions.
143
+ */
144
+ private setupBrowserListeners(): void {
145
+ if (typeof window === "undefined") return;
146
+
147
+ // A. Uncaught JavaScript Runtime Crashes
148
+ window.addEventListener("error", (event) => {
149
+ // Prevent loop tracing reportli requests
150
+ if (event.filename && event.filename.includes("rapid-processor")) return;
151
+
152
+ try {
153
+ const error = event.error || {
154
+ message: event.message,
155
+ filename: event.filename,
156
+ lineno: event.lineno,
157
+ colno: event.colno,
158
+ stack: `${event.message} at ${event.filename}:${event.lineno}:${event.colno}`,
159
+ };
160
+
161
+ const errorObject = this.normalizeError(error);
162
+ const { errorType, severity } = this.classifyError(errorObject);
163
+
164
+ const payload: ExceptionPayload = {
165
+ apiKey: this.config?.apiKey || "",
166
+ projectId: this.config?.projectId || "proj-sandbox",
167
+ projectName: this.config?.projectName || "SaaS App",
168
+ errorType: errorType,
169
+ errorMessage: errorObject.message,
170
+ errorStack: errorObject.stack || "",
171
+ framework: this.config?.framework || "React/Next.js Client",
172
+ user_email: this.config?.userEmail || "anonymous",
173
+ severity: severity,
174
+ status: "open",
175
+ };
176
+
177
+ this.sendCrashReport(payload);
178
+ } catch (err) {
179
+ console.error("[Reportli SDK] Inner listener exception:", err);
180
+ }
181
+ });
182
+
183
+ // B. Unhandled Promise Rejections (e.g. async/await loops)
184
+ window.addEventListener("unhandledrejection", (event) => {
185
+ try {
186
+ const reason = event.reason;
187
+ const errorObject = this.normalizeError(reason);
188
+ const { errorType, severity } = this.classifyError(errorObject, "unhandledrejection");
189
+
190
+ const payload: ExceptionPayload = {
191
+ apiKey: this.config?.apiKey || "",
192
+ projectId: this.config?.projectId || "proj-sandbox",
193
+ projectName: this.config?.projectName || "SaaS App",
194
+ errorType: errorType,
195
+ errorMessage: `Unhandled Promise: ${errorObject.message}`,
196
+ errorStack: errorObject.stack || "",
197
+ framework: this.config?.framework || "React/Next.js Client",
198
+ user_email: this.config?.userEmail || "anonymous",
199
+ severity: severity,
200
+ status: "open",
201
+ };
202
+
203
+ this.sendCrashReport(payload);
204
+ } catch (err) {
205
+ console.error("[Reportli SDK] Promise rejection listener exception:", err);
206
+ }
207
+ });
208
+
209
+ // C. Resource loading errors (capture phase for elements failing to load)
210
+ window.addEventListener(
211
+ "error",
212
+ (event) => {
213
+ try {
214
+ const target = event.target || event.srcElement;
215
+ if (!target) return;
216
+
217
+ const tagName = (target as HTMLElement).tagName;
218
+ if (!tagName) return;
219
+
220
+ let resourceType = "";
221
+ let sourceUrl = "";
222
+
223
+ if (tagName === "IMG") {
224
+ resourceType = "Image failed to load";
225
+ sourceUrl = (target as HTMLImageElement).src;
226
+ } else if (tagName === "SCRIPT") {
227
+ resourceType = "Script failed to load";
228
+ sourceUrl = (target as HTMLScriptElement).src;
229
+ } else if (tagName === "LINK") {
230
+ resourceType = "CSS failed to load";
231
+ sourceUrl = (target as HTMLLinkElement).href;
232
+ } else if (tagName === "VIDEO" || tagName === "AUDIO") {
233
+ resourceType = "Video failed to load";
234
+ sourceUrl = (target as HTMLVideoElement).src;
235
+ }
236
+
237
+ if (resourceType) {
238
+ const payload: ExceptionPayload = {
239
+ apiKey: this.config?.apiKey || "",
240
+ projectId: this.config?.projectId || "proj-sandbox",
241
+ projectName: this.config?.projectName || "SaaS App",
242
+ errorType: resourceType,
243
+ errorMessage: `Failed to load static resource asset: ${sourceUrl}`,
244
+ errorStack: `HTML Tag: <${tagName.toLowerCase()}> failed to download at location ${window.location.href}`,
245
+ framework: this.config?.framework || "React/Next.js Client",
246
+ user_email: this.config?.userEmail || "anonymous",
247
+ severity: "low",
248
+ status: "open",
249
+ };
250
+ this.sendCrashReport(payload);
251
+ }
252
+ } catch (err) {
253
+ console.error("[Reportli SDK] Resource load listener failure:", err);
254
+ }
255
+ },
256
+ true // capture phase is required for element errors
257
+ );
258
+
259
+ // D. Network Fetch Interceptors (Monkey patch global fetch to trace bad status codes)
260
+ this.interceptFetch();
261
+ this.interceptXhr();
262
+ }
263
+
264
+ /**
265
+ * Registers node process listeners for server instances.
266
+ */
267
+ private setupNodeListeners(): void {
268
+ if (typeof process === "undefined") return;
269
+
270
+ process.on("uncaughtException", (error) => {
271
+ try {
272
+ const errorObject = this.normalizeError(error);
273
+ const { errorType, severity } = this.classifyError(errorObject, "uncaughtexception");
274
+
275
+ const payload: ExceptionPayload = {
276
+ apiKey: this.config?.apiKey || "",
277
+ projectId: this.config?.projectId || "node-server",
278
+ projectName: this.config?.projectName || "Node Server",
279
+ errorType: errorType,
280
+ errorMessage: `Uncaught Exception: ${errorObject.message}`,
281
+ errorStack: errorObject.stack || "",
282
+ framework: this.config?.framework || "NodeJS backend",
283
+ user_email: this.config?.userEmail || "anonymous",
284
+ severity: severity,
285
+ status: "open",
286
+ };
287
+
288
+ // Send-sync block
289
+ this.sendCrashReport(payload);
290
+ } catch (err) {
291
+ console.error("[Reportli SDK] Node fatal uncaughtexception logging failed:", err);
292
+ }
293
+ });
294
+
295
+ process.on("unhandledRejection", (reason) => {
296
+ try {
297
+ const errorObject = this.normalizeError(reason);
298
+ const { errorType, severity } = this.classifyError(errorObject, "unhandledrejection");
299
+
300
+ const payload: ExceptionPayload = {
301
+ apiKey: this.config?.apiKey || "",
302
+ projectId: this.config?.projectId || "node-server",
303
+ projectName: this.config?.projectName || "Node Server",
304
+ errorType: errorType,
305
+ errorMessage: `Unhandled Rejection: ${errorObject.message}`,
306
+ errorStack: errorObject.stack || "",
307
+ framework: this.config?.framework || "NodeJS backend",
308
+ user_email: this.config?.userEmail || "anonymous",
309
+ severity: severity,
310
+ status: "open",
311
+ };
312
+
313
+ this.sendCrashReport(payload);
314
+ } catch (err) {
315
+ console.error("[Reportli SDK] Node fatal unhandledrejection logging failed:", err);
316
+ }
317
+ });
318
+ }
47
319
 
48
- async function sendNow(payload: QueueItem, attempts = 3): Promise<void> {
49
- for (let i = 0; i < attempts; i++) {
320
+ /**
321
+ * Safe payload sender with retry limits.
322
+ */
323
+ private async sendCrashReport(payload: ExceptionPayload): Promise<void> {
50
324
  try {
51
- const res = await fetch(ENDPOINT, {
325
+ // Avoid looping report-sends
326
+ if (payload.errorMessage && (payload.errorMessage.includes("rapid-processor") || payload.errorMessage.includes("api/error") || payload.errorMessage.includes("supabase"))) {
327
+ return;
328
+ }
329
+
330
+ // Default to the Reportli Production Server for NPM users
331
+ let destinationUrl = this.config?.endpoint || `${REPORTLI_BASE_URL}/api/error`;
332
+
333
+ const response = await fetch(destinationUrl, {
52
334
  method: "POST",
53
335
  headers: {
54
336
  "Content-Type": "application/json",
55
- "x-api-key": _config?.apiKey ?? "",
337
+ "x-api-key": this.config?.apiKey || ""
56
338
  },
57
339
  body: JSON.stringify(payload),
58
340
  });
59
- if (res.ok) return; // success — stop retrying
60
- } catch {
61
- // network error wait before retry
341
+ } catch (err) {
342
+ // Fail silently to prevent user software crash
343
+ console.warn("[Reportli SDK] Failed to synchronize crash traces to diagnostics server:", err);
62
344
  }
63
- await sleep(1000 * (i + 1)); // 1s, 2s, 3s
64
345
  }
65
- // give up silently after 3 attempts — never crash user app
66
- }
67
346
 
68
- function sleep(ms: number) {
69
- return new Promise((resolve) => setTimeout(resolve, ms));
70
- }
347
+ /**
348
+ * Sends registration verification to user edge function on first install.
349
+ */
350
+ private sendRegistrationWebhook(isBrowser: boolean): void {
351
+ if (!this.config) return;
71
352
 
72
- // ─── Enqueue ──────────────────────────────────────────────────────────────────
353
+ const key = `reportli_initialized_success_${this.config.apiKey}`;
354
+ if (isBrowser && typeof localStorage !== "undefined") {
355
+ if (localStorage.getItem(key)) {
356
+ return; // Already verified registration autrefois
357
+ }
358
+ localStorage.setItem(key, "true");
359
+ }
73
360
 
74
- function enqueue(payload: QueueItem) {
75
- if (queue.length >= 100) return; // cap queue to prevent memory issues
76
- queue.push(payload);
77
- scheduleFlush();
78
- }
361
+ const deviceDetails = isBrowser
362
+ ? typeof navigator !== "undefined" ? navigator.userAgent : "Browser sandbox"
363
+ : typeof process !== "undefined" ? `NodeJS environment: ${process.version}` : "Cloud instance";
364
+
365
+ const welcomePayload: ExceptionPayload = {
366
+ apiKey: this.config.apiKey,
367
+ projectId: this.config.projectId || "proj-sandbox",
368
+ projectName: this.config.projectName || "SaaS App Component",
369
+ errorType: "SDK_INITIALIZED",
370
+ errorMessage: "Reportli SDK successfully initialized! Error listener is active.",
371
+ errorStack: `SDK Active Diagnostics: Success registration from execution agent. Device context: ${deviceDetails}`,
372
+ framework: this.config.framework || (isBrowser ? "React/Next.js Client" : "Node.js Server"),
373
+ user_email: this.config.userEmail || "anonymous",
374
+ severity: "low",
375
+ status: "info"
376
+ };
79
377
 
80
- // ─── Send immediately (for critical messages) ─────────────────────────────────
378
+ this.sendCrashReport(welcomePayload);
379
+ }
81
380
 
82
- function sendImmediate(payload: QueueItem) {
83
- sendNow(payload).catch(() => {}); // fire and forget
84
- }
381
+ /**
382
+ * Capture fetch network status deviations.
383
+ */
384
+ private interceptFetch(): void {
385
+ if (typeof window === "undefined" || typeof window.fetch !== "function") return;
386
+
387
+ const originalFetch = window.fetch;
388
+ const self = this;
389
+
390
+ window.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
391
+ const url = typeof input === "string" ? input : (input instanceof URL ? input.toString() : input.url);
392
+
393
+ // Skip tracking of reportli's own API calls to avoid circular logging loop
394
+ if (url.includes("rapid-processor") || url.includes("api/error") || url.includes("supabase.co/functions")) {
395
+ return originalFetch.apply(this, arguments as any);
396
+ }
85
397
 
86
- // ─── Helpers ──────────────────────────────────────────────────────────────────
398
+ try {
399
+ const response = await originalFetch.apply(this, arguments as any);
400
+ if (response && !response.ok) {
401
+ self.logNetworkError(url, response.status, response.statusText, init?.method || "GET");
402
+ }
403
+ return response;
404
+ } catch (err: any) {
405
+ // Fetch throwing means CORS, connection refused, dns failures or timeout aborts
406
+ self.logNetworkFailure(url, err, init?.method || "GET");
407
+ throw err;
408
+ }
409
+ };
410
+ }
87
411
 
88
- function getUrl(): string {
89
- if (typeof window !== "undefined") return window.location.href;
90
- try { return require("os").hostname(); } catch { return "server"; }
91
- }
412
+ /**
413
+ * Trace XHR status failures.
414
+ */
415
+ private interceptXhr(): void {
416
+ if (typeof window === "undefined" || typeof XMLHttpRequest === "undefined") return;
417
+
418
+ const originalOpen = XMLHttpRequest.prototype.open;
419
+ const originalSend = XMLHttpRequest.prototype.send;
420
+ const self = this;
421
+
422
+ XMLHttpRequest.prototype.open = function (method: string, url: string) {
423
+ (this as any)._reportli_method = method;
424
+ (this as any)._reportli_url = url;
425
+ return originalOpen.apply(this, arguments as any);
426
+ } as any;
427
+
428
+ XMLHttpRequest.prototype.send = function () {
429
+ const xhrInstance = this;
430
+
431
+ xhrInstance.addEventListener("loadend", () => {
432
+ try {
433
+ const url = (xhrInstance as any)._reportli_url || "";
434
+ const method = (xhrInstance as any)._reportli_method || "GET";
435
+ const status = xhrInstance.status;
436
+
437
+ // Skip self calls
438
+ if (url.includes("rapid-processor") || url.includes("supabase.co/functions")) {
439
+ return;
440
+ }
441
+
442
+ if (status >= 400 || status === 0) {
443
+ if (status === 0) {
444
+ self.logNetworkFailure(url, new Error("CORS or Connection Refused"), method);
445
+ } else {
446
+ self.logNetworkError(url, status, xhrInstance.statusText || "XHR Error", method);
447
+ }
448
+ }
449
+ } catch (e) {
450
+ console.error("[Reportli SDK] Failed to extract XHR statistics:", e);
451
+ }
452
+ });
92
453
 
93
- function getBrowser(): string {
94
- if (typeof navigator !== "undefined") return navigator.userAgent;
95
- return `Node.js ${typeof process !== "undefined" ? process.version : "unknown"}`;
96
- }
454
+ return originalSend.apply(this, arguments as any);
455
+ } as any;
456
+ }
97
457
 
98
- function getEnvironment(): string {
99
- return _config?.environment ?? (typeof window !== "undefined" ? "browser" : "server");
100
- }
458
+ private logNetworkError(url: string, status: number, statusText: string, method: string) {
459
+ let errorType = `API ${status} Error`;
460
+ let severity: "medium" | "high" | "critical" = "medium";
461
+
462
+ if (status === 400) errorType = "API 400 Bad Request";
463
+ else if (status === 401) { errorType = "API 401 Unauthorized"; severity = "high"; }
464
+ else if (status === 403) { errorType = "API 403 Forbidden"; severity = "high"; }
465
+ else if (status === 404) errorType = "API 404 Not Found";
466
+ else if (status === 500) { errorType = "API 500 Internal Server Error"; severity = "high"; }
467
+ else if (status === 503) { errorType = "API 503 Service Unavailable"; severity = "high"; }
468
+
469
+ const payload: ExceptionPayload = {
470
+ apiKey: this.config?.apiKey || "",
471
+ projectId: this.config?.projectId || "proj-sandbox",
472
+ projectName: this.config?.projectName || "SaaS App",
473
+ errorType: errorType,
474
+ errorMessage: `HTTP API failed with code ${status}: ${method} ${url}`,
475
+ errorStack: `Network Request Header Context: [Method: ${method}] Url: ${url} Response Description: ${statusText || "Dev server returned error."}`,
476
+ framework: this.config?.framework || "React/Next.js Client",
477
+ user_email: this.config?.userEmail || "anonymous",
478
+ severity: severity,
479
+ status: "open",
480
+ };
101
481
 
102
- function parseStack(stack: string | undefined): { file: string; line: number; column: number } {
103
- if (!stack) return { file: "unknown", line: 0, column: 0 };
104
- const match =
105
- stack.match(/at .+ \((.+):(\d+):(\d+)\)/) ||
106
- stack.match(/at (.+):(\d+):(\d+)/);
107
- return {
108
- file: match?.[1]?.split("/")?.pop() || "unknown",
109
- line: parseInt(match?.[2] || "0"),
110
- column: parseInt(match?.[3] || "0"),
111
- };
112
- }
482
+ this.sendCrashReport(payload);
483
+ }
113
484
 
114
- function getErrorCode(error: any): string {
115
- return (
116
- error?.code ||
117
- error?.status?.toString() ||
118
- error?.statusCode?.toString() ||
119
- error?.name ||
120
- "ERR_UNKNOWN"
121
- );
122
- }
485
+ private logNetworkFailure(url: string, error: Error | any, method: string) {
486
+ const errorMsg = String(error.message || error || "");
487
+ let errorType = "Fetch failed error";
488
+ let severity: "medium" | "high" = "high";
123
489
 
124
- function classifyError(error: any, context?: string): { category: string; severity: "low" | "medium" | "high" | "critical" } {
125
- const msg = String(error?.message || error || "").toLowerCase();
126
- const name = String(error?.name || "").toLowerCase();
127
-
128
- // Payment always critical
129
- if (msg.includes("stripe") || msg.includes("payment") || msg.includes("card declined") || msg.includes("checkout") || msg.includes("refund"))
130
- return { category: "Payment Error", severity: "critical" };
131
-
132
- // Auth
133
- if (msg.includes("jwt") || msg.includes("token expired") || msg.includes("unauthorized") || msg.includes("session") || msg.includes("oauth") || msg.includes("login failed"))
134
- return { category: "Auth Error", severity: "high" };
135
-
136
- // Database
137
- if (msg.includes("supabase") || msg.includes("database") || msg.includes("query") || msg.includes("connection lost") || msg.includes("transaction") || msg.includes("duplicate key") || msg.includes("foreign key"))
138
- return { category: "Database Error", severity: "critical" };
139
-
140
- // React/Next.js
141
- if (msg.includes("hydration") || msg.includes("does not match server"))
142
- return { category: "Hydration Error", severity: "high" };
143
- if (msg.includes("invalid hook") || msg.includes("rules of hooks"))
144
- return { category: "Hook Error", severity: "critical" };
145
- if (msg.includes("render") || msg.includes("error boundary"))
146
- return { category: "Render Error", severity: "critical" };
147
- if (msg.includes("dynamic import") || msg.includes("failed to fetch dynamically"))
148
- return { category: "Import Error", severity: "high" };
149
-
150
- // Network
151
- if (msg.includes("cors") || msg.includes("cross-origin"))
152
- return { category: "CORS Error", severity: "high" };
153
- if (msg.includes("fetch failed") || msg.includes("failed to fetch") || name === "fetcherror")
154
- return { category: "Network Error", severity: "high" };
155
- if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("etimedout"))
156
- return { category: "Timeout Error", severity: "medium" };
157
- if (msg.includes("websocket"))
158
- return { category: "WebSocket Error", severity: "high" };
159
-
160
- // HTTP status codes
161
- if (msg.includes("http 401") || msg.includes("xhr 401"))
162
- return { category: "Auth Error", severity: "high" };
163
- if (msg.includes("http 403") || msg.includes("xhr 403"))
164
- return { category: "Auth Error", severity: "high" };
165
- if (msg.includes("http 404") || msg.includes("xhr 404"))
166
- return { category: "Not Found Error", severity: "medium" };
167
- if (msg.includes("http 500") || msg.includes("xhr 500"))
168
- return { category: "Server Error", severity: "critical" };
169
- if (msg.includes("http 503") || msg.includes("xhr 503"))
170
- return { category: "Server Error", severity: "critical" };
171
-
172
- // Memory
173
- if (msg.includes("maximum call stack") || msg.includes("out of memory") || msg.includes("heap limit"))
174
- return { category: "Memory Error", severity: "critical" };
175
-
176
- // Server
177
- if (msg.includes("cannot find module") || msg.includes("module not found"))
178
- return { category: "Module Error", severity: "critical" };
179
- if (msg.includes("econnrefused") || msg.includes("connection refused"))
180
- return { category: "Connection Error", severity: "critical" };
181
-
182
- // Email & Jobs
183
- if (msg.includes("email") || msg.includes("smtp") || msg.includes("sendgrid"))
184
- return { category: "Email Error", severity: "high" };
185
- if (msg.includes("cron") || msg.includes("webhook") || msg.includes("queue"))
186
- return { category: "Job Error", severity: "high" };
187
-
188
- // Files
189
- if (msg.includes("upload") || msg.includes("file size") || msg.includes("invalid file"))
190
- return { category: "File Error", severity: "medium" };
191
-
192
- // Storage
193
- if (msg.includes("quota exceeded") || msg.includes("localstorage") || msg.includes("indexeddb"))
194
- return { category: "Storage Error", severity: "medium" };
195
-
196
- // JS core
197
- if (name === "typeerror") return { category: "TypeError", severity: "high" };
198
- if (name === "referenceerror") return { category: "ReferenceError", severity: "critical" };
199
- if (name === "rangeerror") return { category: "RangeError", severity: "high" };
200
- if (name === "syntaxerror") return { category: "SyntaxError", severity: "high" };
201
-
202
- // Context based
203
- if (context === "unhandledrejection") return { category: "Promise Error", severity: "medium" };
204
- if (context === "express") return { category: "Server Error", severity: "high" };
205
- if (context === "resource") return { category: "Resource Error", severity: "low" };
206
-
207
- return { category: "Unknown Error", severity: "medium" };
208
- }
490
+ if (errorMsg.includes("timeout") || errorMsg.includes("abort")) {
491
+ errorType = "Request timeout error";
492
+ severity = "medium";
493
+ } else if (errorMsg.includes("CORS") || errorMsg.includes("origin") || errorMsg.includes("Access-Control")) {
494
+ errorType = "CORS error";
495
+ }
209
496
 
210
- function buildErrorPayload(error: any, context?: string): QueueItem {
211
- const message = error?.message || String(error) || "Unknown error";
212
- const stack = error?.stack || "";
213
- const { file, line, column } = parseStack(stack);
214
- const { category, severity } = classifyError(error, context);
215
-
216
- return {
217
- type: "ERROR",
218
- apiKey: _config?.apiKey,
219
- message,
220
- code: getErrorCode(error),
221
- stack,
222
- file,
223
- line,
224
- column,
225
- url: getUrl(),
226
- timestamp: new Date().toISOString(),
227
- environment: getEnvironment(),
228
- browser: getBrowser(),
229
- error_category: category,
230
- severity,
231
- status: "open",
232
- context: context || "auto",
233
- };
234
- }
497
+ const payload: ExceptionPayload = {
498
+ apiKey: this.config?.apiKey || "",
499
+ projectId: this.config?.projectId || "proj-sandbox",
500
+ projectName: this.config?.projectName || "SaaS App",
501
+ errorType: errorType,
502
+ errorMessage: `Failed to complete HTTP request: [${method}] ${url}`,
503
+ errorStack: `Network crash context: ${errorMsg}\nConnection status: Failed to resolve.`,
504
+ framework: this.config?.framework || "React/Next.js Client",
505
+ user_email: this.config?.userEmail || "anonymous",
506
+ severity: severity,
507
+ status: "open",
508
+ };
235
509
 
236
- // ─── Browser Listeners ────────────────────────────────────────────────────────
510
+ this.sendCrashReport(payload);
511
+ }
237
512
 
238
- function activateBrowserListeners() {
239
- // JS errors + resource errors
240
- window.addEventListener("error", (event) => {
241
- // Resource load error (img, script, link, video)
242
- const target = event.target as HTMLElement;
243
- if (target && target.tagName && ["IMG", "SCRIPT", "LINK", "VIDEO", "AUDIO"].includes(target.tagName)) {
244
- const src = (target as any).src || (target as any).href || "unknown";
245
- enqueue(buildErrorPayload({ name: "ResourceError", message: `${target.tagName} failed to load: ${src}`, stack: "" }, "resource"));
246
- return;
513
+ /**
514
+ * Helper to safely format raw objects / strings into structured Errors
515
+ */
516
+ private normalizeError(err: any): { name: string; message: string; stack?: string } {
517
+ if (err instanceof Error) {
518
+ return {
519
+ name: err.name,
520
+ message: err.message,
521
+ stack: err.stack,
522
+ };
523
+ }
524
+
525
+ if (typeof err === "string") {
526
+ return {
527
+ name: "Error",
528
+ message: err,
529
+ stack: new Error(err).stack,
530
+ };
531
+ }
532
+
533
+ if (err && typeof err === "object") {
534
+ return {
535
+ name: err.name || err.code || "Error",
536
+ message: err.message || err.reason || JSON.stringify(err),
537
+ stack: err.stack || err.traceback || new Error(err.message).stack,
538
+ };
247
539
  }
248
540
 
249
- const err = (event as ErrorEvent).error || {
541
+ return {
250
542
  name: "Error",
251
- message: (event as ErrorEvent).message,
252
- stack: `at ${(event as ErrorEvent).filename}:${(event as ErrorEvent).lineno}:${(event as ErrorEvent).colno}`,
543
+ message: "An unhandled execution trace slipped past standard boundaries.",
544
+ stack: new Error().stack,
253
545
  };
254
- enqueue(buildErrorPayload(err, "window"));
255
- }, true);
256
-
257
- // Unhandled promise rejections
258
- window.addEventListener("unhandledrejection", (event) => {
259
- const err = event.reason instanceof Error
260
- ? event.reason
261
- : { name: "UnhandledRejection", message: String(event.reason || "Unhandled Promise Rejection"), stack: "" };
262
- enqueue(buildErrorPayload(err, "unhandledrejection"));
263
- });
264
-
265
- // Intercept fetch — catches all API errors automatically
266
- const originalFetch = window.fetch;
267
- window.fetch = async function (...args: Parameters<typeof fetch>): Promise<Response> {
268
- const url = typeof args[0] === "string"
269
- ? args[0]
270
- : args[0] instanceof URL
271
- ? args[0].toString()
272
- : (args[0] as Request)?.url ?? "";
273
-
274
- // Never intercept our own requests
275
- if (url.includes("rapid-processor")) return originalFetch(...args);
546
+ }
276
547
 
277
- try {
278
- const response = await originalFetch(...args);
279
- if (!response.ok) {
280
- enqueue(buildErrorPayload({
281
- name: `HTTP_${response.status}`,
282
- message: `HTTP ${response.status}: ${(args[1] as RequestInit)?.method || "GET"} ${url}`,
283
- stack: `${(args[1] as RequestInit)?.method || "GET"} ${url} → ${response.status} ${response.statusText}`,
284
- status: response.status,
285
- }, "fetch"));
548
+ /**
549
+ * Comprehensive classification rules checking substring and classifications
550
+ */
551
+ private classifyError(error: any, context?: string): { errorType: string; severity: "low" | "medium" | "high" | "critical" } {
552
+ const msg = String(error.message || error || "").toLowerCase();
553
+ const name = String(error.name || "").toLowerCase();
554
+
555
+ // 1. Storage Errors
556
+ if (name.includes("quotaexceeded") || msg.includes("quota exceeded") || msg.includes("localstorage") || msg.includes("sessionstorage") || msg.includes("indexeddb")) {
557
+ return { errorType: "localStorage quota exceeded", severity: "medium" };
558
+ }
559
+
560
+ // 2. React/Next.js/Vue Hydration Mismatches & Hook Violations
561
+ if (msg.includes("hydration") || msg.includes("does not match server") || msg.includes("text content did not match")) {
562
+ return { errorType: "React hydration error", severity: "high" };
563
+ }
564
+ if (msg.includes("invalid hook call") || msg.includes("rules of hooks")) {
565
+ return { errorType: "Invalid hook call error", severity: "critical" };
566
+ }
567
+ if (msg.includes("failed prop type") || msg.includes("invalid prop")) {
568
+ return { errorType: "Props type error", severity: "low" };
569
+ }
570
+ if (msg.includes("render") || msg.includes("react error boundary") || msg.includes("component render")) {
571
+ return { errorType: "Component render error", severity: "critical" };
572
+ }
573
+ if (msg.includes("route not found") || msg.includes("cannot find route") || msg.includes("404 route")) {
574
+ return { errorType: "Route not found error", severity: "medium" };
575
+ }
576
+ if (msg.includes("failed to fetch dynamically imported") || msg.includes("dynamic import")) {
577
+ return { errorType: "Dynamic import error", severity: "high" };
578
+ }
579
+ if (msg.includes("suspense") || msg.includes("fallback")) {
580
+ return { errorType: "Suspense boundary error", severity: "medium" };
581
+ }
582
+
583
+ // 3. Engine standard throws
584
+ if (name === "typeerror" || msg.startsWith("typeerror")) {
585
+ return { errorType: "TypeError", severity: "high" };
586
+ }
587
+ if (name === "referenceerror" || msg.startsWith("referenceerror")) {
588
+ return { errorType: "ReferenceError", severity: "critical" };
589
+ }
590
+ if (name === "rangeerror" || msg.startsWith("rangeerror")) {
591
+ if (msg.includes("maximum call stack") || msg.includes("stack overflow")) {
592
+ return { errorType: "Stack overflow error", severity: "critical" };
286
593
  }
287
- return response;
288
- } catch (err: any) {
289
- enqueue(buildErrorPayload({
290
- name: "FetchError",
291
- message: `Fetch failed: ${(args[1] as RequestInit)?.method || "GET"} ${url} — ${err.message}`,
292
- stack: err.stack,
293
- }, "fetch"));
294
- throw err;
594
+ return { errorType: "RangeError", severity: "high" };
595
+ }
596
+ if (name === "syntaxerror" || msg.startsWith("syntaxerror")) {
597
+ return { errorType: "SyntaxError", severity: "high" };
598
+ }
599
+ if (name === "evalerror") {
600
+ return { errorType: "EvalError", severity: "medium" };
601
+ }
602
+ if (name === "urierror") {
603
+ return { errorType: "URIError", severity: "medium" };
295
604
  }
296
- };
297
605
 
298
- // Intercept XHR
299
- const originalOpen = XMLHttpRequest.prototype.open;
300
- const originalSend = XMLHttpRequest.prototype.send;
301
-
302
- XMLHttpRequest.prototype.open = function (method: string, url: string, ...rest: any[]) {
303
- (this as any)._r_method = method;
304
- (this as any)._r_url = url;
305
- return originalOpen.call(this, method, url, ...rest);
306
- } as any;
307
-
308
- XMLHttpRequest.prototype.send = function (...args: any[]) {
309
- const url: string = (this as any)._r_url || "";
310
- const method: string = (this as any)._r_method || "GET";
311
-
312
- if (!url.includes("rapid-processor")) {
313
- this.addEventListener("loadend", () => {
314
- if (this.status >= 400 || this.status === 0) {
315
- enqueue(buildErrorPayload({
316
- name: `XHR_${this.status}`,
317
- message: `XHR ${this.status}: ${method} ${url}`,
318
- stack: `${method} ${url} → ${this.status} ${this.statusText}`,
319
- status: this.status,
320
- }, "xhr"));
321
- }
322
- });
606
+ // 4. Payment & Billing Errors (Stripe/Paypal)
607
+ if (msg.includes("stripe") && (msg.includes("key") || msg.includes("init") || msg.includes("key is required") || msg.includes("is not defined"))) {
608
+ return { errorType: "Stripe initialization error", severity: "critical" };
609
+ }
610
+ if (msg.includes("stripe") && (msg.includes("payment") || msg.includes("processing") || msg.includes("charge"))) {
611
+ return { errorType: "Payment processing error", severity: "critical" };
612
+ }
613
+ if (msg.includes("card declined") || msg.includes("card_declined") || msg.includes("declined")) {
614
+ return { errorType: "Card declined error", severity: "high" };
615
+ }
616
+ if (msg.includes("checkout session") || msg.includes("create checkout")) {
617
+ return { errorType: "Checkout session error", severity: "high" };
618
+ }
619
+ if (msg.includes("refund failed") || msg.includes("refund_failed")) {
620
+ return { errorType: "Refund failed error", severity: "high" };
323
621
  }
324
622
 
325
- return originalSend.apply(this, args);
326
- } as any;
623
+ // 5. Database Errors (Supabase / PG / Mongo)
624
+ if (msg.includes("supabase query") || msg.includes("postgresterror")) {
625
+ return { errorType: "Supabase query error", severity: "critical" };
626
+ }
627
+ if (msg.includes("unique constraint") || msg.includes("duplicate key")) {
628
+ return { errorType: "Unique constraint error", severity: "high" };
629
+ }
630
+ if (msg.includes("foreign key")) {
631
+ return { errorType: "Foreign key error", severity: "high" };
632
+ }
633
+ if (msg.includes("transaction failed") || msg.includes("rollback")) {
634
+ return { errorType: "Transaction failed error", severity: "critical" };
635
+ }
636
+ if (msg.includes("connection lost") || msg.includes("db connection") || msg.includes("connection refused") || msg.includes("econnrefused")) {
637
+ return { errorType: "Connection lost error", severity: "critical" };
638
+ }
639
+ if (msg.includes("query timeout") || msg.includes("statement timeout")) {
640
+ return { errorType: "Query timeout error", severity: "high" };
641
+ }
327
642
 
328
- // Disconnect when page closes
329
- window.addEventListener("beforeunload", () => {
330
- try {
331
- navigator.sendBeacon(
332
- ENDPOINT,
333
- JSON.stringify({
334
- type: "SDK_DISCONNECTED",
335
- apiKey: _config?.apiKey,
336
- timestamp: new Date().toISOString(),
337
- environment: getEnvironment(),
338
- url: getUrl(),
339
- })
340
- );
341
- } catch { /* silent */ }
342
- });
343
- }
643
+ // 6. JWT & Auth Server Errors
644
+ if (msg.includes("jwt expired") || msg.includes("token expired") || msg.includes("jsonwebtokenexpired")) {
645
+ return { errorType: "JWT expired error", severity: "high" };
646
+ }
647
+ if (msg.includes("jwt verification") || msg.includes("invalid signature") || msg.includes("jwt malformed")) {
648
+ return { errorType: "JWT verification error", severity: "high" };
649
+ }
650
+ if (msg.includes("firebase-admin") || msg.includes("firebase admin")) {
651
+ return { errorType: "Firebase admin error", severity: "critical" };
652
+ }
653
+ if (msg.includes("session creation") || msg.includes("create session")) {
654
+ return { errorType: "Session creation error", severity: "high" };
655
+ }
656
+ if (msg.includes("password hash") || msg.includes("bcrypt") || msg.includes("argon2")) {
657
+ return { errorType: "Password hash error", severity: "critical" };
658
+ }
344
659
 
345
- // ─── Server Listeners ─────────────────────────────────────────────────────────
346
-
347
- function activateServerListeners() {
348
- process.on("uncaughtException", (error: Error) => {
349
- enqueue(buildErrorPayload(error, "uncaughtException"));
350
- flush(); // flush immediately on crash
351
- });
352
-
353
- process.on("unhandledRejection", (reason: any) => {
354
- enqueue(buildErrorPayload(
355
- reason instanceof Error ? reason : { name: "UnhandledRejection", message: String(reason), stack: "" },
356
- "unhandledrejection"
357
- ));
358
- });
359
-
360
- const shutdown = async (signal: string) => {
361
- await sendNow({
362
- type: "SDK_DISCONNECTED",
363
- apiKey: _config?.apiKey,
364
- timestamp: new Date().toISOString(),
365
- environment: getEnvironment(),
366
- url: getUrl(),
367
- signal,
368
- });
369
- process.exit(0);
370
- };
660
+ // 7. General Client Authentication Errors
661
+ if (msg.includes("auth/id-token-expired") || msg.includes("id token expired")) {
662
+ return { errorType: "Token expired error", severity: "high" };
663
+ }
664
+ if (msg.includes("invalid-credential") || msg.includes("auth/invalid-credential") || msg.includes("invalid token")) {
665
+ return { errorType: "Invalid token error", severity: "high" };
666
+ }
667
+ if (msg.includes("session ended") || msg.includes("session expired")) {
668
+ return { errorType: "Session ended error", severity: "medium" };
669
+ }
670
+ if (msg.includes("oauth callback") || msg.includes("oauth error")) {
671
+ return { errorType: "OAuth callback error", severity: "high" };
672
+ }
673
+ if (msg.includes("login failed") || msg.includes("invalid credentials")) {
674
+ return { errorType: "Login failed error", severity: "medium" };
675
+ }
676
+ if (msg.includes("unauthorized") || msg.includes("permission denied") || msg.includes("forbidden") || msg.includes("unauthorized access") || msg.includes("permission_denied")) {
677
+ return { errorType: "Unauthorized access error", severity: "high" };
678
+ }
371
679
 
372
- process.on("SIGTERM", () => shutdown("SIGTERM"));
373
- process.on("SIGINT", () => shutdown("SIGINT"));
374
- }
680
+ // 8. Background & Queue
681
+ if (msg.includes("cron job") || msg.includes("cron_failed") || msg.includes("cron failed")) {
682
+ return { errorType: "Cron job failed", severity: "high" };
683
+ }
684
+ if (msg.includes("queue processing") || msg.includes("bullmq") || msg.includes("redis queue") || msg.includes("celery error")) {
685
+ return { errorType: "Queue processing error", severity: "high" };
686
+ }
687
+ if (msg.includes("webhook delivery") || msg.includes("webhook_failed") || msg.includes("webhook failed")) {
688
+ return { errorType: "Webhook delivery failed", severity: "high" };
689
+ }
690
+ if (msg.includes("retry limit exceeded") || msg.includes("max retries")) {
691
+ return { errorType: "Retry limit exceeded", severity: "high" };
692
+ }
693
+
694
+ // 9. Node Errors (Server backend context)
695
+ if (msg.includes("out of memory") || msg.includes("oom") || msg.includes("heap limit") || msg.includes("heap out of memory")) {
696
+ return { errorType: "Out of memory error", severity: "critical" };
697
+ }
698
+ if (msg.includes("cannot find module") || msg.includes("module_not_found") || msg.includes("module not found")) {
699
+ return { errorType: "Module not found error", severity: "critical" };
700
+ }
701
+ if (msg.includes("process crash") || msg.includes("sigterm") || msg.includes("sigint") || msg.includes("process.exit")) {
702
+ return { errorType: "Process crash error", severity: "critical" };
703
+ }
704
+
705
+ // 10. Express Server Specifics
706
+ if (context === "express" || msg.includes("route handler") || msg.includes("api router")) {
707
+ return { errorType: "Route handler error", severity: "high" };
708
+ }
709
+ if (msg.includes("middleware")) {
710
+ return { errorType: "Middleware error", severity: "high" };
711
+ }
712
+ if (msg.includes("validation error") || msg.includes("zoderror") || msg.includes("joi validation")) {
713
+ return { errorType: "Request validation error", severity: "medium" };
714
+ }
715
+ if (msg.includes("body parser") || msg.includes("multer") || msg.includes("multipart") || msg.includes("body-parser")) {
716
+ return { errorType: "Body parser error", severity: "high" };
717
+ }
718
+ if (msg.includes("too many requests") || msg.includes("rate limit") || msg.includes("429")) {
719
+ return { errorType: "Rate limit error", severity: "high" };
720
+ }
721
+
722
+ // 11. Files, Uploads, CDNs
723
+ if (msg.includes("file upload") || msg.includes("upload failed") || msg.includes("cloudinary") || msg.includes("s3 upload") || msg.includes("multipart upload")) {
724
+ return { errorType: "File upload failed", severity: "high" };
725
+ }
726
+ if (msg.includes("file size exceeded") || msg.includes("payload too large") || msg.includes("size validation")) {
727
+ return { errorType: "File size exceeded", severity: "medium" };
728
+ }
729
+ if (msg.includes("invalid file type") || msg.includes("mime type mismatch") || msg.includes("file format")) {
730
+ return { errorType: "Invalid file type", severity: "medium" };
731
+ }
732
+ if (msg.includes("storage quota exceeded") || msg.includes("bucket quota") || msg.includes("exhausted quota")) {
733
+ return { errorType: "Storage quota exceeded", severity: "high" };
734
+ }
735
+ if (msg.includes("cdn ") || msg.includes("upload to cdn") || msg.includes("cdn distribution")) {
736
+ return { errorType: "CDN upload failed", severity: "high" };
737
+ }
375
738
 
376
- // ─── Public API ───────────────────────────────────────────────────────────────
739
+ // 12. Email System failures
740
+ if (msg.includes("email sending failed") || msg.includes("sendgrid") || msg.includes("nodemailer") || msg.includes("resend")) {
741
+ return { errorType: "Email sending failed", severity: "high" };
742
+ }
743
+ if (msg.includes("smtp connection") || msg.includes("smtp error") || msg.includes("mail connection")) {
744
+ return { errorType: "SMTP connection error", severity: "high" };
745
+ }
746
+ if (msg.includes("template rendering") || msg.includes("mjml") || msg.includes("pug render")) {
747
+ return { errorType: "Template rendering error", severity: "medium" };
748
+ }
749
+ if (msg.includes("onesignal") || msg.includes("push notification") || msg.includes("fcm payload")) {
750
+ return { errorType: "OneSignal API error", severity: "high" };
751
+ }
377
752
 
378
- export const Reportli = {
379
- init(cfg: Config) {
380
- if (initialized) return;
381
- if (!cfg?.apiKey) return;
753
+ // 13. Networking / API / CORS
754
+ if (msg.includes("cors") || msg.includes("cross-origin") || msg.includes("preflight") || msg.includes("access-control-allow")) {
755
+ return { errorType: "CORS error", severity: "high" };
756
+ }
757
+ if (msg.includes("fetch failed") || msg.includes("failed to fetch")) {
758
+ return { errorType: "Fetch failed error", severity: "high" };
759
+ }
760
+ if (msg.includes("timeout") || msg.includes("aborted") || msg.includes("timed out") || msg.includes("etimedout")) {
761
+ return { errorType: "Request timeout error", severity: "medium" };
762
+ }
763
+ if (msg.includes("websocket connection") || msg.includes("ws connection failed") || msg.includes("ws://") || msg.includes("wss://")) {
764
+ return { errorType: "WebSocket connection error", severity: "high" };
765
+ }
766
+ if (msg.includes("websocket closed") || msg.includes("websocket disconnected") || msg.includes("ws.close")) {
767
+ return { errorType: "WebSocket disconnect error", severity: "medium" };
768
+ }
382
769
 
383
- _config = cfg;
384
- initialized = true;
770
+ // 14. Standard Promise Failures
771
+ if (context === "unhandledrejection") {
772
+ return { errorType: "Unhandled promise rejection", severity: "medium" };
773
+ }
774
+ if (msg.includes("async") && msg.includes("await")) {
775
+ return { errorType: "Async await failure", severity: "high" };
776
+ }
777
+ if (msg.includes("promise chain") || msg.includes("promise.all") || msg.includes("promise.race")) {
778
+ return { errorType: "Promise chain error", severity: "high" };
779
+ }
780
+ if (msg.includes("promise timed out") || msg.includes("promise timeout")) {
781
+ return { errorType: "Promise timeout error", severity: "medium" };
782
+ }
385
783
 
386
- // Send SDK_INITIALIZED immediately — not queued
387
- sendImmediate({
388
- type: "SDK_INITIALIZED",
389
- apiKey: cfg.apiKey,
390
- timestamp: new Date().toISOString(),
391
- environment: getEnvironment(),
392
- url: getUrl(),
393
- browser: getBrowser(),
394
- });
784
+ // Fallbacks
785
+ if (context === "uncaughtexception") {
786
+ return { errorType: "Uncaught exception", severity: "high" };
787
+ }
395
788
 
396
- // Activate listeners
397
- if (typeof window !== "undefined") {
398
- activateBrowserListeners();
399
- } else if (typeof process !== "undefined" && process.versions?.node) {
400
- activateServerListeners();
401
- }
402
- },
403
-
404
- capture(error: unknown) {
405
- if (!initialized) return;
406
- const err = error instanceof Error
407
- ? error
408
- : { name: "ManualCapture", message: String(error), stack: new Error().stack };
409
- enqueue(buildErrorPayload(err, "manual"));
410
- },
411
-
412
- captureMessage(message: string) {
413
- if (!initialized) return;
414
- enqueue(buildErrorPayload({ name: "Message", message, stack: "" }, "manual"));
415
- },
416
-
417
- errorHandler() {
418
- return function (err: any, _req: any, _res: any, next: any) {
419
- enqueue(buildErrorPayload(err, "express"));
420
- next(err);
421
- };
422
- },
423
- };
789
+ return { errorType: "Uncaught exception", severity: "medium" };
790
+ }
791
+ }
424
792
 
793
+ export const Reportli = new ReportliTracker();
425
794
  export default Reportli;