@honeybadger-io/js 3.2.8 → 4.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ var https = require('https');
4
4
  var http = require('http');
5
5
  var url = require('url');
6
6
  var os = require('os');
7
- var fs = require('fs');
8
7
  var domain = require('domain');
8
+ var fs = require('fs');
9
9
 
10
10
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
11
 
@@ -13,8 +13,8 @@ var https__default = /*#__PURE__*/_interopDefaultLegacy(https);
13
13
  var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
14
14
  var url__default = /*#__PURE__*/_interopDefaultLegacy(url);
15
15
  var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
16
- var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
17
16
  var domain__default = /*#__PURE__*/_interopDefaultLegacy(domain);
17
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
18
18
 
19
19
  /*! *****************************************************************************
20
20
  Copyright (c) Microsoft Corporation.
@@ -221,7 +221,8 @@ function objectIsEmpty(obj) {
221
221
  function makeBacktrace(stack, shift) {
222
222
  if (shift === void 0) { shift = 0; }
223
223
  try {
224
- var backtrace = parse(stack).map(function (line) {
224
+ var backtrace = parse(stack)
225
+ .map(function (line) {
225
226
  return {
226
227
  file: line.file,
227
228
  method: line.methodName,
@@ -237,24 +238,46 @@ function makeBacktrace(stack, shift) {
237
238
  return [];
238
239
  }
239
240
  }
241
+ function getSourceForBacktrace(backtrace, getSourceFileHandler, cb) {
242
+ if (!getSourceFileHandler || !backtrace || !backtrace.length) {
243
+ cb([]);
244
+ return;
245
+ }
246
+ var result = [];
247
+ var getSourceFromFile = function (index) {
248
+ if (index === void 0) { index = 0; }
249
+ if (!backtrace.length) {
250
+ return cb(result);
251
+ }
252
+ var trace = backtrace.splice(0)[index];
253
+ getSourceFileHandler(trace.file, (function (fileContent) {
254
+ result[index] = getSourceCodeSnippet(fileContent, trace.number);
255
+ getSourceFromFile(index + 1);
256
+ }));
257
+ };
258
+ getSourceFromFile();
259
+ }
240
260
  function runBeforeNotifyHandlers(notice, handlers) {
261
+ var result = true;
241
262
  for (var i = 0, len = handlers.length; i < len; i++) {
242
263
  var handler = handlers[i];
243
264
  if (handler(notice) === false) {
244
- return false;
265
+ result = false;
245
266
  }
246
267
  }
247
- return true;
268
+ return result;
248
269
  }
249
270
  function runAfterNotifyHandlers(notice, handlers, error) {
250
- if (error === void 0) { error = undefined; }
271
+ if (notice && notice.afterNotify) {
272
+ notice.afterNotify(error, notice);
273
+ }
251
274
  for (var i = 0, len = handlers.length; i < len; i++) {
252
275
  handlers[i](error, notice);
253
276
  }
254
277
  return true;
255
278
  }
256
279
  // Returns a new object with properties from other object.
257
- function newObject(obj) {
280
+ function shallowClone(obj) {
258
281
  if (typeof (obj) !== 'object' || obj === null) {
259
282
  return {};
260
283
  }
@@ -281,8 +304,14 @@ function sanitize(obj, maxDepth) {
281
304
  return false;
282
305
  }
283
306
  function canSerialize(obj) {
284
- // Functions are TMI and Symbols can't convert to strings.
285
- if (/function|symbol/.test(typeof (obj))) {
307
+ var typeOfObj = typeof obj;
308
+ // Functions are TMI
309
+ if (/function/.test(typeOfObj)) {
310
+ // Let special toJSON method pass as it's used by JSON.stringify (#722)
311
+ return obj.name === 'toJSON';
312
+ }
313
+ // Symbols can't convert to strings.
314
+ if (/symbol/.test(typeOfObj)) {
286
315
  return false;
287
316
  }
288
317
  if (obj === null) {
@@ -331,7 +360,7 @@ function sanitize(obj, maxDepth) {
331
360
  return serialize(obj, depth);
332
361
  }
333
362
  catch (e) {
334
- return "[ERROR] " + e;
363
+ return "[ERROR] ".concat(e);
335
364
  }
336
365
  }
337
366
  return safeSerialize(obj);
@@ -344,8 +373,13 @@ function logger(client) {
344
373
  for (var _i = 0; _i < arguments.length; _i++) {
345
374
  args[_i] = arguments[_i];
346
375
  }
347
- if (method === 'debug' && !client.config.debug) {
348
- return;
376
+ if (method === 'debug') {
377
+ if (!client.config.debug) {
378
+ return;
379
+ }
380
+ // Log at default level so that you don't need to also enable verbose
381
+ // logging in Chrome.
382
+ method = 'log';
349
383
  }
350
384
  args.unshift('[Honeybadger]');
351
385
  (_a = client.config.logger)[method].apply(_a, args);
@@ -368,12 +402,12 @@ function makeNotice(thing) {
368
402
  if (!thing) {
369
403
  notice = {};
370
404
  }
371
- else if (Object.prototype.toString.call(thing) === '[object Error]') {
405
+ else if (thing instanceof Error || Object.prototype.toString.call(thing) === '[object Error]') {
372
406
  var e = thing;
373
407
  notice = merge(thing, { name: e.name, message: e.message, stack: e.stack });
374
408
  }
375
409
  else if (typeof thing === 'object') {
376
- notice = newObject(thing);
410
+ notice = shallowClone(thing);
377
411
  }
378
412
  else {
379
413
  var m = String(thing);
@@ -384,7 +418,7 @@ function makeNotice(thing) {
384
418
  function endpoint(config, path) {
385
419
  var endpoint = config.endpoint.trim().replace(/\/$/, '');
386
420
  path = path.trim().replace(/(^\/|\/$)/g, '');
387
- return endpoint + "/" + path;
421
+ return "".concat(endpoint, "/").concat(path);
388
422
  }
389
423
  function generateStackTrace() {
390
424
  try {
@@ -443,7 +477,9 @@ function filter(obj, filters) {
443
477
  return newObj;
444
478
  }
445
479
  if (is('Array', obj)) {
446
- return obj.map(function (v) { return filter(v); });
480
+ return obj.map(function (v) {
481
+ return filter(v);
482
+ });
447
483
  }
448
484
  if (is('Function', obj)) {
449
485
  return '[FUNC]';
@@ -479,7 +515,7 @@ function filterUrl(url, filters) {
479
515
  query.split(/[&]\s?/).forEach(function (pair) {
480
516
  var _a = pair.split('=', 2), key = _a[0], value = _a[1];
481
517
  if (filterMatch(key, filters)) {
482
- result = result.replace(key + "=" + value, key + "=[FILTERED]");
518
+ result = result.replace("".concat(key, "=").concat(value), "".concat(key, "=[FILTERED]"));
483
519
  }
484
520
  });
485
521
  return result;
@@ -493,34 +529,64 @@ function formatCGIData(vars, prefix) {
493
529
  });
494
530
  return formattedVars;
495
531
  }
532
+ function getSourceCodeSnippet(fileData, lineNumber, sourceRadius) {
533
+ if (sourceRadius === void 0) { sourceRadius = 2; }
534
+ if (!fileData) {
535
+ return null;
536
+ }
537
+ var lines = fileData.split('\n');
538
+ // add one empty line because array index starts from 0, but error line number is counted from 1
539
+ lines.unshift('');
540
+ var start = lineNumber - sourceRadius;
541
+ var end = lineNumber + sourceRadius;
542
+ var result = {};
543
+ for (var i = start; i <= end; i++) {
544
+ var line = lines[i];
545
+ if (typeof line === 'string') {
546
+ result[i] = line;
547
+ }
548
+ }
549
+ return result;
550
+ }
551
+
552
+ var GlobalStore$1 = /** @class */ (function () {
553
+ function GlobalStore(store) {
554
+ this.store = store;
555
+ }
556
+ GlobalStore.prototype.getStore = function () {
557
+ return this.store;
558
+ };
559
+ GlobalStore.prototype.run = function (store, callback) {
560
+ var args = [];
561
+ for (var _i = 2; _i < arguments.length; _i++) {
562
+ args[_i - 2] = arguments[_i];
563
+ }
564
+ this.store = store;
565
+ return callback.apply(void 0, args);
566
+ };
567
+ return GlobalStore;
568
+ }());
496
569
 
