reportli 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +308 -114
- package/dist/index.js.map +2 -2
- package/dist/index.mjs +316 -114
- package/dist/index.mjs.map +2 -2
- package/package.json +1 -1
- package/src/index.ts +390 -145
package/src/index.ts
CHANGED
|
@@ -1,180 +1,425 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/index.ts — Reportli SDK v1.0.3
|
|
2
2
|
|
|
3
3
|
const ENDPOINT =
|
|
4
4
|
"https://fahikyfmgdyzejdfftox.supabase.co/functions/v1/rapid-processor";
|
|
5
5
|
|
|
6
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
6
8
|
type Config = {
|
|
7
9
|
apiKey: string;
|
|
8
10
|
environment?: string;
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
type QueueItem = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
// ─── State ────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
11
17
|
let initialized = false;
|
|
12
|
-
let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
let _config: Config;
|
|
19
|
+
const queue: QueueItem[] = [];
|
|
20
|
+
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
21
|
+
let isFlushing = false;
|
|
22
|
+
|
|
23
|
+
// ─── Queue & Batch Send ───────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function scheduleFlush() {
|
|
26
|
+
if (flushTimer) return;
|
|
27
|
+
flushTimer = setTimeout(() => {
|
|
28
|
+
flushTimer = null;
|
|
29
|
+
flush();
|
|
30
|
+
}, 2000); // batch every 2 seconds
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function flush() {
|
|
34
|
+
if (isFlushing || queue.length === 0) return;
|
|
35
|
+
isFlushing = true;
|
|
36
|
+
|
|
37
|
+
const batch = queue.splice(0, 10); // send max 10 at a time
|
|
38
|
+
|
|
39
|
+
for (const payload of batch) {
|
|
40
|
+
await sendNow(payload);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
isFlushing = false;
|
|
44
|
+
|
|
45
|
+
if (queue.length > 0) flush(); // flush remaining
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function sendNow(payload: QueueItem, attempts = 3): Promise<void> {
|
|
49
|
+
for (let i = 0; i < attempts; i++) {
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(ENDPOINT, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"x-api-key": _config?.apiKey ?? "",
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(payload),
|
|
58
|
+
});
|
|
59
|
+
if (res.ok) return; // success — stop retrying
|
|
60
|
+
} catch {
|
|
61
|
+
// network error — wait before retry
|
|
62
|
+
}
|
|
63
|
+
await sleep(1000 * (i + 1)); // 1s, 2s, 3s
|
|
64
|
+
}
|
|
65
|
+
// give up silently after 3 attempts — never crash user app
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sleep(ms: number) {
|
|
69
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Enqueue ──────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function enqueue(payload: QueueItem) {
|
|
75
|
+
if (queue.length >= 100) return; // cap queue to prevent memory issues
|
|
76
|
+
queue.push(payload);
|
|
77
|
+
scheduleFlush();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Send immediately (for critical messages) ─────────────────────────────────
|
|
81
|
+
|
|
82
|
+
function sendImmediate(payload: QueueItem) {
|
|
83
|
+
sendNow(payload).catch(() => {}); // fire and forget
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function getUrl(): string {
|
|
89
|
+
if (typeof window !== "undefined") return window.location.href;
|
|
90
|
+
try { return require("os").hostname(); } catch { return "server"; }
|
|
24
91
|
}
|
|
25
92
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
93
|
+
function getBrowser(): string {
|
|
94
|
+
if (typeof navigator !== "undefined") return navigator.userAgent;
|
|
95
|
+
return `Node.js ${typeof process !== "undefined" ? process.version : "unknown"}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getEnvironment(): string {
|
|
99
|
+
return _config?.environment ?? (typeof window !== "undefined" ? "browser" : "server");
|
|
100
|
+
}
|
|
101
|
+
|
|
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
|
+
}
|
|
113
|
+
|
|
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
|
+
}
|
|
123
|
+
|
|
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
|
+
}
|
|
209
|
+
|
|
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 {
|
|
36
217
|
type: "ERROR",
|
|
37
|
-
apiKey:
|
|
38
|
-
message
|
|
39
|
-
code:
|
|
40
|
-
stack
|
|
41
|
-
file
|
|
42
|
-
line
|
|
43
|
-
column
|
|
44
|
-
url:
|
|
45
|
-
typeof window !== "undefined" ? window.location.href : null,
|
|
218
|
+
apiKey: _config?.apiKey,
|
|
219
|
+
message,
|
|
220
|
+
code: getErrorCode(error),
|
|
221
|
+
stack,
|
|
222
|
+
file,
|
|
223
|
+
line,
|
|
224
|
+
column,
|
|
225
|
+
url: getUrl(),
|
|
46
226
|
timestamp: new Date().toISOString(),
|
|
47
|
-
environment:
|
|
48
|
-
browser:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
: "node",
|
|
52
|
-
error_category: data.code ?? "Error",
|
|
53
|
-
severity: data.severity ?? "high",
|
|
227
|
+
environment: getEnvironment(),
|
|
228
|
+
browser: getBrowser(),
|
|
229
|
+
error_category: category,
|
|
230
|
+
severity,
|
|
54
231
|
status: "open",
|
|
55
|
-
|
|
232
|
+
context: context || "auto",
|
|
233
|
+
};
|
|
56
234
|
}
|
|
57
235
|
|
|
58
|
-
|
|
59
|
-
init(cfg: Config) {
|
|
60
|
-
if (initialized) return;
|
|
236
|
+
// ─── Browser Listeners ────────────────────────────────────────────────────────
|
|
61
237
|
|
|
62
|
-
|
|
63
|
-
|
|
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;
|
|
247
|
+
}
|
|
64
248
|
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
sendError({
|
|
73
|
-
message: event.error.message,
|
|
74
|
-
code: event.error.name,
|
|
75
|
-
stack: event.error.stack,
|
|
76
|
-
file: event.filename,
|
|
77
|
-
line: event.lineno,
|
|
78
|
-
column: event.colno,
|
|
79
|
-
});
|
|
80
|
-
} else if (event.target) {
|
|
81
|
-
// Resource load failure
|
|
82
|
-
sendError({
|
|
83
|
-
message: "Resource failed to load",
|
|
84
|
-
code: "ResourceLoadError",
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}, true);
|
|
88
|
-
|
|
89
|
-
// Promise rejections
|
|
90
|
-
window.addEventListener("unhandledrejection", (event: any) => {
|
|
91
|
-
const err = event.reason;
|
|
92
|
-
sendError({
|
|
93
|
-
message: err?.message ?? String(err),
|
|
94
|
-
code: err?.name ?? "UnhandledPromiseRejection",
|
|
95
|
-
stack: err?.stack,
|
|
96
|
-
});
|
|
97
|
-
});
|
|
249
|
+
const err = (event as ErrorEvent).error || {
|
|
250
|
+
name: "Error",
|
|
251
|
+
message: (event as ErrorEvent).message,
|
|
252
|
+
stack: `at ${(event as ErrorEvent).filename}:${(event as ErrorEvent).lineno}:${(event as ErrorEvent).colno}`,
|
|
253
|
+
};
|
|
254
|
+
enqueue(buildErrorPayload(err, "window"));
|
|
255
|
+
}, true);
|
|
98
256
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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);
|
|
112
276
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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"));
|
|
121
286
|
}
|
|
122
|
-
|
|
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;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
123
297
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
(this as any).__reportli_url = url;
|
|
134
|
-
return open.call(this, method, url, ...rest);
|
|
135
|
-
};
|
|
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;
|
|
136
307
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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"));
|
|
144
321
|
}
|
|
145
322
|
});
|
|
323
|
+
}
|
|
146
324
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
message: "XHR connection failed",
|
|
150
|
-
code: "XHRConnectionError",
|
|
151
|
-
});
|
|
152
|
-
});
|
|
325
|
+
return originalSend.apply(this, args);
|
|
326
|
+
} as any;
|
|
153
327
|
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
}
|
|
344
|
+
|
|
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
|
+
};
|
|
371
|
+
|
|
372
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
373
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
export const Reportli = {
|
|
379
|
+
init(cfg: Config) {
|
|
380
|
+
if (initialized) return;
|
|
381
|
+
if (!cfg?.apiKey) return;
|
|
382
|
+
|
|
383
|
+
_config = cfg;
|
|
384
|
+
initialized = true;
|
|
385
|
+
|
|
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
|
+
});
|
|
395
|
+
|
|
396
|
+
// Activate listeners
|
|
397
|
+
if (typeof window !== "undefined") {
|
|
398
|
+
activateBrowserListeners();
|
|
399
|
+
} else if (typeof process !== "undefined" && process.versions?.node) {
|
|
400
|
+
activateServerListeners();
|
|
401
|
+
}
|
|
156
402
|
},
|
|
157
403
|
|
|
158
404
|
capture(error: unknown) {
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
});
|
|
165
|
-
} else {
|
|
166
|
-
sendError({
|
|
167
|
-
message: String(error),
|
|
168
|
-
code: "ManualCapture",
|
|
169
|
-
});
|
|
170
|
-
}
|
|
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"));
|
|
171
410
|
},
|
|
172
411
|
|
|
173
412
|
captureMessage(message: string) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
};
|
|
179
422
|
},
|
|
180
|
-
};
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export default Reportli;
|