diff --git a/CHANGELOG.md b/CHANGELOG.md index 5977af66b9b0..ff2854a5076c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased - [browser] feat: Use framesToPop for InvaliantViolations in React errors (#2204) +- [browser] fix: Make sure that falsy values are captured in unhandledrejections (#2207) +- [loader] fix: Loader should also retrigger falsy values as errors (#2207) ## 5.6.1 diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index d6bd160546ad..62d24c455937 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -94,7 +94,7 @@ export class GlobalHandlers implements Integration { message.error && isString(message.error.message) ? message.error.message : 'No error message'; } - if (stacktrace.mechanism === 'onunhandledrejection' && stacktrace.incomplete) { + if (stacktrace.mechanism === 'onunhandledrejection' && (stacktrace.incomplete || stacktrace.mode === 'failed')) { return this._eventFromIncompleteRejection(stacktrace, error); } @@ -166,8 +166,8 @@ export class GlobalHandlers implements Integration { if (event.exception.values && event.exception.values[0]) { event.exception.values[0].mechanism = { data: { - incomplete: true, mode: stacktrace.mode, + ...(stacktrace.incomplete && { incomplete: stacktrace.incomplete }), ...(stacktrace.message && { message: stacktrace.message }), ...(stacktrace.name && { name: stacktrace.name }), }, diff --git a/packages/browser/src/loader.js b/packages/browser/src/loader.js index 4e685f4fe7a4..17edc9c76075 100644 --- a/packages/browser/src/loader.js +++ b/packages/browser/src/loader.js @@ -31,8 +31,8 @@ // content.p = promise rejection // content.f = function call the Sentry if ( - (content.e || - content.p || + ('e' in content || + 'p' in content || (content.f && content.f.indexOf('capture') > -1) || (content.f && content.f.indexOf('showReportDialog') > -1)) && lazy @@ -139,9 +139,9 @@ // And now capture all previously caught exceptions for (var i = 0; i < data.length; i++) { - if (data[i].e && tracekitErrorHandler) { + if ('e' in data[i] && tracekitErrorHandler) { tracekitErrorHandler.apply(_window, data[i].e); - } else if (data[i].p && tracekitUnhandledRejectionHandler) { + } else if ('p' in data[i] && tracekitUnhandledRejectionHandler) { tracekitUnhandledRejectionHandler.apply(_window, [data[i].p]); } } diff --git a/packages/browser/src/tracekit.ts b/packages/browser/src/tracekit.ts index 9cfbf07a3c7f..735655b24c4e 100644 --- a/packages/browser/src/tracekit.ts +++ b/packages/browser/src/tracekit.ts @@ -272,7 +272,12 @@ TraceKit._report = (function reportModuleWrapper() { * @see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent */ function _traceKitWindowOnUnhandledRejection(e: any) { - var err = e && typeof e.reason !== 'undefined' ? e.reason : e; + var err = e; + // You cannot itterate over non-objects, but we want to check + // for the existence in any value, not for the value itself + try { + err = e && 'reason' in e ? e.reason : e; + } catch (_oO) {} var stack = TraceKit._computeStackTrace(err); stack.mechanism = 'onunhandledrejection'; _notifyHandlers(stack, true, err); @@ -906,8 +911,8 @@ TraceKit._computeStackTrace = (function _computeStackTraceWrapper() { return { original: ex, - name: ex.name, - message: ex.message, + name: ex && ex.name, + message: ex && ex.message, mode: 'failed', }; } diff --git a/packages/browser/test/integration/suites/builtins.js b/packages/browser/test/integration/suites/builtins.js index 5eb9a901b8ee..61312e81e6ee 100644 --- a/packages/browser/test/integration/suites/builtins.js +++ b/packages/browser/test/integration/suites/builtins.js @@ -43,174 +43,266 @@ describe("wrapped built-ins", function() { }); }); - it("should capture unhandledrejection with error", function() { - return runInSandbox(sandbox, function() { - if (isChrome()) { - Promise.reject(new Error("test2")); - } else { - window.resolveTest({ window: window }); - } - }).then(function(summary) { - if (summary.window.isChrome()) { - assert.equal(summary.events[0].exception.values[0].value, "test2"); - assert.equal(summary.events[0].exception.values[0].type, "Error"); - assert.isAtLeast( - summary.events[0].exception.values[0].stacktrace.frames.length, - 1 - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.handled, - false - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.type, - "onunhandledrejection" - ); - } + describe("unhandledrejection", function() { + it("should capture unhandledrejection with error", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject(new Error("test2")); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + assert.equal(summary.events[0].exception.values[0].value, "test2"); + assert.equal(summary.events[0].exception.values[0].type, "Error"); + assert.isAtLeast( + summary.events[0].exception.values[0].stacktrace.frames.length, + 1 + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + } + }); }); - }); - it("should capture unhandledrejection with a string", function() { - return runInSandbox(sandbox, function() { - if (isChrome()) { - Promise.reject("test"); - } else { - window.resolveTest({ window: window }); - } - }).then(function(summary) { - if (summary.window.isChrome()) { - // non-error rejections doesnt provide stacktraces so we can skip the assertion - assert.equal( - summary.events[0].exception.values[0].value, - "Non-Error promise rejection captured with value: test" - ); - assert.equal( - summary.events[0].exception.values[0].type, - "UnhandledRejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.handled, - false - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.type, - "onunhandledrejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.data.incomplete, - true - ); - } + it("should capture unhandledrejection with a string", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject("test"); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with value: test" + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.data.incomplete, + true + ); + } + }); }); - }); - it("should capture unhandledrejection with a monster string", function() { - return runInSandbox(sandbox, function() { - if (isChrome()) { - Promise.reject("test".repeat(100)); - } else { - window.resolveTest({ window: window }); - } - }).then(function(summary) { - if (summary.window.isChrome()) { - // non-error rejections doesnt provide stacktraces so we can skip the assertion - assert.equal(summary.events[0].exception.values[0].value.length, 253); - assert.include( - summary.events[0].exception.values[0].value, - "Non-Error promise rejection captured with value: " - ); - assert.equal( - summary.events[0].exception.values[0].type, - "UnhandledRejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.handled, - false - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.type, - "onunhandledrejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.data.incomplete, - true - ); - } + it("should capture unhandledrejection with a monster string", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject("test".repeat(100)); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal(summary.events[0].exception.values[0].value.length, 253); + assert.include( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with value: " + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.data.incomplete, + true + ); + } + }); }); - }); - it("should capture unhandledrejection with an object", function() { - return runInSandbox(sandbox, function() { - if (isChrome()) { - Promise.reject({ a: "b", b: "c", c: "d" }); - } else { - window.resolveTest({ window: window }); - } - }).then(function(summary) { - if (summary.window.isChrome()) { - // non-error rejections doesnt provide stacktraces so we can skip the assertion - assert.equal( - summary.events[0].exception.values[0].value, - "Non-Error promise rejection captured with keys: a, b, c" - ); - assert.equal( - summary.events[0].exception.values[0].type, - "UnhandledRejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.handled, - false - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.type, - "onunhandledrejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.data.incomplete, - true - ); - } + it("should capture unhandledrejection with an object", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject({ a: "b", b: "c", c: "d" }); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with keys: a, b, c" + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.data.incomplete, + true + ); + } + }); }); - }); - it("should capture unhandledrejection with an monster object", function() { - return runInSandbox(sandbox, function() { - if (isChrome()) { - var a = { - a: "1".repeat("100"), - b: "2".repeat("100"), - c: "3".repeat("100"), - }; - a.d = a.a; - a.e = a; - Promise.reject(a); - } else { - window.resolveTest({ window: window }); - } - }).then(function(summary) { - if (summary.window.isChrome()) { - // non-error rejections doesnt provide stacktraces so we can skip the assertion - assert.equal( - summary.events[0].exception.values[0].value, - "Non-Error promise rejection captured with keys: a, b, c, d, e" - ); - assert.equal( - summary.events[0].exception.values[0].type, - "UnhandledRejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.handled, - false - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.type, - "onunhandledrejection" - ); - assert.equal( - summary.events[0].exception.values[0].mechanism.data.incomplete, - true - ); - } + it("should capture unhandledrejection with an monster object", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + var a = { + a: "1".repeat("100"), + b: "2".repeat("100"), + c: "3".repeat("100"), + }; + a.d = a.a; + a.e = a; + Promise.reject(a); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with keys: a, b, c, d, e" + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.data.incomplete, + true + ); + } + }); + }); + + it("should capture unhandledrejection with a number", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject(1337); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with value: 1337" + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + } + }); + }); + + it("should capture unhandledrejection with null", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject(null); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with value: null" + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + } + }); + }); + + it("should capture unhandledrejection with an undefined", function() { + return runInSandbox(sandbox, function() { + if (isChrome()) { + Promise.reject(undefined); + } else { + window.resolveTest({ window: window }); + } + }).then(function(summary) { + if (summary.window.isChrome()) { + // non-error rejections doesnt provide stacktraces so we can skip the assertion + assert.equal( + summary.events[0].exception.values[0].value, + "Non-Error promise rejection captured with value: undefined" + ); + assert.equal( + summary.events[0].exception.values[0].type, + "UnhandledRejection" + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.handled, + false + ); + assert.equal( + summary.events[0].exception.values[0].mechanism.type, + "onunhandledrejection" + ); + } + }); }); });