@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.
@@ -35,7 +35,7 @@
35
35
 
36
36
  var src = {};
37
37
 
38
- var events = {};
38
+ var console_events = {};
39
39
 
40
40
  var util$1 = {};
41
41
 
@@ -242,7 +242,7 @@
242
242
  }
243
243
  };
244
244
  Object.defineProperty(exports, "__esModule", { value: true });
245
- 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;
245
+ 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;
246
246
  /* eslint-disable prefer-rest-params */
247
247
  var stackTraceParser = __importStar(require$$0$1);
248
248
  function merge(obj1, obj2) {
@@ -418,6 +418,88 @@
418
418
  };
419
419
  }
420
420
  exports.runBeforeNotifyHandlers = runBeforeNotifyHandlers;
421
+ function runBeforeEventHandlers(payload, handlers, logger) {
422
+ return __awaiter(this, void 0, void 0, function () {
423
+ var i, len, result, err_1;
424
+ return __generator(this, function (_a) {
425
+ switch (_a.label) {
426
+ case 0:
427
+ i = 0, len = handlers.length;
428
+ _a.label = 1;
429
+ case 1:
430
+ if (!(i < len)) return [3 /*break*/, 7];
431
+ result = void 0;
432
+ _a.label = 2;
433
+ case 2:
434
+ _a.trys.push([2, 4, , 5]);
435
+ return [4 /*yield*/, handlers[i](payload)];
436
+ case 3:
437
+ result = _a.sent();
438
+ return [3 /*break*/, 5];
439
+ case 4:
440
+ err_1 = _a.sent();
441
+ // A buggy handler should not suppress unrelated events. Log and treat as no-op.
442
+ logger === null || logger === void 0 ? void 0 : logger.error('beforeEvent handler threw; continuing', err_1);
443
+ return [3 /*break*/, 6];
444
+ case 5:
445
+ if (result === false) {
446
+ return [2 /*return*/, false];
447
+ }
448
+ _a.label = 6;
449
+ case 6:
450
+ i++;
451
+ return [3 /*break*/, 1];
452
+ case 7: return [2 /*return*/, true];
453
+ }
454
+ });
455
+ });
456
+ }
457
+ exports.runBeforeEventHandlers = runBeforeEventHandlers;
458
+ function resolveInsights(config) {
459
+ var _a, _b;
460
+ var insights = config.insights;
461
+ if (!insights || insights.enabled !== true) {
462
+ return { console: false, http: false };
463
+ }
464
+ return {
465
+ console: (_a = insights.console) !== null && _a !== void 0 ? _a : false,
466
+ http: (_b = insights.http) !== null && _b !== void 0 ? _b : false,
467
+ };
468
+ }
469
+ exports.resolveInsights = resolveInsights;
470
+ function fnv1a(str) {
471
+ var h = 0x811c9dc5;
472
+ for (var i = 0, len = str.length; i < len; i++) {
473
+ h ^= str.charCodeAt(i);
474
+ h = Math.imul(h, 0x01000193);
475
+ }
476
+ return h >>> 0;
477
+ }
478
+ function clampSampleRate(rate) {
479
+ if (rate < 0)
480
+ return 0;
481
+ if (rate > 100)
482
+ return 100;
483
+ return rate;
484
+ }
485
+ function shouldSampleEvent(event, configRate) {
486
+ var meta = event._hb;
487
+ var override = meta && Number.isFinite(meta.sampleRate) ? meta.sampleRate : undefined;
488
+ var rawRate = override !== undefined ? override : configRate;
489
+ // Guard against non-finite rates (e.g. NaN from Number(invalidConfig)); an
490
+ // unusable rate must not silently drop every event, so fall back to send-all.
491
+ var rate = clampSampleRate(Number.isFinite(rawRate) ? rawRate : 100);
492
+ if (rate <= 0)
493
+ return false;
494
+ if (rate >= 100)
495
+ return true;
496
+ var requestId = event.request_id;
497
+ if (typeof requestId === 'string' && requestId.length > 0) {
498
+ return fnv1a(requestId) % 100 < rate;
499
+ }
500
+ return Math.random() * 100 < rate;
501
+ }
502
+ exports.shouldSampleEvent = shouldSampleEvent;
421
503
  function runAfterNotifyHandlers(notice, handlers, error) {
422
504
  if (notice && notice.afterNotify) {
423
505
  notice.afterNotify(error, notice);
@@ -756,8 +838,36 @@
756
838
  return formattedVars;
757
839
  }
758
840
  exports.formatCGIData = formatCGIData;
841
+ /**
842
+ * Deep-copies a value exactly the way a JSON round trip does -- `toJSON` is
843
+ * honored, so dates become ISO strings -- except that circular references are
844
+ * replaced with '[RECURSION]' instead of throwing.
845
+ *
846
+ * Only true cycles are replaced: a value referenced twice in sibling branches
847
+ * is copied twice, as a JSON round trip would.
848
+ *
849
+ * Anything else a round trip rejects (a BigInt, a getter that throws) still
850
+ * throws here. Callers that must not fail are expected to handle it: silently
851
+ * substituting a placeholder would let a malformed value -- a string where an
852
+ * object is expected, say -- travel on as though it were valid.
853
+ */
759
854
  function clone(obj) {
760
- return JSON.parse(JSON.stringify(obj));
855
+ var ancestors = [];
856
+ return JSON.parse(JSON.stringify(obj, function (_key, value) {
857
+ if (typeof value !== 'object' || value === null) {
858
+ return value;
859
+ }
860
+ // `this` is the object `value` sits in, so anything stacked above it has
861
+ // been fully visited and is no longer an ancestor of `value`.
862
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
863
+ ancestors.pop();
864
+ }
865
+ if (ancestors.indexOf(value) !== -1) {
866
+ return '[RECURSION]';
867
+ }
868
+ ancestors.push(value);
869
+ return value;
870
+ }));
761
871
  }
