@honeybadger-io/js 6.15.3 → 6.16.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.
@@ -5,7 +5,8 @@ var require$$1$1 = require('domain');
5
5
  var require$$1 = require('fs');
6
6
  var require$$2 = require('path');
7
7
  var require$$3 = require('util');
8
- var require$$0$3 = require('url');
8
+ var require$$0$3 = require('crypto');
9
+ var require$$0$4 = require('url');
9
10
  var require$$3$1 = require('http');
10
11
  var require$$4 = require('https');
11
12
 
@@ -17,6 +18,7 @@ var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
17
18
  var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
18
19
  var require$$3__default = /*#__PURE__*/_interopDefaultLegacy(require$$3);
19
20
  var require$$0__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$0$3);
21
+ var require$$0__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$0$4);
20
22
  var require$$3__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$3$1);
21
23
  var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
22
24
 
@@ -51,7 +53,7 @@ var server$1 = {};
51
53
 
52
54
  var src = {};
53
55
 
54
- var events = {};
56
+ var console_events = {};
55
57
 
56
58
  var util$1 = {};
57
59
 
@@ -258,7 +260,7 @@ var require$$0$1 = /*@__PURE__*/getAugmentedNamespace(stackTraceParser_esm);
258
260
  }
259
261
  };
260
262
  Object.defineProperty(exports, "__esModule", { value: true });
261
- exports.logDeprecatedMethod = exports.globalThisOrWindow = exports.isBrowserConfig = exports.clone = exports.formatCGIData = exports.filterUrl = exports.filter = exports.generateStackTrace = exports.endpoint = exports.instrumentConsole = exports.instrument = exports.isErrorObject = exports.makeNotice = exports.logger = exports.sanitize = exports.shallowClone = exports.runAfterNotifyHandlers = exports.runBeforeNotifyHandlers = exports.getSourceForBacktrace = exports.getCauses = exports.calculateBacktraceShift = exports.DEFAULT_BACKTRACE_SHIFT = exports.makeBacktrace = exports.objectIsExtensible = exports.objectIsEmpty = exports.mergeNotice = exports.merge = void 0;
263
+ exports.logDeprecatedMethod = exports.globalThisOrWindow = exports.isBrowserConfig = exports.clone = exports.formatCGIData = exports.filterUrl = exports.filter = exports.generateStackTrace = exports.endpoint = exports.instrumentConsole = exports.instrument = exports.isErrorObject = exports.makeNotice = exports.logger = exports.sanitize = exports.shallowClone = exports.runAfterNotifyHandlers = exports.shouldSampleEvent = exports.resolveInsights = exports.runBeforeEventHandlers = exports.runBeforeNotifyHandlers = exports.getSourceForBacktrace = exports.getCauses = exports.calculateBacktraceShift = exports.DEFAULT_BACKTRACE_SHIFT = exports.makeBacktrace = exports.objectIsExtensible = exports.objectIsEmpty = exports.mergeNotice = exports.merge = void 0;
262
264
  /* eslint-disable prefer-rest-params */
263
265
  var stackTraceParser = __importStar(require$$0$1);
264
266
  function merge(obj1, obj2) {
@@ -434,6 +436,88 @@ var require$$0$1 = /*@__PURE__*/getAugmentedNamespace(stackTraceParser_esm);
434
436
  };
435
437
  }
436
438
  exports.runBeforeNotifyHandlers = runBeforeNotifyHandlers;
439
+ function runBeforeEventHandlers(payload, handlers, logger) {
440
+ return __awaiter(this, void 0, void 0, function () {
441
+ var i, len, result, err_1;
442
+ return __generator(this, function (_a) {
443
+ switch (_a.label) {
444
+ case 0:
445
+ i = 0, len = handlers.length;
446
+ _a.label = 1;
447
+ case 1:
448
+ if (!(i < len)) return [3 /*break*/, 7];
449
+ result = void 0;
450
+ _a.label = 2;
451
+ case 2:
452
+ _a.trys.push([2, 4, , 5]);
453
+ return [4 /*yield*/, handlers[i](payload)];
454
+ case 3:
455
+ result = _a.sent();
456
+ return [3 /*break*/, 5];
457
+ case 4:
458
+ err_1 = _a.sent();
459
+ // A buggy handler should not suppress unrelated events. Log and treat as no-op.
460
+ logger === null || logger === void 0 ? void 0 : logger.error('beforeEvent handler threw; continuing', err_1);
461
+ return [3 /*break*/, 6];
462
+ case 5:
463
+ if (result === false) {
464
+ return [2 /*return*/, false];
465
+ }
466
+ _a.label = 6;
467
+ case 6:
468
+ i++;
469
+ return [3 /*break*/, 1];
470
+ case 7: return [2 /*return*/, true];
471
+ }
472
+ });
473
+ });
474
+ }
475
+ exports.runBeforeEventHandlers = runBeforeEventHandlers;
476
+ function resolveInsights(config) {
477
+ var _a, _b;
478
+ var insights = config.insights;
479
+ if (!insights || insights.enabled !== true) {
480
+ return { console: false, http: false };
481
+ }
482
+ return {
483
+ console: (_a = insights.console) !== null && _a !== void 0 ? _a : false,
484
+ http: (_b = insights.http) !== null && _b !== void 0 ? _b : false,
485
+ };
486
+ }
487
+ exports.resolveInsights = resolveInsights;
488
+ function fnv1a(str) {
489
+ var h = 0x811c9dc5;
490
+ for (var i = 0, len = str.length; i < len; i++) {
491
+ h ^= str.charCodeAt(i);
492
+ h = Math.imul(h, 0x01000193);
493
+ }
494
+ return h >>> 0;
495
+ }
496
+ function clampSampleRate(rate) {
497
+ if (rate < 0)
498
+ return 0;
499
+ if (rate > 100)
500
+ return 100;
501
+ return rate;
502
+ }
503
+ function shouldSampleEvent(event, configRate) {
504
+ var meta = event._hb;
505
+ var override = meta && Number.isFinite(meta.sampleRate) ? meta.sampleRate : undefined;
506
+ var rawRate = override !== undefined ? override : configRate;
507
+ // Guard against non-finite rates (e.g. NaN from Number(invalidConfig)); an
508
+ // unusable rate must not silently drop every event, so fall back to send-all.
509
+ var rate = clampSampleRate(Number.isFinite(rawRate) ? rawRate : 100);
510
+ if (rate <= 0)
511
+ return false;
512
+ if (rate >= 100)
513
+ return true;
514
+ var requestId = event.request_id;
515
+ if (typeof requestId === 'string' && requestId.length > 0) {
516
+ return fnv1a(requestId) % 100 < rate;
517
+ }
518
+ return Math.random() * 100 < rate;
519
+ }
520
+ exports.shouldSampleEvent = shouldSampleEvent;
437
521
  function runAfterNotifyHandlers(notice, handlers, error) {
438
522
  if (notice && notice.afterNotify) {
439
523
  notice.afterNotify(error, notice);
@@ -772,8 +856,36 @@ var require$$0$1 = /*@__PURE__*/getAugmentedNamespace(stackTraceParser_esm);
772
856
  return formattedVars;
773
857
  }
774
858
  exports.formatCGIData = formatCGIData;
859
+ /**
860
+ * Deep-copies a value exactly the way a JSON round trip does -- `toJSON` is
861
+ * honored, so dates become ISO strings -- except that circular references are
862
+ * replaced with '[RECURSION]' instead of throwing.
863
+ *
864
+ * Only true cycles are replaced: a value referenced twice in sibling branches
865
+ * is copied twice, as a JSON round trip would.
866
+ *
867
+ * Anything else a round trip rejects (a BigInt, a getter that throws) still
868
+ * throws here. Callers that must not fail are expected to handle it: silently
869
+ * substituting a placeholder would let a malformed value -- a string where an
870
+ * object is expected, say -- travel on as though it were valid.
871
+ */
775
872
  function clone(obj) {
776
- return JSON.parse(JSON.stringify(obj));
873
+ var ancestors = [];
874
+ return JSON.parse(JSON.stringify(obj, function (_key, value) {
875
+ if (typeof value !== 'object' || value === null) {
876
+ return value;
877
+ }
878
+ // `this` is the object `value` sits in, so anything stacked above it has
879
+ // been fully visited and is no longer an ancestor of `value`.
880
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
881
+ ancestors.pop();
882
+ }
883
+ if (ancestors.indexOf(value) !== -1) {
884
+ return '[RECURSION]';
885
+ }
886
+ ancestors.push(value);
887
+ return value;
888
+ }));
777
889
  }
778
890
  exports.clone = clone;
779
891
  var THRESHOLD_COLUMN_NUMBER = 10000;
@@ -854,7 +966,7 @@ var require$$0$1 = /*@__PURE__*/getAugmentedNamespace(stackTraceParser_esm);
854
966
 
855
967
  } (util$1));
856
968
 
