@honeybadger-io/js 3.2.7 → 4.0.0-beta.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.
Files changed (38) hide show
  1. package/dist/browser/honeybadger.js +267 -116
  2. package/dist/browser/honeybadger.js.map +1 -1
  3. package/dist/browser/honeybadger.min.js +1 -1
  4. package/dist/browser/honeybadger.min.js.map +1 -1
  5. package/dist/browser/types/browser/util.d.ts +1 -1
  6. package/dist/browser/types/core/client.d.ts +23 -3
  7. package/dist/browser/types/core/error.d.ts +3 -0
  8. package/dist/browser/types/core/store.d.ts +7 -0
  9. package/dist/browser/types/core/types.d.ts +8 -4
  10. package/dist/browser/types/core/util.d.ts +4 -2
  11. package/dist/browser/types/server/async_store.d.ts +5 -0
  12. package/dist/browser/types/server/aws_lambda.d.ts +9 -0
  13. package/dist/browser/types/server/integrations/uncaught_exception.d.ts +2 -0
  14. package/dist/browser/types/server/integrations/unhandled_rejection.d.ts +2 -0
  15. package/dist/browser/types/server/middleware.d.ts +4 -0
  16. package/dist/browser/types/server/util.d.ts +9 -0
  17. package/dist/browser/types/server.d.ts +14 -0
  18. package/dist/server/honeybadger.d.ts +3 -1
  19. package/dist/server/honeybadger.js +375 -139
  20. package/dist/server/types/browser/integrations/breadcrumbs.d.ts +2 -0
  21. package/dist/server/types/browser/integrations/event_listeners.d.ts +2 -0
  22. package/dist/server/types/browser/integrations/onerror.d.ts +3 -0
  23. package/dist/server/types/browser/integrations/onunhandledrejection.d.ts +2 -0
  24. package/dist/server/types/browser/integrations/timers.d.ts +2 -0
  25. package/dist/server/types/browser/util.d.ts +18 -0
  26. package/dist/server/types/browser.d.ts +15 -0
  27. package/dist/server/types/core/client.d.ts +23 -3
  28. package/dist/server/types/core/error.d.ts +3 -0
  29. package/dist/server/types/core/store.d.ts +7 -0
  30. package/dist/server/types/core/types.d.ts +8 -4
  31. package/dist/server/types/core/util.d.ts +4 -2
  32. package/dist/server/types/server/async_store.d.ts +5 -0
  33. package/dist/server/types/server/aws_lambda.d.ts +9 -0
  34. package/dist/server/types/server/middleware.d.ts +3 -5
  35. package/dist/server/types/server/util.d.ts +7 -0
  36. package/dist/server/types/server.d.ts +3 -1
  37. package/honeybadger.d.ts +2 -1
  38. package/package.json +25 -22
@@ -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,17 +238,39 @@ 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
  }
@@ -309,7 +332,7 @@ function sanitize(obj, maxDepth) {
309
332
  }
310
333
  // Serialize inside arrays
311
334
  if (Array.isArray(obj)) {
312
- return obj.map(function (o) { return serialize(o, depth + 1); });
335
+ return obj.map(function (o) { return safeSerialize(o, depth + 1); });
313
336
  }
314
337
  // Serialize inside objects
