Skip to content

Commit 2fffd81

Browse files
committed
Merge pull request #456 from getsentry/show-dialog
Add Raven.showReportDialog (experimental)
2 parents 6d5a919 + fbe377e commit 2fffd81

File tree

4 files changed

+151
-22
lines changed

4 files changed

+151
-22
lines changed

example/index.html

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
<head>
44
<title>Scratch Disk</title>
55
</head>
6-
<script src="../vendor/TraceKit/tracekit.js"></script>
7-
<script src="../src/raven.js"></script>
6+
<script src="../dist/raven.js"></script>
87
<!-- <script src="scratch.min.js"></script> -->
98
<script src="scratch.js" crossorigin></script>
109
<script src="file.min.js" crossorigin></script>
@@ -14,7 +13,8 @@
1413
//awesome
1514
Raven.config('http://50dbe04cd1224d439e9c49bf1d0464df@localhost:8000/1', {
1615
whitelistUrls: [
17-
/localhost/
16+
/localhost/,
17+
/127\.0\.0\.1/
1818
],
1919
dataCallback: function(data) {
2020
console.log(data);
@@ -25,7 +25,8 @@
2525
Raven.setUserContext({
2626
2727
id: 5
28-
})
28+
});
29+
2930

3031
</script>
3132
<body>
@@ -36,6 +37,7 @@
3637
<button onclick="derp()">window.onerror</button>
3738
<button onclick="testOptions()">test options</button>
3839
<button onclick="throwString()">throw string</button>
40+
<button onclick="showDialog()">show dialog</button>
3941

4042
</body>
4143
</html>

example/scratch.js

+5
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ function testOptions() {
4040
function throwString() {
4141
throw 'oops';
4242
}
43+
44+
function showDialog() {
45+
broken();
46+
Raven.showReportDialog();
47+
}

src/raven.js

+55-10
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Raven.prototype = {
107107
});
108108
}
109109

