@error-explorer/node 1.1.1

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,1597 @@
1
+ import * as os2 from 'os';
2
+ import * as path from 'path';
3
+ import * as crypto from 'crypto';
4
+ import * as http from 'http';
5
+ import * as https from 'https';
6
+
7
+ // src/config/Config.ts
8
+ var DEFAULT_ENDPOINT = "https://error-explorer.com/api/v1/webhook";
9
+ function parseDsn(dsn) {
10
+ let url;
11
+ try {
12
+ url = new URL(dsn);
13
+ } catch {
14
+ throw new Error(`Invalid DSN format: ${dsn}. Expected format: https://token@host/path`);
15
+ }
16
+ const token = url.username;
17
+ if (!token) {
18
+ throw new Error("DSN must contain a token (username part)");
19
+ }
20
+ url.username = "";
21
+ url.password = "";
22
+ return {
23
+ token,
24
+ endpoint: url.toString().replace(/\/$/, "")
25
+ };
26
+ }
27
+ function detectEnvironment() {
28
+ return process.env["NODE_ENV"] || "development";
29
+ }
30
+ function getDefaultServerName() {
31
+ try {
32
+ return os2.hostname();
33
+ } catch {
34
+ return "unknown";
35
+ }
36
+ }
37
+ function resolveConfig(options) {
38
+ let token;
39
+ let endpoint;
40
+ if (options.dsn) {
41
+ const parsed = parseDsn(options.dsn);
42
+ token = parsed.token;
43
+ endpoint = options.endpoint || parsed.endpoint;
44
+ } else if (options.token) {
45
+ token = options.token;
46
+ endpoint = options.endpoint || DEFAULT_ENDPOINT;
47
+ } else {
48
+ throw new Error("Either token or dsn must be provided");
49
+ }
50
+ if (!token.startsWith("ee_")) {
51
+ console.warn('[ErrorExplorer] Token should start with "ee_" prefix');
52
+ }
53
+ return {
54
+ token,
55
+ endpoint,
56
+ environment: options.environment || detectEnvironment(),
57
+ release: options.release || "",
58
+ serverName: options.serverName || getDefaultServerName(),
59
+ autoCapture: {
60
+ uncaughtExceptions: options.autoCapture?.uncaughtExceptions ?? true,
61
+ unhandledRejections: options.autoCapture?.unhandledRejections ?? true,
62
+ console: options.autoCapture?.console ?? false
63
+ },
64
+ breadcrumbs: {
65
+ enabled: options.breadcrumbs?.enabled ?? true,
66
+ maxBreadcrumbs: options.breadcrumbs?.maxBreadcrumbs ?? 50,
67
+ http: options.breadcrumbs?.http ?? true,
68
+ console: options.breadcrumbs?.console ?? true
69
+ },
70
+ beforeSend: options.beforeSend,
71
+ ignoreErrors: options.ignoreErrors || [],
72
+ maxRetries: options.maxRetries ?? 3,
73
+ timeout: options.timeout ?? 5e3,
74
+ debug: options.debug ?? false,
75
+ hmacSecret: options.hmacSecret,
76
+ exitOnUncaughtException: options.exitOnUncaughtException ?? true
77
+ };
78
+ }
79
+ function matchesPattern(str, patterns) {
80
+ return patterns.some((pattern) => {
81
+ if (typeof pattern === "string") {
82
+ return str.includes(pattern);
83
+ }
84
+ return pattern.test(str);
85
+ });
86
+ }
87
+ function parseStackTrace(stack) {
88
+ if (!stack) {
89
+ return [];
90
+ }
91
+ const frames = [];
92
+ const lines = stack.split("\n");
93
+ for (const line of lines) {
94
+ const frame = parseStackLine(line);
95
+ if (frame) {
96
+ frames.push(frame);
97
+ }
98
+ }
99
+ return frames;
100
+ }
101
+ function parseStackLine(line) {
102
+ if (!line.trim().startsWith("at ")) {
103
+ return null;
104
+ }
105
+ const content = line.replace(/^\s*at\s+/, "").trim();
106
+ const withFunctionMatch = content.match(
107
+ /^(?:async\s+)?(.+?)\s+\((.+?):(\d+):(\d+)\)$/
108
+ );
109
+ if (withFunctionMatch) {
110
+ const [, fn, file, lineNo, colNo] = withFunctionMatch;
111
+ return createFrame(fn, file, lineNo, colNo);
112
+ }
113
+ const anonymousMatch = content.match(/^(.+?):(\d+):(\d+)$/);
114
+ if (anonymousMatch) {
115
+ const [, file, lineNo, colNo] = anonymousMatch;
116
+ return createFrame(void 0, file, lineNo, colNo);
117
+ }
118
+ return null;
119
+ }
120
+ function createFrame(fn, file, lineNo, colNo) {
121
+ const frame = {};
122
+ if (fn) {
123
+ frame.function = cleanFunctionName(fn);
124
+ }
125
+ if (file) {
126
+ frame.filename = file;
127
+ frame.abs_path = path.isAbsolute(file) ? file : void 0;
128
+ frame.in_app = !file.includes("node_modules") && !file.startsWith("node:");
129
+ if (frame.in_app && file.includes("/")) {
130
+ const parts = file.split("/");
131
+ frame.module = parts[parts.length - 1]?.replace(/\.[^/.]+$/, "");
132
+ }
133
+ }
134
+ if (lineNo) {
135
+ frame.lineno = parseInt(lineNo, 10);
136
+ }
137
+ if (colNo) {
138
+ frame.colno = parseInt(colNo, 10);
139
+ }
140
+ return frame;
141
+ }
142
+ function cleanFunctionName(name) {
143
+ name = name.replace(/^Object\./, "");
144
+ name = name.replace(/^Module\./, "");
145
+ if (name === "<anonymous>") {
146
+ return "(anonymous)";
147
+ }
148
+ return name;
149
+ }
150
+ function getErrorName(error) {
151
+ if (error instanceof Error) {
152
+ return error.name || error.constructor.name;
153
+ }
154
+ if (typeof error === "object" && error !== null) {
155
+ const obj = error;
156
+ if (typeof obj["name"] === "string") {
157
+ return obj["name"];
158
+ }
159
+ }
160
+ return "Error";
161
+ }
162
+ function getErrorMessage(error) {
163
+ if (error instanceof Error) {
164
+ return error.message;
165
+ }
166
+ if (typeof error === "string") {
167
+ return error;
168
+ }
169
+ if (typeof error === "object" && error !== null) {
170
+ const obj = error;
171
+ if (typeof obj["message"] === "string") {
172
+ return obj["message"];
173
+ }
174
+ try {
175
+ return JSON.stringify(error);
176
+ } catch {
177
+ return String(error);
178
+ }
179
+ }
180
+ return String(error);
181
+ }
182
+ function getErrorStack(error) {
183
+ if (error instanceof Error) {
184
+ return error.stack;
185
+ }
186
+ if (typeof error === "object" && error !== null) {
187
+ const obj = error;
188
+ if (typeof obj["stack"] === "string") {
189
+ return obj["stack"];
190
+ }
191
+ }
192
+ return void 0;
193
+ }
194
+ function generateUuid() {
195
+ return crypto.randomUUID();
196
+ }
197
+ function generateTransactionId() {
198
+ return `txn_${crypto.randomBytes(12).toString("hex")}`;
199
+ }
200
+
201
+ // src/context/ProcessContext.ts
202
+ function collectProcessContext() {
203
+ const memoryUsage = process.memoryUsage();
204
+ const cpuUsage = process.cpuUsage();
205
+ return {
206
+ pid: process.pid,
207
+ ppid: process.ppid,
208
+ uptime: process.uptime(),
209
+ memory: {
210
+ rss: memoryUsage.rss,
211
+ heapTotal: memoryUsage.heapTotal,
212
+ heapUsed: memoryUsage.heapUsed,
213
+ external: memoryUsage.external,
214
+ arrayBuffers: memoryUsage.arrayBuffers
215
+ },
216
+ cpu: {
217
+ user: cpuUsage.user,
218
+ system: cpuUsage.system
219
+ }
220
+ };
221
+ }
222
+ var cachedOsContext = null;
223
+ function collectOsContext() {
224
+ if (cachedOsContext) {
225
+ return cachedOsContext;
226
+ }
227
+ cachedOsContext = {
228
+ name: os2.platform(),
229
+ version: os2.release(),
230
+ arch: os2.arch(),
231
+ kernel_version: os2.version()
232
+ };
233
+ return cachedOsContext;
234
+ }
235
+ function resetOsContext() {
236
+ cachedOsContext = null;
237
+ }
238
+
239
+ // src/context/RuntimeContext.ts
240
+ var cachedRuntimeContext = null;
241
+ function collectRuntimeContext() {
242
+ if (cachedRuntimeContext) {
243
+ return cachedRuntimeContext;
244
+ }
245
+ cachedRuntimeContext = {
246
+ name: "node",
247
+ version: process.version
248
+ };
249
+ return cachedRuntimeContext;
250
+ }
251
+ function resetRuntimeContext() {
252
+ cachedRuntimeContext = null;
253
+ }
254
+ var serverContext = {
255
+ name: void 0,
256
+ hostname: os2.hostname()
257
+ };
258
+ function getServerContext() {
259
+ return serverContext;
260
+ }
261
+ function setServerName(name) {
262
+ serverContext = {
263
+ ...serverContext,
264
+ name
265
+ };
266
+ }
267
+ function resetServerContext() {
268
+ serverContext = {
269
+ name: void 0,
270
+ hostname: os2.hostname()
271
+ };
272
+ }
273
+
274
+ // src/context/UserContext.ts
275
+ var UserContextManager = class {
276
+ user = null;
277
+ /**
278
+ * Set user context
279
+ */
280
+ setUser(user) {
281
+ this.user = user;
282
+ }
283
+ /**
284
+ * Get current user context
285
+ */
286
+ getUser() {
287
+ return this.user;
288
+ }
289
+ /**
290
+ * Clear user context
291
+ */
292
+ clearUser() {
293
+ this.user = null;
294
+ }
295
+ /**
296
+ * Reset manager state
297
+ */
298
+ reset() {
299
+ this.user = null;
300
+ }
301
+ };
302
+ var userContextManager = null;
303
+ function getUserContextManager() {
304
+ if (!userContextManager) {
305
+ userContextManager = new UserContextManager();
306
+ }
307
+ return userContextManager;
308
+ }
309
+ function resetUserContextManager() {
310
+ if (userContextManager) {
311
+ userContextManager.reset();
312
+ }
313
+ userContextManager = null;
314
+ }
315
+
316
+ // src/context/RequestContext.ts
317
+ function extractRequestContext(req) {
318
+ const headers = {};
319
+ const sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
320
+ for (const [key, value] of Object.entries(req.headers)) {
321
+ const lowerKey = key.toLowerCase();
322
+ if (!sensitiveHeaders.includes(lowerKey)) {
323
+ headers[key] = value;
324
+ } else {
325
+ headers[key] = "[Filtered]";
326
+ }
327
+ }
328
+ const context = {
329
+ method: req.method,
330
+ headers,
331
+ url: getFullUrl(req)
332
+ };
333
+ const url = req.url || "";
334
+ const queryIndex = url.indexOf("?");
335
+ if (queryIndex !== -1) {
336
+ context.query_string = url.substring(queryIndex + 1);
337
+ }
338
+ const expressReq = req;
339
+ if (expressReq.body !== void 0) {
340
+ context.data = sanitizeBody(expressReq.body);
341
+ }
342
+ if (expressReq.cookies) {
343
+ context.cookies = sanitizeCookies(expressReq.cookies);
344
+ }
345
+ return context;
346
+ }
347
+ function getFullUrl(req) {
348
+ const expressReq = req;
349
+ const path2 = expressReq.originalUrl || req.url || "/";
350
+ const protocol = req.headers["x-forwarded-proto"] || "http";
351
+ const host = req.headers.host || "localhost";
352
+ return `${protocol}://${host}${path2}`;
353
+ }
354
+ function sanitizeBody(body) {
355
+ if (!body || typeof body !== "object") {
356
+ return body;
357
+ }
358
+ const sensitiveKeys = ["password", "secret", "token", "api_key", "apiKey", "credit_card", "creditCard", "cvv", "ssn"];
359
+ const sanitized = {};
360
+ for (const [key, value] of Object.entries(body)) {
361
+ const lowerKey = key.toLowerCase();
362
+ if (sensitiveKeys.some((sk) => lowerKey.includes(sk))) {
363
+ sanitized[key] = "[Filtered]";
364
+ } else if (typeof value === "object" && value !== null) {
365
+ sanitized[key] = sanitizeBody(value);
366
+ } else {
367
+ sanitized[key] = value;
368
+ }
369
+ }
370
+ return sanitized;
371
+ }
372
+ function sanitizeCookies(cookies) {
373
+ const sensitiveNames = ["session", "sess", "token", "auth", "jwt", "sid"];
374
+ const sanitized = {};
375
+ for (const [key, value] of Object.entries(cookies)) {
376
+ const lowerKey = key.toLowerCase();
377
+ if (sensitiveNames.some((sn) => lowerKey.includes(sn))) {
378
+ sanitized[key] = "[Filtered]";
379
+ } else {
380
+ sanitized[key] = value;
381
+ }
382
+ }
383
+ return sanitized;
384
+ }
385
+
386
+ // src/breadcrumbs/BreadcrumbManager.ts
387
+ var BreadcrumbManager = class {
388
+ breadcrumbs = [];
389
+ maxBreadcrumbs = 50;
390
+ /**
391
+ * Initialize with configuration
392
+ */
393
+ init(config) {
394
+ this.maxBreadcrumbs = config.breadcrumbs.maxBreadcrumbs;
395
+ this.breadcrumbs = [];
396
+ }
397
+ /**
398
+ * Add a breadcrumb
399
+ */
400
+ add(breadcrumb) {
401
+ const internal = {
402
+ ...breadcrumb,
403
+ timestamp: breadcrumb.timestamp ?? Date.now()
404
+ };
405
+ this.breadcrumbs.push(internal);
406
+ if (this.breadcrumbs.length > this.maxBreadcrumbs) {
407
+ this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
408
+ }
409
+ }
410
+ /**
411
+ * Get all breadcrumbs (oldest first)
412
+ */
413
+ getAll() {
414
+ return [...this.breadcrumbs];
415
+ }
416
+ /**
417
+ * Get the last N breadcrumbs
418
+ */
419
+ getLast(count) {
420
+ return this.breadcrumbs.slice(-count);
421
+ }
422
+ /**
423
+ * Clear all breadcrumbs
424
+ */
425
+ clear() {
426
+ this.breadcrumbs = [];
427
+ }
428
+ /**
429
+ * Reset manager
430
+ */
431
+ reset() {
432
+ this.breadcrumbs = [];
433
+ this.maxBreadcrumbs = 50;
434
+ }
435
+ };
436
+ var breadcrumbManager = null;
437
+ function initBreadcrumbManager(config) {
438
+ if (!breadcrumbManager) {
439
+ breadcrumbManager = new BreadcrumbManager();
440
+ }
441
+ breadcrumbManager.init(config);
442
+ return breadcrumbManager;
443
+ }
444
+ function getBreadcrumbManager() {
445
+ return breadcrumbManager;
446
+ }
447
+ function resetBreadcrumbManager() {
448
+ if (breadcrumbManager) {
449
+ breadcrumbManager.reset();
450
+ }
451
+ breadcrumbManager = null;
452
+ }
453
+
454
+ // src/breadcrumbs/ConsoleTracker.ts
455
+ var ConsoleTracker = class {
456
+ originalMethods = null;
457
+ started = false;
458
+ /**
459
+ * Start tracking console calls
460
+ */
461
+ start() {
462
+ if (this.started) {
463
+ return;
464
+ }
465
+ this.originalMethods = {
466
+ log: console.log.bind(console),
467
+ info: console.info.bind(console),
468
+ warn: console.warn.bind(console),
469
+ error: console.error.bind(console),
470
+ debug: console.debug.bind(console)
471
+ };
472
+ const methods = ["log", "info", "warn", "error", "debug"];
473
+ for (const method of methods) {
474
+ this.wrapConsoleMethod(method);
475
+ }
476
+ this.started = true;
477
+ }
478
+ /**
479
+ * Stop tracking console calls
480
+ */
481
+ stop() {
482
+ if (!this.started || !this.originalMethods) {
483
+ return;
484
+ }
485
+ console.log = this.originalMethods.log;
486
+ console.info = this.originalMethods.info;
487
+ console.warn = this.originalMethods.warn;
488
+ console.error = this.originalMethods.error;
489
+ console.debug = this.originalMethods.debug;
490
+ this.originalMethods = null;
491
+ this.started = false;
492
+ }
493
+ /**
494
+ * Wrap a console method to add breadcrumbs
495
+ */
496
+ wrapConsoleMethod(method) {
497
+ const original = this.originalMethods[method];
498
+ const self = this;
499
+ console[method] = function(...args) {
500
+ self.addBreadcrumb(method, args);
501
+ original(...args);
502
+ };
503
+ }
504
+ /**
505
+ * Add a breadcrumb for a console call
506
+ */
507
+ addBreadcrumb(method, args) {
508
+ const manager = getBreadcrumbManager();
509
+ if (!manager) {
510
+ return;
511
+ }
512
+ const level = this.methodToLevel(method);
513
+ const message = this.formatArgs(args);
514
+ manager.add({
515
+ type: "console",
516
+ category: `console.${method}`,
517
+ message,
518
+ level,
519
+ data: {
520
+ arguments: this.serializeArgs(args)
521
+ }
522
+ });
523
+ }
524
+ /**
525
+ * Map console method to breadcrumb level
526
+ */
527
+ methodToLevel(method) {
528
+ switch (method) {
529
+ case "error":
530
+ return "error";
531
+ case "warn":
532
+ return "warning";
533
+ case "debug":
534
+ return "debug";
535
+ default:
536
+ return "info";
537
+ }
538
+ }
539
+ /**
540
+ * Format arguments into a string message
541
+ */
542
+ formatArgs(args) {
543
+ return args.map((arg) => {
544
+ if (typeof arg === "string") {
545
+ return arg;
546
+ }
547
+ try {
548
+ return JSON.stringify(arg);
549
+ } catch {
550
+ return String(arg);
551
+ }
552
+ }).join(" ").substring(0, 500);
553
+ }
554
+ /**
555
+ * Serialize arguments for data storage
556
+ */
557
+ serializeArgs(args) {
558
+ return args.map((arg) => {
559
+ if (typeof arg === "string" || typeof arg === "number" || typeof arg === "boolean") {
560
+ return arg;
561
+ }
562
+ if (arg === null || arg === void 0) {
563
+ return arg;
564
+ }
565
+ if (arg instanceof Error) {
566
+ return {
567
+ name: arg.name,
568
+ message: arg.message
569
+ };
570
+ }
571
+ try {
572
+ return JSON.parse(JSON.stringify(arg));
573
+ } catch {
574
+ return "[Unserializable]";
575
+ }
576
+ });
577
+ }
578
+ /**
579
+ * Reset tracker state
580
+ */
581
+ reset() {
582
+ this.stop();
583
+ }
584
+ };
585
+ var consoleTracker = null;
586
+ function getConsoleTracker() {
587
+ if (!consoleTracker) {
588
+ consoleTracker = new ConsoleTracker();
589
+ }
590
+ return consoleTracker;
591
+ }
592
+ function resetConsoleTracker() {
593
+ if (consoleTracker) {
594
+ consoleTracker.reset();
595
+ }
596
+ consoleTracker = null;
597
+ }
598
+ var HttpTracker = class {
599
+ originalHttpRequest = null;
600
+ originalHttpsRequest = null;
601
+ started = false;
602
+ patchFailed = false;
603
+ /**
604
+ * Start tracking HTTP requests
605
+ */
606
+ start() {
607
+ if (this.started || this.patchFailed) {
608
+ return;
609
+ }
610
+ try {
611
+ this.originalHttpRequest = http.request;
612
+ this.originalHttpsRequest = https.request;
613
+ this.wrapRequestMethod(http, "http");
614
+ this.wrapRequestMethod(https, "https");
615
+ this.started = true;
616
+ } catch {
617
+ this.patchFailed = true;
618
+ this.originalHttpRequest = null;
619
+ this.originalHttpsRequest = null;
620
+ }
621
+ }
622
+ /**
623
+ * Stop tracking HTTP requests
624
+ */
625
+ stop() {
626
+ if (!this.started) {
627
+ return;
628
+ }
629
+ try {
630
+ if (this.originalHttpRequest) {
631
+ Object.defineProperty(http, "request", {
632
+ value: this.originalHttpRequest,
633
+ writable: true,
634
+ configurable: true
635
+ });
636
+ }
637
+ if (this.originalHttpsRequest) {
638
+ Object.defineProperty(https, "request", {
639
+ value: this.originalHttpsRequest,
640
+ writable: true,
641
+ configurable: true
642
+ });
643
+ }
644
+ } catch {
645
+ }
646
+ this.originalHttpRequest = null;
647
+ this.originalHttpsRequest = null;
648
+ this.started = false;
649
+ }
650
+ /**
651
+ * Wrap a module's request method
652
+ */
653
+ wrapRequestMethod(mod, _protocol) {
654
+ const originalRequest = mod.request.bind(mod);
655
+ const self = this;
656
+ const wrappedRequest = function(...args) {
657
+ const [urlOrOptions, optionsOrCallback, maybeCallback] = args;
658
+ const { url, method, hostname: hostname3 } = self.parseRequestArgs(
659
+ urlOrOptions,
660
+ optionsOrCallback
661
+ );
662
+ const startTime = Date.now();
663
+ const req = originalRequest.apply(mod, args);
664
+ req.on("response", (res) => {
665
+ const duration = Date.now() - startTime;
666
+ self.addBreadcrumb(method, url, hostname3, res.statusCode, duration);
667
+ });
668
+ req.on("error", (error) => {
669
+ const duration = Date.now() - startTime;
670
+ self.addErrorBreadcrumb(method, url, hostname3, error, duration);
671
+ });
672
+ return req;
673
+ };
674
+ Object.defineProperty(mod, "request", {
675
+ value: wrappedRequest,
676
+ writable: true,
677
+ configurable: true
678
+ });
679
+ }
680
+ /**
681
+ * Parse request arguments to extract URL info
682
+ */
683
+ parseRequestArgs(urlOrOptions, optionsOrCallback) {
684
+ let url = "";
685
+ let method = "GET";
686
+ let hostname3 = "";
687
+ if (typeof urlOrOptions === "string") {
688
+ url = urlOrOptions;
689
+ try {
690
+ const parsed = new URL(urlOrOptions);
691
+ hostname3 = parsed.hostname;
692
+ } catch {
693
+ hostname3 = urlOrOptions;
694
+ }
695
+ } else if (urlOrOptions instanceof URL) {
696
+ url = urlOrOptions.toString();
697
+ hostname3 = urlOrOptions.hostname;
698
+ } else if (typeof urlOrOptions === "object") {
699
+ const opts = urlOrOptions;
700
+ hostname3 = opts.hostname || opts.host || "localhost";
701
+ const port = opts.port ? `:${opts.port}` : "";
702
+ const path2 = opts.path || "/";
703
+ const protocol = opts.protocol || "http:";
704
+ url = `${protocol}//${hostname3}${port}${path2}`;
705
+ method = opts.method || "GET";
706
+ }
707
+ if (optionsOrCallback && typeof optionsOrCallback === "object" && optionsOrCallback.method) {
708
+ method = optionsOrCallback.method;
709
+ }
710
+ return { url, method: method.toUpperCase(), hostname: hostname3 };
711
+ }
712
+ /**
713
+ * Add a breadcrumb for a successful HTTP request
714
+ */
715
+ addBreadcrumb(method, url, hostname3, statusCode, duration) {
716
+ const manager = getBreadcrumbManager();
717
+ if (!manager) {
718
+ return;
719
+ }
720
+ manager.add({
721
+ type: "http",
722
+ category: "http.client",
723
+ message: `${method} ${url}`,
724
+ level: statusCode && statusCode >= 400 ? "warning" : "info",
725
+ data: {
726
+ method,
727
+ url: this.truncateUrl(url),
728
+ hostname: hostname3,
729
+ status_code: statusCode,
730
+ duration_ms: duration
731
+ }
732
+ });
733
+ }
734
+ /**
735
+ * Add a breadcrumb for a failed HTTP request
736
+ */
737
+ addErrorBreadcrumb(method, url, hostname3, error, duration) {
738
+ const manager = getBreadcrumbManager();
739
+ if (!manager) {
740
+ return;
741
+ }
742
+ manager.add({
743
+ type: "http",
744
+ category: "http.client",
745
+ message: `${method} ${url} failed: ${error.message}`,
746
+ level: "error",
747
+ data: {
748
+ method,
749
+ url: this.truncateUrl(url),
750
+ hostname: hostname3,
751
+ error: error.message,
752
+ duration_ms: duration
753
+ }
754
+ });
755
+ }
756
+ /**
757
+ * Truncate URL for storage
758
+ */
759
+ truncateUrl(url) {
760
+ const maxLength = 200;
761
+ if (url.length <= maxLength) {
762
+ return url;
763
+ }
764
+ return url.substring(0, maxLength) + "...";
765
+ }
766
+ /**
767
+ * Reset tracker state
768
+ */
769
+ reset() {
770
+ this.stop();
771
+ this.patchFailed = false;
772
+ }
773
+ };
774
+ var httpTracker = null;
775
+ function getHttpTracker() {
776
+ if (!httpTracker) {
777
+ httpTracker = new HttpTracker();
778
+ }
779
+ return httpTracker;
780
+ }
781
+ function resetHttpTracker() {
782
+ if (httpTracker) {
783
+ httpTracker.reset();
784
+ }
785
+ httpTracker = null;
786
+ }
787
+
788
+ // src/capture/ProcessCapture.ts
789
+ var ProcessCapture = class {
790
+ handler = null;
791
+ uncaughtHandler = null;
792
+ rejectionHandler = null;
793
+ started = false;
794
+ exitOnUncaught = true;
795
+ /**
796
+ * Start capturing process errors
797
+ */
798
+ start(handler, exitOnUncaught = true) {
799
+ if (this.started) {
800
+ return;
801
+ }
802
+ this.handler = handler;
803
+ this.exitOnUncaught = exitOnUncaught;
804
+ this.uncaughtHandler = this.handleUncaughtException.bind(this);
805
+ this.rejectionHandler = this.handleUnhandledRejection.bind(this);
806
+ process.on("uncaughtException", this.uncaughtHandler);
807
+ process.on("unhandledRejection", this.rejectionHandler);
808
+ this.started = true;
809
+ }
810
+ /**
811
+ * Start only uncaughtException capture
812
+ */
813
+ startUncaughtException(handler, exitOnUncaught = true) {
814
+ if (this.uncaughtHandler) {
815
+ return;
816
+ }
817
+ this.handler = handler;
818
+ this.exitOnUncaught = exitOnUncaught;
819
+ this.uncaughtHandler = this.handleUncaughtException.bind(this);
820
+ process.on("uncaughtException", this.uncaughtHandler);
821
+ }
822
+ /**
823
+ * Start only unhandledRejection capture
824
+ */
825
+ startUnhandledRejection(handler) {
826
+ if (this.rejectionHandler) {
827
+ return;
828
+ }
829
+ this.handler = handler;
830
+ this.rejectionHandler = this.handleUnhandledRejection.bind(this);
831
+ process.on("unhandledRejection", this.rejectionHandler);
832
+ }
833
+ /**
834
+ * Stop capturing process errors
835
+ */
836
+ stop() {
837
+ if (this.uncaughtHandler) {
838
+ process.off("uncaughtException", this.uncaughtHandler);
839
+ this.uncaughtHandler = null;
840
+ }
841
+ if (this.rejectionHandler) {
842
+ process.off("unhandledRejection", this.rejectionHandler);
843
+ this.rejectionHandler = null;
844
+ }
845
+ this.handler = null;
846
+ this.started = false;
847
+ }
848
+ /**
849
+ * Handle uncaught exception
850
+ */
851
+ handleUncaughtException(error) {
852
+ if (!this.handler) {
853
+ return;
854
+ }
855
+ const captured = {
856
+ error,
857
+ message: error.message,
858
+ type: "uncaughtException",
859
+ severity: "critical"
860
+ };
861
+ try {
862
+ this.handler(captured);
863
+ } catch {
864
+ }
865
+ if (this.exitOnUncaught) {
866
+ setTimeout(() => {
867
+ process.exit(1);
868
+ }, 100);
869
+ }
870
+ }
871
+ /**
872
+ * Handle unhandled rejection
873
+ */
874
+ handleUnhandledRejection(reason, promise) {
875
+ if (!this.handler) {
876
+ return;
877
+ }
878
+ const error = reason instanceof Error ? reason : new Error(String(reason));
879
+ const captured = {
880
+ error,
881
+ message: error.message,
882
+ type: "unhandledRejection",
883
+ severity: "error",
884
+ promise
885
+ };
886
+ try {
887
+ this.handler(captured);
888
+ } catch {
889
+ }
890
+ }
891
+ /**
892
+ * Reset capture state
893
+ */
894
+ reset() {
895
+ this.stop();
896
+ }
897
+ };
898
+ var processCapture = null;
899
+ function getProcessCapture() {
900
+ if (!processCapture) {
901
+ processCapture = new ProcessCapture();
902
+ }
903
+ return processCapture;
904
+ }
905
+ function resetProcessCapture() {
906
+ if (processCapture) {
907
+ processCapture.reset();
908
+ }
909
+ processCapture = null;
910
+ }
911
+
912
+ // src/capture/ConsoleCapture.ts
913
+ var ConsoleCapture = class {
914
+ handler = null;
915
+ originalConsoleError = null;
916
+ started = false;
917
+ /**
918
+ * Start capturing console.error calls
919
+ */
920
+ start(handler) {
921
+ if (this.started) {
922
+ return;
923
+ }
924
+ this.handler = handler;
925
+ this.originalConsoleError = console.error.bind(console);
926
+ const self = this;
927
+ console.error = function(...args) {
928
+ self.originalConsoleError(...args);
929
+ self.handleConsoleError(args);
930
+ };
931
+ this.started = true;
932
+ }
933
+ /**
934
+ * Stop capturing console.error
935
+ */
936
+ stop() {
937
+ if (!this.started || !this.originalConsoleError) {
938
+ return;
939
+ }
940
+ console.error = this.originalConsoleError;
941
+ this.originalConsoleError = null;
942
+ this.handler = null;
943
+ this.started = false;
944
+ }
945
+ /**
946
+ * Handle console.error call
947
+ */
948
+ handleConsoleError(args) {
949
+ if (!this.handler) {
950
+ return;
951
+ }
952
+ let error;
953
+ let message;
954
+ const firstArg = args[0];
955
+ if (firstArg instanceof Error) {
956
+ error = firstArg;
957
+ message = error.message;
958
+ } else {
959
+ message = args.map((arg) => {
960
+ if (typeof arg === "string") return arg;
961
+ try {
962
+ return JSON.stringify(arg);
963
+ } catch {
964
+ return String(arg);
965
+ }
966
+ }).join(" ");
967
+ error = new Error(message);
968
+ }
969
+ const captured = {
970
+ error,
971
+ message,
972
+ type: "unhandledRejection",
973
+ // Use this type for console errors
974
+ severity: "error"
975
+ };
976
+ try {
977
+ this.handler(captured);
978
+ } catch {
979
+ }
980
+ }
981
+ /**
982
+ * Reset capture state
983
+ */
984
+ reset() {
985
+ this.stop();
986
+ }
987
+ };
988
+ var consoleCapture = null;
989
+ function getConsoleCapture() {
990
+ if (!consoleCapture) {
991
+ consoleCapture = new ConsoleCapture();
992
+ }
993
+ return consoleCapture;
994
+ }
995
+ function resetConsoleCapture() {
996
+ if (consoleCapture) {
997
+ consoleCapture.reset();
998
+ }
999
+ consoleCapture = null;
1000
+ }
1001
+ var HmacSigner = class {
1002
+ secret;
1003
+ constructor(secret) {
1004
+ this.secret = secret;
1005
+ }
1006
+ /**
1007
+ * Sign a payload with HMAC-SHA256
1008
+ * @param payload - The JSON payload string to sign
1009
+ * @param timestamp - Unix timestamp (defaults to current time)
1010
+ * @returns Hex-encoded signature
1011
+ */
1012
+ sign(payload, timestamp) {
1013
+ const ts = timestamp ?? Math.floor(Date.now() / 1e3);
1014
+ const signedPayload = `${ts}.${payload}`;
1015
+ const hmac = crypto.createHmac("sha256", this.secret);
1016
+ hmac.update(signedPayload);
1017
+ return hmac.digest("hex");
1018
+ }
1019
+ /**
1020
+ * Build headers for a signed request
1021
+ * @param payload - The JSON payload string
1022
+ * @returns Headers object with signature and timestamp
1023
+ */
1024
+ buildHeaders(payload) {
1025
+ const timestamp = Math.floor(Date.now() / 1e3);
1026
+ const signature = this.sign(payload, timestamp);
1027
+ return {
1028
+ "X-Webhook-Signature": signature,
1029
+ "X-Webhook-Timestamp": String(timestamp)
1030
+ };
1031
+ }
1032
+ /**
1033
+ * Verify a signature (useful for testing)
1034
+ * @param payload - The payload that was signed
1035
+ * @param signature - The signature to verify
1036
+ * @param timestamp - The timestamp used in signing
1037
+ * @param maxAge - Maximum age in seconds (default: 5 minutes)
1038
+ */
1039
+ verify(payload, signature, timestamp, maxAge = 300) {
1040
+ const now = Math.floor(Date.now() / 1e3);
1041
+ if (Math.abs(now - timestamp) > maxAge) {
1042
+ return false;
1043
+ }
1044
+ const expected = this.sign(payload, timestamp);
1045
+ return crypto.timingSafeEqual(
1046
+ Buffer.from(signature, "hex"),
1047
+ Buffer.from(expected, "hex")
1048
+ );
1049
+ }
1050
+ };
1051
+
1052
+ // src/transport/HttpTransport.ts
1053
+ var HttpTransport = class {
1054
+ endpoint;
1055
+ token;
1056
+ timeout;
1057
+ maxRetries;
1058
+ hmacSigner = null;
1059
+ debug;
1060
+ constructor(options) {
1061
+ this.endpoint = options.endpoint;
1062
+ this.token = options.token;
1063
+ this.timeout = options.timeout;
1064
+ this.maxRetries = options.maxRetries;
1065
+ this.debug = options.debug ?? false;
1066
+ if (options.hmacSecret) {
1067
+ this.hmacSigner = new HmacSigner(options.hmacSecret);
1068
+ }
1069
+ }
1070
+ /**
1071
+ * Send an event to the Error Explorer API
1072
+ */
1073
+ async send(event) {
1074
+ const payload = JSON.stringify(event);
1075
+ let lastError = null;
1076
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1077
+ try {
1078
+ const success = await this.doSend(payload);
1079
+ if (success) {
1080
+ if (this.debug) {
1081
+ console.log("[ErrorExplorer] Event sent successfully");
1082
+ }
1083
+ return true;
1084
+ }
1085
+ } catch (error) {
1086
+ lastError = error;
1087
+ if (this.debug) {
1088
+ console.error(`[ErrorExplorer] Send attempt ${attempt + 1} failed:`, error);
1089
+ }
1090
+ if (attempt < this.maxRetries) {
1091
+ await this.sleep(Math.pow(2, attempt) * 100);
1092
+ }
1093
+ }
1094
+ }
1095
+ if (this.debug && lastError) {
1096
+ console.error("[ErrorExplorer] All send attempts failed:", lastError);
1097
+ }
1098
+ return false;
1099
+ }
1100
+ /**
1101
+ * Perform the actual HTTP request
1102
+ */
1103
+ doSend(payload) {
1104
+ return new Promise((resolve, reject) => {
1105
+ const url = new URL(this.endpoint);
1106
+ const isHttps = url.protocol === "https:";
1107
+ const transport = isHttps ? https : http;
1108
+ const headers = {
1109
+ "Content-Type": "application/json",
1110
+ "Content-Length": String(Buffer.byteLength(payload)),
1111
+ "X-Webhook-Token": this.token,
1112
+ "User-Agent": "@error-explorer/node/1.0.0",
1113
+ // Include Host header for proper virtual host routing
1114
+ "Host": url.host
1115
+ };
1116
+ if (this.hmacSigner) {
1117
+ const hmacHeaders = this.hmacSigner.buildHeaders(payload);
1118
+ Object.assign(headers, hmacHeaders);
1119
+ }
1120
+ const options = {
1121
+ method: "POST",
1122
+ hostname: url.hostname,
1123
+ port: url.port || (isHttps ? 443 : 80),
1124
+ path: url.pathname,
1125
+ headers,
1126
+ timeout: this.timeout
1127
+ };
1128
+ const req = transport.request(options, (res) => {
1129
+ let data = "";
1130
+ res.on("data", (chunk) => {
1131
+ data += chunk;
1132
+ });
1133
+ res.on("end", () => {
1134
+ const statusCode = res.statusCode || 0;
1135
+ if (statusCode >= 200 && statusCode < 300) {
1136
+ resolve(true);
1137
+ } else if (statusCode === 429) {
1138
+ const retryAfter = res.headers["retry-after"];
1139
+ reject(new Error(`Rate limited. Retry after: ${retryAfter}`));
1140
+ } else if (statusCode >= 500) {
1141
+ reject(new Error(`Server error: ${statusCode}`));
1142
+ } else {
1143
+ if (this.debug) {
1144
+ console.error(`[ErrorExplorer] Client error ${statusCode}:`, data);
1145
+ }
1146
+ resolve(false);
1147
+ }
1148
+ });
1149
+ });
1150
+ req.on("error", (error) => {
1151
+ reject(error);
1152
+ });
1153
+ req.on("timeout", () => {
1154
+ req.destroy();
1155
+ reject(new Error("Request timeout"));
1156
+ });
1157
+ req.write(payload);
1158
+ req.end();
1159
+ });
1160
+ }
1161
+ /**
1162
+ * Send synchronously (blocking) - for use in exit handlers
1163
+ */
1164
+ sendSync(event) {
1165
+ this.send(event).catch((err) => {
1166
+ if (this.debug) {
1167
+ console.error("[ErrorExplorer] Sync send failed:", err);
1168
+ }
1169
+ });
1170
+ }
1171
+ /**
1172
+ * Sleep for a given duration
1173
+ */
1174
+ sleep(ms) {
1175
+ return new Promise((resolve) => setTimeout(resolve, ms));
1176
+ }
1177
+ };
1178
+
1179
+ // src/ErrorExplorer.ts
1180
+ var SDK_NAME = "@error-explorer/node";
1181
+ var SDK_VERSION = "1.0.0";
1182
+ var ErrorExplorerClient = class {
1183
+ config = null;
1184
+ transport = null;
1185
+ initialized = false;
1186
+ tags = {};
1187
+ extra = {};
1188
+ contexts = {};
1189
+ /**
1190
+ * Initialize the SDK
1191
+ */
1192
+ init(options) {
1193
+ if (this.initialized) {
1194
+ console.warn("[ErrorExplorer] Already initialized");
1195
+ return;
1196
+ }
1197
+ try {
1198
+ this.config = resolveConfig(options);
1199
+ if (this.config.serverName) {
1200
+ setServerName(this.config.serverName);
1201
+ }
1202
+ this.transport = new HttpTransport({
1203
+ endpoint: this.config.endpoint,
1204
+ token: this.config.token,
1205
+ timeout: this.config.timeout,
1206
+ maxRetries: this.config.maxRetries,
1207
+ hmacSecret: this.config.hmacSecret,
1208
+ debug: this.config.debug
1209
+ });
1210
+ if (this.config.breadcrumbs.enabled) {
1211
+ initBreadcrumbManager(this.config);
1212
+ this.startBreadcrumbTrackers();
1213
+ }
1214
+ if (this.config.autoCapture.uncaughtExceptions || this.config.autoCapture.unhandledRejections) {
1215
+ const processCapture2 = getProcessCapture();
1216
+ if (this.config.autoCapture.uncaughtExceptions) {
1217
+ processCapture2.startUncaughtException(
1218
+ this.handleCapturedError.bind(this),
1219
+ this.config.exitOnUncaughtException
1220
+ );
1221
+ }
1222
+ if (this.config.autoCapture.unhandledRejections) {
1223
+ processCapture2.startUnhandledRejection(this.handleCapturedError.bind(this));
1224
+ }
1225
+ }
1226
+ if (this.config.autoCapture.console) {
1227
+ getConsoleCapture().start(this.handleCapturedError.bind(this));
1228
+ }
1229
+ this.initialized = true;
1230
+ if (this.config.debug) {
1231
+ console.log("[ErrorExplorer] Initialized", {
1232
+ endpoint: this.config.endpoint,
1233
+ environment: this.config.environment,
1234
+ serverName: this.config.serverName
1235
+ });
1236
+ }
1237
+ } catch (error) {
1238
+ console.error("[ErrorExplorer] Initialization failed:", error);
1239
+ throw error;
1240
+ }
1241
+ }
1242
+ /**
1243
+ * Check if SDK is initialized
1244
+ */
1245
+ isInitialized() {
1246
+ return this.initialized;
1247
+ }
1248
+ /**
1249
+ * Set user context
1250
+ */
1251
+ setUser(user) {
1252
+ getUserContextManager().setUser(user);
1253
+ }
1254
+ /**
1255
+ * Clear user context
1256
+ */
1257
+ clearUser() {
1258
+ getUserContextManager().clearUser();
1259
+ }
1260
+ /**
1261
+ * Set a single tag
1262
+ */
1263
+ setTag(key, value) {
1264
+ this.tags[key] = value;
1265
+ }
1266
+ /**
1267
+ * Set multiple tags
1268
+ */
1269
+ setTags(tags) {
1270
+ this.tags = { ...this.tags, ...tags };
1271
+ }
1272
+ /**
1273
+ * Set extra data
1274
+ */
1275
+ setExtra(key, value) {
1276
+ this.extra[key] = value;
1277
+ }
1278
+ /**
1279
+ * Set a named context
1280
+ */
1281
+ setContext(name, context) {
1282
+ this.contexts[name] = context;
1283
+ }
1284
+ /**
1285
+ * Add a manual breadcrumb
1286
+ */
1287
+ addBreadcrumb(breadcrumb) {
1288
+ const manager = getBreadcrumbManager();
1289
+ if (manager) {
1290
+ manager.add(breadcrumb);
1291
+ }
1292
+ }
1293
+ /**
1294
+ * Capture an exception manually
1295
+ */
1296
+ captureException(error, context) {
1297
+ if (!this.initialized || !this.config) {
1298
+ console.warn("[ErrorExplorer] Not initialized");
1299
+ return "";
1300
+ }
1301
+ const eventId = generateUuid();
1302
+ const event = this.buildEvent(error, context, "error");
1303
+ this.processAndSend(event);
1304
+ return eventId;
1305
+ }
1306
+ /**
1307
+ * Capture a message manually
1308
+ */
1309
+ captureMessage(message, level = "info") {
1310
+ if (!this.initialized || !this.config) {
1311
+ console.warn("[ErrorExplorer] Not initialized");
1312
+ return "";
1313
+ }
1314
+ const eventId = generateUuid();
1315
+ const event = this.buildEvent(new Error(message), void 0, level);
1316
+ event.exception_class = "Message";
1317
+ this.processAndSend(event);
1318
+ return eventId;
1319
+ }
1320
+ /**
1321
+ * Flush all pending events (best effort)
1322
+ */
1323
+ async flush(timeout = 5e3) {
1324
+ if (!this.initialized) {
1325
+ return false;
1326
+ }
1327
+ await new Promise((resolve) => setTimeout(resolve, Math.min(timeout, 100)));
1328
+ return true;
1329
+ }
1330
+ /**
1331
+ * Close the SDK and cleanup
1332
+ */
1333
+ async close(timeout = 5e3) {
1334
+ if (!this.initialized) {
1335
+ return true;
1336
+ }
1337
+ await this.flush(timeout);
1338
+ this.stopAllTrackers();
1339
+ resetBreadcrumbManager();
1340
+ resetProcessCapture();
1341
+ resetConsoleCapture();
1342
+ resetConsoleTracker();
1343
+ resetHttpTracker();
1344
+ resetUserContextManager();
1345
+ resetServerContext();
1346
+ resetOsContext();
1347
+ resetRuntimeContext();
1348
+ this.config = null;
1349
+ this.transport = null;
1350
+ this.initialized = false;
1351
+ this.tags = {};
1352
+ this.extra = {};
1353
+ this.contexts = {};
1354
+ return true;
1355
+ }
1356
+ /**
1357
+ * Handle a captured error from auto-capture
1358
+ */
1359
+ handleCapturedError(captured) {
1360
+ if (!this.config) {
1361
+ return;
1362
+ }
1363
+ if (this.shouldIgnoreError(captured.message)) {
1364
+ if (this.config.debug) {
1365
+ console.log("[ErrorExplorer] Ignoring error:", captured.message);
1366
+ }
1367
+ return;
1368
+ }
1369
+ const event = this.buildEvent(captured.error, void 0, captured.severity);
1370
+ event.tags = {
1371
+ ...event.tags,
1372
+ capture_type: captured.type
1373
+ };
1374
+ this.processAndSend(event);
1375
+ }
1376
+ /**
1377
+ * Check if an error should be ignored
1378
+ */
1379
+ shouldIgnoreError(message) {
1380
+ if (!this.config) {
1381
+ return true;
1382
+ }
1383
+ return matchesPattern(message, this.config.ignoreErrors);
1384
+ }
1385
+ /**
1386
+ * Build a complete error event
1387
+ */
1388
+ buildEvent(error, context, severity = "error") {
1389
+ const config = this.config;
1390
+ const message = getErrorMessage(error);
1391
+ const name = getErrorName(error);
1392
+ const stack = getErrorStack(error);
1393
+ const frames = parseStackTrace(stack);
1394
+ const topFrame = frames[0];
1395
+ const event = {
1396
+ message,
1397
+ exception_class: name,
1398
+ file: topFrame?.filename,
1399
+ line: topFrame?.lineno,
1400
+ column: topFrame?.colno,
1401
+ stack_trace: stack,
1402
+ frames,
1403
+ severity: context?.level ?? severity,
1404
+ environment: config.environment,
1405
+ release: config.release || void 0,
1406
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1407
+ // User context
1408
+ user: context?.user ?? getUserContextManager().getUser() ?? void 0,
1409
+ // Server context
1410
+ server: getServerContext(),
1411
+ // Runtime context
1412
+ runtime: collectRuntimeContext(),
1413
+ // OS context
1414
+ os: collectOsContext(),
1415
+ // Process context
1416
+ process: collectProcessContext(),
1417
+ // Request context (if provided)
1418
+ request: context?.request ? extractRequestContext(context.request) : void 0,
1419
+ // Breadcrumbs
1420
+ breadcrumbs: getBreadcrumbManager()?.getAll(),
1421
+ // Tags (merge global + context)
1422
+ tags: { ...this.tags, ...context?.tags },
1423
+ // Extra data
1424
+ extra: { ...this.extra, ...context?.extra },
1425
+ // Custom contexts
1426
+ contexts: this.contexts,
1427
+ // SDK info
1428
+ sdk: {
1429
+ name: SDK_NAME,
1430
+ version: SDK_VERSION
1431
+ },
1432
+ // Fingerprint for grouping
1433
+ fingerprint: context?.fingerprint
1434
+ };
1435
+ return event;
1436
+ }
1437
+ /**
1438
+ * Process event through beforeSend and send
1439
+ */
1440
+ async processAndSend(event) {
1441
+ if (!this.config || !this.transport) {
1442
+ return;
1443
+ }
1444
+ let processedEvent = event;
1445
+ if (this.config.beforeSend) {
1446
+ try {
1447
+ const result = this.config.beforeSend(event);
1448
+ processedEvent = result instanceof Promise ? await result : result;
1449
+ } catch (e) {
1450
+ console.error("[ErrorExplorer] beforeSend threw an error:", e);
1451
+ processedEvent = event;
1452
+ }
1453
+ }
1454
+ if (!processedEvent) {
1455
+ if (this.config.debug) {
1456
+ console.log("[ErrorExplorer] Event dropped by beforeSend");
1457
+ }
1458
+ return;
1459
+ }
1460
+ await this.transport.send(processedEvent);
1461
+ }
1462
+ /**
1463
+ * Start all breadcrumb trackers
1464
+ */
1465
+ startBreadcrumbTrackers() {
1466
+ if (!this.config) {
1467
+ return;
1468
+ }
1469
+ const bc = this.config.breadcrumbs;
1470
+ if (bc.console) {
1471
+ getConsoleTracker().start();
1472
+ }
1473
+ if (bc.http) {
1474
+ getHttpTracker().start();
1475
+ }
1476
+ }
1477
+ /**
1478
+ * Stop all breadcrumb trackers
1479
+ */
1480
+ stopAllTrackers() {
1481
+ resetConsoleTracker();
1482
+ resetHttpTracker();
1483
+ }
1484
+ };
1485
+ var ErrorExplorer = new ErrorExplorerClient();
1486
+
1487
+ // src/middleware/express.ts
1488
+ function requestHandler(options = {}) {
1489
+ const { extractUser, breadcrumbs = true } = options;
1490
+ return (req, res, next) => {
1491
+ const transaction = generateTransactionId();
1492
+ req.errorExplorer = {
1493
+ transaction,
1494
+ startTime: Date.now()
1495
+ };
1496
+ if (extractUser) {
1497
+ try {
1498
+ const user = extractUser(req);
1499
+ if (user) {
1500
+ req.errorExplorer.user = user;
1501
+ }
1502
+ } catch {
1503
+ }
1504
+ }
1505
+ if (breadcrumbs) {
1506
+ const manager = getBreadcrumbManager();
1507
+ if (manager) {
1508
+ manager.add({
1509
+ type: "http",
1510
+ category: "http.request",
1511
+ message: `${req.method} ${req.path || req.url}`,
1512
+ level: "info",
1513
+ data: {
1514
+ method: req.method,
1515
+ url: req.originalUrl || req.url,
1516
+ transaction
1517
+ }
1518
+ });
1519
+ }
1520
+ }
1521
+ if (breadcrumbs) {
1522
+ const originalEnd = res.end;
1523
+ let ended = false;
1524
+ res.end = function(...args) {
1525
+ if (!ended) {
1526
+ ended = true;
1527
+ const duration = req.errorExplorer?.startTime ? Date.now() - req.errorExplorer.startTime : void 0;
1528
+ const manager = getBreadcrumbManager();
1529
+ if (manager) {
1530
+ manager.add({
1531
+ type: "http",
1532
+ category: "http.response",
1533
+ message: `${req.method} ${req.path || req.url} - ${res.statusCode}`,
1534
+ level: res.statusCode >= 400 ? "warning" : "info",
1535
+ data: {
1536
+ method: req.method,
1537
+ url: req.originalUrl || req.url,
1538
+ status_code: res.statusCode,
1539
+ duration_ms: duration,
1540
+ transaction
1541
+ }
1542
+ });
1543
+ }
1544
+ }
1545
+ return originalEnd.apply(res, args);
1546
+ };
1547
+ }
1548
+ next();
1549
+ };
1550
+ }
1551
+ function errorHandler(options = {}) {
1552
+ const { callNext = true, onError } = options;
1553
+ return (error, req, res, next) => {
1554
+ if (res.headersSent) {
1555
+ if (callNext) {
1556
+ next(error);
1557
+ }
1558
+ return;
1559
+ }
1560
+ const requestContext = extractRequestContext(req);
1561
+ const user = req.errorExplorer?.user;
1562
+ const transaction = req.errorExplorer?.transaction;
1563
+ ErrorExplorer.captureException(error, {
1564
+ request: req,
1565
+ user,
1566
+ tags: {
1567
+ transaction: transaction || "unknown",
1568
+ route: req.path || req.url || "unknown",
1569
+ method: req.method || "unknown"
1570
+ },
1571
+ extra: {
1572
+ requestContext,
1573
+ query: req.query,
1574
+ params: req.params
1575
+ }
1576
+ });
1577
+ if (onError) {
1578
+ try {
1579
+ onError(error, req, res);
1580
+ } catch {
1581
+ }
1582
+ }
1583
+ if (callNext) {
1584
+ next(error);
1585
+ }
1586
+ };
1587
+ }
1588
+ function setupExpress(requestOptions = {}, errorOptions = {}) {
1589
+ return {
1590
+ requestHandler: requestHandler(requestOptions),
1591
+ errorHandler: errorHandler(errorOptions)
1592
+ };
1593
+ }
1594
+
1595
+ export { errorHandler, requestHandler, setupExpress };
1596
+ //# sourceMappingURL=express.js.map
1597
+ //# sourceMappingURL=express.js.map