Skip to content

Commit 258c024

Browse files
authored
feat: Handle DOMError and DOMException gracefully (#1310)
1 parent 26fcb59 commit 258c024

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

src/raven.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ var md5 = require('../vendor/md5/md5');
66
var RavenConfigError = require('./configError');
77

88
var utils = require('./utils');
9+
var isErrorEvent = utils.isErrorEvent;
10+
var isDOMError = utils.isDOMError;
11+
var isDOMException = utils.isDOMException;
912
var isError = utils.isError;
1013
var isObject = utils.isObject;
1114
var isPlainObject = utils.isPlainObject;
12-
var isErrorEvent = utils.isErrorEvent;
1315
var isUndefined = utils.isUndefined;
1416
var isFunction = utils.isFunction;
1517
var isString = utils.isString;
@@ -469,6 +471,23 @@ Raven.prototype = {
469471
if (isErrorEvent(ex) && ex.error) {
470472
// If it is an ErrorEvent with `error` property, extract it to get actual Error
471473
ex = ex.error;
474+
} else if (isDOMError(ex) || isDOMException(ex)) {
475+
// If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers)
476+
// then we just extract the name and message, as they don't provide anything else
477+
// https://developer.mozilla.org/en-US/docs/Web/API/DOMError
478+
// https://developer.mozilla.org/en-US/docs/Web/API/DOMException
479+
var name = ex.name || (isDOMError(ex) ? 'DOMError' : 'DOMException');
480+
var message = ex.message ? name + ': ' + ex.message : name;
481+
482+
return this.captureMessage(
483+
message,
484+
objectMerge(options, {
485+
// neither DOMError or DOMException provide stack trace and we most likely wont get it this way as well
486+
// but it's barely any overhead so we may at least try
487+
stacktrace: true,
488+
trimHeadFrames: options.trimHeadFrames + 1
489+
})
490+
);
472491
} else if (isError(ex)) {
473492
// we have a real Error object
474493
ex = ex;
@@ -480,6 +499,7 @@ Raven.prototype = {
480499
ex = new Error(options.message);
481500
} else {
482501
// If none of previous checks were valid, then it means that
502+
// it's not a DOMError/DOMException
483503
// it's not a plain Object
484504
// it's not a valid ErrorEvent (one with an error property)
485505
// it's not an Error

src/utils.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function isObject(what) {
1212
// Yanked from https://git.io/vS8DV re-used under CC0
1313
// with some tiny modifications
1414
function isError(value) {
15-
switch ({}.toString.call(value)) {
15+
switch (Object.prototype.toString.call(value)) {
1616
case '[object Error]':
1717
return true;
1818
case '[object Exception]':
@@ -25,7 +25,15 @@ function isError(value) {
2525
}
2626

2727
function isErrorEvent(value) {
28-
return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]';
28+
return Object.prototype.toString.call(value) === '[object ErrorEvent]';
29+
}
30+
31+
function isDOMError(value) {
32+
return Object.prototype.toString.call(value) === '[object DOMError]';
33+
}
34+
35+
function isDOMException(value) {
36+
return Object.prototype.toString.call(value) === '[object DOMException]';
2937
}
3038

3139
function isUndefined(what) {
@@ -68,6 +76,24 @@ function supportsErrorEvent() {
6876
}
6977
}
7078

79+
function supportsDOMError() {
80+
try {
81+
new DOMError(''); // eslint-disable-line no-new
82+
return true;
83+
} catch (e) {
84+
return false;
85+
}
86+
}
87+
88+
function supportsDOMException() {
89+
try {
90+
new DOMException(''); // eslint-disable-line no-new
91+
return true;
92+
} catch (e) {
93+
return false;
94+
}
95+
}
96+
7197
function supportsFetch() {
7298
if (!('fetch' in _window)) return false;
7399

@@ -583,13 +609,17 @@ module.exports = {
583609
isObject: isObject,
584610
isError: isError,
585611
isErrorEvent: isErrorEvent,
612+
isDOMError: isDOMError,
613+
isDOMException: isDOMException,
586614
isUndefined: isUndefined,
587615
isFunction: isFunction,
588616
isPlainObject: isPlainObject,
589617
isString: isString,
590618
isArray: isArray,
591619
isEmptyObject: isEmptyObject,
592620
supportsErrorEvent: supportsErrorEvent,
621+
supportsDOMError: supportsDOMError,
622+
supportsDOMException: supportsDOMException,
593623
supportsFetch: supportsFetch,
594624
supportsReferrerPolicy: supportsReferrerPolicy,
595625
supportsPromiseRejectionEvent: supportsPromiseRejectionEvent,

test/raven.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ _Raven.prototype._getUuid = function() {
2323
var utils = require('../src/utils');
2424
var joinRegExp = utils.joinRegExp;
2525
var supportsErrorEvent = utils.supportsErrorEvent;
26+
var supportsDOMError = utils.supportsDOMError;
27+
var supportsDOMException = utils.supportsDOMException;
2628
var supportsFetch = utils.supportsFetch;
2729
var supportsReferrerPolicy = utils.supportsReferrerPolicy;
2830
var supportsPromiseRejectionEvent = utils.supportsPromiseRejectionEvent;
@@ -3124,6 +3126,29 @@ describe('Raven (public API)', function() {
31243126
});
31253127
}
31263128

3129+
if (supportsDOMError()) {
3130+
it('should just pull name and message from DOMError and call captureMessage', function() {
3131+
var error = new DOMError('pickleRick', 'Morty');
3132+
this.sinon.stub(Raven, 'isSetup').returns(true);
3133+
this.sinon.spy(Raven, 'captureMessage');
3134+
Raven.captureException(error);
3135+
var call = Raven.captureMessage.getCall(0).args[0];
3136+
assert.equal(call, 'pickleRick: Morty');
3137+
});
3138+
}
3139+
3140+
if (supportsDOMException()) {
3141+
it('should just pull name and message from DOMException and call captureMessage', function() {
3142+
// Yes, in DOMException order of arguments is reversed...
3143+
var error = new DOMException('Morty', 'pickleRick');
3144+
this.sinon.stub(Raven, 'isSetup').returns(true);
3145+
this.sinon.spy(Raven, 'captureMessage');
3146+
Raven.captureException(error);
3147+
var call = Raven.captureMessage.getCall(0).args[0];
3148+
assert.equal(call, 'pickleRick: Morty');
3149+
});
3150+
}
3151+
31273152
it("should treat Schrodinger's Error in the same way as regular Error", function() {
31283153
// Schrodinger's Error is an object that is and is not an Error at the same time
31293154
// Like... error, but not really.

test/utils.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ var isString = utils.isString;
1212
var isArray = utils.isArray;
1313
var isObject = utils.isObject;
1414
var isEmptyObject = utils.isEmptyObject;
15+
var isDOMError = utils.isDOMError;
16+
var isDOMException = utils.isDOMException;
1517
var isError = utils.isError;
1618
var isErrorEvent = utils.isErrorEvent;
1719
var supportsErrorEvent = utils.supportsErrorEvent;
20+
var supportsDOMError = utils.supportsDOMError;
21+
var supportsDOMException = utils.supportsDOMException;
1822
var wrappedCallback = utils.wrappedCallback;
1923
var joinRegExp = utils.joinRegExp;
2024
var objectMerge = utils.objectMerge;
@@ -113,6 +117,24 @@ describe('utils', function() {
113117
});
114118
}
115119

120+
if (supportsDOMError()) {
121+
describe('isDOMError', function() {
122+
it('should work as advertised', function() {
123+
assert.isFalse(isDOMError(new Error()));
124+
assert.isTrue(isDOMError(new DOMError('')));
125+
});
126+
});
127+
}
128+
129+
if (supportsDOMException()) {
130+
describe('isDOMException', function() {
131+
it('should work as advertised', function() {
132+
assert.isFalse(isDOMException(new Error()));
133+
assert.isTrue(isDOMException(new DOMException('')));
134+
});
135+
});
136+
}
137+
116138
describe('isError', function() {
117139
function testErrorFromDifferentContext(createError) {
118140
var iframe = document.createElement('iframe');

0 commit comments

Comments
 (0)