Skip to content

Vendor+IE8-ify json-stringify-safe, fix handler with XDomainRequest #883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
<button onclick="testSynthetic()">test synthetic</button>
<button onclick="throwString()">throw string</button>
<button onclick="showDialog()">show dialog</button>
<button onclick="captureMessage()">capture message</button>
<button onclick="captureException()">capture exception</button>
<button onclick="blobExample()">blob example</button>
<input/>

Expand Down
8 changes: 8 additions & 0 deletions example/scratch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down
32 changes: 17 additions & 15 deletions src/raven.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(' '),
Expand Down Expand Up @@ -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();
Expand All @@ -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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quickly experimented w/ having a helper onerror function that could be called from either the XDomainRequest handler or onreadystatechange, but it quickly ended up taking up more code so nm ...

err.request = request;
opts.onError(err);
}
}
}

// NOTE: auth is intentionally sent as part of query string (NOT as custom
Expand Down
248 changes: 248 additions & 0 deletions test/vendor/json-stringify-safe.test.js
Original file line number Diff line number Diff line change
@@ -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
}
47 changes: 47 additions & 0 deletions vendor/json-stringify-safe/stringify.js
Original file line number Diff line number Diff line change
@@ -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)
}
}