497
570
  var notifier = {
498
571
  name: 'honeybadger-js',
499
572
  url: 'https://github.com/honeybadger-io/honeybadger-js',
500
- version: '3.2.8'
573
+ version: '4.0.0-beta.2'
501
574
  };
502
- // Split at commas
503
- var TAG_SEPARATOR = /,/;
504
- // Removes any non-word characters
505
- var TAG_SANITIZER = /[^\w]/g;
506
- // Checks for blank strings
507
- var STRING_EMPTY = '';
575
+ // Split at commas and spaces
576
+ var TAG_SEPARATOR = /,|\s+/;
508
577
  // Checks for non-blank characters
509
578
  var NOT_BLANK = /\S/;
510
579
  var Client = /** @class */ (function () {
511
580
  function Client(opts) {
512
581
  if (opts === void 0) { opts = {}; }
513
- /** @internal */
514
582
  this.__pluginsExecuted = false;
515
- /** @internal */
516
- this.__context = {};
517
- /** @internal */
518
- this.__breadcrumbs = [];
519
- /** @internal */
583
+ this.__store = null;
520
584
  this.__beforeNotifyHandlers = [];
521
- /** @internal */
522
585
  this.__afterNotifyHandlers = [];
523
- this.config = __assign({ apiKey: null, endpoint: 'https://api.honeybadger.io', environment: null, hostname: null, projectRoot: null, component: null, action: null, revision: null, reportData: null, breadcrumbsEnabled: true, maxBreadcrumbs: 40, maxObjectDepth: 8, logger: console, developmentEnvironments: ['dev', 'development', 'test'], disabled: false, debug: false, tags: null, enableUncaught: true, enableUnhandledRejection: true, afterUncaught: function () { return true; }, filters: ['creditcard', 'password'], __plugins: [] }, opts);
586
+ this.config = __assign({ apiKey: null, endpoint: 'https://api.honeybadger.io', environment: null, hostname: null, projectRoot: null, component: null, action: null, revision: null, reportData: null, breadcrumbsEnabled: true, maxBreadcrumbs: 40, maxObjectDepth: 8, logger: console, developmentEnvironments: ['dev', 'development', 'test'], debug: false, tags: null, enableUncaught: true, enableUnhandledRejection: true, afterUncaught: function () { return true; }, filters: ['creditcard', 'password'], __plugins: [] }, opts);
587
+ // First, we go with the global (shared) store.
588
+ // Webserver middleware can then switch to the AsyncStore for async context tracking.
589
+ this.__store = new GlobalStore$1({ context: {}, breadcrumbs: [] });
524
590
  this.logger = logger(this);
525
591
  }
526
592
  Client.prototype.factory = function (_opts) {
@@ -541,6 +607,10 @@ var Client = /** @class */ (function () {
541
607
  }
542
608
  return this;
543
609
  };
610
+ /** @internal */
611
+ Client.prototype.__setStore = function (store) {
612
+ this.__store = store;
613
+ };
544
614
  Client.prototype.beforeNotify = function (handler) {
545
615
  this.__beforeNotifyHandlers.push(handler);
546
616
  return this;
@@ -551,41 +621,129 @@ var Client = /** @class */ (function () {
551
621
  };
552
622
  Client.prototype.setContext = function (context) {
553
623
  if (typeof context === 'object') {
554
- this.__context = merge(this.__context, context);
624
+ var store = this.__store.getStore();
625
+ store.context = merge(store.context, context);
555
626
  }
556
627
  return this;
557
628
  };
558
629
  Client.prototype.resetContext = function (context) {
559
630
  this.logger.warn('Deprecation warning: `Honeybadger.resetContext()` has been deprecated; please use `Honeybadger.clear()` instead.');
631
+ var store = this.__store.getStore();
560
632
  if (typeof context === 'object' && context !== null) {
561
- this.__context = merge({}, context);
633
+ store.context = context;
562
634
  }
563
635
  else {
564
- this.__context = {};
636
+ store.context = {};
565
637
  }
566
638
  return this;
567
639
  };
568
640
  Client.prototype.clear = function () {
569
- this.__context = {};
570
- this.__breadcrumbs = [];
641
+ var store = this.__store.getStore();
642
+ store.context = {};
643
+ store.breadcrumbs = [];
571
644
  return this;
572
645
  };
573
- Client.prototype.notify = function (notice, name, extra) {
646
+ Client.prototype.notify = function (noticeable, name, extra) {
647
+ var _this = this;
574
648
  if (name === void 0) { name = undefined; }
575
649
  if (extra === void 0) { extra = undefined; }
576
- if (this.config.disabled) {
577
- this.logger.warn('Deprecation warning: instead of `disabled: true`, use `reportData: false` to explicitly disable Honeybadger reporting. (Dropping notice: honeybadger.js is disabled)');
578
- return false;
650
+ var preConditionError = null;
651
+ var notice = this.makeNotice(noticeable, name, extra);
652
+ if (!notice) {
653
+ this.logger.debug('failed to build error report');
654
+ preConditionError = new Error('failed to build error report');
579
655
  }
580
- if (!this.__reportData()) {
581
- this.logger.debug('Dropping notice: honeybadger.js is in development mode');
582
- return false;
656
+ if (!preConditionError && this.config.reportData === false) {
657
+ this.logger.debug('skipping error report: honeybadger.js is disabled', notice);
658
+ preConditionError = new Error('honeybadger.js is disabled');
583
659
  }
584
- if (!this.config.apiKey) {
585
- this.logger.warn('Unable to send error report: no API key has been configured');
660
+ if (!preConditionError && this.__developmentMode()) {
661
+ this.logger.log('honeybadger.js is in development mode; the following error report will be sent in production.', notice);
662
+ preConditionError = new Error('honeybadger.js is in development mode');
663
+ }
664
+ if (!preConditionError && !this.config.apiKey) {
665
+ this.logger.warn('could not send error report: no API key has been configured', notice);
666
+ preConditionError = new Error('missing API key');
667
+ }
668
+ var beforeNotifyResult = runBeforeNotifyHandlers(notice, this.__beforeNotifyHandlers);
669
+ if (!preConditionError && !beforeNotifyResult) {
670
+ this.logger.debug('skipping error report: beforeNotify handlers returned false', notice);
671
+ preConditionError = new Error('beforeNotify handlers returned false');
672
+ }
673
+ if (preConditionError) {
674
+ runAfterNotifyHandlers(notice, this.__afterNotifyHandlers, preConditionError);
586
675
  return false;
587
676
  }
588
- notice = makeNotice(notice);
677
+ this.addBreadcrumb('Honeybadger Notice', {
678
+ category: 'notice',
679
+ metadata: {
680
+ message: notice.message,
681
+ name: notice.name,
682
+ stack: notice.stack
683
+ }
684
+ });
685
+ var breadcrumbs = this.__getStoreOrDefaultObject().breadcrumbs;
686
+ notice.__breadcrumbs = this.config.breadcrumbsEnabled ? breadcrumbs.slice() : [];
687
+ // we need to have the source file data before the beforeNotifyHandlers,
688
+ // in case they modify them
689
+ var sourceCodeData = notice && notice.backtrace ? notice.backtrace.map(function (trace) { return shallowClone(trace); }) : null;
690
+ getSourceForBacktrace(sourceCodeData, this.__getSourceFileHandler, function (sourcePerTrace) {
691
+ sourcePerTrace.forEach(function (source, index) {
692
+ notice.backtrace[index].source = source;
693
+ });
694
+ _this.__send(notice);
695
+ });
696
+ return true;
697
+ };
698
+ /**
699
+ * An async version of {@link notify} that resolves only after the notice has been reported to Honeybadger.
700
+ * Implemented using the {@link afterNotify} hook.
701
+ * Rejects if for any reason the report failed to be reported.
702
+ * Useful in serverless environments (AWS Lambda).
703
+ */
704
+ Client.prototype.notifyAsync = function (noticeable, name, extra) {
705
+ var _this = this;
706
+ if (name === void 0) { name = undefined; }
707
+ if (extra === void 0) { extra = undefined; }
708
+ return new Promise(function (resolve, reject) {
709
+ var applyAfterNotify = function (partialNotice) {
710
+ var originalAfterNotify = partialNotice.afterNotify;
711
+ partialNotice.afterNotify = function (err) {
712
+ originalAfterNotify === null || originalAfterNotify === void 0 ? void 0 : originalAfterNotify.call(_this, err);
713
+ if (err) {
714
+ return reject(err);
715
+ }
716
+ resolve();
717
+ };
718
+ };
719
+ // We have to respect any afterNotify hooks that come from the arguments
720
+ var objectToOverride;
721
+ if (noticeable.afterNotify) {
722
+ objectToOverride = noticeable;
723
+ }
724
+ else if (name && name.afterNotify) {
725
+ objectToOverride = name;
726
+ }
727
+ else if (extra && extra.afterNotify) {
728
+ objectToOverride = extra;
729
+ }
730
+ else if (name && typeof name === 'object') {
731
+ objectToOverride = name;
732
+ }
733
+ else if (extra) {
734
+ objectToOverride = extra;
735
+ }
736
+ else {
737
+ objectToOverride = name = {};
738
+ }
739
+ applyAfterNotify(objectToOverride);
740
+ _this.notify(noticeable, name, extra);
741
+ });
742
+ };
743
+ Client.prototype.makeNotice = function (noticeable, name, extra) {
744
+ if (name === void 0) { name = undefined; }
745
+ if (extra === void 0) { extra = undefined; }
746
+ var notice = makeNotice(noticeable);
589
747
  if (name && !(typeof name === 'object')) {
590
748
  var n = String(name);
591
749
  name = { name: n };
@@ -597,17 +755,18 @@ var Client = /** @class */ (function () {
597
755
  notice = mergeNotice(notice, extra);
598
756
  }
599
757
  if (objectIsEmpty(notice)) {
600
- return false;
758
+ return null;
601
759
  }
760
+ var context = this.__getStoreOrDefaultObject().context;
602
761
  var noticeTags = this.__constructTags(notice.tags);
603
- var contextTags = this.__constructTags(this.__context["tags"]);
762
+ var contextTags = this.__constructTags(context["tags"]);
604
763
  var configTags = this.__constructTags(this.config.tags);
605
764
  // Turning into a Set will remove duplicates
606
765
  var tags = noticeTags.concat(contextTags).concat(configTags);
607
766
  var uniqueTags = tags.filter(function (item, index) { return tags.indexOf(item) === index; });
608
767
  notice = merge(notice, {
609
768
  name: notice.name || 'Error',
610
- context: merge(this.__context, notice.context),
769
+ context: merge(context, notice.context),
611
770
  projectRoot: notice.projectRoot || this.config.projectRoot,
612
771
  environment: notice.environment || this.config.environment,
613
772
  component: notice.component || this.config.component,
@@ -621,52 +780,40 @@ var Client = /** @class */ (function () {
621
780
  backtraceShift = 2;
622
781
  }
623
782
  notice.backtrace = makeBacktrace(notice.stack, backtraceShift);
624
- if (!runBeforeNotifyHandlers(notice, this.__beforeNotifyHandlers)) {
625
- return false;
626
- }
627
- this.addBreadcrumb('Honeybadger Notice', {
628
- category: 'notice',
629
- metadata: {
630
- message: notice.message,
631
- name: notice.name,
632
- stack: notice.stack
633
- }
634
- });
635
- notice.__breadcrumbs = this.config.breadcrumbsEnabled ? this.__breadcrumbs.slice() : [];
636
- return this.__send(notice);
783
+ return notice;
637
784
  };
638
785
  Client.prototype.addBreadcrumb = function (message, opts) {
639
786
  if (!this.config.breadcrumbsEnabled) {
640
787
  return;
641
788
  }
642
789
  opts = opts || {};
643
- var metadata = newObject(opts.metadata);
790
+ var metadata = shallowClone(opts.metadata);
644
791
  var category = opts.category || 'custom';
645
792
  var timestamp = new Date().toISOString();
646
- this.__breadcrumbs.push({
793
+ var store = this.__store.getStore();
794
+ var breadcrumbs = store.breadcrumbs;
795
+ breadcrumbs.push({
647
796
  category: category,
648
797
  message: message,
649
798
  metadata: metadata,
650
799
  timestamp: timestamp
651
800
  });
652
801
  var limit = this.config.maxBreadcrumbs;
653
- if (this.__breadcrumbs.length > limit) {
654
- this.__breadcrumbs = this.__breadcrumbs.slice(this.__breadcrumbs.length - limit);
802
+ if (breadcrumbs.length > limit) {
803
+ breadcrumbs = breadcrumbs.slice(breadcrumbs.length - limit);
655
804
  }
805
+ store.breadcrumbs = breadcrumbs;
656
806
  return this;
657
807
  };
658
- /** @internal */
659
- Client.prototype.__reportData = function () {
660
- if (this.config.reportData !== null) {
661
- return this.config.reportData;
808
+ Client.prototype.__developmentMode = function () {
809
+ if (this.config.reportData === true) {
810
+ return false;
662
811
  }
663
- return !(this.config.environment && this.config.developmentEnvironments.includes(this.config.environment));
812
+ return (this.config.environment && this.config.developmentEnvironments.includes(this.config.environment));
664
813
  };
665
- /** @internal */
666
814
  Client.prototype.__send = function (_notice) {
667
815
  throw (new Error('Must implement send in subclass'));
668
816
  };
669
- /** @internal */
670
817
  Client.prototype.__buildPayload = function (notice) {
671
818
  var headers = filter(notice.headers, this.config.filters) || {};
672
819
  var cgiData = filter(__assign(__assign({}, notice.cgiData), formatCGIData(headers, 'HTTP_')), this.config.filters);
@@ -702,14 +849,22 @@ var Client = /** @class */ (function () {
702
849
  details: notice.details || {}
703
850
  };
704
851
  };
705
- /** @internal */
706
852
  Client.prototype.__constructTags = function (tags) {
707
853
  if (!tags) {
708
854
  return [];
709
855
  }
710
- return tags.toString().split(TAG_SEPARATOR).map(function (tag) {
711
- return tag.replace(TAG_SANITIZER, STRING_EMPTY);
712
- }).filter(function (tag) { return NOT_BLANK.test(tag); });
856
+ return tags.toString().split(TAG_SEPARATOR).filter(function (tag) { return NOT_BLANK.test(tag); });
857
+ };
858
+ /**
859
+ * For ALS, the store may be uninitialized (if .run()` has not been called).
860
+ * This provides an easy way to read the existing store object or fall back to a default.
861
+ * Returns *a copy* of the store.
862
+ * @internal
863
+ */
864
+ Client.prototype.__getStoreOrDefaultObject = function () {
865
+ var existingStore = this.__store.getStore();
866
+ var store = existingStore || {};
867
+ return __assign({ context: {}, breadcrumbs: [] }, store);
713
868
  };
714
869
  return Client;
715
870
  }());
@@ -720,7 +875,7 @@ function fatallyLogAndExit(err) {
720
875
  process.exit(1);
721
876
  }
722
877
  function getStats(cb) {
723
- var load = os__default['default'].loadavg(), stats = {
878
+ var load = os__default["default"].loadavg(), stats = {
724
879
  load: {
725
880
  one: load[0],
726
881
  five: load[1],
@@ -728,8 +883,8 @@ function getStats(cb) {
728
883
  },
729
884
  mem: {}
730
885
  };
731
- if (fs__default['default'].existsSync('/proc/meminfo')) {
732
- return fs__default['default'].readFile('/proc/meminfo', 'utf8', parseStats);
886
+ if (fs__default["default"].existsSync('/proc/meminfo')) {
887
+ return fs__default["default"].readFile('/proc/meminfo', 'utf8', parseStats);
733
888
  }
734
889
  fallback();
735
890
  function parseStats(err, memData) {
@@ -752,21 +907,125 @@ function getStats(cb) {
752
907
  }
753
908
  function fallback() {
754
909
  stats.mem = {
755
- free: os__default['default'].freemem(),
756
- total: os__default['default'].totalmem()
910
+ free: os__default["default"].freemem(),
911
+ total: os__default["default"].totalmem()
757
912
  };
758
913
  return cb(stats);
759
914
  }
760
915
  }
916
+ /**
917
+ * Get source file if possible, used to build `notice.backtrace.source`
918
+ *
919
+ * @param path to source code
920
+ * @param cb callback with fileContent
921
+ */
922
+ function getSourceFile(path, cb) {
923
+ fs__default["default"].readFile(path, 'utf-8', function (err, data) {
924
+ cb(err ? null : data);
925
+ });
926
+ }
927
+
928
+ var Store;
929
+ try {
930
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
931
+ var AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
932
+ Store = new AsyncLocalStorage();
933
+ }
934
+ catch (e) {
935
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
936
+ var GlobalStore = require('../core/store').GlobalStore;
937
+ Store = new GlobalStore();
938
+ }
939
+ var AsyncStore = Store;
940
+
941
+ function isHandlerSync(handler) {
942
+ return handler.length > 2;
943
+ }
944
+ function reportToHoneybadger(hb, err, callback) {
945
+ hb.notify(err, {
946
+ afterNotify: function () {
947
+ hb.clear();
948
+ callback(err);
949
+ }
950
+ });
951
+ }
952
+ function asyncHandler(handler, hb) {
953
+ return function wrappedLambdaHandler(event, context) {
954
+ hb.__setStore(AsyncStore);
955
+ return new Promise(function (resolve, reject) {
956
+ AsyncStore.run({ context: {}, breadcrumbs: [] }, function () {
957
+ try {
958
+ handler(event, context)
959
+ .then(resolve)
960
+ .catch(function (err) { return reportToHoneybadger(hb, err, reject); });
961
+ }
962
+ catch (err) {
963
+ reportToHoneybadger(hb, err, reject);
964
+ }
965
+ });
966
+ });
967
+ };
968
+ }
969
+ function syncHandler(handler, hb) {
970
+ return function wrappedLambdaHandler(event, context, cb) {
971
+ hb.__setStore(AsyncStore);
972
+ AsyncStore.run({ context: {}, breadcrumbs: [] }, function () {
973
+ try {
974
+ handler(event, context, function (error, result) {
975
+ if (error) {
976
+ return reportToHoneybadger(hb, error, cb);
977
+ }
978
+ cb(null, result);
979
+ });
980
+ }
981
+ catch (err) {
982
+ reportToHoneybadger(hb, err, cb);
983
+ }
984
+ });
985
+ };
986
+ }
987
+ function lambdaHandler(handler) {
988
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
989
+ var hb = this;
990
+ if (isHandlerSync(handler)) {
991
+ return syncHandler(handler, hb);
992
+ }
993
+ return asyncHandler(handler, hb);
994
+ }
995
+ var listenerRemoved = false;
996
+ /**
997
+ * Removes AWS Lambda default listener that
998
+ * exits the process before letting us report to honeybadger.
999
+ */
1000
+ function removeAwsDefaultUncaughtExceptionListener() {
1001
+ if (listenerRemoved) {
1002
+ return;
1003
+ }
1004
+ listenerRemoved = true;
1005
+ var listeners = process.listeners('uncaughtException');
1006
+ if (listeners.length === 0) {
1007
+ return;
1008
+ }
1009
+ // We assume it's the first listener
1010
+ process.removeListener('uncaughtException', listeners[0]);
1011
+ }
761
1012
 
762
1013
  var count = 0;
1014
+ function removeAwsLambdaListener() {
1015
+ var isLambda = !!process.env.LAMBDA_TASK_ROOT;
1016
+ if (!isLambda) {
1017
+ return;
1018
+ }
1019
+ removeAwsDefaultUncaughtExceptionListener();
1020
+ }
763
1021
  function uncaughtException () {
764
1022
  return {
765
1023
  load: function (client) {
766
1024
  if (!client.config.enableUncaught) {
767
1025
  return;
768
1026
  }
769
- process.on('uncaughtException', function (uncaughtError) {
1027
+ removeAwsLambdaListener();
1028
+ process.on('uncaughtException', function honeybadgerUncaughtExceptionListener(uncaughtError) {
770
1029
  // Prevent recursive errors
771
1030
  if (count > 1) {
772
1031
  fatallyLogAndExit(uncaughtError);
@@ -810,7 +1069,7 @@ function fullUrl(req) {
810
1069
  // @ts-ignore The old @types/node incorrectly defines `address` as string|Address
811
1070
  var port = address ? address.port : undefined;
812
1071
  // @ts-ignore
813
- return url__default['default'].format({
1072
+ return url__default["default"].format({
814
1073
  protocol: req.protocol,
815
1074
  hostname: req.hostname,
816
1075
  port: port,
@@ -819,10 +1078,7 @@ function fullUrl(req) {
819
1078
  });
820
1079
  }
821
1080
  function requestHandler(req, res, next) {
822
- this.clear();
823
- var dom = domain__default['default'].create();
824
- dom.on('error', next);
825
- dom.run(next);
1081
+ this.run(next, next);
826
1082
  }
827
1083
  function errorHandler(err, req, _res, next) {
828
1084
  this.notify(err, {
@@ -837,50 +1093,12 @@ function errorHandler(err, req, _res, next) {
837
1093
  });
838
1094
  return next(err);
839
1095
  }
840
- function lambdaHandler(handler) {
841
- return function lambdaHandler(event, context, callback) {
842
- // in the case of an async handler, the length of the handler will be less than 3 (no callback function).
843
- // if this is the case, we have to explicitly call the callback function from the function we are returning.
844
- // we don't have to do that if the handler has third callback function parameter,
845
- // because it will be called directly from inside the handler.
846
- var shouldInvokeCallbackExplicitly = handler.length < 3;
847
- // eslint-disable-next-line prefer-rest-params
848
- var args = arguments;
849
- var dom = domain__default['default'].create();
850
- // eslint-disable-next-line @typescript-eslint/no-this-alias
851
- var hb = this;
852
- var hbHandler = function (err) {
853
- var willNotify = hb.notify(err, {
854
- afterNotify: function () {
855
- hb.clear();
856
- callback(err);
857
- }
858
- });
859
- if (!willNotify) {
860
- callback(err);
861
- }
862
- };
863
- dom.on('error', hbHandler);
864
- dom.run(function () {
865
- process.nextTick(function () {
866
- Promise.resolve(handler.apply(this, args))
867
- .then(function (res) {
868
- hb.clear();
869
- if (shouldInvokeCallbackExplicitly) {
870
- callback(null, res);
871
- }
872
- })
873
- .catch(hbHandler);
874
- });
875
- });
876
- }.bind(this);
877
- }
878
1096
 
879
1097
  var Honeybadger = /** @class */ (function (_super) {
880
1098
  __extends(Honeybadger, _super);
881
1099
  function Honeybadger(opts) {
882
1100
  if (opts === void 0) { opts = {}; }
883
- var _this = _super.call(this, __assign({ afterUncaught: fatallyLogAndExit, projectRoot: process.cwd(), hostname: os__default['default'].hostname() }, opts)) || this;
1101
+ var _this = _super.call(this, __assign({ afterUncaught: fatallyLogAndExit, projectRoot: process.cwd(), hostname: os__default["default"].hostname() }, opts)) || this;
884
1102
  /** @internal */
885
1103
  _this.__beforeNotifyHandlers = [
886
1104
  function (notice) {
@@ -893,6 +1111,7 @@ var Honeybadger = /** @class */ (function (_super) {
893
1111
  });
894
1112
  }
895
1113
  ];
1114
+ _this.__getSourceFileHandler = getSourceFile.bind(_this);
896
1115
  _this.errorHandler = errorHandler.bind(_this);
897
1116
  _this.requestHandler = requestHandler.bind(_this);
898
1117
  _this.lambdaHandler = lambdaHandler.bind(_this);
@@ -905,13 +1124,9 @@ var Honeybadger = /** @class */ (function (_super) {
905
1124
  Honeybadger.prototype.__send = function (notice) {
906
1125
  var _this = this;
907
1126
  var protocol = new url.URL(this.config.endpoint).protocol;
908
- var transport = (protocol === "http:" ? http__default['default'] : https__default['default']);
1127
+ var transport = (protocol === "http:" ? http__default["default"] : https__default["default"]);
909
1128
  var payload = this.__buildPayload(notice);
910
1129
  payload.server.pid = process.pid;
911
- var handlers = Array.prototype.slice.call(this.__afterNotifyHandlers);
912
- if (notice.afterNotify) {
913
- handlers.unshift(notice.afterNotify);
914
- }
915
1130
  getStats(function (stats) {
916
1131
  payload.server.stats = stats;
917
1132
  var data = Buffer.from(JSON.stringify(sanitize(payload, _this.config.maxObjectDepth)), 'utf8');
@@ -924,32 +1139,50 @@ var Honeybadger = /** @class */ (function (_super) {
924
1139
  }
925
1140
  };
926
1141
  var req = transport.request(endpoint(_this.config, '/v1/notices/js'), options, function (res) {
927
- _this.logger.debug("statusCode: " + res.statusCode);
1142
+ _this.logger.debug("statusCode: ".concat(res.statusCode));
928
1143
  var body = '';
929
1144
  res.on('data', function (chunk) {
930
1145
  body += chunk;
931
1146
  });
932
1147
  res.on('end', function () {
933
1148
  if (res.statusCode !== 201) {
934
- runAfterNotifyHandlers(notice, handlers, new Error("Bad HTTP response: " + res.statusCode));
935
- _this.logger.warn("Error report failed: unknown response from server. code=" + res.statusCode);
1149
+ runAfterNotifyHandlers(notice, _this.__afterNotifyHandlers, new Error("Bad HTTP response: ".concat(res.statusCode)));
1150
+ _this.logger.warn("Error report failed: unknown response from server. code=".concat(res.statusCode));
936
1151
  return;
937
1152
  }
938
1153
  var uuid = JSON.parse(body).id;
939
1154
  runAfterNotifyHandlers(merge(notice, {
940
1155
  id: uuid
941
- }), handlers);
942
- _this.logger.info('Error report sent.', "id=" + uuid);
1156
+ }), _this.__afterNotifyHandlers);
1157
+ _this.logger.info("Error report sent \u26A1 https://app.honeybadger.io/notice/".concat(uuid));
943
1158
  });
944
1159
  });
945
1160
  req.on('error', function (err) {
946
- _this.logger.error('Error report failed: an unknown error occurred.', "message=" + err.message);
947
- runAfterNotifyHandlers(notice, handlers, err);
1161
+ _this.logger.error('Error report failed: an unknown error occurred.', "message=".concat(err.message));
1162
+ runAfterNotifyHandlers(notice, _this.__afterNotifyHandlers, err);
948
1163
  });
949
1164
  req.write(data);
950
1165
  req.end();
951
1166
  });
952
- return true;
1167
+ };
1168
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1169
+ Honeybadger.prototype.run = function (handler, onError) {
1170
+ var _this = this;
1171
+ var storeObject = this.__getStoreOrDefaultObject();
1172
+ this.__setStore(AsyncStore);
1173
+ if (onError) {
1174
+ // ALS is fine for context-tracking, but `domain` allows us to catch errors
1175
+ // thrown asynchronously (timers, event emitters)
1176
+ // We can't use unhandledRejection/uncaughtException listeners; they're global and shared across all requests
1177
+ // But the `onError` handler might be request-specific.
1178
+ // Note that this doesn't still handle all cases. `domain` has its own problems:
1179
+ // See https://github.com/honeybadger-io/honeybadger-js/pull/711
1180
+ var dom = domain__default["default"].create();
1181
+ var onErrorWithContext = function (err) { return _this.__store.run(storeObject, function () { return onError(err); }); };
1182
+ dom.on('error', onErrorWithContext);
1183
+ handler = dom.bind(handler);
1184
+ }
1185
+ return this.__store.run(storeObject, handler);
953
1186
  };
954
1187
  return Honeybadger;
955
1188
  }(Client));
@@ -957,7 +1190,7 @@ var server = new Honeybadger({
957
1190
  __plugins: [
958
1191
  uncaughtException(),
959
1192
  unhandledRejection()
960
- ]
1193
+ ],
961
1194
  });
962
1195
 
963
1196
  module.exports = server;