reportli 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,387 +1,180 @@
1
- /**
2
- * Reportli SDK v1.0.1
3
- * Your AI agent that never sleeps.
4
- * Watches your SaaS, catches every error, files the GitHub issue — automatically.
5
- */
1
+ // reportli.ts
6
2
 
7
- const ENDPOINT = "https://fahikyfmgdyzejdfftox.supabase.co/functions/v1/rapid-processor";
3
+ const ENDPOINT =
4
+ "https://fahikyfmgdyzejdfftox.supabase.co/functions/v1/rapid-processor";
8
5
 
9
- const isBrowser = typeof window !== "undefined";
10
- const isNode = typeof process !== "undefined" && !!(process.versions?.node);
11
-
12
- let _apiKey = "";
13
-
14
- // ─── Helpers ──────────────────────────────────────────────────────────────────
15
-
16
- function getEnvironment(): string {
17
- return isBrowser ? "browser" : "server";
18
- }
19
-
20
- function getUrl(): string {
21
- if (isBrowser) return window.location.href;
22
- try { return require("os").hostname(); } catch { return "server"; }
23
- }
24
-
25
- function getBrowser(): string {
26
- if (isBrowser) return navigator.userAgent;
27
- return `Node.js ${typeof process !== "undefined" ? process.version : "unknown"}`;
28
- }
29
-
30
- function parseStack(stack: string | undefined): { file: string; line: number; column: number } {
31
- if (!stack) return { file: "unknown", line: 0, column: 0 };
32
- const match =
33
- stack.match(/at .+ \((.+):(\d+):(\d+)\)/) ||
34
- stack.match(/at (.+):(\d+):(\d+)/);
35
- return {
36
- file: match?.[1]?.split("/")?.pop() || "unknown",
37
- line: parseInt(match?.[2] || "0"),
38
- column: parseInt(match?.[3] || "0"),
39
- };
40
- }
41
-
42
- function getErrorCode(error: any): string {
43
- return error?.code || error?.status?.toString() || error?.statusCode?.toString() || error?.name || "ERR_UNKNOWN";
44
- }
45
-
46
- function classifyError(error: any, context?: string): { errorType: string; severity: "low" | "medium" | "high" | "critical" } {
47
- const msg = String(error?.message || error || "").toLowerCase();
48
- const name = String(error?.name || "").toLowerCase();
49
-
50
- if (name.includes("quotaexceeded") || msg.includes("quota exceeded") || msg.includes("localstorage") || msg.includes("indexeddb"))
51
- return { errorType: "localStorage quota exceeded", severity: "medium" };
52
- if (msg.includes("hydration") || msg.includes("does not match server"))
53
- return { errorType: "React hydration error", severity: "high" };
54
- if (msg.includes("invalid hook call") || msg.includes("rules of hooks"))
55
- return { errorType: "Invalid hook call error", severity: "critical" };
56
- if (msg.includes("failed prop type") || msg.includes("invalid prop"))
57
- return { errorType: "Props type error", severity: "low" };
58
- if (msg.includes("render") || msg.includes("react error boundary"))
59
- return { errorType: "Component render error", severity: "critical" };
60
- if (msg.includes("route not found") || msg.includes("404 route"))
61
- return { errorType: "Route not found error", severity: "medium" };
62
- if (msg.includes("failed to fetch dynamically imported") || msg.includes("dynamic import"))
63
- return { errorType: "Dynamic import error", severity: "high" };
64
- if (msg.includes("suspense"))
65
- return { errorType: "Suspense boundary error", severity: "medium" };
66
- if (name === "typeerror" || msg.startsWith("typeerror"))
67
- return { errorType: "TypeError", severity: "high" };
68
- if (name === "referenceerror")
69
- return { errorType: "ReferenceError", severity: "critical" };
70
- if (name === "rangeerror" || msg.includes("maximum call stack"))
71
- return { errorType: "Stack overflow error", severity: "critical" };
72
- if (name === "syntaxerror")
73
- return { errorType: "SyntaxError", severity: "high" };
74
- if (msg.includes("stripe") && (msg.includes("init") || msg.includes("key")))
75
- return { errorType: "Stripe initialization error", severity: "critical" };
76
- if (msg.includes("payment") || msg.includes("card declined"))
77
- return { errorType: "Payment processing error", severity: "critical" };
78
- if (msg.includes("checkout session"))
79
- return { errorType: "Checkout session error", severity: "high" };
80
- if (msg.includes("refund failed"))
81
- return { errorType: "Refund failed error", severity: "high" };
82
- if (msg.includes("supabase") || msg.includes("postgresterror"))
83
- return { errorType: "Supabase query error", severity: "critical" };
84
- if (msg.includes("unique constraint") || msg.includes("duplicate key"))
85
- return { errorType: "Unique constraint error", severity: "high" };
86
- if (msg.includes("foreign key"))
87
- return { errorType: "Foreign key error", severity: "high" };
88
- if (msg.includes("transaction failed") || msg.includes("rollback"))
89
- return { errorType: "Transaction failed error", severity: "critical" };
90
- if (msg.includes("connection lost") || msg.includes("econnrefused"))
91
- return { errorType: "Connection lost error", severity: "critical" };
92
- if (msg.includes("query timeout"))
93
- return { errorType: "Query timeout error", severity: "high" };
94
- if (msg.includes("jwt expired") || msg.includes("token expired"))
95
- return { errorType: "JWT expired error", severity: "high" };
96
- if (msg.includes("jwt") && msg.includes("invalid"))
97
- return { errorType: "JWT verification error", severity: "high" };
98
- if (msg.includes("firebase admin"))
99
- return { errorType: "Firebase admin error", severity: "critical" };
100
- if (msg.includes("unauthorized") || msg.includes("permission denied"))
101
- return { errorType: "Unauthorized access error", severity: "high" };
102
- if (msg.includes("login failed") || msg.includes("invalid credentials"))
103
- return { errorType: "Login failed error", severity: "medium" };
104
- if (msg.includes("session ended") || msg.includes("session expired"))
105
- return { errorType: "Session ended error", severity: "medium" };
106
- if (msg.includes("oauth"))
107
- return { errorType: "OAuth callback error", severity: "high" };
108
- if (msg.includes("cron job") || msg.includes("cron failed"))
109
- return { errorType: "Cron job failed", severity: "high" };
110
- if (msg.includes("queue processing") || msg.includes("bullmq"))
111
- return { errorType: "Queue processing error", severity: "high" };
112
- if (msg.includes("webhook"))
113
- return { errorType: "Webhook delivery failed", severity: "high" };
114
- if (msg.includes("retry limit"))
115
- return { errorType: "Retry limit exceeded", severity: "high" };
116
- if (msg.includes("out of memory") || msg.includes("heap limit"))
117
- return { errorType: "Out of memory error", severity: "critical" };
118
- if (msg.includes("cannot find module"))
119
- return { errorType: "Module not found error", severity: "critical" };
120
- if (msg.includes("email") || msg.includes("smtp") || msg.includes("sendgrid") || msg.includes("nodemailer"))
121
- return { errorType: "Email sending failed", severity: "high" };
122
- if (msg.includes("onesignal") || msg.includes("push notification"))
123
- return { errorType: "OneSignal API error", severity: "high" };
124
- if (msg.includes("file upload") || msg.includes("upload failed"))
125
- return { errorType: "File upload failed", severity: "high" };
126
- if (msg.includes("file size exceeded") || msg.includes("payload too large"))
127
- return { errorType: "File size exceeded", severity: "medium" };
128
- if (msg.includes("invalid file type") || msg.includes("mime type"))
129
- return { errorType: "Invalid file type", severity: "medium" };
130
- if (msg.includes("cors") || msg.includes("cross-origin"))
131
- return { errorType: "CORS error", severity: "high" };
132
- if (msg.includes("fetch failed") || msg.includes("failed to fetch"))
133
- return { errorType: "Fetch failed error", severity: "high" };
134
- if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("etimedout"))
135
- return { errorType: "Request timeout error", severity: "medium" };
136
- if (msg.includes("websocket"))
137
- return { errorType: "WebSocket connection error", severity: "high" };
138
- if (context === "unhandledrejection")
139
- return { errorType: "Unhandled promise rejection", severity: "medium" };
140
- if (context === "uncaughtexception")
141
- return { errorType: "Uncaught exception", severity: "high" };
142
- if (context === "express")
143
- return { errorType: "Route handler error", severity: "high" };
144
- return { errorType: "Uncaught exception", severity: "medium" };
145
- }
146
-
147
- function normalizeError(err: any): { name: string; message: string; stack?: string } {
148
- if (err instanceof Error) return { name: err.name, message: err.message, stack: err.stack };
149
- if (typeof err === "string") return { name: "Error", message: err, stack: new Error(err).stack };
150
- if (err && typeof err === "object") return {
151
- name: err.name || err.code || "Error",
152
- message: err.message || err.reason || JSON.stringify(err),
153
- stack: err.stack || new Error(err.message).stack,
154
- };
155
- return { name: "Error", message: "Unknown error", stack: new Error().stack };
156
- }
157
-
158
- // ─── Send ─────────────────────────────────────────────────────────────────────
6
+ type Config = {
7
+ apiKey: string;
8
+ environment?: string;
9
+ };
159
10
 
