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)
+ }
+}