diff --git a/example/index.html b/example/index.html index 0051b996991a..b9dff45eae46 100644 --- a/example/index.html +++ b/example/index.html @@ -36,6 +36,8 @@ + + diff --git a/example/scratch.js b/example/scratch.js index 48507f9c5d1f..9d164ac9bf14 100644 --- a/example/scratch.js +++ b/example/scratch.js @@ -68,6 +68,14 @@ function blobExample() { xhr.send(); } +function captureMessage() { + Raven.captureMessage('lol did it broke'); +} + +function captureException() { + Raven.captureException(new Error('lol did it broke')); +} + function a() { b(); } function b() { c(); } function c() { d(); } diff --git a/src/raven.js b/src/raven.js index f0991d9c0967..f30c1383c516 100644 --- a/src/raven.js +++ b/src/raven.js @@ -2,14 +2,13 @@ 'use strict'; var TraceKit = require('../vendor/TraceKit/tracekit'); +var stringify = require('../vendor/json-stringify-safe/stringify'); var RavenConfigError = require('./configError'); var utils = require('./utils'); var isError = utils.isError, isObject = utils.isObject; -var stringify = require('json-stringify-safe'); - var wrapConsoleMethod = require('./console').wrapMethod; var dsnKeys = 'source protocol user pass host port path'.split(' '), @@ -1585,24 +1584,18 @@ Raven.prototype = { if (!hasCORS) return; var url = opts.url; - function handler() { - if (request.status === 200) { - if (opts.onSuccess) { - opts.onSuccess(); - } - } else if (opts.onError) { - var err = new Error('Sentry error code: ' + request.status); - err.request = request; - opts.onError(err); - } - } if ('withCredentials' in request) { request.onreadystatechange = function () { if (request.readyState !== 4) { return; + } else if (request.status === 200) { + opts.onSuccess && opts.onSuccess(); + } else if (opts.onError) { + var err = new Error('Sentry error code: ' + request.status); + err.request = request; + opts.onError(err); } - handler(); }; } else { request = new XDomainRequest(); @@ -1611,7 +1604,16 @@ Raven.prototype = { url = url.replace(/^https?:/, ''); // onreadystatechange not supported by XDomainRequest - request.onload = handler; + if (opts.onSuccess) { + request.onload = opts.onSuccess; + } + if (opts.onError) { + request.onerror = function () { + var err = new Error('Sentry error code: XDomainRequest'); + err.request = request; + opts.onError(err); + } + } } // NOTE: auth is intentionally sent as part of query string (NOT as custom diff --git a/test/vendor/json-stringify-safe.test.js b/test/vendor/json-stringify-safe.test.js new file mode 100644 index 000000000000..626efbdad5fe --- /dev/null +++ b/test/vendor/json-stringify-safe.test.js @@ -0,0 +1,248 @@ +/*global Mocha, assert*/ +var Sinon = require("sinon") +var stringify = require('../../vendor/json-stringify-safe/stringify'); + +function jsonify(obj) { return JSON.stringify(obj, null, 2) } + +describe("Stringify", function() { + it("must stringify circular objects", function() { + var obj = {name: "Alice"} + obj.self = obj + var json = stringify(obj, null, 2) + assert.deepEqual(json, jsonify({name: "Alice", self: "[Circular ~]"})) + }) + + it("must stringify circular objects with intermediaries", function() { + var obj = {name: "Alice"} + obj.identity = {self: obj} + var json = stringify(obj, null, 2) + assert.deepEqual(json, jsonify({name: "Alice", identity: {self: "[Circular ~]"}})) + }) + + it("must stringify circular objects deeper", function() { + var obj = {name: "Alice", child: {name: "Bob"}} + obj.child.self = obj.child + + assert.deepEqual(stringify(obj, null, 2), jsonify({ + name: "Alice", + child: {name: "Bob", self: "[Circular ~.child]"} + })) + }) + + it("must stringify circular objects deeper with intermediaries", function() { + var obj = {name: "Alice", child: {name: "Bob"}} + obj.child.identity = {self: obj.child} + + assert.deepEqual(stringify(obj, null, 2), jsonify({ + name: "Alice", + child: {name: "Bob", identity: {self: "[Circular ~.child]"}} + })) + }) + + it("must stringify circular objects in an array", function() { + var obj = {name: "Alice"} + obj.self = [obj, obj] + + assert.deepEqual(stringify(obj, null, 2), jsonify({ + name: "Alice", self: ["[Circular ~]", "[Circular ~]"] + })) + }) + + it("must stringify circular objects deeper in an array", function() { + var obj = {name: "Alice", children: [{name: "Bob"}, {name: "Eve"}]} + obj.children[0].self = obj.children[0] + obj.children[1].self = obj.children[1] + + assert.deepEqual(stringify(obj, null, 2), jsonify({ + name: "Alice", + children: [ + {name: "Bob", self: "[Circular ~.children.0]"}, + {name: "Eve", self: "[Circular ~.children.1]"} + ] + })) + }) + + it("must stringify circular arrays", function() { + var obj = [] + obj.push(obj) + obj.push(obj) + var json = stringify(obj, null, 2) + assert.deepEqual(json, jsonify(["[Circular ~]", "[Circular ~]"])) + }) + + it("must stringify circular arrays with intermediaries", function() { + var obj = [] + obj.push({name: "Alice", self: obj}) + obj.push({name: "Bob", self: obj}) + + assert.deepEqual(stringify(obj, null, 2), jsonify([ + {name: "Alice", self: "[Circular ~]"}, + {name: "Bob", self: "[Circular ~]"} + ])) + }) + + it("must stringify repeated objects in objects", function() { + var obj = {} + var alice = {name: "Alice"} + obj.alice1 = alice + obj.alice2 = alice + + assert.deepEqual(stringify(obj, null, 2), jsonify({ + alice1: {name: "Alice"}, + alice2: {name: "Alice"} + })) + }) + + it("must stringify repeated objects in arrays", function() { + var alice = {name: "Alice"} + var obj = [alice, alice] + var json = stringify(obj, null, 2) + assert.deepEqual(json, jsonify([{name: "Alice"}, {name: "Alice"}])) + }) + + it("must call given decycler and use its output", function() { + var obj = {} + obj.a = obj + obj.b = obj + + var decycle = Sinon.spy(function() { return decycle.callCount }) + var json = stringify(obj, null, 2, decycle) + assert.deepEqual(json, jsonify({a: 1, b: 2}, null, 2)) + + assert.strictEqual(decycle.callCount, 2) + assert.strictEqual(decycle.thisValues[0], obj) + assert.strictEqual(decycle.args[0][0], "a") + assert.strictEqual(decycle.args[0][1], obj) + assert.strictEqual(decycle.thisValues[1], obj) + assert.strictEqual(decycle.args[1][0], "b") + assert.strictEqual(decycle.args[1][1], obj) + }) + + it("must call replacer and use its output", function() { + var obj = {name: "Alice", child: {name: "Bob"}} + + var replacer = Sinon.spy(bangString) + var json = stringify(obj, replacer, 2) + assert.deepEqual(json, jsonify({name: "Alice!", child: {name: "Bob!"}})) + + assert.strictEqual(replacer.callCount, 4) + assert.strictEqual(replacer.args[0][0], "") + assert.strictEqual(replacer.args[0][1], obj) + assert.strictEqual(replacer.thisValues[1], obj) + assert.strictEqual(replacer.args[1][0], "name") + assert.strictEqual(replacer.args[1][1], "Alice") + assert.strictEqual(replacer.thisValues[2], obj) + assert.strictEqual(replacer.args[2][0], "child") + assert.strictEqual(replacer.args[2][1], obj.child) + assert.strictEqual(replacer.thisValues[3], obj.child) + assert.strictEqual(replacer.args[3][0], "name") + assert.strictEqual(replacer.args[3][1], "Bob") + }) + + it("must call replacer after describing circular references", function() { + var obj = {name: "Alice"} + obj.self = obj + + var replacer = Sinon.spy(bangString) + var json = stringify(obj, replacer, 2) + assert.deepEqual(json, jsonify({name: "Alice!", self: "[Circular ~]!"})) + + assert.strictEqual(replacer.callCount, 3) + assert.strictEqual(replacer.args[0][0], "") + assert.strictEqual(replacer.args[0][1], obj) + assert.strictEqual(replacer.thisValues[1], obj) + assert.strictEqual(replacer.args[1][0], "name") + assert.strictEqual(replacer.args[1][1], "Alice") + assert.strictEqual(replacer.thisValues[2], obj) + assert.strictEqual(replacer.args[2][0], "self") + assert.strictEqual(replacer.args[2][1], "[Circular ~]") + }) + + it("must call given decycler and use its output for nested objects", + function() { + var obj = {} + obj.a = obj + obj.b = {self: obj} + + var decycle = Sinon.spy(function() { return decycle.callCount }) + var json = stringify(obj, null, 2, decycle) + assert.deepEqual(json, jsonify({a: 1, b: {self: 2}})) + + assert.strictEqual(decycle.callCount, 2) + assert.strictEqual(decycle.args[0][0], "a") + assert.strictEqual(decycle.args[0][1], obj) + assert.strictEqual(decycle.args[1][0], "self") + assert.strictEqual(decycle.args[1][1], obj) + }) + + it("must use decycler's output when it returned null", function() { + var obj = {a: "b"} + obj.self = obj + obj.selves = [obj, obj] + + function decycle() { return null } + assert.deepEqual(stringify(obj, null, 2, decycle), jsonify({ + a: "b", + self: null, + selves: [null, null] + })) + }) + + it("must use decycler's output when it returned undefined", function() { + var obj = {a: "b"} + obj.self = obj + obj.selves = [obj, obj] + + function decycle() {} + assert.deepEqual(stringify(obj, null, 2, decycle), jsonify({ + a: "b", + selves: [null, null] + })) + }) + + it("must throw given a decycler that returns a cycle", function() { + var obj = {} + obj.self = obj + var err + function identity(key, value) { return value } + try { stringify(obj, null, 2, identity) } catch (ex) { err = ex } + assert.ok(err instanceof TypeError) + }) + + describe(".getSerialize", function() { + it("must stringify circular objects", function() { + var obj = {a: "b"} + obj.circularRef = obj + obj.list = [obj, obj] + + var json = JSON.stringify(obj, stringify.getSerialize(), 2) + assert.deepEqual(json, jsonify({ + "a": "b", + "circularRef": "[Circular ~]", + "list": ["[Circular ~]", "[Circular ~]"] + })) + }) + + // This is the behavior as of Mar 3, 2015. + // The serializer function keeps state inside the returned function and + // so far I'm not sure how to not do that. JSON.stringify's replacer is not + // called _after_ serialization. + xit("must return a function that could be called twice", function() { + var obj = {name: "Alice"} + obj.self = obj + + var json + var serializer = stringify.getSerialize() + + json = JSON.stringify(obj, serializer, 2) + assert.deepEqual(json, jsonify({name: "Alice", self: "[Circular ~]"})) + + json = JSON.stringify(obj, serializer, 2) + assert.deepEqual(json, jsonify({name: "Alice", self: "[Circular ~]"})) + }) + }) +}) + +function bangString(key, value) { + return typeof value == "string" ? value + "!" : value +} diff --git a/vendor/json-stringify-safe/stringify.js b/vendor/json-stringify-safe/stringify.js new file mode 100644 index 000000000000..57c3de7a8e0c --- /dev/null +++ b/vendor/json-stringify-safe/stringify.js @@ -0,0 +1,47 @@ +'use strict'; + +/* + json-stringify-safe + Like JSON.stringify, but doesn't throw on circular references. + + Originally forked from https://github.com/isaacs/json-stringify-safe + version 5.0.1 on 3/8/2017 and modified for IE8 compatibility. + Tests for this are in test/vendor. + + ISC license: https://github.com/isaacs/json-stringify-safe/blob/master/LICENSE +*/ + +exports = module.exports = stringify +exports.getSerialize = serializer + +function indexOf(haystack, needle) { + for (var i = 0; i < haystack.length; ++i) { + if (haystack[i] === needle) return i; + } + return -1; +} + +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) +} + +function serializer(replacer, cycleReplacer) { + var stack = [], keys = [] + + if (cycleReplacer == null) cycleReplacer = function(key, value) { + if (stack[0] === value) return '[Circular ~]' + return '[Circular ~.' + keys.slice(0, indexOf(stack, value)).join('.') + ']' + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = indexOf(stack, this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~indexOf(stack, value)) value = cycleReplacer.call(this, key, value) + } + else stack.push(value) + + return replacer == null ? value : replacer.call(this, key, value) + } +}