160
- async function send(payload: object): Promise<void> {
161
- try {
162
- if ((payload as any).message?.includes("rapid-processor")) return;
163
- await fetch(ENDPOINT, {
164
- method: "POST",
165
- headers: {
166
- "Content-Type": "application/json",
167
- "x-api-key": _apiKey,
168
- },
169
- body: JSON.stringify(payload),
170
- });
171
- } catch {
172
- // Fail silently — never break user app
173
- }
11
+ let initialized = false;
12
+ let config: Config;
13
+
14
+ function post(payload: Record<string, unknown>) {
15
+ fetch(ENDPOINT, {
16
+ method: "POST",
17
+ headers: {
18
+ "Content-Type": "application/json",
19
+ },
20
+ body: JSON.stringify(payload),
21
+ }).catch(() => {
22
+ // Never throw from the SDK.
23
+ });
174
24
  }
175
25
 
176
- // ─── Build error payload ──────────────────────────────────────────────────────
177
-
178
- function buildErrorPayload(error: any, context?: string): object {
179
- const normalized = normalizeError(error);
180
- const { file, line, column } = parseStack(normalized.stack);
181
- const { errorType, severity } = classifyError(normalized, context);
182
-
183
- return {
26
+ function sendError(data: {
27
+ message: string;
28
+ code?: string;
29
+ stack?: string;
30
+ file?: string;
31
+ line?: number;
32
+ column?: number;
33
+ severity?: string;
34
+ }) {
35
+ post({
184
36
  type: "ERROR",
185
- apiKey: _apiKey,
186
- message: normalized.message,
187
- code: getErrorCode(error),
188
- stack: normalized.stack || "",
189
- file,
190
- line,
191
- column,
192
- url: getUrl(),
37
+ apiKey: config.apiKey,
38
+ message: data.message,
39
+ code: data.code ?? "Error",
40
+ stack: data.stack ?? null,
41
+ file: data.file ?? null,
42
+ line: data.line ?? null,
43
+ column: data.column ?? null,
44
+ url:
45
+ typeof window !== "undefined" ? window.location.href : null,
193
46
  timestamp: new Date().toISOString(),
194
- environment: getEnvironment(),
195
- browser: getBrowser(),
196
- error_category: errorType,
197
- severity,
47
+ environment: config.environment ?? "production",
48
+ browser:
49
+ typeof navigator !== "undefined"
50
+ ? navigator.userAgent
51
+ : "node",
52
+ error_category: data.code ?? "Error",
53
+ severity: data.severity ?? "high",
198
54
  status: "open",
199
- };
200
- }
201
-
202
- // ─── Browser listeners ────────────────────────────────────────────────────────
203
-
204
- function activateBrowserListeners(): void {
205
- // JS runtime errors + resource load errors
206
- window.addEventListener("error", (event) => {
207
- if ((event as any).filename?.includes("rapid-processor")) return;
208
-
209
- const target = event.target as HTMLElement;
210
- if (target && target.tagName) {
211
- const tag = target.tagName;
212
- const src = (target as any).src || (target as any).href || "";
213
- if (["IMG", "SCRIPT", "LINK", "VIDEO", "AUDIO", "SOURCE"].includes(tag)) {
214
- send(buildErrorPayload({
215
- name: "ResourceError",
216
- message: `${tag} failed to load: ${src}`,
217
- stack: `at ${window.location.href}`,
218
- }));
219
- return;
220
- }
221
- }
222
-
223
- const err = (event as ErrorEvent).error || {
224
- name: "Error",
225
- message: (event as ErrorEvent).message,
226
- stack: `${(event as ErrorEvent).message} at ${(event as ErrorEvent).filename}:${(event as ErrorEvent).lineno}:${(event as ErrorEvent).colno}`,
227
- };
228
- send(buildErrorPayload(err));
229
- }, true);
230
-
231
- // Unhandled promise rejections
232
- window.addEventListener("unhandledrejection", (event) => {
233
- send(buildErrorPayload(event.reason || { message: "Unhandled Promise Rejection" }, "unhandledrejection"));
234
- });
235
-
236
- // Intercept fetch
237
- const originalFetch = window.fetch;
238
- window.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
239
- const url = typeof input === "string" ? input : (input instanceof URL ? input.toString() : (input as Request).url);
240
- if (url.includes("rapid-processor") || url.includes("supabase.co/functions")) {
241
- return originalFetch.apply(this, arguments as any);
242
- }
243
- try {
244
- const response = await originalFetch.apply(this, arguments as any);
245
- if (!response.ok) {
246
- send(buildErrorPayload({
247
- name: `API ${response.status} Error`,
248
- message: `HTTP ${response.status}: ${init?.method || "GET"} ${url}`,
249
- stack: `${init?.method || "GET"} ${url} → ${response.status} ${response.statusText}`,
250
- status: response.status,
251
- }));
252
- }
253
- return response;
254
- } catch (err: any) {
255
- send(buildErrorPayload({
256
- name: "FetchError",
257
- message: `Fetch failed: ${init?.method || "GET"} ${url} — ${err.message}`,
258
- stack: err.stack,
259
- }));
260
- throw err;
261
- }
262
- };
263
-
264
- // Intercept XHR
265
- const originalOpen = XMLHttpRequest.prototype.open;
266
- const originalSend = XMLHttpRequest.prototype.send;
267
-
268
- XMLHttpRequest.prototype.open = function (method: string, url: string) {
269
- (this as any)._r_method = method;
270
- (this as any)._r_url = url;
271
- return originalOpen.apply(this, arguments as any);
272
- } as any;
273
-
274
- XMLHttpRequest.prototype.send = function () {
275
- this.addEventListener("loadend", () => {
276
- const url: string = (this as any)._r_url || "";
277
- const method: string = (this as any)._r_method || "GET";
278
- if (url.includes("rapid-processor")) return;
279
- if (this.status >= 400 || this.status === 0) {
280
- send(buildErrorPayload({
281
- name: `XHR ${this.status} Error`,
282
- message: `XHR ${this.status}: ${method} ${url}`,
283
- stack: `${method} ${url} → ${this.status} ${this.statusText}`,
284
- status: this.status,
285
- }));
286
- }
287
- });
288
- return originalSend.apply(this, arguments as any);
289
- } as any;
290
-
291
- // Disconnect when page closes
292
- window.addEventListener("beforeunload", () => {
293
- try {
294
- navigator.sendBeacon(ENDPOINT, JSON.stringify({
295
- type: "SDK_DISCONNECTED",
296
- apiKey: _apiKey,
297
- timestamp: new Date().toISOString(),
298
- environment: "browser",
299
- url: getUrl(),
300
- }));
301
- } catch { /* silent */ }
302
55
  });
303
56
  }
304
57
 
305
- // ─── Server listeners ─────────────────────────────────────────────────────────
306
-
307
- function activateServerListeners(): void {
308
- process.on("uncaughtException", (error: Error) => {
309
- send(buildErrorPayload(error, "uncaughtexception"));
310
- });
311
-
312
- process.on("unhandledRejection", (reason: any) => {
313
- send(buildErrorPayload(
314
- reason instanceof Error ? reason : { name: "UnhandledRejection", message: String(reason), stack: "" },
315
- "unhandledrejection"
316
- ));
317
- });
318
-
319
- const shutdown = async () => {
320
- await send({
321
- type: "SDK_DISCONNECTED",
322
- apiKey: _apiKey,
323
- timestamp: new Date().toISOString(),
324
- environment: "server",
325
- url: getUrl(),
58
+ export const Reportli = {
59
+ init(cfg: Config) {
60
+ if (initialized) return;
61
+
62
+ config = cfg;
63
+ initialized = true;
64
+
65
+ console.log(
66
+ "✅ Reportli initialized successfully. Error monitoring is active."
67
+ );
68
+
69
+ // JS errors
70
+ window.addEventListener("error", (event: any) => {
71
+ if (event.error instanceof Error) {
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
+ });
326
97
  });
327
- process.exit(0);
328
- };
329
98
 
330
- process.on("SIGTERM", shutdown);
331
- process.on("SIGINT", shutdown);
332
- }
99
+ // fetch interception
100
+ const originalFetch = window.fetch;
101
+ window.fetch = async (...args) => {
102
+ try {
103
+ const response = await originalFetch(...args);
104
+
105
+ if (!response.ok) {
106
+ sendError({
107
+ message: `Fetch HTTP ${response.status}`,
108
+ code: `HTTP_${response.status}`,
109
+ severity: "medium",
110
+ });
111
+ }
112
+
113
+ return response;
114
+ } catch (e: any) {
115
+ sendError({
116
+ message: e?.message ?? "Fetch failed",
117
+ code: "FetchError",
118
+ stack: e?.stack,
119
+ });
120
+ throw e;
121
+ }
122
+ };
333
123
 
334
- // ─── Public API ───────────────────────────────────────────────────────────────
124
+ // XMLHttpRequest interception
125
+ const open = XMLHttpRequest.prototype.open;
126
+ const send = XMLHttpRequest.prototype.send;
127
+
128
+ XMLHttpRequest.prototype.open = function (
129
+ method,
130
+ url,
131
+ ...rest
132
+ ) {
133
+ (this as any).__reportli_url = url;
134
+ return open.call(this, method, url, ...rest);
135
+ };
335
136
 
336
- const Reportli = {
337
- init({ apiKey }: { apiKey: string }): void {
338
- if (!apiKey || _apiKey) return;
339
- _apiKey = apiKey;
137
+ XMLHttpRequest.prototype.send = function (...args) {
138
+ this.addEventListener("load", function () {
139
+ if (this.status >= 400) {
140
+ sendError({
141
+ message: `XHR HTTP ${this.status}`,
142
+ code: `XHR_${this.status}`,
143
+ });
144
+ }
145
+ });
340
146
 
341
- // Send SDK_INITIALIZED once only
342
- if (isBrowser && typeof localStorage !== "undefined") {
343
- const key = `reportli_init_${apiKey}`;
344
- if (!localStorage.getItem(key)) {
345
- localStorage.setItem(key, "true");
346
- send({
347
- type: "SDK_INITIALIZED",
348
- apiKey: _apiKey,
349
- timestamp: new Date().toISOString(),
350
- environment: getEnvironment(),
351
- url: getUrl(),
352
- browser: getBrowser(),
147
+ this.addEventListener("error", function () {
148
+ sendError({
149
+ message: "XHR connection failed",
150
+ code: "XHRConnectionError",
353
151
  });
354
- }
355
- } else {
356
- send({
357
- type: "SDK_INITIALIZED",
358
- apiKey: _apiKey,
359
- timestamp: new Date().toISOString(),
360
- environment: getEnvironment(),
361
- url: getUrl(),
362
- browser: getBrowser(),
363
152
  });
364
- }
365
153
 
366
- if (isBrowser) {
367
- activateBrowserListeners();
368
- } else if (isNode) {
369
- activateServerListeners();
370
- }
154
+ return send.apply(this, args as any);
155
+ };
371
156
  },
372
157
 
373
- capture(error: any): void {
374
- if (!_apiKey) return;
375
- send(buildErrorPayload(error, "manual"));
158
+ capture(error: unknown) {
159
+ if (error instanceof Error) {
160
+ sendError({
161
+ message: error.message,
162
+ code: error.name,
163
+ stack: error.stack,
164
+ });
165
+ } else {
166
+ sendError({
167
+ message: String(error),
168
+ code: "ManualCapture",
169
+ });
170
+ }
376
171
  },
377
172
 
378
- errorHandler() {
379
- return function (err: any, _req: any, _res: any, next: any) {
380
- send(buildErrorPayload(err, "express"));
381
- next(err);
382
- };
173
+ captureMessage(message: string) {
174
+ sendError({
175
+ message,
176
+ code: "Message",
177
+ severity: "low",
178
+ });
383
179
  },
384
- };
385
-
386
- export default Reportli;
387
- export { Reportli };
180
+ };