857
- Object.defineProperty(events, "__esModule", { value: true });
969
+ Object.defineProperty(console_events, "__esModule", { value: true });
858
970
  var util_1$8 = util$1;
859
971
  function default_1$3(_window) {
860
972
  if (_window === void 0) { _window = (0, util_1$8.globalThisOrWindow)(); }
@@ -862,7 +974,7 @@ function default_1$3(_window) {
862
974
  shouldReloadOnConfigure: false,
863
975
  load: function (client) {
864
976
  function sendEventsToInsights() {
865
- return client.config.eventsEnabled;
977
+ return (0, util_1$8.resolveInsights)(client.config).console;
866
978
  }
867
979
  if (!sendEventsToInsights()) {
868
980
  return;
@@ -889,7 +1001,7 @@ function default_1$3(_window) {
889
1001
  }
890
1002
  };
891
1003
  }
892
- events.default = default_1$3;
1004
+ console_events.default = default_1$3;
893
1005
 
894
1006
  var client$1 = {};
895
1007
 
@@ -911,11 +1023,21 @@ var GlobalStore = /** @class */ (function () {
911
1023
  };
912
1024
  GlobalStore.prototype.getContents = function (key) {
913
1025
  var value = key ? this.contents[key] : this.contents;
914
- return JSON.parse(JSON.stringify(value));
1026
+ // `clone` keeps JSON round trip semantics but tolerates values a bare
1027
+ // round trip throws on -- notably circular references, which arrive via
1028
+ // context or breadcrumb metadata the host application controls.
1029
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1030
+ return (0, util_1$7.clone)(value);
915
1031
  };
916
1032
  GlobalStore.prototype.setContext = function (context) {
917
1033
  this.contents.context = (0, util_1$7.merge)(this.contents.context, context || {});
918
1034
  };
1035
+ GlobalStore.prototype.setEventContext = function (eventContext) {
1036
+ this.contents.eventContext = (0, util_1$7.merge)(this.contents.eventContext, eventContext || {});
1037
+ };
1038
+ GlobalStore.prototype.clearEventContext = function () {
1039
+ this.contents.eventContext = {};
1040
+ };
919
1041
  GlobalStore.prototype.addBreadcrumb = function (breadcrumb) {
920
1042
  if (this.contents.breadcrumbs.length == this.breadcrumbsLimit) {
921
1043
  this.contents.breadcrumbs.shift();
@@ -924,6 +1046,7 @@ var GlobalStore = /** @class */ (function () {
924
1046
  };
925
1047
  GlobalStore.prototype.clear = function () {
926
1048
  this.contents.context = {};
1049
+ this.contents.eventContext = {};
927
1050
  this.contents.breadcrumbs = [];
928
1051
  };
929
1052
  GlobalStore.prototype.run = function (callback) {
@@ -933,7 +1056,7 @@ var GlobalStore = /** @class */ (function () {
933
1056
  }());
934
1057
  store.GlobalStore = GlobalStore;
935
1058
 
936
- var throttled_events_logger = {};
1059
+ var throttled_events_worker = {};
937
1060
 
938
1061
  class NdJson {
939
1062
  static parse(data) {
@@ -968,8 +1091,18 @@ defaults.CONFIG = {
968
1091
  revision: null,
969
1092
  reportData: null,
970
1093
  breadcrumbsEnabled: true,
971
- // we could decide the value of eventsEnabled based on `env` and `developmentEnvironments`
1094
+ // deprecated: `eventsEnabled: true` auto-enables insights.enabled + insights.console
972
1095
  eventsEnabled: false,
1096
+ insights: {
1097
+ enabled: false,
1098
+ console: false,
1099
+ http: false,
1100
+ },
1101
+ events: {
1102
+ dispatchIntervalSeconds: 10,
1103
+ bulkThreshold: 500,
1104
+ sampleRatePercentage: 100,
1105
+ },
973
1106
  maxBreadcrumbs: 40,
974
1107
  maxObjectDepth: 8,
975
1108
  logger: console,
@@ -1032,72 +1165,179 @@ var __generator$4 = (commonjsGlobal && commonjsGlobal.__generator) || function (
1032
1165
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
1033
1166
  }
1034
1167
  };
1035
- Object.defineProperty(throttled_events_logger, "__esModule", { value: true });
1036
- throttled_events_logger.ThrottledEventsLogger = void 0;
1168
+ Object.defineProperty(throttled_events_worker, "__esModule", { value: true });
1169
+ throttled_events_worker.ThrottledEventsWorker = void 0;
1037
1170
  var json_nd_1 = require$$0;
1038
1171
  var util_1$6 = util$1;
1039
1172
  var defaults_1$1 = defaults;
1040
- var ThrottledEventsLogger = /** @class */ (function () {
1041
- function ThrottledEventsLogger(config, transport) {
1173
+ var ThrottledEventsWorker = /** @class */ (function () {
1174
+ function ThrottledEventsWorker(config, transport) {
1042
1175
  this.config = config;
1043
1176
  this.transport = transport;
1044
1177
  this.queue = [];
1045
- this.isProcessing = false;
1178
+ this.cooldownTimer = null;
1179
+ // Timestamp (ms) until which the post-send cooldown window is active. A timer
1180
+ // is only armed while there are queued events waiting for this window; when the
1181
+ // queue drains empty we keep the timestamp but drop the timer, so an idle worker
1182
+ // never holds a pending timer (which would otherwise keep a Node process alive
1183
+ // for the full interval.
1184
+ this.cooldownUntil = 0;
1185
+ this.inFlight = null;
1046
1186
  this.config = __assign$2(__assign$2({}, defaults_1$1.CONFIG), config);
1047
1187
  this.logger = this.originalLogger();
1048
1188
  }
1049
- ThrottledEventsLogger.prototype.configure = function (opts) {
1189
+ ThrottledEventsWorker.prototype.configure = function (opts) {
1050
1190
  for (var k in opts) {
1051
1191
  this.config[k] = opts[k];
1052
1192
  }
1053
1193
  };
1054
- ThrottledEventsLogger.prototype.log = function (payload) {
1194
+ ThrottledEventsWorker.prototype.log = function (payload) {
1055
1195
  this.queue.push(payload);
1056
- if (!this.isProcessing) {
1196
+ if (this.inFlight) {
1197
+ return;
1198
+ }
1199
+ // Crossing the bulk threshold preempts any pending cooldown and dispatches now.
1200
+ if (this.queue.length >= this.bulkThreshold()) {
1201
+ this.clearCooldownTimer();
1057
1202
  this.processQueue();
1203
+ return;
1058
1204
  }
1205
+ // A timer is already counting down the cooldown window; the queued event
1206
+ // will be picked up when it fires.
1207
+ if (this.cooldownTimer) {
1208
+ return;
1209
+ }
1210
+ // We're inside a cooldown window but no timer is armed (the queue had drained
1211
+ // empty after the previous send). Arm one for the remaining time so this event
1212
+ // still respects the interval.
1213
+ var remaining = this.cooldownRemainingMs();
1214
+ if (remaining > 0) {
1215
+ this.scheduleCooldownTimer(remaining);
1216
+ return;
1217
+ }
1218
+ this.processQueue();
1059
1219
  };
1060
- ThrottledEventsLogger.prototype.flushAsync = function () {
1220
+ ThrottledEventsWorker.prototype.flushAsync = function () {
1221
+ var _this = this;
1222
+ var _a;
1061
1223
  this.logger.debug('[Honeybadger] Flushing events');
1062
- return this.send();
1224
+ this.clearCooldownTimer();
1225
+ var previous = (_a = this.inFlight) !== null && _a !== void 0 ? _a : Promise.resolve();
1226
+ var flush = previous.then(function () { return _this.drainAll(); });
1227
+ // Mark the flush as in-flight so a concurrent log() doesn't start an
1228
+ // overlapping processQueue() drain while we're draining here.
1229
+ this.inFlight = flush;
1230
+ var clear = function () {
1231
+ if (_this.inFlight === flush) {
1232
+ _this.inFlight = null;
1233
+ }
1234
+ };
1235
+ flush.then(clear, clear);
1236
+ return flush;
1237
+ };
1238
+ ThrottledEventsWorker.prototype.drainAll = function () {
1239
+ return __awaiter$4(this, void 0, void 0, function () {
1240
+ return __generator$4(this, function (_a) {
1241
+ switch (_a.label) {
1242
+ case 0:
1243
+ if (!(this.queue.length > 0)) return [3 /*break*/, 2];
1244
+ return [4 /*yield*/, this.send()];
1245
+ case 1:
1246
+ _a.sent();
1247
+ return [3 /*break*/, 0];
1248
+ case 2: return [2 /*return*/];
1249
+ }
1250
+ });
1251
+ });
1063
1252
  };
1064
- ThrottledEventsLogger.prototype.processQueue = function () {
1253
+ ThrottledEventsWorker.prototype.processQueue = function () {
1065
1254
  var _this = this;
1066
- if (this.queue.length === 0 || this.isProcessing) {
1255
+ if (this.queue.length === 0 || this.inFlight) {
1067
1256
  return;
1068
1257
  }
1069
- this.isProcessing = true;
1070
- this.send()
1071
- .then(function () {
1072
- setTimeout(function () {
1073
- _this.isProcessing = false;
1074
- _this.processQueue();
1075
- }, 50);
1076
- })
1258
+ var inFlight = this.send()
1077
1259
  .catch(function (error) {
1078
1260
  _this.logger.error('[Honeybadger] Error making HTTP request:', error);
1079
- // Continue processing the queue even if there's an error
1080
- setTimeout(function () {
1081
- _this.isProcessing = false;
1082
- _this.processQueue();
1083
- }, 50);
1261
+ })
1262
+ .then(function () {
1263
+ // Only reset if we're still the current operation; a flushAsync() may
1264
+ // have taken ownership of inFlight while this send was resolving.
1265
+ if (_this.inFlight === inFlight) {
1266
+ _this.inFlight = null;
1267
+ _this.scheduleNextDispatch();
1268
+ }
1084
1269
  });
1270
+ this.inFlight = inFlight;
1271
+ };
1272
+ ThrottledEventsWorker.prototype.scheduleNextDispatch = function () {
1273
+ var intervalMs = this.dispatchIntervalMs();
1274
+ // Capture the cooldown deadline at dispatch time so a later config change to
1275
+ // the interval doesn't retroactively shorten a window already in progress.
1276
+ this.cooldownUntil = Date.now() + intervalMs;
1277
+ if (this.queue.length >= this.bulkThreshold()) {
1278
+ this.processQueue();
1279
+ return;
1280
+ }
1281
+ // Only arm a timer when there's queued work waiting for it. If the queue is
1282
+ // empty, `cooldownUntil` alone enforces the window: the next log() will arm a
1283
+ // timer for whatever time remains, and an idle worker holds no timer at all.
1284
+ if (this.queue.length > 0) {
1285
+ this.scheduleCooldownTimer(intervalMs);
1286
+ }
1287
+ };
1288
+ ThrottledEventsWorker.prototype.cooldownRemainingMs = function () {
1289
+ if (!this.cooldownUntil) {
1290
+ return 0;
1291
+ }
1292
+ return Math.max(0, this.cooldownUntil - Date.now());
1085
1293
  };
1086
- ThrottledEventsLogger.prototype.send = function () {
1294
+ ThrottledEventsWorker.prototype.scheduleCooldownTimer = function (ms) {
1295
+ var _this = this;
1296
+ this.cooldownTimer = setTimeout(function () {
1297
+ _this.cooldownTimer = null;
1298
+ _this.processQueue();
1299
+ }, ms);
1300
+ // In Node, don't let a pending cooldown timer keep the process alive for the
1301
+ // whole batching interval. `unref` lets the event loop go idle so the process
1302
+ // can exit (or a shutdown / `beforeExit` handler can run) promptly instead of
1303
+ // blocking for `dispatchIntervalSeconds`. Queued events aren't lost: a
1304
+ // `flushAsync()` on the way out still drains them before exit. (`unref` is
1305
+ // Node-only; the browser's numeric setTimeout handle has no such method.)
1306
+ if (typeof this.cooldownTimer === 'object' && this.cooldownTimer !== null && typeof this.cooldownTimer.unref === 'function') {
1307
+ this.cooldownTimer.unref();
1308
+ }
1309
+ };
1310
+ ThrottledEventsWorker.prototype.clearCooldownTimer = function () {
1311
+ if (this.cooldownTimer) {
1312
+ clearTimeout(this.cooldownTimer);
1313
+ this.cooldownTimer = null;
1314
+ }
1315
+ };
1316
+ ThrottledEventsWorker.prototype.send = function () {
1087
1317
  return __awaiter$4(this, void 0, void 0, function () {
1088
1318
  var eventsData, data;
1089
1319
  return __generator$4(this, function (_a) {
1090
1320
  if (this.queue.length === 0) {
1091
1321
  return [2 /*return*/];
1092
1322
  }
1093
- eventsData = this.queue.slice();
1094
- this.queue = [];
1323
+ eventsData = this.queue.splice(0, this.bulkThreshold());
1095
1324
  data = json_nd_1.NdJson.stringify(eventsData);
1096
1325
  return [2 /*return*/, this.makeHttpRequest(data)];
1097
1326
  });
1098
1327
  });
1099
1328
  };
1100
- ThrottledEventsLogger.prototype.makeHttpRequest = function (data) {
1329
+ ThrottledEventsWorker.prototype.bulkThreshold = function () {
1330
+ var _a;
1331
+ var v = (_a = this.config.events) === null || _a === void 0 ? void 0 : _a.bulkThreshold;
1332
+ return typeof v === 'number' && v > 0 ? v : defaults_1$1.CONFIG.events.bulkThreshold;
1333
+ };
1334
+ ThrottledEventsWorker.prototype.dispatchIntervalMs = function () {
1335
+ var _a;
1336
+ var v = (_a = this.config.events) === null || _a === void 0 ? void 0 : _a.dispatchIntervalSeconds;
1337
+ var seconds = typeof v === 'number' && v >= 0 ? v : defaults_1$1.CONFIG.events.dispatchIntervalSeconds;
1338
+ return seconds * 1000;
1339
+ };
1340
+ ThrottledEventsWorker.prototype.makeHttpRequest = function (data) {
1101
1341
  return __awaiter$4(this, void 0, void 0, function () {
1102
1342
  var _this = this;
1103
1343
  return __generator$4(this, function (_a) {
@@ -1112,10 +1352,13 @@ var ThrottledEventsLogger = /** @class */ (function () {
1112
1352
  maxObjectDepth: this.config.maxObjectDepth,
1113
1353
  logger: this.logger,
1114
1354
  }, data)
1115
- .then(function () {
1116
- if (_this.config.debug) {
1355
+ .then(function (resp) {
1356
+ if ([200, 201].includes(resp.statusCode)) {
1117
1357
  _this.logger.debug('[Honeybadger] Events sent successfully');
1118
1358
  }
1359
+ else {
1360
+ _this.logger.debug("[Honeybadger] Events failed[".concat(resp.statusCode, "]: ").concat(resp.body));
1361
+ }
1119
1362
  })
1120
1363
  .catch(function (err) {
1121
1364
  _this.logger.error("[Honeybadger] Error sending events: ".concat(err.message));
@@ -1126,28 +1369,40 @@ var ThrottledEventsLogger = /** @class */ (function () {
1126
1369
  /**
1127
1370
  * todo: improve this
1128
1371
  *
1129
- * The EventsLogger overrides the console methods to enable automatic instrumentation
1372
+ * The events plugin overrides the console methods to enable automatic instrumentation
1130
1373
  * of console logs to the Honeybadger API.
1131
1374
  * So if we want to log something in here we need to use the original methods.
1132
1375
  */
1133
- ThrottledEventsLogger.prototype.originalLogger = function () {
1134
- var _a, _b, _c, _d, _e;
1376
+ ThrottledEventsWorker.prototype.originalLogger = function () {
1377
+ var _this = this;
1378
+ var _a, _b, _c, _d;
1135
1379
  return {
1136
1380
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1137
1381
  log: (_a = console.log.__hb_original) !== null && _a !== void 0 ? _a : console.log,
1138
1382
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1139
1383
  info: (_b = console.info.__hb_original) !== null && _b !== void 0 ? _b : console.info,
1384
+ debug: function () {
1385
+ var _a;
1386
+ var args = [];
1387
+ for (var _i = 0; _i < arguments.length; _i++) {
1388
+ args[_i] = arguments[_i];
1389
+ }
1390
+ if (!_this.config.debug) {
1391
+ return;
1392
+ }
1393
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1394
+ var func = (_a = console.debug.__hb_original) !== null && _a !== void 0 ? _a : console.debug;
1395
+ return func.apply(void 0, args);
1396
+ },
1140
1397
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1141
- debug: (_c = console.debug.__hb_original) !== null && _c !== void 0 ? _c : console.debug,
1142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1143
- warn: (_d = console.warn.__hb_original) !== null && _d !== void 0 ? _d : console.warn,
1398
+ warn: (_c = console.warn.__hb_original) !== null && _c !== void 0 ? _c : console.warn,
1144
1399
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1145
- error: (_e = console.error.__hb_original) !== null && _e !== void 0 ? _e : console.error,
1400
+ error: (_d = console.error.__hb_original) !== null && _d !== void 0 ? _d : console.error,
1146
1401
  };
1147
1402
  };
1148
- return ThrottledEventsLogger;
1403
+ return ThrottledEventsWorker;
1149
1404
  }());
1150
- throttled_events_logger.ThrottledEventsLogger = ThrottledEventsLogger;
1405
+ throttled_events_worker.ThrottledEventsWorker = ThrottledEventsWorker;
1151
1406
 
1152
1407
  var __assign$1 = (commonjsGlobal && commonjsGlobal.__assign) || function () {
1153
1408
  __assign$1 = Object.assign || function(t) {
@@ -1200,7 +1455,7 @@ Object.defineProperty(client$1, "__esModule", { value: true });
1200
1455
  client$1.Client = void 0;
1201
1456
  var util_1$5 = util$1;
1202
1457
  var store_1 = store;
1203
- var throttled_events_logger_1 = throttled_events_logger;
1458
+ var throttled_events_worker_1 = throttled_events_worker;
1204
1459
  var defaults_1 = defaults;
1205
1460
  // Split at commas and spaces
1206
1461
  var TAG_SEPARATOR = /,|\s+/;
@@ -1212,17 +1467,20 @@ var Client = /** @class */ (function () {
1212
1467
  this.__pluginsLoaded = false;
1213
1468
  this.__store = null;
1214
1469
  this.__beforeNotifyHandlers = [];
1470
+ this.__beforeEventHandlers = [];
1215
1471
  this.__afterNotifyHandlers = [];
1472
+ this.__pendingEvents = new Set();
1216
1473
  this.__notifier = {
1217
1474
  name: '@honeybadger-io/core',
1218
1475
  url: 'https://github.com/honeybadger-io/honeybadger-js/tree/master/packages/core',
1219
- version: '6.15.3'
1476
+ version: '6.16.1'
1220
1477
  };
1221
1478
  this.config = __assign$1(__assign$1({}, defaults_1.CONFIG), opts);
1222
1479
  this.__initStore();
1223
1480
  this.__transport = transport;
1224
- this.__eventsLogger = new throttled_events_logger_1.ThrottledEventsLogger(this.config, this.__transport);
1481
+ this.__eventsWorker = new throttled_events_worker_1.ThrottledEventsWorker(this.config, this.__transport);
1225
1482
  this.logger = (0, util_1$5.logger)(this);
1483
+ this.__applyEventsEnabledShim(opts);
1226
1484
  }
1227
1485
  Client.prototype.getVersion = function () {
1228
1486
  return this.__notifier.version;
@@ -1245,10 +1503,24 @@ var Client = /** @class */ (function () {
1245
1503
  for (var k in opts) {
1246
1504
  this.config[k] = opts[k];
1247
1505
  }
1248
- this.__eventsLogger.configure(this.config);
1506
+ this.__applyEventsEnabledShim(opts);
1507
+ this.__eventsWorker.configure(this.config);
1249
1508
  this.loadPlugins();
1250
1509
  return this;
1251
1510
  };
1511
+ /**
1512
+ * Backwards compatibility: `eventsEnabled: true` used to opt into console events.
1513
+ * The deprecated flag auto-enables `insights.enabled` and `insights.console`;
1514
+ * explicit user-provided insights values win over the shim.
1515
+ */
1516
+ Client.prototype.__applyEventsEnabledShim = function (opts) {
1517
+ var _a;
1518
+ if (opts.eventsEnabled !== true) {
1519
+ return;
1520
+ }
1521
+ this.logger.warn('Deprecation warning: `eventsEnabled` has been deprecated; please use `insights.enabled` and `insights.console` instead.');
1522
+ this.config.insights = __assign$1(__assign$1(__assign$1({}, ((_a = this.config.insights) !== null && _a !== void 0 ? _a : {})), { enabled: true, console: true }), opts.insights);
1523
+ };
1252
1524
  Client.prototype.loadPlugins = function () {
1253
1525
  var _this = this;
1254
1526
  var pluginsToLoad = this.__pluginsLoaded
@@ -1258,12 +1530,16 @@ var Client = /** @class */ (function () {
1258
1530
  this.__pluginsLoaded = true;
1259
1531
  };
1260
1532
  Client.prototype.__initStore = function () {
1261
- this.__store = new store_1.GlobalStore({ context: {}, breadcrumbs: [] }, this.config.maxBreadcrumbs);
1533
+ this.__store = new store_1.GlobalStore({ context: {}, eventContext: {}, breadcrumbs: [] }, this.config.maxBreadcrumbs);
1262
1534
  };
1263
1535
  Client.prototype.beforeNotify = function (handler) {
1264
1536
  this.__beforeNotifyHandlers.push(handler);
1265
1537
  return this;
1266
1538
  };
1539
+ Client.prototype.beforeEvent = function (handler) {
1540
+ this.__beforeEventHandlers.push(handler);
1541
+ return this;
1542
+ };
1267
1543
  Client.prototype.afterNotify = function (handler) {
1268
1544
  this.__afterNotifyHandlers.push(handler);
1269
1545
  return this;
@@ -1274,6 +1550,16 @@ var Client = /** @class */ (function () {
1274
1550
  }
1275
1551
  return this;
1276
1552
  };
1553
+ Client.prototype.setEventContext = function (eventContext) {
1554
+ if (typeof eventContext === 'object' && eventContext != null) {
1555
+ this.__store.setEventContext(eventContext);
1556
+ }
1557
+ return this;
1558
+ };
1559
+ Client.prototype.clearEventContext = function () {
1560
+ this.__store.clearEventContext();
1561
+ return this;
1562
+ };
1277
1563
  Client.prototype.resetContext = function (context) {
1278
1564
  this.logger.warn('Deprecation warning: `Honeybadger.resetContext()` has been deprecated; please use `Honeybadger.clear()` instead.');
1279
1565
  this.__store.clear();
@@ -1286,31 +1572,63 @@ var Client = /** @class */ (function () {
1286
1572
  this.__store.clear();
1287
1573
  return this;
1288
1574
  };
1575
+ /**
1576
+ * Reporting an error must never break the host application, so any failure
1577
+ * inside the reporting path is logged and swallowed rather than thrown back
1578
+ * into the caller.
1579
+ */
1289
1580
  Client.prototype.notify = function (noticeable, name, extra) {
1290
1581
  var _this = this;
1291
1582
  if (name === void 0) { name = undefined; }
1292
1583
  if (extra === void 0) { extra = undefined; }
1293
- var notice = this.makeNotice(noticeable, name, extra);
1294
- // we need to have the source file data before the beforeNotifyHandlers,
1295
- // in case they modify them
1296
- var sourceCodeData = notice && notice.backtrace ? notice.backtrace.map(function (trace) { return (0, util_1$5.shallowClone)(trace); }) : null;
1297
- var preConditionsResult = this.__runPreconditions(notice);
1298
- if (preConditionsResult instanceof Error) {
1299
- (0, util_1$5.runAfterNotifyHandlers)(notice, this.__afterNotifyHandlers, preConditionsResult);
1584
+ // The body is inlined in this try/catch rather than delegated to a private
1585
+ // method: generated backtraces are trimmed by a fixed frame count
1586
+ // (DEFAULT_BACKTRACE_SHIFT), so an extra call frame here would shift every
1587
+ // generated backtrace into Honeybadger's own source.
1588
+ try {
1589
+ var notice_1 = this.makeNotice(noticeable, name, extra);
1590
+ // we need to have the source file data before the beforeNotifyHandlers,
1591
+ // in case they modify them
1592
+ var sourceCodeData_1 = notice_1 && notice_1.backtrace ? notice_1.backtrace.map(function (trace) { return (0, util_1$5.shallowClone)(trace); }) : null;
1593
+ var preConditionsResult = this.__runPreconditions(notice_1);
1594
+ if (preConditionsResult instanceof Error) {
1595
+ (0, util_1$5.runAfterNotifyHandlers)(notice_1, this.__afterNotifyHandlers, preConditionsResult);
1596
+ return false;
1597
+ }
1598
+ if (preConditionsResult instanceof Promise) {
1599
+ preConditionsResult.then(function (result) {
1600
+ if (result instanceof Error) {
1601
+ (0, util_1$5.runAfterNotifyHandlers)(notice_1, _this.__afterNotifyHandlers, result);
1602
+ return false;
1603
+ }
1604
+ return _this.__send(notice_1, sourceCodeData_1);
1605
+ }).catch(function (err) {
1606
+ // __send installs its own catch, which logs and runs the afterNotify
1607
+ // handlers. Anything arriving here failed before that chain existed,
1608
+ // so it is still unreported -- and notify() has already returned
1609
+ // true, leaving notifyAsync() waiting on those handlers.
1610
+ _this.__logReportingFailure(err);
1611
+ (0, util_1$5.runAfterNotifyHandlers)(notice_1, _this.__afterNotifyHandlers, err instanceof Error ? err : new Error(String(err)));
1612
+ });
1613
+ return true;
1614
+ }
1615
+ this.__send(notice_1, sourceCodeData_1).catch(function (_err) { });
1616
+ return true;
1617
+ }
1618
+ catch (err) {
1619
+ // Reporting an error must never break the host application, so a failure
1620
+ // in the reporting path is logged rather than thrown back at the caller.
1621
+ // notifyAsync() settles on the false return below.
1622
+ this.__logReportingFailure(err);
1300
1623
  return false;
1301
1624
  }
1302
- if (preConditionsResult instanceof Promise) {
1303
- preConditionsResult.then(function (result) {
1304
- if (result instanceof Error) {
1305
- (0, util_1$5.runAfterNotifyHandlers)(notice, _this.__afterNotifyHandlers, result);
1306
- return false;
1307
- }
1308
- return _this.__send(notice, sourceCodeData);
1309
- });
1310
- return true;
1625
+ };
1626
+ /** The logger is host-supplied, so even reporting a failure gets a guard. */
1627
+ Client.prototype.__logReportingFailure = function (err) {
1628
+ try {
1629
+ this.logger.error('Error report failed: an internal error occurred while reporting', err);
1311
1630
  }
1312
- this.__send(notice, sourceCodeData).catch(function (_err) { });
1313
- return true;
1631
+ catch (_loggerErr) { /* nothing left to report it with */ }
1314
1632
  };
1315
1633
  /**
1316
1634
  * An async version of {@link notify} that resolves only after the notice has been reported to Honeybadger.
@@ -1326,11 +1644,19 @@ var Client = /** @class */ (function () {
1326
1644
  var applyAfterNotify = function (partialNotice) {
1327
1645
  var originalAfterNotify = partialNotice.afterNotify;
1328
1646
  partialNotice.afterNotify = function (err) {
1329
- originalAfterNotify === null || originalAfterNotify === void 0 ? void 0 : originalAfterNotify.call(_this, err);
1330
- if (err) {
1331
- return reject(err);
1647
+ // Settle from a `finally` so a throwing handler cannot leave the
1648
+ // caller awaiting forever; its error propagates as it always has.
1649
+ try {
1650
+ originalAfterNotify === null || originalAfterNotify === void 0 ? void 0 : originalAfterNotify.call(_this, err);
1651
+ }
1652
+ finally {
1653
+ if (err) {
1654
+ reject(err);
1655
+ }
1656
+ else {
1657
+ resolve();
1658
+ }
1332
1659
  }
1333
- resolve();
1334
1660
  };
1335
1661
  };
1336
1662
  // We have to respect any afterNotify hooks that come from the arguments
@@ -1354,7 +1680,14 @@ var Client = /** @class */ (function () {
1354
1680
  objectToOverride = name = {};
1355
1681
  }
1356
1682
  applyAfterNotify(objectToOverride);
1357
- _this.notify(noticeable, name, extra);
1683
+ // `notify` never throws, so a false return is the only signal that the
1684
+ // report was abandoned. Every expected failure has already settled this
1685
+ // promise through the afterNotify hook above (a second reject is a
1686
+ // no-op); this catches the unexpected ones, which would otherwise leave
1687
+ // the caller awaiting forever.
1688
+ if (!_this.notify(noticeable, name, extra)) {
1689
+ reject(new Error('Error report failed: see logs for details'));
1690
+ }
1358
1691
  });
1359
1692
  };
1360
1693
  Client.prototype.makeNotice = function (noticeable, name, extra) {
@@ -1428,19 +1761,59 @@ var Client = /** @class */ (function () {
1428
1761
  this.event('log', data);
1429
1762
  };
1430
1763
  Client.prototype.event = function (type, data) {
1764
+ var _this = this;
1431
1765
  var _a;
1432
1766
  if (typeof type === 'object') {
1433
1767
  data = type;
1434
1768
  type = (_a = type['event_type']) !== null && _a !== void 0 ? _a : undefined;
1435
1769
  }
1436
- this.__eventsLogger.log(__assign$1({ event_type: type, ts: new Date().toISOString() }, data));
1770
+ var eventContext = this.__store.getContents('eventContext') || {};
1771
+ var payload = __assign$1(__assign$1({ event_type: type, ts: new Date().toISOString() }, eventContext), data);
1772
+ // Track in-flight handler chains so flushAsync() can await them before
1773
+ // delegating to the events logger; otherwise a caller that awaits flushAsync()
1774
+ // immediately after event() may flush before the payload enters the queue.
1775
+ var inFlight = (0, util_1$5.runBeforeEventHandlers)(payload, this.__beforeEventHandlers, this.logger)
1776
+ .then(function (shouldSend) {
1777
+ var _a, _b;
1778
+ if (!shouldSend) {
1779
+ _this.logger.debug('skipping event: beforeEvent handler returned false');
1780
+ return;
1781
+ }
1782
+ var sampleRate = (_b = (_a = _this.config.events) === null || _a === void 0 ? void 0 : _a.sampleRatePercentage) !== null && _b !== void 0 ? _b : 100;
1783
+ if (!(0, util_1$5.shouldSampleEvent)(payload, sampleRate)) {
1784
+ _this.logger.debug('skipping event: dropped by sampleRatePercentage');
1785
+ return;
1786
+ }
1787
+ delete payload._hb;
1788
+ _this.__eventsWorker.log(payload);
1789
+ })
1790
+ .catch(function (err) {
1791
+ // should not happen, because each handler is wrapped with a try/catch
1792
+ _this.logger.error('beforeEvent handler chain failed; dropping event', err);
1793
+ })
1794
+ .finally(function () {
1795
+ _this.__pendingEvents.delete(inFlight);
1796
+ });
1797
+ this.__pendingEvents.add(inFlight);
1437
1798
  };
1438
1799
  /**
1439
1800
  * This method currently flushes the event (Insights) queue.
1440
1801
  * In the future, it should also flush the error queue (assuming an error throttler is implemented).
1441
1802
  */
1442
1803
  Client.prototype.flushAsync = function () {
1443
- return this.__eventsLogger.flushAsync();
1804
+ return __awaiter$3(this, void 0, void 0, function () {
1805
+ return __generator$3(this, function (_a) {
1806
+ switch (_a.label) {
1807
+ case 0:
1808
+ if (!(this.__pendingEvents.size > 0)) return [3 /*break*/, 2];
1809
+ return [4 /*yield*/, Promise.allSettled(Array.from(this.__pendingEvents))];
1810
+ case 1:
1811
+ _a.sent();
1812
+ _a.label = 2;
1813
+ case 2: return [2 /*return*/, this.__eventsWorker.flushAsync()];
1814
+ }
1815
+ });
1816
+ });
1444
1817
  };
1445
1818
  Client.prototype.__getBreadcrumbs = function () {
1446
1819
  return this.__store.getContents('breadcrumbs').slice();
@@ -1632,7 +2005,7 @@ Object.defineProperty(types, "__esModule", { value: true });
1632
2005
  };
1633
2006
  Object.defineProperty(exports, "__esModule", { value: true });
1634
2007
  exports.Plugins = exports.Defaults = exports.Util = exports.Types = exports.Client = void 0;
1635
- var events_1 = __importDefault(events);
2008
+ var console_events_1 = __importDefault(console_events);
1636
2009
  var client_1 = client$1;
1637
2010
  Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_1.Client; } });
1638
2011
  __exportStar(store, exports);
@@ -1640,7 +2013,12 @@ Object.defineProperty(types, "__esModule", { value: true });
1640
2013
  exports.Util = __importStar(util$1);
1641
2014
  exports.Defaults = __importStar(defaults);
1642
2015
  exports.Plugins = {
1643
- events: events_1.default
2016
+ consoleEvents: console_events_1.default,
2017
+ /**
2018
+ * @deprecated Use `consoleEvents` instead. Kept as an alias for backwards
2019
+ * compatibility and will be removed in a future major version.
2020
+ */
2021
+ events: console_events_1.default,
1644
2022
  };
1645
2023
 
1646
2024
  } (src));
@@ -1802,8 +2180,145 @@ var uncaught_exception_monitor = {};
1802
2180
 
1803
2181
  var aws_lambda = {};
1804
2182
 
2183
+ var http_event = {};
2184
+
2185
+ /**
2186
+ * Shared helpers for HTTP-event instrumentation.
2187
+ *
2188
+ * Used by inbound (Express, Fastify, Lambda, Next.js) and — in a later PR —
2189
+ * outbound (`http`, `https`, `fetch`) integrations.
2190
+ */
2191
+ Object.defineProperty(http_event, "__esModule", { value: true });
2192
+ http_event.buildRequestEventPayload = http_event.durationMs = http_event.startTimer = http_event.seedRequestEventContext = http_event.getOrCreateCorrelationId = http_event.getOrCreateRequestId = void 0;
2193
+ var crypto_1 = require$$0__default$1["default"];
2194
+ /**
2195
+ * Reads a header value from a Node-style headers bag in a case-insensitive
2196
+ * manner. When the value is an array (e.g. `Set-Cookie`), returns the first
2197
+ * element. Returns `undefined` when the header is missing or empty.
2198
+ */
2199
+ function readHeader(headers, name) {
2200
+ if (!headers)
2201
+ return undefined;
2202
+ var lower = name.toLowerCase();
2203
+ var value = headers[lower];
2204
+ if (value === undefined) {
2205
+ for (var _i = 0, _a = Object.keys(headers); _i < _a.length; _i++) {
2206
+ var key = _a[_i];
2207
+ if (key.toLowerCase() === lower) {
2208
+ value = headers[key];
2209
+ break;
2210
+ }
2211
+ }
2212
+ }
2213
+ if (Array.isArray(value))
2214
+ value = value[0];
2215
+ if (typeof value !== 'string')
2216
+ return undefined;
2217
+ var trimmed = value.trim();
2218
+ return trimmed.length ? trimmed : undefined;
2219
+ }
2220
+ /**
2221
+ * Generates a UUID-like identifier. Prefers Node's `crypto.randomUUID()` when
2222
+ * available (Node 14.17+); falls back to a Math.random-based v4-shaped string
2223
+ * for older Node versions.
2224
+ */
2225
+ function generateId() {
2226
+ if (typeof crypto_1.randomUUID === 'function') {
2227
+ try {
2228
+ return (0, crypto_1.randomUUID)();
2229
+ }
2230
+ catch (_e) {
2231
+ // fall through to manual generation
2232
+ }
2233
+ }
2234
+ // v4-shaped, not crypto-quality. Acceptable since this is a correlation id,
2235
+ // not a security token.
2236
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (ch) {
2237
+ var r = (Math.random() * 16) | 0;
2238
+ var v = ch === 'x' ? r : (r & 0x3) | 0x8;
2239
+ return v.toString(16);
2240
+ });
2241
+ }
2242
+ /**
2243
+ * Returns a unique-per-request id. Reads `x-request-id`, then `request-id`
2244
+ * headers; falls back to a generated id when neither is present.
2245
+ */
2246
+ function getOrCreateRequestId(headers) {
2247
+ var _a, _b;
2248
+ return ((_b = (_a = readHeader(headers, 'x-request-id')) !== null && _a !== void 0 ? _a : readHeader(headers, 'request-id')) !== null && _b !== void 0 ? _b : generateId());
2249
+ }
2250
+ http_event.getOrCreateRequestId = getOrCreateRequestId;
2251
+ /**
2252
+ * Returns a correlation id that may span multiple requests in a logical trace.
2253
+ * Reads `x-correlation-id`, then `x-amzn-trace-id`; falls back to the supplied
2254
+ * `requestId` so callers always have both fields populated.
2255
+ */
2256
+ function getOrCreateCorrelationId(headers, requestId) {
2257
+ var _a, _b;
2258
+ return ((_b = (_a = readHeader(headers, 'x-correlation-id')) !== null && _a !== void 0 ? _a : readHeader(headers, 'x-amzn-trace-id')) !== null && _b !== void 0 ? _b : requestId);
2259
+ }
2260
+ http_event.getOrCreateCorrelationId = getOrCreateCorrelationId;
2261
+ /**
2262
+ * Convenience helper for framework integrations: read request/correlation ids
2263
+ * from the request headers and return both. When no headers contain either
2264
+ * value, both ids are generated and `requestId === correlationId`.
2265
+ */
2266
+ function seedRequestEventContext(headers) {
2267
+ var requestId = getOrCreateRequestId(headers);
2268
+ var correlationId = getOrCreateCorrelationId(headers, requestId);
2269
+ return { request_id: requestId, correlation_id: correlationId };
2270
+ }
2271
+ http_event.seedRequestEventContext = seedRequestEventContext;
2272
+ /**
2273
+ * Starts a high-resolution timer.
2274
+ */
2275
+ function startTimer() {
2276
+ return process.hrtime.bigint();
2277
+ }
2278
+ http_event.startTimer = startTimer;
2279
+ var NS_PER_MS = BigInt(1000000);
2280
+ /**
2281
+ * Returns the integer number of milliseconds elapsed since `start`.
2282
+ */
2283
+ function durationMs(start) {
2284
+ var diff = process.hrtime.bigint() - start;
2285
+ return Number(diff / NS_PER_MS);
2286
+ }
2287
+ http_event.durationMs = durationMs;
2288
+ /**
2289
+ * Builds the per-request event payload. `request_id` and `correlation_id` are
2290
+ * NOT added here — they live on `eventContext` and are merged onto every event
2291
+ * by the client.
2292
+ */
2293
+ function buildRequestEventPayload(input) {
2294
+ var payload = {};
2295
+ if (input.method !== undefined)
2296
+ payload.method = input.method;
2297
+ if (input.path !== undefined)
2298
+ payload.path = input.path;
2299
+ if (input.route !== undefined)
2300
+ payload.route = input.route;
2301
+ if (input.status !== undefined)
2302
+ payload.status = input.status;
2303
+ if (input.duration !== undefined)
2304
+ payload.duration = input.duration;
2305
+ if (input.extra) {
2306
+ for (var _i = 0, _a = Object.keys(input.extra); _i < _a.length; _i++) {
2307
+ var k = _a[_i];
2308
+ if (!Object.prototype.hasOwnProperty.call(payload, k)) {
2309
+ payload[k] = input.extra[k];
2310
+ }
2311
+ }
2312
+ }
2313
+ return payload;
2314
+ }
2315
+ http_event.buildRequestEventPayload = buildRequestEventPayload;
2316
+
1805
2317
  Object.defineProperty(aws_lambda, "__esModule", { value: true });
1806
2318
  aws_lambda.removeAwsDefaultUncaughtExceptionListener = aws_lambda.lambdaHandler = void 0;
2319
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2320
+ var core_1$5 = src;
2321
+ var http_event_1$1 = http_event;
1807
2322
  function isHandlerSync(handler) {
1808
2323
  return handler.length > 2;
1809
2324
  }
@@ -1815,28 +2330,132 @@ function reportToHoneybadger(hb, err, callback) {
1815
2330
  }
1816
2331
  });
1817
2332
  }
2333
+ // Detects inbound HTTP Lambda events (API Gateway v1 REST, API Gateway v2 HTTP, and ALB).
2334
+ // API Gateway v1 and ALB carry `httpMethod` at the top level; API Gateway v2 nests
2335
+ // method under `requestContext.http`.
2336
+ function isHttpLambdaEvent(event) {
2337
+ if (!event || typeof event !== 'object') {
2338
+ return false;
2339
+ }
2340
+ var e = event;
2341
+ if (typeof e.httpMethod === 'string') {
2342
+ return true;
2343
+ }
2344
+ var rc = e.requestContext;
2345
+ var http = rc && rc.http;
2346
+ return !!(http && typeof http.method === 'string');
2347
+ }
2348
+ function getLambdaMethod(event) {
2349
+ if (!event || typeof event !== 'object') {
2350
+ return undefined;
2351
+ }
2352
+ var e = event;
2353
+ if (typeof e.httpMethod === 'string') {
2354
+ return e.httpMethod;
2355
+ }
2356
+ var rc = e.requestContext;
2357
+ var http = rc && rc.http;
2358
+ if (http && typeof http.method === 'string') {
2359
+ return http.method;
2360
+ }
2361
+ return undefined;
2362
+ }
2363
+ function getLambdaPath(event) {
2364
+ if (!event || typeof event !== 'object') {
2365
+ return undefined;
2366
+ }
2367
+ var e = event;
2368
+ var rc = e.requestContext;
2369
+ var http = rc && rc.http;
2370
+ if (http && typeof http.path === 'string') {
2371
+ return http.path;
2372
+ }
2373
+ if (typeof e.rawPath === 'string') {
2374
+ return e.rawPath;
2375
+ }
2376
+ if (typeof e.path === 'string') {
2377
+ return e.path;
2378
+ }
2379
+ return undefined;
2380
+ }
2381
+ function lambdaStatus(result) {
2382
+ if (result && typeof result === 'object') {
2383
+ var status_1 = result.statusCode;
2384
+ if (typeof status_1 === 'number')
2385
+ return status_1;
2386
+ }
2387
+ return 200;
2388
+ }
2389
+ function getLambdaHeaders(event) {
2390
+ if (!event || typeof event !== 'object')
2391
+ return {};
2392
+ var headers = event.headers;
2393
+ if (!headers || typeof headers !== 'object')
2394
+ return {};
2395
+ return headers;
2396
+ }
2397
+ function seedLambdaEventContext(hb, event, context) {
2398
+ if (isHttpLambdaEvent(event)) {
2399
+ hb.setEventContext((0, http_event_1$1.seedRequestEventContext)(getLambdaHeaders(event)));
2400
+ return;
2401
+ }
2402
+ // Non-HTTP triggers (SQS, S3, EventBridge, scheduled) have no inbound headers.
2403
+ // Use the AWS request id as the request_id, and X-Ray's trace id (if set) as
2404
+ // the correlation_id, otherwise mirror request_id.
2405
+ var requestId = context.awsRequestId;
2406
+ var correlationId = process.env._X_AMZN_TRACE_ID || requestId;
2407
+ hb.setEventContext({ request_id: requestId, correlation_id: correlationId });
2408
+ }
2409
+ // Returns an emitter for the `request.handled` event, or null when HTTP
2410
+ // instrumentation is off (nothing to emit). The emitter also owns delivery:
2411
+ // Lambda may freeze the execution environment the moment the handler completes,
2412
+ // so after recording the event it flushes the queue and returns that promise.
2413
+ // Callers must await it before resolving/invoking the callback. Delivery failures
2414
+ // are logged by the events worker and must not break the handler.
2415
+ function createLambdaRequestEmitter(hb, event) {
2416
+ if (!isHttpLambdaEvent(event))
2417
+ return null;
2418
+ if (!core_1$5.Util.resolveInsights(hb.config).http)
2419
+ return null;
2420
+ var start = (0, http_event_1$1.startTimer)();
2421
+ var emitted = false;
2422
+ return function emit(status) {
2423
+ if (emitted)
2424
+ return Promise.resolve();
2425
+ emitted = true;
2426
+ hb.event('request.handled', (0, http_event_1$1.buildRequestEventPayload)({
2427
+ method: getLambdaMethod(event),
2428
+ path: getLambdaPath(event),
2429
+ status: status,
2430
+ duration: (0, http_event_1$1.durationMs)(start),
2431
+ }));
2432
+ return hb.flushAsync().catch(function () { });
2433
+ };
2434
+ }
1818
2435
  function asyncHandler(handler, hb) {
1819
2436
  return function wrappedLambdaHandler(event, context) {
1820
2437
  return new Promise(function (resolve, reject) {
1821
2438
  hb.run(function () {
2439
+ seedLambdaEventContext(hb, event, context);
2440
+ var emit = createLambdaRequestEmitter(hb, event);
1822
2441
  var timeoutHandler = setupTimeoutWarning(hb, context);
1823
2442
  try {
1824
- var result = handler(event, context);
2443
+ var result_1 = handler(event, context);
1825
2444
  // check if handler returns a promise
1826
- if (result && result.then) {
1827
- result
1828
- .then(resolve)
1829
- .catch(function (err) { return reportToHoneybadger(hb, err, reject); })
2445
+ if (result_1 && result_1.then) {
2446
+ result_1
2447
+ .then(function (value) { return Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(lambdaStatus(value))).then(function () { return resolve(value); }); })
2448
+ .catch(function (err) { return Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(500)).then(function () { return reportToHoneybadger(hb, err, reject); }); })
1830
2449
  .finally(function () { return clearTimeout(timeoutHandler); });
1831
2450
  }
1832
2451
  else {
1833
2452
  clearTimeout(timeoutHandler);
1834
- resolve(result);
2453
+ Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(lambdaStatus(result_1))).then(function () { return resolve(result_1); });
1835
2454
  }
1836
2455
  }
1837
2456
  catch (err) {
1838
2457
  clearTimeout(timeoutHandler);
1839
- reportToHoneybadger(hb, err, reject);
2458
+ Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(500)).then(function () { return reportToHoneybadger(hb, err, reject); });
1840
2459
  }
1841
2460
  });
1842
2461
  });
@@ -1845,19 +2464,22 @@ function asyncHandler(handler, hb) {
1845
2464
  function syncHandler(handler, hb) {
1846
2465
  return function wrappedLambdaHandler(event, context, cb) {
1847
2466
  hb.run(function () {
2467
+ seedLambdaEventContext(hb, event, context);
2468
+ var emit = createLambdaRequestEmitter(hb, event);
1848
2469
  var timeoutHandler = setupTimeoutWarning(hb, context);
1849
2470
  try {
1850
2471
  handler(event, context, function (error, result) {
1851
2472
  clearTimeout(timeoutHandler);
1852
2473
  if (error) {
1853
- return reportToHoneybadger(hb, error, cb);
2474
+ Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(500)).then(function () { return reportToHoneybadger(hb, error, cb); });
2475
+ return;
1854
2476
  }
1855
- cb(null, result);
2477
+ Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(lambdaStatus(result))).then(function () { return cb(null, result); });
1856
2478
  });
1857
2479
  }
1858
2480
  catch (err) {
1859
2481
  clearTimeout(timeoutHandler);
1860
- reportToHoneybadger(hb, err, cb);
2482
+ Promise.resolve(emit === null || emit === void 0 ? void 0 : emit(500)).then(function () { return reportToHoneybadger(hb, err, cb); });
1861
2483
  }
1862
2484
  });
1863
2485
  };
@@ -2130,6 +2752,7 @@ var ShutdownMonitor = /** @class */ (function () {
2130
2752
  function ShutdownMonitor() {
2131
2753
  this.__isReporting = false;
2132
2754
  this.__listener = this.makeListener();
2755
+ this.__beforeExitListener = this.makeBeforeExitListener();
2133
2756
  }
2134
2757
  ShutdownMonitor.prototype.setClient = function (client) {
2135
2758
  this.__client = client;
@@ -2158,6 +2781,35 @@ var ShutdownMonitor = /** @class */ (function () {
2158
2781
  }); };
2159
2782
  return honeybadgerShutdownListener;
2160
2783
  };
2784
+ // `beforeExit` fires when the event loop drains and the process is about to
2785
+ // exit on its own — a natural exit, which the SIGTERM/SIGINT listeners above do
2786
+ // *not* cover. We flush any queued events (e.g. batched Insights events whose
2787
+ // cooldown timer is unref'd and therefore won't hold the process open) so they
2788
+ // aren't dropped on the way out. Unlike the signal path we don't force-exit;
2789
+ // flushing schedules real I/O, which keeps the process alive until delivery, and
2790
+ // then Node exits naturally. The `__isReporting` guard stops the flush's own I/O
2791
+ // from re-triggering us.
2792
+ ShutdownMonitor.prototype.makeBeforeExitListener = function () {
2793
+ var _this = this;
2794
+ // noinspection UnnecessaryLocalVariableJS
2795
+ var honeybadgerBeforeExitListener = function () { return __awaiter$1(_this, void 0, void 0, function () {
2796
+ return __generator$1(this, function (_a) {
2797
+ switch (_a.label) {
2798
+ case 0:
2799
+ if (this.__isReporting || !this.__client) {
2800
+ return [2 /*return*/];
2801
+ }
2802
+ this.__isReporting = true;
2803
+ return [4 /*yield*/, this.__client.flushAsync()];
2804
+ case 1:
2805
+ _a.sent();
2806
+ this.__isReporting = false;
2807
+ return [2 /*return*/];
2808
+ }
2809
+ });
2810
+ }); };
2811
+ return honeybadgerBeforeExitListener;
2812
+ };
2161
2813
  ShutdownMonitor.prototype.maybeAddListener = function () {
2162
2814
  for (var _i = 0, _a = ShutdownMonitor.KILL_SIGNALS; _i < _a.length; _i++) {
2163
2815
  var signal = _a[_i];
@@ -2166,6 +2818,10 @@ var ShutdownMonitor = /** @class */ (function () {
2166
2818
  process.on(signal, this.__listener);
2167
2819
  }
2168
2820
  }
2821
+ var beforeExitListeners = process.listeners('beforeExit') || [];
2822
+ if (!beforeExitListeners.includes(this.__beforeExitListener)) {
2823
+ process.on('beforeExit', this.__beforeExitListener);
2824
+ }
2169
2825
  };
2170
2826
  ShutdownMonitor.prototype.maybeRemoveListener = function () {
2171
2827
  for (var _i = 0, _a = ShutdownMonitor.KILL_SIGNALS; _i < _a.length; _i++) {
@@ -2175,6 +2831,10 @@ var ShutdownMonitor = /** @class */ (function () {
2175
2831
  process.removeListener(signal, this.__listener);
2176
2832
  }
2177
2833
  }
2834
+ var beforeExitListeners = process.listeners('beforeExit') || [];
2835
+ if (beforeExitListeners.includes(this.__beforeExitListener)) {
2836
+ process.removeListener('beforeExit', this.__beforeExitListener);
2837
+ }
2178
2838
  };
2179
2839
  ShutdownMonitor.prototype.hasOtherShutdownListeners = function (signal) {
2180
2840
  var _this = this;
@@ -2199,14 +2859,9 @@ function default_1() {
2199
2859
  return {
2200
2860
  load: function (client) {
2201
2861
  shutdownMonitor.setClient(client);
2202
- // at the moment, the shutdown monitor only sends events from the queue
2203
- // if we implement a queue for throttling errors, we won't have to check for `config.eventsEnabled`
2204
- if (client.config.eventsEnabled) {
2205
- shutdownMonitor.maybeAddListener();
2206
- }
2207
- else {
2208
- shutdownMonitor.maybeRemoveListener();
2209
- }
2862
+ // programmatic events can be queued at any time, so the flush listener
2863
+ // is always registered
2864
+ shutdownMonitor.maybeAddListener();
2210
2865
  },
2211
2866
  shouldReloadOnConfigure: true,
2212
2867
  };
@@ -2220,7 +2875,9 @@ var __importDefault = (commonjsGlobal && commonjsGlobal.__importDefault) || func
2220
2875
  };
2221
2876
  Object.defineProperty(middleware, "__esModule", { value: true });
2222
2877
  middleware.errorHandler = middleware.requestHandler = void 0;
2223
- var url_1$1 = __importDefault(require$$0__default$1["default"]);
2878
+ var url_1$1 = __importDefault(require$$0__default$2["default"]);
2879
+ var core_1$4 = src;
2880
+ var http_event_1 = http_event;
2224
2881
  function fullUrl(req) {
2225
2882
  var connection = req.connection;
2226
2883
  var address = connection && connection.address();
@@ -2235,8 +2892,43 @@ function fullUrl(req) {
2235
2892
  query: req.query
2236
2893
  });
2237
2894
  }
2895
+ function instrumentInboundRequest(client, req, res) {
2896
+ // Completion is observed via response events, which requires a Node
2897
+ // ServerResponse. A non-EventEmitter response (e.g. a FastifyReply when this
2898
+ // middleware is mistakenly registered as a fastify preHandler) would break
2899
+ // every request — degrade to "no request event" instead. Fastify users
2900
+ // should register fastifyPlugin() from server/fastify.
2901
+ if (typeof res.once !== 'function') {
2902
+ client.logger.debug('skipping insights instrumentation for http requests: response object is not an EventEmitter and does not have a .once() hook');
2903
+ return;
2904
+ }
2905
+ var start = (0, http_event_1.startTimer)();
2906
+ var emitted = false;
2907
+ var emit = function () {
2908
+ var _a;
2909
+ if (emitted)
2910
+ return;
2911
+ emitted = true;
2912
+ client.event('request.handled', (0, http_event_1.buildRequestEventPayload)({
2913
+ method: req.method,
2914
+ path: req.path,
2915
+ route: (_a = req.route) === null || _a === void 0 ? void 0 : _a.path,
2916
+ status: res.statusCode,
2917
+ duration: (0, http_event_1.durationMs)(start),
2918
+ }));
2919
+ };
2920
+ res.once('finish', emit);
2921
+ res.once('close', emit);
2922
+ }
2238
2923
  function requestHandler(req, res, next) {
2239
- this.withRequest(req, next, next);
2924
+ var _this = this;
2925
+ this.withRequest(req, function () {
2926
+ _this.setEventContext((0, http_event_1.seedRequestEventContext)(req.headers));
2927
+ if (core_1$4.Util.resolveInsights(_this.config).http) {
2928
+ instrumentInboundRequest(_this, req, res);
2929
+ }
2930
+ next();
2931
+ }, next);
2240
2932
  }
2241
2933
  middleware.requestHandler = requestHandler;
2242
2934
  function errorHandler(err, req, _res, next) {
@@ -2294,12 +2986,12 @@ var __importStar = (commonjsGlobal && commonjsGlobal.__importStar) || function (
2294
2986
  };
2295
2987
  Object.defineProperty(transport, "__esModule", { value: true });
2296
2988
  transport.ServerTransport = void 0;
2297
- var core_1$2 = src;
2298
- var url_1 = require$$0__default$1["default"];
2989
+ var core_1$3 = src;
2990
+ var url_1 = require$$0__default$2["default"];
2299
2991
  var util_1 = util;
2300
2992
  var http = __importStar(require$$3__default$1["default"]);
2301
2993
  var https = __importStar(require$$4__default["default"]);
2302
- var sanitize = core_1$2.Util.sanitize;
2994
+ var sanitize = core_1$3.Util.sanitize;
2303
2995
  var ServerTransport = /** @class */ (function () {
2304
2996
  function ServerTransport(headers) {
2305
2997
  if (headers === void 0) { headers = {}; }
@@ -2375,6 +3067,8 @@ var async_store = {};
2375
3067
 
2376
3068
  Object.defineProperty(async_store, "__esModule", { value: true });
2377
3069
  async_store.AsyncStore = void 0;
3070
+ var core_1$2 = src;
3071
+ var clone = core_1$2.Util.clone;
2378
3072
  var kHoneybadgerStore = Symbol.for('kHoneybadgerStore');
2379
3073
  var AsyncStore = /** @class */ (function () {
2380
3074
  function AsyncStore(asyncLocalStorage, contents, breadcrumbsLimit) {
@@ -2405,7 +3099,10 @@ var AsyncStore = /** @class */ (function () {
2405
3099
  };
2406
3100
  AsyncStore.prototype.getContents = function (key) {
2407
3101
  var value = key ? this.__currentContents()[key] : this.__currentContents();
2408
- return JSON.parse(JSON.stringify(value));
3102
+ // Deep copy like a JSON round trip, but tolerate circular references in
3103
+ // host-supplied context and breadcrumb metadata. See GlobalStore.
3104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3105
+ return clone(value);
2409
3106
  };
2410
3107
  AsyncStore.prototype.available = function () {
2411
3108
  return !!this.als.getStore();
@@ -2413,6 +3110,12 @@ var AsyncStore = /** @class */ (function () {
2413
3110
  AsyncStore.prototype.setContext = function (context) {
2414
3111
  Object.assign(this.__currentContents().context, context || {});
2415
3112
  };
3113
+ AsyncStore.prototype.setEventContext = function (eventContext) {
3114
+ Object.assign(this.__currentContents().eventContext, eventContext || {});
3115
+ };
3116
+ AsyncStore.prototype.clearEventContext = function () {
3117
+ this.__currentContents().eventContext = {};
3118
+ };
2416
3119
  AsyncStore.prototype.addBreadcrumb = function (breadcrumb) {
2417
3120
  if (this.__currentContents().breadcrumbs.length == this.breadcrumbsLimit) {
2418
3121
  this.__currentContents().breadcrumbs.shift();
@@ -2421,6 +3124,7 @@ var AsyncStore = /** @class */ (function () {
2421
3124
  };
2422
3125
  AsyncStore.prototype.clear = function () {
2423
3126
  this.__currentContents().context = {};
3127
+ this.__currentContents().eventContext = {};
2424
3128
  this.__currentContents().breadcrumbs = [];
2425
3129
  };
2426
3130
  AsyncStore.prototype.run = function (callback, request) {
@@ -2457,7 +3161,7 @@ var async_store_1 = async_store;
2457
3161
  */
2458
3162
  var StackedStore = /** @class */ (function () {
2459
3163
  function StackedStore(breadcrumbsLimit) {
2460
- this.contents = { context: {}, breadcrumbs: [] };
3164
+ this.contents = { context: {}, eventContext: {}, breadcrumbs: [] };
2461
3165
  this.asyncStore = async_store_1.AsyncStore.create(this.contents, breadcrumbsLimit);
2462
3166
  this.globalStore = core_1$1.GlobalStore.create(this.contents, breadcrumbsLimit);
2463
3167
  }
@@ -2475,6 +3179,12 @@ var StackedStore = /** @class */ (function () {
2475
3179
  StackedStore.prototype.setContext = function (context) {
2476
3180
  this.__activeStore().setContext(context);
2477
3181
  };
3182
+ StackedStore.prototype.setEventContext = function (eventContext) {
3183
+ this.__activeStore().setEventContext(eventContext);
3184
+ };
3185
+ StackedStore.prototype.clearEventContext = function () {
3186
+ this.__activeStore().clearEventContext();
3187
+ };
2478
3188
  StackedStore.prototype.addBreadcrumb = function (breadcrumb) {
2479
3189
  this.__activeStore().addBreadcrumb(breadcrumb);
2480
3190
  };
@@ -3121,7 +3831,7 @@ client.CheckInsClient = CheckInsClient;
3121
3831
  (0, uncaught_exception_plugin_1.default)(),
3122
3832
  (0, unhandled_rejection_plugin_1.default)(),
3123
3833
  (0, shutdown_plugin_1.default)(),
3124
- core_1.Plugins.events(),
3834
+ core_1.Plugins.consoleEvents(),
3125
3835
  ];
3126
3836
  var Honeybadger = /** @class */ (function (_super) {
3127
3837
  __extends(Honeybadger, _super);
@@ -3279,7 +3989,7 @@ client.CheckInsClient = CheckInsClient;
3279
3989
  var NOTIFIER = {
3280
3990
  name: '@honeybadger-io/js',
3281
3991
  url: 'https://github.com/honeybadger-io/honeybadger-js/tree/master/packages/js',
3282
- version: '6.15.3'
3992
+ version: '6.16.1'
3283
3993
  };
3284
3994
  var userAgent = function () {
3285
3995
  return "Honeybadger JS Server Client ".concat(NOTIFIER.version, ", ").concat(os_1.default.version(), "; ").concat(os_1.default.platform());