From 26e7a8318ae51049660982d309746e3675ca7c74 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Thu, 7 Jan 2016 11:11:13 -0800 Subject: [PATCH 1/2] Add Raven.showReportDialog (experimental) --- example/index.html | 10 ++++++---- example/scratch.js | 5 +++++ src/raven.js | 43 ++++++++++++++++++++++++++++++++++++++----- test/raven.test.js | 16 ++++++++-------- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/example/index.html b/example/index.html index b7ebbdd018eb..f6d6823179d4 100644 --- a/example/index.html +++ b/example/index.html @@ -3,8 +3,7 @@ Scratch Disk - - + @@ -14,7 +13,8 @@ //awesome Raven.config('http://50dbe04cd1224d439e9c49bf1d0464df@localhost:8000/1', { whitelistUrls: [ - /localhost/ + /localhost/, + /127\.0\.0\.1/ ], dataCallback: function(data) { console.log(data); @@ -25,7 +25,8 @@ Raven.setUserContext({ email: 'matt@ydekproductions.com', id: 5 -}) +}); + @@ -36,6 +37,7 @@ + diff --git a/example/scratch.js b/example/scratch.js index f1962bbdfc18..9f1fa27dbcd9 100644 --- a/example/scratch.js +++ b/example/scratch.js @@ -40,3 +40,8 @@ function testOptions() { function throwString() { throw 'oops'; } + +function showDialog() { + broken(); + Raven.showReportDialog(); +} diff --git a/src/raven.js b/src/raven.js index cf52cc9c71dd..b792179a3ee6 100644 --- a/src/raven.js +++ b/src/raven.js @@ -107,6 +107,8 @@ Raven.prototype = { }); } + this._dsn = dsn; + // "Script error." is hard coded into browsers for errors that it can't read. // this is the result of a script being pulled in from an external domain and CORS. this._globalOptions.ignoreErrors.push(/^Script error\.?$/); @@ -123,12 +125,13 @@ Raven.prototype = { // assemble the endpoint from the uri pieces this._globalServer = '//' + uri.host + - (uri.port ? ':' + uri.port : '') + - '/' + path + 'api/' + this._globalProject + '/store/'; + (uri.port ? ':' + uri.port : ''); if (uri.protocol) { this._globalServer = uri.protocol + ':' + this._globalServer; } + this._globalEndpoint = this._globalServer + + '/' + path + 'api/' + this._globalProject + '/store/'; if (this._globalOptions.fetchContext) { TraceKit.remoteFetching = true; @@ -498,6 +501,35 @@ Raven.prototype = { } }, + showReportDialog: function (options) { + if (!window.document) // doesn't work without a document (React native) + return; + + options = options || {}; + + var lastEventId = options.eventId || this.lastEventId(); + if (!lastEventId) + return; + + var encode = encodeURIComponent; + var qs = ''; + qs += '?eventId=' + encode(lastEventId); + qs += '&dsn=' + encode(this._dsn || ''); + + var user = this._globalContext.user; + if (user) { + if (user.name) + qs += '&name=' + encode(user.name); + if (user.email) + qs += '&email=' + encode(user.email); + } + + var script = document.createElement('script'); + script.async = true; + script.src = this._globalServer + '/api/embed/error-page/' + qs; + document.getElementsByTagName('body')[0].appendChild(script); + }, + /**** Private functions ****/ _ignoreNextOnError: function () { var self = this; @@ -933,8 +965,9 @@ Raven.prototype = { if (!this.isSetup()) return; + var url = this._globalEndpoint; (globalOptions.transport || this._makeRequest).call(this, { - url: this._globalServer, + url: url, auth: { sentry_version: '7', sentry_client: 'raven-js/' + this.VERSION, @@ -945,13 +978,13 @@ Raven.prototype = { onSuccess: function success() { self._triggerEvent('success', { data: data, - src: self._globalServer + src: url }); }, onError: function failure() { self._triggerEvent('failure', { data: data, - src: self._globalServer + src: url }); } }); diff --git a/test/raven.test.js b/test/raven.test.js index 64c605786be2..9ce3514819ac 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -1013,7 +1013,7 @@ describe('globals', function() { maxMessageLength: 100, release: 'abc123', }; - Raven._globalServer = 'http://localhost/store/'; + Raven._globalEndpoint = 'http://localhost/store/'; Raven._globalOptions = globalOptions; Raven._send({message: 'bar'}); @@ -1226,7 +1226,7 @@ describe('globals', function() { it('should populate crossOrigin based on options', function() { Raven._makeImageRequest({ - url: Raven._globalServer, + url: Raven._globalEndpoint, auth: {lol: '1'}, data: {foo: 'bar'}, options: { @@ -1239,7 +1239,7 @@ describe('globals', function() { it('should populate crossOrigin if empty string', function() { Raven._makeImageRequest({ - url: Raven._globalServer, + url: Raven._globalEndpoint, auth: {lol: '1'}, data: {foo: 'bar'}, options: { @@ -1252,7 +1252,7 @@ describe('globals', function() { it('should not populate crossOrigin if falsey', function() { Raven._makeImageRequest({ - url: Raven._globalServer, + url: Raven._globalEndpoint, auth: {lol: '1'}, data: {foo: 'bar'}, options: { @@ -1487,7 +1487,7 @@ describe('Raven (public API)', function() { Raven.afterLoad(); assert.equal(Raven._globalKey, 'random'); - assert.equal(Raven._globalServer, 'http://some.other.server:80/api/2/store/'); + assert.equal(Raven._globalEndpoint, 'http://some.other.server:80/api/2/store/'); assert.equal(Raven._globalOptions.some, 'config'); assert.equal(Raven._globalProject, '2'); @@ -1504,7 +1504,7 @@ describe('Raven (public API)', function() { assert.equal(Raven, Raven.config(SENTRY_DSN, {foo: 'bar'}), 'it should return Raven'); assert.equal(Raven._globalKey, 'abc'); - assert.equal(Raven._globalServer, 'http://example.com:80/api/2/store/'); + assert.equal(Raven._globalEndpoint, 'http://example.com:80/api/2/store/'); assert.equal(Raven._globalOptions.foo, 'bar'); assert.equal(Raven._globalProject, '2'); assert.isTrue(Raven.isSetup()); @@ -1514,7 +1514,7 @@ describe('Raven (public API)', function() { Raven.config('//abc@example.com/2'); assert.equal(Raven._globalKey, 'abc'); - assert.equal(Raven._globalServer, '//example.com/api/2/store/'); + assert.equal(Raven._globalEndpoint, '//example.com/api/2/store/'); assert.equal(Raven._globalProject, '2'); assert.isTrue(Raven.isSetup()); }); @@ -1522,7 +1522,7 @@ describe('Raven (public API)', function() { it('should work should work at a non root path', function() { Raven.config('//abc@example.com/sentry/2'); assert.equal(Raven._globalKey, 'abc'); - assert.equal(Raven._globalServer, '//example.com/sentry/api/2/store/'); + assert.equal(Raven._globalEndpoint, '//example.com/sentry/api/2/store/'); assert.equal(Raven._globalProject, '2'); assert.isTrue(Raven.isSetup()); }); From fbe377ef45e9937db0d0706fd793d59204426134 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Thu, 7 Jan 2016 16:32:36 -0800 Subject: [PATCH 2/2] Can pass dsn to `showReportDialog`, add tests --- src/raven.js | 44 ++++++++++++++++---------- test/raven.test.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 16 deletions(-) diff --git a/src/raven.js b/src/raven.js index b792179a3ee6..7cbbdc6e7cc3 100644 --- a/src/raven.js +++ b/src/raven.js @@ -123,13 +123,8 @@ Raven.prototype = { this._globalKey = uri.user; this._globalProject = uri.path.substr(lastSlash + 1); - // assemble the endpoint from the uri pieces - this._globalServer = '//' + uri.host + - (uri.port ? ':' + uri.port : ''); + this._globalServer = this._getGlobalServer(uri); - if (uri.protocol) { - this._globalServer = uri.protocol + ':' + this._globalServer; - } this._globalEndpoint = this._globalServer + '/' + path + 'api/' + this._globalProject + '/store/'; @@ -508,26 +503,32 @@ Raven.prototype = { options = options || {}; var lastEventId = options.eventId || this.lastEventId(); - if (!lastEventId) - return; + if (!lastEventId) { + throw new RavenConfigError('Missing eventId'); + } + + var dsn = options.dsn || this._dsn; + if (!dsn) { + throw new RavenConfigError('Missing DSN'); + } var encode = encodeURIComponent; var qs = ''; qs += '?eventId=' + encode(lastEventId); - qs += '&dsn=' + encode(this._dsn || ''); + qs += '&dsn=' + encode(dsn); - var user = this._globalContext.user; + var user = options.user || this._globalContext.user; if (user) { - if (user.name) - qs += '&name=' + encode(user.name); - if (user.email) - qs += '&email=' + encode(user.email); + if (user.name) qs += '&name=' + encode(user.name); + if (user.email) qs += '&email=' + encode(user.email); } + var globalServer = this._getGlobalServer(this._parseDSN(dsn)); + var script = document.createElement('script'); script.async = true; - script.src = this._globalServer + '/api/embed/error-page/' + qs; - document.getElementsByTagName('body')[0].appendChild(script); + script.src = globalServer + '/api/embed/error-page/' + qs; + (document.head || document.body).appendChild(script); }, /**** Private functions ****/ @@ -715,6 +716,17 @@ Raven.prototype = { return dsn; }, + _getGlobalServer: function(uri) { + // assemble the endpoint from the uri pieces + var globalServer = '//' + uri.host + + (uri.port ? ':' + uri.port : ''); + + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; + } + return globalServer; + }, + _handleOnErrorStackInfo: function() { // if we are intentionally ignoring errors via onerror, bail out if (!this._ignoreOnError) { diff --git a/test/raven.test.js b/test/raven.test.js index 9ce3514819ac..ba2bf7036771 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -5,6 +5,7 @@ var proxyquire = require('proxyquireify')(require); var TraceKit = require('../vendor/TraceKit/tracekit'); + var _Raven = proxyquire('../src/raven', { './utils': { // patched to return a predictable result @@ -2028,4 +2029,80 @@ describe('Raven (public API)', function() { assert.isFalse(Raven.isSetup()); }); }); + + describe('.showReportDialog', function () { + it('should throw a RavenConfigError if no eventId', function () { + assert.throws(function () { + Raven.showReportDialog({ + dsn: SENTRY_DSN // dsn specified via options + }); + }, 'Missing eventId'); + + Raven.config(SENTRY_DSN); + assert.throws(function () { + Raven.showReportDialog(); // dsn specified via Raven.config + }, 'Missing eventId'); + }); + + it('should throw a RavenConfigError if no dsn', function () { + assert.throws(function () { + Raven.showReportDialog({ + eventId: 'abc123' + }); + }, 'Missing DSN'); + }); + + describe('script tag insertion', function () { + beforeEach(function () { + this.appendChildStub = this.sinon.stub(document.head, 'appendChild'); + }); + + it('should specify embed API endpoint and basic query string (DSN, eventId)', function () { + Raven.showReportDialog({ + eventId: 'abc123', + dsn: SENTRY_DSN + }); + + var script = this.appendChildStub.getCall(0).args[0]; + assert.equal(script.src, 'http://example.com/api/embed/error-page/?eventId=abc123&dsn=http%3A%2F%2Fabc%40example.com%3A80%2F2'); + + this.appendChildStub.reset(); + + Raven + .config(SENTRY_DSN) + .captureException(new Error('foo')) // generates lastEventId + .showReportDialog(); + + this.appendChildStub.getCall(0).args[0]; + assert.equal(script.src, 'http://example.com/api/embed/error-page/?eventId=abc123&dsn=http%3A%2F%2Fabc%40example.com%3A80%2F2'); + }); + + it('should specify embed API endpoint and full query string (DSN, eventId, user)', function () { + Raven.showReportDialog({ + eventId: 'abc123', + dsn: SENTRY_DSN, + user: { + name: 'Average Normalperson', + email: 'an@example.com' + } + }); + + var script = this.appendChildStub.getCall(0).args[0]; + 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'); + + this.appendChildStub.reset(); + Raven + .config(SENTRY_DSN) + .captureException(new Error('foo')) // generates lastEventId + .setUserContext({ + name: 'Average Normalperson 2', + email: 'an2@example.com' + }) + .showReportDialog(); + + var script = this.appendChildStub.getCall(0).args[0]; + 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'); + }); + }); + }); });