315
338
  if (typeof (obj) === 'object') {
@@ -317,7 +340,7 @@ function sanitize(obj, maxDepth) {
317
340
  for (var k in obj) {
318
341
  var v = obj[k];
319
342
  if (Object.prototype.hasOwnProperty.call(obj, k) && (k != null) && (v != null)) {
320
- ret[k] = serialize(v, depth + 1);
343
+ ret[k] = safeSerialize(v, depth + 1);
321
344
  }
322
345
  }
323
346
  return ret;
@@ -325,7 +348,16 @@ function sanitize(obj, maxDepth) {
325
348
  // Return everything else untouched
326
349
  return obj;
327
350
  }
328
- return serialize(obj);
351
+ function safeSerialize(obj, depth) {
352
+ if (depth === void 0) { depth = 0; }
353
+ try {
354
+ return serialize(obj, depth);
355
+ }
356
+ catch (e) {
357
+ return "[ERROR] ".concat(e);
358
+ }
359
+ }
360
+ return safeSerialize(obj);
329
361
  }
330
362
  function logger(client) {
331
363
  var log = function (method) {
@@ -335,8 +367,13 @@ function logger(client) {
335
367
  for (var _i = 0; _i < arguments.length; _i++) {
336
368
  args[_i] = arguments[_i];
337
369
  }
338
- if (method === 'debug' && !client.config.debug) {
339
- return;
370
+ if (method === 'debug') {
371
+ if (!client.config.debug) {
372
+ return;
373
+ }
374
+ // Log at default level so that you don't need to also enable verbose
375
+ // logging in Chrome.
376
+ method = 'log';
340
377
  }
341
378
  args.unshift('[Honeybadger]');
342
379
  (_a = client.config.logger)[method].apply(_a, args);
@@ -359,7 +396,7 @@ function makeNotice(thing) {
359
396
  if (!thing) {
360
397
  notice = {};
361
398
  }
362
- else if (Object.prototype.toString.call(thing) === '[object Error]') {
399
+ else if (thing instanceof Error || Object.prototype.toString.call(thing) === '[object Error]') {
363
400
  var e = thing;
364
401
  notice = merge(thing, { name: e.name, message: e.message, stack: e.stack });
365
402
  }
@@ -375,7 +412,7 @@ function makeNotice(thing) {
375
412
  function endpoint(config, path) {
376
413
  var endpoint = config.endpoint.trim().replace(/\/$/, '');
377
414
  path = path.trim().replace(/(^\/|\/$)/g, '');
378
- return endpoint + "/" + path;
415
+ return "".concat(endpoint, "/").concat(path);
379
416
  }
380
417
  function generateStackTrace() {
381
418
  try {
@@ -434,7 +471,9 @@ function filter(obj, filters) {
434
471
  return newObj;
435
472
  }
436
473
  if (is('Array', obj)) {
437
- return obj.map(function (v) { return filter(v); });
474
+ return obj.map(function (v) {
475
+ return filter(v);
476
+ });
438
477
  }
439
478
  if (is('Function', obj)) {
440
479
  return '[FUNC]';
@@ -470,7 +509,7 @@ function filterUrl(url, filters) {
470
509
  query.split(/[&]\s?/).forEach(function (pair) {
471
510
  var _a = pair.split('=', 2), key = _a[0], value = _a[1];
472
511
  if (filterMatch(key, filters)) {
473
- result = result.replace(key + "=" + value, key + "=[FILTERED]");
512
+ result = result.replace("".concat(key, "=").concat(value), "".concat(key, "=[FILTERED]"));
474
513
  }
475
514
  });
476
515
  return result;
@@ -484,34 +523,64 @@ function formatCGIData(vars, prefix) {
484
523
  });
485
524
  return formattedVars;
486
525
  }
526
+ function getSourceCodeSnippet(fileData, lineNumber, sourceRadius) {
527
+ if (sourceRadius === void 0) { sourceRadius = 2; }
528
+ if (!fileData) {
529
+ return null;
530
+ }
531
+ var lines = fileData.split('\n');
532
+ // add one empty line because array index starts from 0, but error line number is counted from 1
533
+ lines.unshift('');
534
+ var start = lineNumber - sourceRadius;
535
+ var end = lineNumber + sourceRadius;
536
+ var result = {};
537
+ for (var i = start; i <= end; i++) {
538
+ var line = lines[i];
539
+ if (typeof line === 'string') {
540
+ result[i] = line;
541
+ }
542
+ }
543
+ return result;
544
+ }
545
+
546
+ var GlobalStore$1 = /** @class */ (function () {
547
+ function GlobalStore(store) {
548
+ this.store = store;
549
+ }
550
+ GlobalStore.prototype.getStore = function () {
551
+ return this.store;
552
+ };
553
+ GlobalStore.prototype.run = function (store, callback) {
554
+ var args = [];
555
+ for (var _i = 2; _i < arguments.length; _i++) {
556
+ args[_i - 2] = arguments[_i];
557
+ }
558
+ this.store = store;
559
+ return callback.apply(void 0, args);
560
+ };
561
+ return GlobalStore;
562
+ }());
487
563
 
488
564
  var notifier = {
489
565
  name: 'honeybadger-js',
490
566
  url: 'https://github.com/honeybadger-io/honeybadger-js',
491
- version: '3.2.7'
567
+ version: '4.0.0-beta.1'
492
568
  };
493
- // Split at commas
494
- var TAG_SEPARATOR = /,/;
495
- // Removes any non-word characters
496
- var TAG_SANITIZER = /[^\w]/g;
497
- // Checks for blank strings
498
- var STRING_EMPTY = '';
569
+ // Split at commas and spaces
570
+ var TAG_SEPARATOR = /,|\s+/;
499
571
  // Checks for non-blank characters
500
572
  var NOT_BLANK = /\S/;
501
573
  var Client = /** @class */ (function () {
502
574
  function Client(opts) {
503
575
  if (opts === void 0) { opts = {}; }
504
- /** @internal */
505
576
  this.__pluginsExecuted = false;
506
- /** @internal */
507
- this.__context = {};
508
- /** @internal */
509
- this.__breadcrumbs = [];
510
- /** @internal */
577
+ this.__store = null;
511
578
  this.__beforeNotifyHandlers = [];
512
- /** @internal */
513
579
  this.__afterNotifyHandlers = [];
514
- 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);
580
+ 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);
581
+ // First, we go with the global (shared) store.
582
+ // Webserver middleware can then switch to the AsyncStore for async context tracking.
583
+ this.__store = new GlobalStore$1({ context: {}, breadcrumbs: [] });
515
584
  this.logger = logger(this);
516
585
  }
517
586
  Client.prototype.factory = function (_opts) {
@@ -532,6 +601,10 @@ var Client = /** @class */ (function () {
532
601
  }
533
602
  return this;
534
603
  };
604
+ /** @internal */
605
+ Client.prototype.__setStore = function (store) {
606
+ this.__store = store;
607
+ };
535
608
  Client.prototype.beforeNotify = function (handler) {
536
609
  this.__beforeNotifyHandlers.push(handler);
537
610
  return this;
@@ -542,41 +615,129 @@ var Client = /** @class */ (function () {
542
615
  };
543
616
  Client.prototype.setContext = function (context) {
544
617
  if (typeof context === 'object') {
545
- this.__context = merge(this.__context, context);
618
+ var store = this.__store.getStore();
619
+ store.context = merge(store.context, context);
546
620
  }
547
621
  return this;
548
622
  };
549
623
  Client.prototype.resetContext = function (context) {
550
624
  this.logger.warn('Deprecation warning: `Honeybadger.resetContext()` has been deprecated; please use `Honeybadger.clear()` instead.');
625
+ var store = this.__store.getStore();
551
626
  if (typeof context === 'object' && context !== null) {
552
- this.__context = merge({}, context);
627
+ store.context = context;
553
628
  }
554
629
  else {
555
- this.__context = {};
630
+ store.context = {};
556
631
  }
557
632
  return this;
558
633
  };
559
634
  Client.prototype.clear = function () {
560
- this.__context = {};
561
- this.__breadcrumbs = [];
635
+ var store = this.__store.getStore();
636
+ store.context = {};
637
+ store.breadcrumbs = [];
562
638
  return this;
563
639
  };
564
- Client.prototype.notify = function (notice, name, extra) {
640
+ Client.prototype.notify = function (noticeable, name, extra) {
641
+ var _this = this;
565
642
  if (name === void 0) { name = undefined; }
566
643
  if (extra === void 0) { extra = undefined; }
567
- if (this.config.disabled) {
568
- this.logger.warn('Deprecation warning: instead of `disabled: true`, use `reportData: false` to explicitly disable Honeybadger reporting. (Dropping notice: honeybadger.js is disabled)');
569
- return false;
644
+ var preConditionError = null;
645
+ var notice = this.makeNotice(noticeable, name, extra);
646
+ if (!notice) {
647
+ this.logger.debug('failed to build error report');
648
+ preConditionError = new Error('failed to build error report');
570
649
  }
571
- if (!this.__reportData()) {
572
- this.logger.debug('Dropping notice: honeybadger.js is in development mode');
573
- return false;
650
+ if (!preConditionError && this.config.reportData === false) {
651
+ this.logger.debug('skipping error report: honeybadger.js is disabled', notice);
652
+ preConditionError = new Error('honeybadger.js is disabled');
574
653
  }
575
- if (!this.config.apiKey) {
576
- this.logger.warn('Unable to send error report: no API key has been configured');
654
+ if (!preConditionError && this.__developmentMode()) {
655
+ this.logger.log('honeybadger.js is in development mode; the following error report will be sent in production.', notice);
656
+ preConditionError = new Error('honeybadger.js is in development mode');
657
+ }
658
+ if (!preConditionError && !this.config.apiKey) {
659
+ this.logger.warn('could not send error report: no API key has been configured', notice);
660
+ preConditionError = new Error('missing API key');
661
+ }
662
+ var beforeNotifyResult = runBeforeNotifyHandlers(notice, this.__beforeNotifyHandlers);
663
+ if (!preConditionError && !beforeNotifyResult) {
664
+ this.logger.debug('skipping error report: beforeNotify handlers returned false', notice);
665
+ preConditionError = new Error('beforeNotify handlers returned false');
666
+ }
667
+ if (preConditionError) {
668
+ runAfterNotifyHandlers(notice, this.__afterNotifyHandlers, preConditionError);
577
669
  return false;
578
670
  }
579
- notice = makeNotice(notice);
671
+ this.addBreadcrumb('Honeybadger Notice', {
672
+ category: 'notice',
673
+ metadata: {
674
+ message: notice.message,
675
+ name: notice.name,
676
+ stack: notice.stack
677
+ }
678
+ });
679
+ var breadcrumbs = this.__getStoreOrDefaultObject().breadcrumbs;
680
+ notice.__breadcrumbs = this.config.breadcrumbsEnabled ? breadcrumbs.slice() : [];
681
+ // we need to have the source file data before the beforeNotifyHandlers,
682
+ // in case they modify them
683
+ var sourceCodeData = notice && notice.backtrace ? notice.backtrace.map(function (trace) { return newObject(trace); }) : null;
684
+ getSourceForBacktrace(sourceCodeData, this.__getSourceFileHandler, function (sourcePerTrace) {
685
+ sourcePerTrace.forEach(function (source, index) {
686
+ notice.backtrace[index].source = source;
687
+ });
688
+ _this.__send(notice);
689
+ });
690
+ return true;
691
+ };
692
+ /**
693
+ * An async version of {@link notify} that resolves only after the notice has been reported to Honeybadger.
694
+ * Implemented using the {@link afterNotify} hook.
695
+ * Rejects if for any reason the report failed to be reported.
696
+ * Useful in serverless environments (AWS Lambda).
697
+ */
698
+ Client.prototype.notifyAsync = function (noticeable, name, extra) {
699
+ var _this = this;
700
+ if (name === void 0) { name = undefined; }
701
+ if (extra === void 0) { extra = undefined; }
702
+ return new Promise(function (resolve, reject) {
703
+ var applyAfterNotify = function (partialNotice) {
704
+ var originalAfterNotify = partialNotice.afterNotify;
705
+ partialNotice.afterNotify = function (err) {
706
+ originalAfterNotify === null || originalAfterNotify === void 0 ? void 0 : originalAfterNotify.call(_this, err);
707
+ if (err) {
708
+ return reject(err);
709
+ }
710
+ resolve();
711
+ };
712
+ };
713
+ // We have to respect any afterNotify hooks that come from the arguments
714
+ var objectToOverride;
715
+ if (noticeable.afterNotify) {
716
+ objectToOverride = noticeable;
717
+ }
718
+ else if (name && name.afterNotify) {
719
+ objectToOverride = name;
720
+ }
721
+ else if (extra && extra.afterNotify) {
722
+ objectToOverride = extra;
723
+ }
724
+ else if (name && typeof name === 'object') {
725
+ objectToOverride = name;
726
+ }
727
+ else if (extra) {
728
+ objectToOverride = extra;
729
+ }
730
+ else {
731
+ objectToOverride = name = {};
732
+ }
733
+ applyAfterNotify(objectToOverride);
734
+ _this.notify(noticeable, name, extra);
735
+ });
736
+ };
737
+ Client.prototype.makeNotice = function (noticeable, name, extra) {
738
+ if (name === void 0) { name = undefined; }
739
+ if (extra === void 0) { extra = undefined; }
740
+ var notice = makeNotice(noticeable);
580
741
  if (name && !(typeof name === 'object')) {
581
742
  var n = String(name);
582
743
  name = { name: n };
@@ -588,17 +749,18 @@ var Client = /** @class */ (function () {
588
749
  notice = mergeNotice(notice, extra);
589
750
  }
590
751
  if (objectIsEmpty(notice)) {
591
- return false;
752
+ return null;
592
753
  }
754
+ var context = this.__getStoreOrDefaultObject().context;
593
755
  var noticeTags = this.__constructTags(notice.tags);
594
- var contextTags = this.__constructTags(this.__context["tags"]);
756
+ var contextTags = this.__constructTags(context["tags"]);
595
757
  var configTags = this.__constructTags(this.config.tags);
596
758
  // Turning into a Set will remove duplicates
597
759
  var tags = noticeTags.concat(contextTags).concat(configTags);
598
760
  var uniqueTags = tags.filter(function (item, index) { return tags.indexOf(item) === index; });
599
761
  notice = merge(notice, {
600
762
  name: notice.name || 'Error',
601
- context: merge(this.__context, notice.context),
763
+ context: merge(context, notice.context),
602
764
  projectRoot: notice.projectRoot || this.config.projectRoot,
603
765
  environment: notice.environment || this.config.environment,
604
766
  component: notice.component || this.config.component,
@@ -612,19 +774,7 @@ var Client = /** @class */ (function () {
612
774
  backtraceShift = 2;
613
775
  }
614
776
  notice.backtrace = makeBacktrace(notice.stack, backtraceShift);
615
- if (!runBeforeNotifyHandlers(notice, this.__beforeNotifyHandlers)) {
616
- return false;
617
- }
618
- this.addBreadcrumb('Honeybadger Notice', {
619
- category: 'notice',
620
- metadata: {
621
- message: notice.message,
622
- name: notice.name,
623
- stack: notice.stack
624
- }
625
- });
626
- notice.__breadcrumbs = this.config.breadcrumbsEnabled ? this.__breadcrumbs.slice() : [];
627
- return this.__send(notice);
777
+ return notice;
628
778
  };
629
779
  Client.prototype.addBreadcrumb = function (message, opts) {
630
780
  if (!this.config.breadcrumbsEnabled) {
@@ -634,30 +784,30 @@ var Client = /** @class */ (function () {
634
784
  var metadata = newObject(opts.metadata);
635
785
  var category = opts.category || 'custom';
636
786
  var timestamp = new Date().toISOString();
637
- this.__breadcrumbs.push({
787
+ var store = this.__store.getStore();
788
+ var breadcrumbs = store.breadcrumbs;
789
+ breadcrumbs.push({
638
790
  category: category,
639
791
  message: message,
640
792
  metadata: metadata,
641
793
  timestamp: timestamp
642
794
  });
643
795
  var limit = this.config.maxBreadcrumbs;
644
- if (this.__breadcrumbs.length > limit) {
645
- this.__breadcrumbs = this.__breadcrumbs.slice(this.__breadcrumbs.length - limit);
796
+ if (breadcrumbs.length > limit) {
797
+ breadcrumbs = breadcrumbs.slice(breadcrumbs.length - limit);
646
798
  }
799
+ store.breadcrumbs = breadcrumbs;
647
800
  return this;
648
801
  };
649
- /** @internal */
650
- Client.prototype.__reportData = function () {
651
- if (this.config.reportData !== null) {
652
- return this.config.reportData;
802
+ Client.prototype.__developmentMode = function () {
803
+ if (this.config.reportData === true) {
804
+ return false;
653
805
  }
654
- return !(this.config.environment && this.config.developmentEnvironments.includes(this.config.environment));
806
+ return (this.config.environment && this.config.developmentEnvironments.includes(this.config.environment));
655
807
  };
656
- /** @internal */
657
808
  Client.prototype.__send = function (_notice) {
658
809
  throw (new Error('Must implement send in subclass'));
659
810
  };
660
- /** @internal */
661
811
  Client.prototype.__buildPayload = function (notice) {
662
812
  var headers = filter(notice.headers, this.config.filters) || {};
663
813
  var cgiData = filter(__assign(__assign({}, notice.cgiData), formatCGIData(headers, 'HTTP_')), this.config.filters);
@@ -693,14 +843,22 @@ var Client = /** @class */ (function () {
693
843
  details: notice.details || {}
694
844
  };
695
845
  };
696
- /** @internal */
697
846
  Client.prototype.__constructTags = function (tags) {
698
847
  if (!tags) {
699
848
  return [];
700
849
  }
701
- return tags.toString().split(TAG_SEPARATOR).map(function (tag) {
702
- return tag.replace(TAG_SANITIZER, STRING_EMPTY);
703
- }).filter(function (tag) { return NOT_BLANK.test(tag); });
850
+ return tags.toString().split(TAG_SEPARATOR).filter(function (tag) { return NOT_BLANK.test(tag); });
851
+ };
852
+ /**
853
+ * For ALS, the store may be uninitialized (if .run()` has not been called).
854
+ * This provides an easy way to read the existing store object or fall back to a default.
855
+ * Returns *a copy* of the store.
856
+ * @internal
857
+ */
858
+ Client.prototype.__getStoreOrDefaultObject = function () {
859
+ var existingStore = this.__store.getStore();
860
+ var store = existingStore || {};
861
+ return __assign({ context: {}, breadcrumbs: [] }, store);
704
862
  };
705
863
  return Client;
706
864
  }());
@@ -711,7 +869,7 @@ function fatallyLogAndExit(err) {
711
869
  process.exit(1);
712
870
  }
713
871
  function getStats(cb) {
714
- var load = os__default['default'].loadavg(), stats = {
872
+ var load = os__default["default"].loadavg(), stats = {
715
873
  load: {
716
874
  one: load[0],
717
875
  five: load[1],
@@ -719,8 +877,8 @@ function getStats(cb) {
719
877
  },
720
878
  mem: {}
721
879
  };
722
- if (fs__default['default'].existsSync('/proc/meminfo')) {
723
- return fs__default['default'].readFile('/proc/meminfo', 'utf8', parseStats);
880
+ if (fs__default["default"].existsSync('/proc/meminfo')) {
881
+ return fs__default["default"].readFile('/proc/meminfo', 'utf8', parseStats);
724
882
  }
725
883
  fallback();
726
884
  function parseStats(err, memData) {
@@ -743,21 +901,125 @@ function getStats(cb) {
743
901
  }
744
902
  function fallback() {
745
903
  stats.mem = {
746
- free: os__default['default'].freemem(),
747
- total: os__default['default'].totalmem()
904
+ free: os__default["default"].freemem(),
905
+ total: os__default["default"].totalmem()
748
906
  };
749
907
  return cb(stats);
750
908
  }
751
909
  }
910
+ /**
911
+ * Get source file if possible, used to build `notice.backtrace.source`
912
+ *
913
+ * @param path to source code
914
+ * @param cb callback with fileContent
915
+ */
916
+ function getSourceFile(path, cb) {
917
+ fs__default["default"].readFile(path, 'utf-8', function (err, data) {
918
+ cb(err ? null : data);
919
+ });
920
+ }
921
+
922
+ var Store;
923
+ try {
924
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
925
+ var AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
926
+ Store = new AsyncLocalStorage();
927
+ }
928
+ catch (e) {
929
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
930
+ var GlobalStore = require('../core/store').GlobalStore;
931
+ Store = new GlobalStore();
932
+ }
933
+ var AsyncStore = Store;
934
+
935
+ function isHandlerSync(handler) {
936
+ return handler.length > 2;
937
+ }
938
+ function reportToHoneybadger(hb, err, callback) {
939
+ hb.notify(err, {
940
+ afterNotify: function () {
941
+ hb.clear();
942
+ callback(err);
943
+ }
944
+ });
945
+ }
946
+ function asyncHandler(handler, hb) {
947
+ return function wrappedLambdaHandler(event, context) {
948
+ hb.__setStore(AsyncStore);
949
+ return new Promise(function (resolve, reject) {
950
+ AsyncStore.run({ context: {}, breadcrumbs: [] }, function () {
951
+ try {
952
+ handler(event, context)
953
+ .then(resolve)
954
+ .catch(function (err) { return reportToHoneybadger(hb, err, reject); });
955
+ }
956
+ catch (err) {
957
+ reportToHoneybadger(hb, err, reject);
958
+ }
959
+ });
960
+ });
961
+ };
962
+ }
963
+ function syncHandler(handler, hb) {
964
+ return function wrappedLambdaHandler(event, context, cb) {
965
+ hb.__setStore(AsyncStore);
966
+ AsyncStore.run({ context: {}, breadcrumbs: [] }, function () {
967
+ try {
968
+ handler(event, context, function (error, result) {
969
+ if (error) {
970
+ return reportToHoneybadger(hb, error, cb);
971
+ }
972
+ cb(null, result);
973
+ });
974
+ }
975
+ catch (err) {
976
+ reportToHoneybadger(hb, err, cb);
977
+ }
978
+ });
979
+ };
980
+ }
981
+ function lambdaHandler(handler) {
982
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
983
+ var hb = this;
984
+ if (isHandlerSync(handler)) {
985
+ return syncHandler(handler, hb);
986
+ }
987
+ return asyncHandler(handler, hb);
988
+ }
989
+ var listenerRemoved = false;
990
+ /**
991
+ * Removes AWS Lambda default listener that
992
+ * exits the process before letting us report to honeybadger.
993
+ */
994
+ function removeAwsDefaultUncaughtExceptionListener() {
995
+ if (listenerRemoved) {
996
+ return;
997
+ }
998
+ listenerRemoved = true;
999
+ var listeners = process.listeners('uncaughtException');
1000
+ if (listeners.length === 0) {
1001
+ return;
1002
+ }
1003
+ // We assume it's the first listener
1004
+ process.removeListener('uncaughtException', listeners[0]);
1005
+ }
752
1006
 
753
1007
  var count = 0;
1008
+ function removeAwsLambdaListener() {
1009
+ var isLambda = !!process.env.LAMBDA_TASK_ROOT;
1010
+ if (!isLambda) {
1011
+ return;
1012
+ }
1013
+ removeAwsDefaultUncaughtExceptionListener();
1014
+ }
754
1015
  function uncaughtException () {
755
1016
  return {
756
1017
  load: function (client) {
757
1018
  if (!client.config.enableUncaught) {
758
1019
  return;
759
1020
  }
760
- process.on('uncaughtException', function (uncaughtError) {
1021
+ removeAwsLambdaListener();
1022
+ process.on('uncaughtException', function honeybadgerUncaughtExceptionListener(uncaughtError) {
761
1023
  // Prevent recursive errors
762
1024
  if (count > 1) {
763
1025
  fatallyLogAndExit(uncaughtError);
@@ -801,7 +1063,7 @@ function fullUrl(req) {
801
1063
  // @ts-ignore The old @types/node incorrectly defines `address` as string|Address
802
1064
  var port = address ? address.port : undefined;
803
1065
  // @ts-ignore
804
- return url__default['default'].format({
1066
+ return url__default["default"].format({
805
1067
  protocol: req.protocol,
806
1068
  hostname: req.hostname,
807
1069
  port: port,
@@ -810,10 +1072,7 @@ function fullUrl(req) {
810
1072
  });
811
1073
  }
812
1074
  function requestHandler(req, res, next) {
813
- this.clear();
814
- var dom = domain__default['default'].create();
815
- dom.on('error', next);
816
- dom.run(next);
1075
+ this.run(next, next);
817
1076
  }
818
1077
  function errorHandler(err, req, _res, next) {
819
1078
  this.notify(err, {
@@ -828,50 +1087,12 @@ function errorHandler(err, req, _res, next) {
828
1087
  });
829
1088
  return next(err);
830
1089
  }
831
- function lambdaHandler(handler) {
832
- return function lambdaHandler(event, context, callback) {
833
- // in the case of an async handler, the length of the handler will be less than 3 (no callback function).
834
- // if this is the case, we have to explicitly call the callback function from the function we are returning.
835
- // we don't have to do that if the handler has third callback function parameter,
836
- // because it will be called directly from inside the handler.
837
- var shouldInvokeCallbackExplicitly = handler.length < 3;
838
- // eslint-disable-next-line prefer-rest-params
839
- var args = arguments;
840
- var dom = domain__default['default'].create();
841
- // eslint-disable-next-line @typescript-eslint/no-this-alias
842
- var hb = this;
843
- var hbHandler = function (err) {
844
- var willNotify = hb.notify(err, {
845
- afterNotify: function () {
846
- hb.clear();
847
- callback(err);
848
- }
849
- });
850
- if (!willNotify) {
851
- callback(err);
852
- }
853
- };
854
- dom.on('error', hbHandler);
855
- dom.run(function () {
856
- process.nextTick(function () {
857
- Promise.resolve(handler.apply(this, args))
858
- .then(function (res) {
859
- hb.clear();
860
- if (shouldInvokeCallbackExplicitly) {
861
- callback(null, res);
862
- }
863
- })
864
- .catch(hbHandler);
865
- });
866
- });
867
- }.bind(this);
868
- }
869
1090
 
870
1091
  var Honeybadger = /** @class */ (function (_super) {
871
1092
  __extends(Honeybadger, _super);
872
1093
  function Honeybadger(opts) {
873
1094
  if (opts === void 0) { opts = {}; }
874
- var _this = _super.call(this, __assign({ afterUncaught: fatallyLogAndExit, projectRoot: process.cwd(), hostname: os__default['default'].hostname() }, opts)) || this;
1095
+ var _this = _super.call(this, __assign({ afterUncaught: fatallyLogAndExit, projectRoot: process.cwd(), hostname: os__default["default"].hostname() }, opts)) || this;
875
1096
  /** @internal */
876
1097
  _this.__beforeNotifyHandlers = [
877
1098
  function (notice) {
@@ -884,6 +1105,7 @@ var Honeybadger = /** @class */ (function (_super) {
884
1105
  });
885
1106
  }
886
1107
  ];
1108
+ _this.__getSourceFileHandler = getSourceFile.bind(_this);
887
1109
  _this.errorHandler = errorHandler.bind(_this);
888
1110
  _this.requestHandler = requestHandler.bind(_this);
889
1111
  _this.lambdaHandler = lambdaHandler.bind(_this);
@@ -896,13 +1118,9 @@ var Honeybadger = /** @class */ (function (_super) {
896
1118
  Honeybadger.prototype.__send = function (notice) {
897
1119
  var _this = this;
898
1120
  var protocol = new url.URL(this.config.endpoint).protocol;
899
- var transport = (protocol === "http:" ? http__default['default'] : https__default['default']);
1121
+ var transport = (protocol === "http:" ? http__default["default"] : https__default["default"]);
900
1122
  var payload = this.__buildPayload(notice);
901
1123
  payload.server.pid = process.pid;
902
- var handlers = Array.prototype.slice.call(this.__afterNotifyHandlers);
903
- if (notice.afterNotify) {
904
- handlers.unshift(notice.afterNotify);
905
- }
906
1124
  getStats(function (stats) {
907
1125
  payload.server.stats = stats;
908
1126
  var data = Buffer.from(JSON.stringify(sanitize(payload, _this.config.maxObjectDepth)), 'utf8');
@@ -915,32 +1133,50 @@ var Honeybadger = /** @class */ (function (_super) {
915
1133
  }
916
1134
  };
917
1135
  var req = transport.request(endpoint(_this.config, '/v1/notices/js'), options, function (res) {
918
- _this.logger.debug("statusCode: " + res.statusCode);
1136
+ _this.logger.debug("statusCode: ".concat(res.statusCode));
919
1137
  var body = '';
920
1138
  res.on('data', function (chunk) {
921
1139
  body += chunk;
922
1140
  });
923
1141
  res.on('end', function () {
924
1142
  if (res.statusCode !== 201) {
925
- runAfterNotifyHandlers(notice, handlers, new Error("Bad HTTP response: " + res.statusCode));
926
- _this.logger.warn("Error report failed: unknown response from server. code=" + res.statusCode);
1143
+ runAfterNotifyHandlers(notice, _this.__afterNotifyHandlers, new Error("Bad HTTP response: ".concat(res.statusCode)));
1144
+ _this.logger.warn("Error report failed: unknown response from server. code=".concat(res.statusCode));
927
1145
  return;
928
1146
  }
929
1147
  var uuid = JSON.parse(body).id;
930
1148
  runAfterNotifyHandlers(merge(notice, {
931
1149
  id: uuid
932
- }), handlers);
933
- _this.logger.info('Error report sent.', "id=" + uuid);
1150
+ }), _this.__afterNotifyHandlers);
1151
+ _this.logger.info("Error report sent \u26A1 https://app.honeybadger.io/notice/".concat(uuid));
934
1152
  });
935
1153
  });
936
1154
  req.on('error', function (err) {
937
- _this.logger.error('Error report failed: an unknown error occurred.', "message=" + err.message);
938
- runAfterNotifyHandlers(notice, handlers, err);
1155
+ _this.logger.error('Error report failed: an unknown error occurred.', "message=".concat(err.message));
1156
+ runAfterNotifyHandlers(notice, _this.__afterNotifyHandlers, err);
939
1157
  });
940
1158
  req.write(data);
941
1159
  req.end();
942
1160
  });
943
- return true;
1161
+ };
1162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1163
+ Honeybadger.prototype.run = function (handler, onError) {
1164
+ var _this = this;
1165
+ var storeObject = this.__getStoreOrDefaultObject();
1166
+ this.__setStore(AsyncStore);
1167
+ if (onError) {
1168
+ // ALS is fine for context-tracking, but `domain` allows us to catch errors
1169
+ // thrown asynchronously (timers, event emitters)
1170
+ // We can't use unhandledRejection/uncaughtException listeners; they're global and shared across all requests
1171
+ // But the `onError` handler might be request-specific.
1172
+ // Note that this doesn't still handle all cases. `domain` has its own problems:
1173
+ // See https://github.com/honeybadger-io/honeybadger-js/pull/711
1174
+ var dom = domain__default["default"].create();
1175
+ var onErrorWithContext = function (err) { return _this.__store.run(storeObject, function () { return onError(err); }); };
1176
+ dom.on('error', onErrorWithContext);
1177
+ handler = dom.bind(handler);
1178
+ }
1179
+ return this.__store.run(storeObject, handler);
944
1180
  };
945
1181
  return Honeybadger;
946
1182
  }(Client));
@@ -948,7 +1184,7 @@ var server = new Honeybadger({
948
1184
  __plugins: [
949
1185
  uncaughtException(),
950
1186
  unhandledRejection()
951
- ]
1187
+ ],
952
1188
  });
953
1189
 
954
1190
  module.exports = server;