110+
this._dsn = dsn;
111+
110112
// "Script error." is hard coded into browsers for errors that it can't read.
111113
// this is the result of a script being pulled in from an external domain and CORS.
112114
this._globalOptions.ignoreErrors.push(/^Script error\.?$/);
@@ -121,14 +123,10 @@ Raven.prototype = {
121123
this._globalKey = uri.user;
122124
this._globalProject = uri.path.substr(lastSlash + 1);
123125

124-
// assemble the endpoint from the uri pieces
125-
this._globalServer = '//' + uri.host +
126-
(uri.port ? ':' + uri.port : '') +
127-
'/' + path + 'api/' + this._globalProject + '/store/';
126+
this._globalServer = this._getGlobalServer(uri);
128127

129-
if (uri.protocol) {
130-
this._globalServer = uri.protocol + ':' + this._globalServer;
131-
}
128+
this._globalEndpoint = this._globalServer +
129+
'/' + path + 'api/' + this._globalProject + '/store/';
132130

133131
if (this._globalOptions.fetchContext) {
134132
TraceKit.remoteFetching = true;
@@ -498,6 +496,41 @@ Raven.prototype = {
498496
}
499497
},
500498

499+
showReportDialog: function (options) {
500+
if (!window.document) // doesn't work without a document (React native)
501+
return;
502+
503+
options = options || {};
504+
505+
var lastEventId = options.eventId || this.lastEventId();
506+
if (!lastEventId) {
507+
throw new RavenConfigError('Missing eventId');
508+
}
509+
510+
var dsn = options.dsn || this._dsn;
511+
if (!dsn) {
512+
throw new RavenConfigError('Missing DSN');
513+
}
514+
515+
var encode = encodeURIComponent;
516+
var qs = '';
517+
qs += '?eventId=' + encode(lastEventId);
518+
qs += '&dsn=' + encode(dsn);
519+
520+
var user = options.user || this._globalContext.user;
521+
if (user) {
522+
if (user.name) qs += '&name=' + encode(user.name);
523+
if (user.email) qs += '&email=' + encode(user.email);
524+
}
525+
526+
var globalServer = this._getGlobalServer(this._parseDSN(dsn));
527+
528+
var script = document.createElement('script');
529+
script.async = true;
530+
script.src = globalServer + '/api/embed/error-page/' + qs;
531+
(document.head || document.body).appendChild(script);
532+
},
533+
501534
/**** Private functions ****/
502535
_ignoreNextOnError: function () {
503536
var self = this;
@@ -683,6 +716,17 @@ Raven.prototype = {
683716
return dsn;
684717
},
685718

719+
_getGlobalServer: function(uri) {
720+
// assemble the endpoint from the uri pieces
721+
var globalServer = '//' + uri.host +
722+
(uri.port ? ':' + uri.port : '');
723+
724+
if (uri.protocol) {
725+
globalServer = uri.protocol + ':' + globalServer;
726+
}
727+
return globalServer;
728+
},
729+
686730
_handleOnErrorStackInfo: function() {
687731
// if we are intentionally ignoring errors via onerror, bail out
688732
if (!this._ignoreOnError) {
@@ -933,8 +977,9 @@ Raven.prototype = {
933977

934978
if (!this.isSetup()) return;
935979

980+
var url = this._globalEndpoint;
936981
(globalOptions.transport || this._makeRequest).call(this, {
937-
url: this._globalServer,
982+
url: url,
938983
auth: {
939984
sentry_version: '7',
940985
sentry_client: 'raven-js/' + this.VERSION,
@@ -945,13 +990,13 @@ Raven.prototype = {
945990
onSuccess: function success() {
946991
self._triggerEvent('success', {
947992
data: data,
948-
src: self._globalServer
993+
src: url
949994
});
950995
},
951996
onError: function failure() {
952997
self._triggerEvent('failure', {
953998
data: data,
954-
src: self._globalServer
999+
src: url
9551000
});
9561001
}
9571002
});

test/raven.test.js

+85-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
var proxyquire = require('proxyquireify')(require);
66

77
var TraceKit = require('../vendor/TraceKit/tracekit');
8+
89
var _Raven = proxyquire('../src/raven', {
910
'./utils': {
1011
// patched to return a predictable result
@@ -1013,7 +1014,7 @@ describe('globals', function() {
10131014
maxMessageLength: 100,
10141015
release: 'abc123',
10151016
};
1016-
Raven._globalServer = 'http://localhost/store/';
1017+
Raven._globalEndpoint = 'http://localhost/store/';
10171018
Raven._globalOptions = globalOptions;
10181019

10191020
Raven._send({message: 'bar'});
@@ -1226,7 +1227,7 @@ describe('globals', function() {
12261227

12271228
it('should populate crossOrigin based on options', function() {
12281229
Raven._makeImageRequest({
1229-
url: Raven._globalServer,
1230+
url: Raven._globalEndpoint,
12301231
auth: {lol: '1'},
12311232
data: {foo: 'bar'},
12321233
options: {
@@ -1239,7 +1240,7 @@ describe('globals', function() {
12391240

12401241
it('should populate crossOrigin if empty string', function() {
12411242
Raven._makeImageRequest({
1242-
url: Raven._globalServer,
1243+
url: Raven._globalEndpoint,
12431244
auth: {lol: '1'},
12441245
data: {foo: 'bar'},
12451246
options: {
@@ -1252,7 +1253,7 @@ describe('globals', function() {
12521253

12531254
it('should not populate crossOrigin if falsey', function() {
12541255
Raven._makeImageRequest({
1255-
url: Raven._globalServer,
1256+
url: Raven._globalEndpoint,
12561257
auth: {lol: '1'},
12571258
data: {foo: 'bar'},
12581259
options: {
@@ -1487,7 +1488,7 @@ describe('Raven (public API)', function() {
14871488
Raven.afterLoad();
14881489

14891490
assert.equal(Raven._globalKey, 'random');
1490-
assert.equal(Raven._globalServer, 'http://some.other.server:80/api/2/store/');
1491+
assert.equal(Raven._globalEndpoint, 'http://some.other.server:80/api/2/store/');
14911492

14921493
assert.equal(Raven._globalOptions.some, 'config');
14931494
assert.equal(Raven._globalProject, '2');
@@ -1504,7 +1505,7 @@ describe('Raven (public API)', function() {
15041505
assert.equal(Raven, Raven.config(SENTRY_DSN, {foo: 'bar'}), 'it should return Raven');
15051506

15061507
assert.equal(Raven._globalKey, 'abc');
1507-
assert.equal(Raven._globalServer, 'http://example.com:80/api/2/store/');
1508+
assert.equal(Raven._globalEndpoint, 'http://example.com:80/api/2/store/');
15081509
assert.equal(Raven._globalOptions.foo, 'bar');
15091510
assert.equal(Raven._globalProject, '2');
15101511
assert.isTrue(Raven.isSetup());
@@ -1514,15 +1515,15 @@ describe('Raven (public API)', function() {
15141515
Raven.config('//[email protected]/2');
15151516

15161517
assert.equal(Raven._globalKey, 'abc');
1517-
assert.equal(Raven._globalServer, '//example.com/api/2/store/');
1518+
assert.equal(Raven._globalEndpoint, '//example.com/api/2/store/');
15181519
assert.equal(Raven._globalProject, '2');
15191520
assert.isTrue(Raven.isSetup());
15201521
});
15211522

15221523
it('should work should work at a non root path', function() {
15231524
Raven.config('//[email protected]/sentry/2');
15241525
assert.equal(Raven._globalKey, 'abc');
1525-
assert.equal(Raven._globalServer, '//example.com/sentry/api/2/store/');
1526+
assert.equal(Raven._globalEndpoint, '//example.com/sentry/api/2/store/');
15261527
assert.equal(Raven._globalProject, '2');
15271528
assert.isTrue(Raven.isSetup());
15281529
});
@@ -2028,4 +2029,80 @@ describe('Raven (public API)', function() {
20282029
assert.isFalse(Raven.isSetup());
20292030
});
20302031
});
2032+
2033+
describe('.showReportDialog', function () {
2034+
it('should throw a RavenConfigError if no eventId', function () {
2035+
assert.throws(function () {
2036+
Raven.showReportDialog({
2037+
dsn: SENTRY_DSN // dsn specified via options
2038+
});
2039+
}, 'Missing eventId');
2040+
2041+
Raven.config(SENTRY_DSN);
2042+
assert.throws(function () {
2043+
Raven.showReportDialog(); // dsn specified via Raven.config
2044+
}, 'Missing eventId');
2045+
});
2046+
2047+
it('should throw a RavenConfigError if no dsn', function () {
2048+
assert.throws(function () {
2049+
Raven.showReportDialog({
2050+
eventId: 'abc123'
2051+
});
2052+
}, 'Missing DSN');
2053+
});
2054+
2055+
describe('script tag insertion', function () {
2056+
beforeEach(function () {
2057+
this.appendChildStub = this.sinon.stub(document.head, 'appendChild');
2058+
});
2059+
2060+
it('should specify embed API endpoint and basic query string (DSN, eventId)', function () {
2061+
Raven.showReportDialog({
2062+
eventId: 'abc123',
2063+
dsn: SENTRY_DSN
2064+
});
2065+
2066+
var script = this.appendChildStub.getCall(0).args[0];
2067+
assert.equal(script.src, 'http://example.com/api/embed/error-page/?eventId=abc123&dsn=http%3A%2F%2Fabc%40example.com%3A80%2F2');
2068+
2069+
this.appendChildStub.reset();
2070+
2071+
Raven
2072+
.config(SENTRY_DSN)
2073+
.captureException(new Error('foo')) // generates lastEventId
2074+
.showReportDialog();
2075+
2076+
this.appendChildStub.getCall(0).args[0];
2077+
assert.equal(script.src, 'http://example.com/api/embed/error-page/?eventId=abc123&dsn=http%3A%2F%2Fabc%40example.com%3A80%2F2');
2078+
});
2079+
2080+
it('should specify embed API endpoint and full query string (DSN, eventId, user)', function () {
2081+
Raven.showReportDialog({
2082+
eventId: 'abc123',
2083+
dsn: SENTRY_DSN,
2084+
user: {
2085+
name: 'Average Normalperson',
2086+
2087+
}
2088+
});
2089+
2090+
var script = this.appendChildStub.getCall(0).args[0];
2091+
assert.equal(script.src, 'http://example.com/api/embed/error-page/?eventId=abc123&dsn=http%3A%2F%2Fabc%40example.com%3A80%2F2&name=Average%20Normalperson&email=an%40example.com');
2092+
2093+
this.appendChildStub.reset();
2094+
Raven
2095+
.config(SENTRY_DSN)
2096+
.captureException(new Error('foo')) // generates lastEventId
2097+
.setUserContext({
2098+
name: 'Average Normalperson 2',
2099+
2100+
})
2101+
.showReportDialog();
2102+
2103+
var script = this.appendChildStub.getCall(0).args[0];
2104+
assert.equal(script.src, 'http://example.com/api/embed/error-page/?eventId=abc123&dsn=http%3A%2F%2Fabc%40example.com%3A80%2F2&name=Average%20Normalperson%202&email=an2%40example.com');
2105+
});
2106+
});
2107+
});
20312108
});

0 commit comments

Comments
 (0)