762
872
  exports.clone = clone;
763
873
  var THRESHOLD_COLUMN_NUMBER = 10000;
@@ -838,7 +948,7 @@
838
948
 
839
949
  } (util$1));
840
950
 
841
- Object.defineProperty(events, "__esModule", { value: true });
951
+ Object.defineProperty(console_events, "__esModule", { value: true });
842
952
  var util_1$4 = util$1;
843
953
  function default_1$4(_window) {
844
954
  if (_window === void 0) { _window = (0, util_1$4.globalThisOrWindow)(); }
@@ -846,7 +956,7 @@
846
956
  shouldReloadOnConfigure: false,
847
957
  load: function (client) {
848
958
  function sendEventsToInsights() {
849
- return client.config.eventsEnabled;
959
+ return (0, util_1$4.resolveInsights)(client.config).console;
850
960
  }
851
961
  if (!sendEventsToInsights()) {
852
962
  return;
@@ -873,7 +983,7 @@
873
983
  }
874
984
  };
875
985
  }
876
- events.default = default_1$4;
986
+ console_events.default = default_1$4;
877
987
 
878
988
  var client = {};
879
989
 
@@ -895,11 +1005,21 @@
895
1005
  };
896
1006
  GlobalStore.prototype.getContents = function (key) {
897
1007
  var value = key ? this.contents[key] : this.contents;
898
- return JSON.parse(JSON.stringify(value));
1008
+ // `clone` keeps JSON round trip semantics but tolerates values a bare
1009
+ // round trip throws on -- notably circular references, which arrive via
1010
+ // context or breadcrumb metadata the host application controls.
1011
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1012
+ return (0, util_1$3.clone)(value);
899
1013
  };
900
1014
  GlobalStore.prototype.setContext = function (context) {
901
1015
  this.contents.context = (0, util_1$3.merge)(this.contents.context, context || {});
902
1016
  };
1017
+ GlobalStore.prototype.setEventContext = function (eventContext) {
1018
+ this.contents.eventContext = (0, util_1$3.merge)(this.contents.eventContext, eventContext || {});
1019
+ };
1020
+ GlobalStore.prototype.clearEventContext = function () {
1021
+ this.contents.eventContext = {};
1022
+ };
903
1023
  GlobalStore.prototype.addBreadcrumb = function (breadcrumb) {
904
1024
  if (this.contents.breadcrumbs.length == this.breadcrumbsLimit) {
905
1025
  this.contents.breadcrumbs.shift();
@@ -908,6 +1028,7 @@
908
1028
  };
909
1029
  GlobalStore.prototype.clear = function () {
910
1030
  this.contents.context = {};
1031
+ this.contents.eventContext = {};
911
1032
  this.contents.breadcrumbs = [];
912
1033
  };
913
1034
  GlobalStore.prototype.run = function (callback) {
@@ -917,7 +1038,7 @@
917
1038
  }());
918
1039
  store.GlobalStore = GlobalStore;
919
1040
 
920
- var throttled_events_logger = {};
1041
+ var throttled_events_worker = {};
921
1042
 
922
1043
  class NdJson {
923
1044
  static parse(data) {
@@ -952,8 +1073,18 @@
952
1073
  revision: null,
953
1074
  reportData: null,
954
1075
  breadcrumbsEnabled: true,
955
- // we could decide the value of eventsEnabled based on `env` and `developmentEnvironments`
1076
+ // deprecated: `eventsEnabled: true` auto-enables insights.enabled + insights.console
956
1077
  eventsEnabled: false,
1078
+ insights: {
1079
+ enabled: false,
1080
+ console: false,
1081
+ http: false,
1082
+ },
1083
+ events: {
1084
+ dispatchIntervalSeconds: 10,
1085
+ bulkThreshold: 500,
1086
+ sampleRatePercentage: 100,
1087
+ },
957
1088
  maxBreadcrumbs: 40,
958
1089
  maxObjectDepth: 8,
959
1090
  logger: console,
@@ -1016,72 +1147,179 @@
1016
1147
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
1017
1148
  }
1018
1149
  };
1019
- Object.defineProperty(throttled_events_logger, "__esModule", { value: true });
1020
- throttled_events_logger.ThrottledEventsLogger = void 0;
1150
+ Object.defineProperty(throttled_events_worker, "__esModule", { value: true });
1151
+ throttled_events_worker.ThrottledEventsWorker = void 0;
1021
1152
  var json_nd_1 = require$$0;
1022
1153
  var util_1$2 = util$1;
1023
1154
  var defaults_1$1 = defaults;
1024
- var ThrottledEventsLogger = /** @class */ (function () {
1025
- function ThrottledEventsLogger(config, transport) {
1155
+ var ThrottledEventsWorker = /** @class */ (function () {
1156
+ function ThrottledEventsWorker(config, transport) {
1026
1157
  this.config = config;
1027
1158
  this.transport = transport;
1028
1159
  this.queue = [];
1029
- this.isProcessing = false;
1160
+ this.cooldownTimer = null;
1161
+ // Timestamp (ms) until which the post-send cooldown window is active. A timer
1162
+ // is only armed while there are queued events waiting for this window; when the
1163
+ // queue drains empty we keep the timestamp but drop the timer, so an idle worker
1164
+ // never holds a pending timer (which would otherwise keep a Node process alive
1165
+ // for the full interval.
1166
+ this.cooldownUntil = 0;
1167
+ this.inFlight = null;
1030
1168
  this.config = __assign$2(__assign$2({}, defaults_1$1.CONFIG), config);
1031
1169
  this.logger = this.originalLogger();
1032
1170
  }
1033
- ThrottledEventsLogger.prototype.configure = function (opts) {
1171
+ ThrottledEventsWorker.prototype.configure = function (opts) {
1034
1172
  for (var k in opts) {
1035
1173
  this.config[k] = opts[k];
1036
1174
  }
1037
1175
  };
1038
- ThrottledEventsLogger.prototype.log = function (payload) {
1176
+ ThrottledEventsWorker.prototype.log = function (payload) {
1039
1177
  this.queue.push(payload);
1040
- if (!this.isProcessing) {
1178
+ if (this.inFlight) {
1179
+ return;
1180
+ }
1181
+ // Crossing the bulk threshold preempts any pending cooldown and dispatches now.
1182
+ if (this.queue.length >= this.bulkThreshold()) {
1183
+ this.clearCooldownTimer();
1041
1184
  this.processQueue();
1185
+ return;
1042
1186
  }
1187
+ // A timer is already counting down the cooldown window; the queued event
1188
+ // will be picked up when it fires.
1189
+ if (this.cooldownTimer) {
1190
+ return;
1191
+ }
1192
+ // We're inside a cooldown window but no timer is armed (the queue had drained
1193
+ // empty after the previous send). Arm one for the remaining time so this event
1194
+ // still respects the interval.
1195
+ var remaining = this.cooldownRemainingMs();
1196
+ if (remaining > 0) {
1197
+ this.scheduleCooldownTimer(remaining);
1198
+ return;
1199
+ }
1200
+ this.processQueue();
1043
1201
  };
1044
- ThrottledEventsLogger.prototype.flushAsync = function () {
1202
+ ThrottledEventsWorker.prototype.flushAsync = function () {
1203
+ var _this = this;
1204
+ var _a;
1045
1205
  this.logger.debug('[Honeybadger] Flushing events');
1046
- return this.send();
1206
+ this.clearCooldownTimer();
1207
+ var previous = (_a = this.inFlight) !== null && _a !== void 0 ? _a : Promise.resolve();
1208
+ var flush = previous.then(function () { return _this.drainAll(); });
1209
+ // Mark the flush as in-flight so a concurrent log() doesn't start an
1210
+ // overlapping processQueue() drain while we're draining here.
1211
+ this.inFlight = flush;
1212
+ var clear = function () {
1213
+ if (_this.inFlight === flush) {
1214
+ _this.inFlight = null;
1215
+ }
1216
+ };
1217
+ flush.then(clear, clear);
1218
+ return flush;
1219
+ };
1220
+ ThrottledEventsWorker.prototype.drainAll = function () {
1221
+ return __awaiter$2(this, void 0, void 0, function () {
1222
+ return __generator$2(this, function (_a) {
1223
+ switch (_a.label) {
1224
+ case 0:
1225
+ if (!(this.queue.length > 0)) return [3 /*break*/, 2];
1226
+ return [4 /*yield*/, this.send()];
1227
+ case 1:
1228
+ _a.sent();
1229
+ return [3 /*break*/, 0];
1230
+ case 2: return [2 /*return*/];
1231
+ }
1232
+ });
1233
+ });
1047
1234
  };
1048
- ThrottledEventsLogger.prototype.processQueue = function () {
1235
+ ThrottledEventsWorker.prototype.processQueue = function () {
1049
1236
  var _this = this;
1050
- if (this.queue.length === 0 || this.isProcessing) {
1237
+ if (this.queue.length === 0 || this.inFlight) {
1051
1238
  return;
1052
1239
  }
1053
- this.isProcessing = true;
1054
- this.send()
1055
- .then(function () {
1056
- setTimeout(function () {
1057
- _this.isProcessing = false;
1058
- _this.processQueue();
1059
- }, 50);
1060
- })
1240
+ var inFlight = this.send()
1061
1241
  .catch(function (error) {
1062
1242
  _this.logger.error('[Honeybadger] Error making HTTP request:', error);
1063
- // Continue processing the queue even if there's an error
1064
- setTimeout(function () {
1065
- _this.isProcessing = false;
1066
- _this.processQueue();
1067
- }, 50);
1243
+ })
1244
+ .then(function () {
1245
+ // Only reset if we're still the current operation; a flushAsync() may
1246
+ // have taken ownership of inFlight while this send was resolving.
1247
+ if (_this.inFlight === inFlight) {
1248
+ _this.inFlight = null;
1249
+ _this.scheduleNextDispatch();
1250
+ }
1068
1251
  });
1252
+ this.inFlight = inFlight;
1253
+ };
1254
+ ThrottledEventsWorker.prototype.scheduleNextDispatch = function () {
1255
+ var intervalMs = this.dispatchIntervalMs();
1256
+ // Capture the cooldown deadline at dispatch time so a later config change to
1257
+ // the interval doesn't retroactively shorten a window already in progress.
1258
+ this.cooldownUntil = Date.now() + intervalMs;
1259
+ if (this.queue.length >= this.bulkThreshold()) {
1260
+ this.processQueue();
1261
+ return;
1262
+ }
1263
+ // Only arm a timer when there's queued work waiting for it. If the queue is
1264
+ // empty, `cooldownUntil` alone enforces the window: the next log() will arm a
1265
+ // timer for whatever time remains, and an idle worker holds no timer at all.
1266
+ if (this.queue.length > 0) {
1267
+ this.scheduleCooldownTimer(intervalMs);
1268
+ }
1269
+ };
1270
+ ThrottledEventsWorker.prototype.cooldownRemainingMs = function () {
1271
+ if (!this.cooldownUntil) {
1272
+ return 0;
1273
+ }
1274
+ return Math.max(0, this.cooldownUntil - Date.now());
1275
+ };
1276
+ ThrottledEventsWorker.prototype.scheduleCooldownTimer = function (ms) {
1277
+ var _this = this;
1278
+ this.cooldownTimer = setTimeout(function () {
1279
+ _this.cooldownTimer = null;
1280
+ _this.processQueue();
1281
+ }, ms);
1282
+ // In Node, don't let a pending cooldown timer keep the process alive for the
1283
+ // whole batching interval. `unref` lets the event loop go idle so the process
1284
+ // can exit (or a shutdown / `beforeExit` handler can run) promptly instead of
1285
+ // blocking for `dispatchIntervalSeconds`. Queued events aren't lost: a
1286
+ // `flushAsync()` on the way out still drains them before exit. (`unref` is
1287
+ // Node-only; the browser's numeric setTimeout handle has no such method.)
1288
+ if (typeof this.cooldownTimer === 'object' && this.cooldownTimer !== null && typeof this.cooldownTimer.unref === 'function') {
1289
+ this.cooldownTimer.unref();
1290
+ }
1069
1291
  };
1070
- ThrottledEventsLogger.prototype.send = function () {
1292
+ ThrottledEventsWorker.prototype.clearCooldownTimer = function () {
1293
+ if (this.cooldownTimer) {
1294
+ clearTimeout(this.cooldownTimer);
1295
+ this.cooldownTimer = null;
1296
+ }
1297
+ };
1298
+ ThrottledEventsWorker.prototype.send = function () {
1071
1299
  return __awaiter$2(this, void 0, void 0, function () {
1072
1300
  var eventsData, data;
1073
1301
  return __generator$2(this, function (_a) {
1074
1302
  if (this.queue.length === 0) {
1075
1303
  return [2 /*return*/];
1076
1304
  }
1077
- eventsData = this.queue.slice();
1078
- this.queue = [];
1305
+ eventsData = this.queue.splice(0, this.bulkThreshold());
1079
1306
  data = json_nd_1.NdJson.stringify(eventsData);
1080
1307
  return [2 /*return*/, this.makeHttpRequest(data)];
1081
1308
  });
1082
1309
  });
1083
1310
  };
1084
- ThrottledEventsLogger.prototype.makeHttpRequest = function (data) {
1311
+ ThrottledEventsWorker.prototype.bulkThreshold = function () {
1312
+ var _a;
1313
+ var v = (_a = this.config.events) === null || _a === void 0 ? void 0 : _a.bulkThreshold;
1314
+ return typeof v === 'number' && v > 0 ? v : defaults_1$1.CONFIG.events.bulkThreshold;
1315
+ };
1316
+ ThrottledEventsWorker.prototype.dispatchIntervalMs = function () {
1317
+ var _a;
1318
+ var v = (_a = this.config.events) === null || _a === void 0 ? void 0 : _a.dispatchIntervalSeconds;
1319
+ var seconds = typeof v === 'number' && v >= 0 ? v : defaults_1$1.CONFIG.events.dispatchIntervalSeconds;
1320
+ return seconds * 1000;
1321
+ };
1322
+ ThrottledEventsWorker.prototype.makeHttpRequest = function (data) {
1085
1323
  return __awaiter$2(this, void 0, void 0, function () {
1086
1324
  var _this = this;
1087
1325
  return __generator$2(this, function (_a) {
@@ -1096,10 +1334,13 @@
1096
1334
  maxObjectDepth: this.config.maxObjectDepth,
1097
1335
  logger: this.logger,
1098
1336
  }, data)
1099
- .then(function () {
1100
- if (_this.config.debug) {
1337
+ .then(function (resp) {
1338
+ if ([200, 201].includes(resp.statusCode)) {
1101
1339
  _this.logger.debug('[Honeybadger] Events sent successfully');
1102
1340
  }
1341
+ else {
1342
+ _this.logger.debug("[Honeybadger] Events failed[".concat(resp.statusCode, "]: ").concat(resp.body));
1343
+ }
1103
1344
  })
1104
1345
  .catch(function (err) {
1105
1346
  _this.logger.error("[Honeybadger] Error sending events: ".concat(err.message));
@@ -1110,28 +1351,40 @@
1110
1351
  /**
1111
1352
  * todo: improve this
1112
1353
  *
1113
- * The EventsLogger overrides the console methods to enable automatic instrumentation
1354
+ * The events plugin overrides the console methods to enable automatic instrumentation
1114
1355
  * of console logs to the Honeybadger API.
1115
1356
  * So if we want to log something in here we need to use the original methods.
1116
1357
  */
1117
- ThrottledEventsLogger.prototype.originalLogger = function () {
1118
- var _a, _b, _c, _d, _e;
1358
+ ThrottledEventsWorker.prototype.originalLogger = function () {
1359
+ var _this = this;
1360
+ var _a, _b, _c, _d;
1119
1361
  return {
1120
1362
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1121
1363
  log: (_a = console.log.__hb_original) !== null && _a !== void 0 ? _a : console.log,
1122
1364
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1123
1365
  info: (_b = console.info.__hb_original) !== null && _b !== void 0 ? _b : console.info,
1366
+ debug: function () {
1367
+ var _a;
1368
+ var args = [];
1369
+ for (var _i = 0; _i < arguments.length; _i++) {
1370
+ args[_i] = arguments[_i];
1371
+ }
1372
+ if (!_this.config.debug) {
1373
+ return;
1374
+ }
1375
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1376
+ var func = (_a = console.debug.__hb_original) !== null && _a !== void 0 ? _a : console.debug;
1377
+ return func.apply(void 0, args);
1378
+ },
1124
1379
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1125
- debug: (_c = console.debug.__hb_original) !== null && _c !== void 0 ? _c : console.debug,
1126
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1127
- warn: (_d = console.warn.__hb_original) !== null && _d !== void 0 ? _d : console.warn,
1380
+ warn: (_c = console.warn.__hb_original) !== null && _c !== void 0 ? _c : console.warn,
1128
1381
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1129
- error: (_e = console.error.__hb_original) !== null && _e !== void 0 ? _e : console.error,
1382
+ error: (_d = console.error.__hb_original) !== null && _d !== void 0 ? _d : console.error,
1130
1383
  };
1131
1384
  };
1132
- return ThrottledEventsLogger;
1385
+ return ThrottledEventsWorker;
1133
1386
  }());
1134
- throttled_events_logger.ThrottledEventsLogger = ThrottledEventsLogger;
1387
+ throttled_events_worker.ThrottledEventsWorker = ThrottledEventsWorker;
1135
1388
 
1136
1389
  var __assign$1 = (commonjsGlobal && commonjsGlobal.__assign) || function () {
1137
1390
  __assign$1 = Object.assign || function(t) {
@@ -1184,7 +1437,7 @@
1184
1437
  client.Client = void 0;
1185
1438
  var util_1$1 = util$1;
1186
1439
  var store_1 = store;
1187
- var throttled_events_logger_1 = throttled_events_logger;
1440
+ var throttled_events_worker_1 = throttled_events_worker;
1188
1441
  var defaults_1 = defaults;
1189
1442
  // Split at commas and spaces
1190
1443
  var TAG_SEPARATOR = /,|\s+/;
@@ -1196,17 +1449,20 @@
1196
1449
  this.__pluginsLoaded = false;
1197
1450
  this.__store = null;
1198
1451
  this.__beforeNotifyHandlers = [];
1452
+ this.__beforeEventHandlers = [];
1199
1453
  this.__afterNotifyHandlers = [];
1454
+ this.__pendingEvents = new Set();
1200
1455
  this.__notifier = {
1201
1456
  name: '@honeybadger-io/core',
1202
1457
  url: 'https://github.com/honeybadger-io/honeybadger-js/tree/master/packages/core',
1203
- version: '6.15.3'
1458
+ version: '6.16.1'
1204
1459
  };
1205
1460
  this.config = __assign$1(__assign$1({}, defaults_1.CONFIG), opts);
1206
1461
  this.__initStore();
1207
1462
  this.__transport = transport;
1208
- this.__eventsLogger = new throttled_events_logger_1.ThrottledEventsLogger(this.config, this.__transport);
1463
+ this.__eventsWorker = new throttled_events_worker_1.ThrottledEventsWorker(this.config, this.__transport);
1209
1464
  this.logger = (0, util_1$1.logger)(this);
1465
+ this.__applyEventsEnabledShim(opts);
1210
1466
  }
1211
1467
  Client.prototype.getVersion = function () {
1212
1468
  return this.__notifier.version;
@@ -1229,10 +1485,24 @@
1229
1485
  for (var k in opts) {
1230
1486
  this.config[k] = opts[k];
1231
1487
  }
1232
- this.__eventsLogger.configure(this.config);
1488
+ this.__applyEventsEnabledShim(opts);
1489
+ this.__eventsWorker.configure(this.config);
1233
1490
  this.loadPlugins();
1234
1491
  return this;
1235
1492
  };
1493
+ /**
1494
+ * Backwards compatibility: `eventsEnabled: true` used to opt into console events.
1495
+ * The deprecated flag auto-enables `insights.enabled` and `insights.console`;
1496
+ * explicit user-provided insights values win over the shim.
1497
+ */
1498
+ Client.prototype.__applyEventsEnabledShim = function (opts) {
1499
+ var _a;
1500
+ if (opts.eventsEnabled !== true) {
1501
+ return;
1502
+ }
1503
+ this.logger.warn('Deprecation warning: `eventsEnabled` has been deprecated; please use `insights.enabled` and `insights.console` instead.');
1504
+ this.config.insights = __assign$1(__assign$1(__assign$1({}, ((_a = this.config.insights) !== null && _a !== void 0 ? _a : {})), { enabled: true, console: true }), opts.insights);
1505
+ };
1236
1506
  Client.prototype.loadPlugins = function () {
1237
1507
  var _this = this;
1238
1508
  var pluginsToLoad = this.__pluginsLoaded
@@ -1242,12 +1512,16 @@
1242
1512
  this.__pluginsLoaded = true;
1243
1513
  };
1244
1514
  Client.prototype.__initStore = function () {
1245
- this.__store = new store_1.GlobalStore({ context: {}, breadcrumbs: [] }, this.config.maxBreadcrumbs);
1515
+ this.__store = new store_1.GlobalStore({ context: {}, eventContext: {}, breadcrumbs: [] }, this.config.maxBreadcrumbs);
1246
1516
  };
1247
1517
  Client.prototype.beforeNotify = function (handler) {
1248
1518
  this.__beforeNotifyHandlers.push(handler);
1249
1519
  return this;
1250
1520
  };
1521
+ Client.prototype.beforeEvent = function (handler) {
1522
+ this.__beforeEventHandlers.push(handler);
1523
+ return this;
1524
+ };
1251
1525
  Client.prototype.afterNotify = function (handler) {
1252
1526
  this.__afterNotifyHandlers.push(handler);
1253
1527
  return this;
@@ -1258,6 +1532,16 @@
1258
1532
  }
1259
1533
  return this;
1260
1534
  };
1535
+ Client.prototype.setEventContext = function (eventContext) {
1536
+ if (typeof eventContext === 'object' && eventContext != null) {
1537
+ this.__store.setEventContext(eventContext);
1538
+ }
1539
+ return this;
1540
+ };
1541
+ Client.prototype.clearEventContext = function () {
1542
+ this.__store.clearEventContext();
1543
+ return this;
1544
+ };
1261
1545
  Client.prototype.resetContext = function (context) {
1262
1546
  this.logger.warn('Deprecation warning: `Honeybadger.resetContext()` has been deprecated; please use `Honeybadger.clear()` instead.');
1263
1547
  this.__store.clear();
@@ -1270,31 +1554,63 @@
1270
1554
  this.__store.clear();
1271
1555
  return this;
1272
1556
  };
1557
+ /**
1558
+ * Reporting an error must never break the host application, so any failure
1559
+ * inside the reporting path is logged and swallowed rather than thrown back
1560
+ * into the caller.
1561
+ */
1273
1562
  Client.prototype.notify = function (noticeable, name, extra) {
1274
1563
  var _this = this;
1275
1564
  if (name === void 0) { name = undefined; }
1276
1565
  if (extra === void 0) { extra = undefined; }
1277
- var notice = this.makeNotice(noticeable, name, extra);
1278
- // we need to have the source file data before the beforeNotifyHandlers,
1279
- // in case they modify them
1280
- var sourceCodeData = notice && notice.backtrace ? notice.backtrace.map(function (trace) { return (0, util_1$1.shallowClone)(trace); }) : null;
1281
- var preConditionsResult = this.__runPreconditions(notice);
1282
- if (preConditionsResult instanceof Error) {
1283
- (0, util_1$1.runAfterNotifyHandlers)(notice, this.__afterNotifyHandlers, preConditionsResult);
1566
+ // The body is inlined in this try/catch rather than delegated to a private
1567
+ // method: generated backtraces are trimmed by a fixed frame count
1568
+ // (DEFAULT_BACKTRACE_SHIFT), so an extra call frame here would shift every
1569
+ // generated backtrace into Honeybadger's own source.
1570
+ try {
1571
+ var notice_1 = this.makeNotice(noticeable, name, extra);
1572
+ // we need to have the source file data before the beforeNotifyHandlers,
1573
+ // in case they modify them
1574
+ var sourceCodeData_1 = notice_1 && notice_1.backtrace ? notice_1.backtrace.map(function (trace) { return (0, util_1$1.shallowClone)(trace); }) : null;
1575
+ var preConditionsResult = this.__runPreconditions(notice_1);
1576
+ if (preConditionsResult instanceof Error) {
1577
+ (0, util_1$1.runAfterNotifyHandlers)(notice_1, this.__afterNotifyHandlers, preConditionsResult);
1578
+ return false;
1579
+ }
1580
+ if (preConditionsResult instanceof Promise) {
1581
+ preConditionsResult.then(function (result) {
1582
+ if (result instanceof Error) {
1583
+ (0, util_1$1.runAfterNotifyHandlers)(notice_1, _this.__afterNotifyHandlers, result);
1584
+ return false;
1585
+ }
1586
+ return _this.__send(notice_1, sourceCodeData_1);
1587
+ }).catch(function (err) {
1588
+ // __send installs its own catch, which logs and runs the afterNotify
1589
+ // handlers. Anything arriving here failed before that chain existed,
1590
+ // so it is still unreported -- and notify() has already returned
1591
+ // true, leaving notifyAsync() waiting on those handlers.
1592
+ _this.__logReportingFailure(err);
1593
+ (0, util_1$1.runAfterNotifyHandlers)(notice_1, _this.__afterNotifyHandlers, err instanceof Error ? err : new Error(String(err)));
1594
+ });
1595
+ return true;
1596
+ }
1597
+ this.__send(notice_1, sourceCodeData_1).catch(function (_err) { });
1598
+ return true;
1599
+ }
1600
+ catch (err) {
1601
+ // Reporting an error must never break the host application, so a failure
1602
+ // in the reporting path is logged rather than thrown back at the caller.
1603
+ // notifyAsync() settles on the false return below.
1604
+ this.__logReportingFailure(err);
1284
1605
  return false;
1285
1606
  }
1286
- if (preConditionsResult instanceof Promise) {
1287
- preConditionsResult.then(function (result) {
1288
- if (result instanceof Error) {
1289
- (0, util_1$1.runAfterNotifyHandlers)(notice, _this.__afterNotifyHandlers, result);
1290
- return false;
1291
- }
1292
- return _this.__send(notice, sourceCodeData);
1293
- });
1294
- return true;
1607
+ };
1608
+ /** The logger is host-supplied, so even reporting a failure gets a guard. */
1609
+ Client.prototype.__logReportingFailure = function (err) {
1610
+ try {
1611
+ this.logger.error('Error report failed: an internal error occurred while reporting', err);
1295
1612
  }
1296
- this.__send(notice, sourceCodeData).catch(function (_err) { });
1297
- return true;
1613
+ catch (_loggerErr) { /* nothing left to report it with */ }
1298
1614
  };
1299
1615
  /**
1300
1616
  * An async version of {@link notify} that resolves only after the notice has been reported to Honeybadger.
@@ -1310,11 +1626,19 @@
1310
1626
  var applyAfterNotify = function (partialNotice) {
1311
1627
  var originalAfterNotify = partialNotice.afterNotify;
1312
1628
  partialNotice.afterNotify = function (err) {
1313
- originalAfterNotify === null || originalAfterNotify === void 0 ? void 0 : originalAfterNotify.call(_this, err);
1314
- if (err) {
1315
- return reject(err);
1629
+ // Settle from a `finally` so a throwing handler cannot leave the
1630
+ // caller awaiting forever; its error propagates as it always has.
1631
+ try {
1632
+ originalAfterNotify === null || originalAfterNotify === void 0 ? void 0 : originalAfterNotify.call(_this, err);
1633
+ }
1634
+ finally {
1635
+ if (err) {
1636
+ reject(err);
1637
+ }
1638
+ else {
1639
+ resolve();
1640
+ }
1316
1641
  }
1317
- resolve();
1318
1642
  };
1319
1643
  };
1320
1644
  // We have to respect any afterNotify hooks that come from the arguments
@@ -1338,7 +1662,14 @@
1338
1662
  objectToOverride = name = {};
1339
1663
  }
1340
1664
  applyAfterNotify(objectToOverride);
1341
- _this.notify(noticeable, name, extra);
1665
+ // `notify` never throws, so a false return is the only signal that the
1666
+ // report was abandoned. Every expected failure has already settled this
1667
+ // promise through the afterNotify hook above (a second reject is a
1668
+ // no-op); this catches the unexpected ones, which would otherwise leave
1669
+ // the caller awaiting forever.
1670
+ if (!_this.notify(noticeable, name, extra)) {
1671
+ reject(new Error('Error report failed: see logs for details'));
1672
+ }
1342
1673
  });
1343
1674
  };
1344
1675
  Client.prototype.makeNotice = function (noticeable, name, extra) {
@@ -1412,19 +1743,59 @@
1412
1743
  this.event('log', data);
1413
1744
  };
1414
1745
  Client.prototype.event = function (type, data) {
1746
+ var _this = this;
1415
1747
  var _a;
1416
1748
  if (typeof type === 'object') {
1417
1749
  data = type;
1418
1750
  type = (_a = type['event_type']) !== null && _a !== void 0 ? _a : undefined;
1419
1751
  }
1420
- this.__eventsLogger.log(__assign$1({ event_type: type, ts: new Date().toISOString() }, data));
1752
+ var eventContext = this.__store.getContents('eventContext') || {};
1753
+ var payload = __assign$1(__assign$1({ event_type: type, ts: new Date().toISOString() }, eventContext), data);
1754
+ // Track in-flight handler chains so flushAsync() can await them before
1755
+ // delegating to the events logger; otherwise a caller that awaits flushAsync()
1756
+ // immediately after event() may flush before the payload enters the queue.
1757
+ var inFlight = (0, util_1$1.runBeforeEventHandlers)(payload, this.__beforeEventHandlers, this.logger)
1758
+ .then(function (shouldSend) {
1759
+ var _a, _b;
1760
+ if (!shouldSend) {
1761
+ _this.logger.debug('skipping event: beforeEvent handler returned false');
1762
+ return;
1763
+ }
1764
+ var sampleRate = (_b = (_a = _this.config.events) === null || _a === void 0 ? void 0 : _a.sampleRatePercentage) !== null && _b !== void 0 ? _b : 100;
1765
+ if (!(0, util_1$1.shouldSampleEvent)(payload, sampleRate)) {
1766
+ _this.logger.debug('skipping event: dropped by sampleRatePercentage');
1767
+ return;
1768
+ }
1769
+ delete payload._hb;
1770
+ _this.__eventsWorker.log(payload);
1771
+ })
1772
+ .catch(function (err) {
1773
+ // should not happen, because each handler is wrapped with a try/catch
1774
+ _this.logger.error('beforeEvent handler chain failed; dropping event', err);
1775
+ })
1776
+ .finally(function () {
1777
+ _this.__pendingEvents.delete(inFlight);
1778
+ });
1779
+ this.__pendingEvents.add(inFlight);
1421
1780
  };
1422
1781
  /**
1423
1782
  * This method currently flushes the event (Insights) queue.
1424
1783
  * In the future, it should also flush the error queue (assuming an error throttler is implemented).
1425
1784
  */
1426
1785
  Client.prototype.flushAsync = function () {
1427
- return this.__eventsLogger.flushAsync();
1786
+ return __awaiter$1(this, void 0, void 0, function () {
1787
+ return __generator$1(this, function (_a) {
1788
+ switch (_a.label) {
1789
+ case 0:
1790
+ if (!(this.__pendingEvents.size > 0)) return [3 /*break*/, 2];
1791
+ return [4 /*yield*/, Promise.allSettled(Array.from(this.__pendingEvents))];
1792
+ case 1:
1793
+ _a.sent();
1794
+ _a.label = 2;
1795
+ case 2: return [2 /*return*/, this.__eventsWorker.flushAsync()];
1796
+ }
1797
+ });
1798
+ });
1428
1799
  };
1429
1800
  Client.prototype.__getBreadcrumbs = function () {
1430
1801
  return this.__store.getContents('breadcrumbs').slice();
@@ -1616,7 +1987,7 @@
1616
1987
  };
1617
1988
  Object.defineProperty(exports, "__esModule", { value: true });
1618
1989
  exports.Plugins = exports.Defaults = exports.Util = exports.Types = exports.Client = void 0;
1619
- var events_1 = __importDefault(events);
1990
+ var console_events_1 = __importDefault(console_events);
1620
1991
  var client_1 = client;
1621
1992
  Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_1.Client; } });
1622
1993
  __exportStar(store, exports);
@@ -1624,7 +1995,12 @@
1624
1995
  exports.Util = __importStar(util$1);
1625
1996
  exports.Defaults = __importStar(defaults);
1626
1997
  exports.Plugins = {
1627
- events: events_1.default
1998
+ consoleEvents: console_events_1.default,
1999
+ /**
2000
+ * @deprecated Use `consoleEvents` instead. Kept as an alias for backwards
2001
+ * compatibility and will be removed in a future major version.
2002
+ */
2003
+ events: console_events_1.default,
1628
2004
  };
1629
2005
 
1630
2006
  } (src));
@@ -2067,7 +2443,11 @@
2067
2443
  metadata: {
2068
2444
  selector: selector,
2069
2445
  text: text,
2070
- event: event
2446
+ // Frameworks decorate the native event with their own properties
2447
+ // (preact/compat adds a `nativeEvent` back-reference), so the raw
2448
+ // event may be circular. Sanitize it here rather than storing a
2449
+ // value we cannot serialize later.
2450
+ event: sanitize$1(event, 3)
2071
2451
  }
2072
2452
  });
2073
2453
  }, _window.location ? true : false); // In CloudFlare workers useCapture must be false. window.location is a hacky way to detect it.
@@ -2773,7 +3153,7 @@
2773
3153
  var NOTIFIER = {
2774
3154
  name: '@honeybadger-io/js',
2775
3155
  url: 'https://github.com/honeybadger-io/honeybadger-js/tree/master/packages/js',
2776
- version: '6.15.3'
3156
+ version: '6.16.1'
2777
3157
  };
2778
3158
  var userAgent = function () {
2779
3159
  if (typeof navigator !== 'undefined') {
@@ -2788,7 +3168,7 @@
2788
3168
  (0, timers_1.default)(),
2789
3169
  (0, event_listeners_1.default)(),
2790
3170
  (0, breadcrumbs_1.default)(),
2791
- core_1.Plugins.events(),
3171
+ core_1.Plugins.consoleEvents(),
2792
3172
  ]
2793
3173
  });
2794
3174
  singleton.setNotifier(NOTIFIER);