Commit 27e3c96c authored by Robert Knight's avatar Robert Knight

Use raven-js for client-side error capture and reporting

Capture exceptions reported via Angular's $exceptionHandler
service and window.onerror and report them via Raven.

The client currently uses the same Sentry DSN as the main
application, which gets the DSN from the SENTRY_DSN
environment variable.

 * Add a <script> tag to all pages on the site which
   defines a window.RAVEN_CONFIG variable which provides the DSN
   and release string needed to configure Raven JS on the client

 * Configure and setup RavenJS in the main site JS, Chrome
   extension and the sidebar.

   For the sidebar, Angular integration is configured
   using Raven's Angular plugin.

 * Configure the user ID associated with Sentry reports
   when the session state is received

 * Extract the code which builds the sidebar/extension
   config dictionary out of the template into
   its own module for easier sharing between the Chrome
   extension and the sidebar's code.
parent 07bc28fd
# initialize Raven. This is required at the top of this file
# so that it happens early in the app's startup flow
if window.RAVEN_CONFIG
require('./raven').init(window.RAVEN_CONFIG)
require('autofill-event') require('autofill-event')
baseURI = require('document-base-uri') baseURI = require('document-base-uri')
angular = require('angular') angular = require('angular')
...@@ -79,6 +85,7 @@ setupHttp = ['$http', ($http) -> ...@@ -79,6 +85,7 @@ setupHttp = ['$http', ($http) ->
setupHost = ['host', (host) -> ] setupHost = ['host', (host) -> ]
module.exports = angular.module('h', [ module.exports = angular.module('h', [
require('./raven').angularModule().name
'angulartics' 'angulartics'
'angulartics.google.analytics' 'angulartics.google.analytics'
'angular-jwt' 'angular-jwt'
...@@ -159,6 +166,7 @@ module.exports = angular.module('h', [ ...@@ -159,6 +166,7 @@ module.exports = angular.module('h', [
.value('AnnotationSync', require('./annotation-sync')) .value('AnnotationSync', require('./annotation-sync'))
.value('AnnotationUISync', require('./annotation-ui-sync')) .value('AnnotationUISync', require('./annotation-ui-sync'))
.value('Discovery', require('./discovery')) .value('Discovery', require('./discovery'))
.value('raven', require('./raven'))
.config(configureDocument) .config(configureDocument)
.config(configureLocation) .config(configureLocation)
......
/**
* This module configures Raven for reporting crashes
* to Sentry.
*
* Logging requires the Sentry DSN and Hypothesis
* version to be provided via the app's settings object.
*
* It also exports an Angular module via angularModule() which integrates
* error logging into any Angular application that it is added to
* as a dependency.
*/
var Raven = require('raven-js')
var enabled = false;
function init(config) {
Raven.config(config.dsn, {
release: config.release,
}).install();
enabled = true;
}
function setUserInfo(info) {
if (info) {
Raven.setUserContext(info);
} else {
Raven.setUserContext();
}
}
/**
* Initializes and returns the Angular module which provides
* a custom wrapper around Angular's $exceptionHandler service,
* logging any exceptions passed to it using Sentry
*/
function angularModule() {
if (enabled) {
if (window.angular) {
var angularPlugin = require('raven-js/plugins/angular');
angularPlugin(Raven, angular);
}
} else {
// define a stub module in environments where Raven is not enabled
angular.module('ngRaven', []);
}
return angular.module('ngRaven');
}
module.exports = {
init: init,
angularModule: angularModule,
setUserInfo: setUserInfo,
};
...@@ -62,7 +62,7 @@ function sessionActions(options) { ...@@ -62,7 +62,7 @@ function sessionActions(options) {
* *
* @ngInject * @ngInject
*/ */
function session($document, $http, $resource, $rootScope, flash) { function session($document, $http, $resource, $rootScope, flash, raven) {
// Headers sent by every request made by the session service. // Headers sent by every request made by the session service.
var headers = {}; var headers = {};
// TODO: Move accounts data management (e.g. profile, edit_profile, // TODO: Move accounts data management (e.g. profile, edit_profile,
...@@ -143,7 +143,17 @@ function session($document, $http, $resource, $rootScope, flash) { ...@@ -143,7 +143,17 @@ function session($document, $http, $resource, $rootScope, flash) {
$rootScope.$broadcast(events.USER_CHANGED, { $rootScope.$broadcast(events.USER_CHANGED, {
initialLoad: isInitialLoad, initialLoad: isInitialLoad,
}); });
// associate error reports with the current user in Sentry
if (resource.state.userid) {
raven.setUserInfo({
id: resource.state.userid,
})
} else {
raven.setUserInfo(undefined);
}
} }
if (groupsChanged) { if (groupsChanged) {
$rootScope.$broadcast(events.GROUPS_CHANGED, { $rootScope.$broadcast(events.GROUPS_CHANGED, {
initialLoad: isInitialLoad, initialLoad: isInitialLoad,
......
'use strict'; 'use strict';
var angular = require('angular');
/** /**
* @ngdoc factory * @ngdoc factory
* @name settings * @name settings
...@@ -17,7 +15,7 @@ function settings($document) { ...@@ -17,7 +15,7 @@ function settings($document) {
'script[type="application/json"]#hypothesis-settings'); 'script[type="application/json"]#hypothesis-settings');
if (settingsElement) { if (settingsElement) {
return angular.fromJson(settingsElement.textContent); return JSON.parse(settingsElement.textContent);
} }
return {}; return {};
......
// configure error reporting
if (window.RAVEN_CONFIG) {
require('./raven').init(window.RAVEN_CONFIG);
}
var CreateGroupFormController = require('./create-group-form'); var CreateGroupFormController = require('./create-group-form');
var DropdownMenuController = require('./dropdown-menu'); var DropdownMenuController = require('./dropdown-menu');
var InstallerController = require('./installer-controller'); var InstallerController = require('./installer-controller');
......
...@@ -9,6 +9,7 @@ describe('h:session', function () { ...@@ -9,6 +9,7 @@ describe('h:session', function () {
var $rootScope; var $rootScope;
var fakeFlash; var fakeFlash;
var fakeRaven;
var fakeXsrf; var fakeXsrf;
var sandbox; var sandbox;
var session; var session;
...@@ -28,9 +29,13 @@ describe('h:session', function () { ...@@ -28,9 +29,13 @@ describe('h:session', function () {
}; };
fakeDocument.prop.withArgs('baseURI').returns('http://foo.com/'); fakeDocument.prop.withArgs('baseURI').returns('http://foo.com/');
fakeFlash = {error: sandbox.spy()}; fakeFlash = {error: sandbox.spy()};
fakeRaven = {
setUserInfo: sandbox.spy(),
};
$provide.value('$document', fakeDocument); $provide.value('$document', fakeDocument);
$provide.value('flash', fakeFlash); $provide.value('flash', fakeFlash);
$provide.value('raven', fakeRaven);
})); }));
...@@ -48,7 +53,7 @@ describe('h:session', function () { ...@@ -48,7 +53,7 @@ describe('h:session', function () {
// There's little point testing every single route here, as they're // There's little point testing every single route here, as they're
// declarative and ultimately we'd be testing ngResource. // declarative and ultimately we'd be testing ngResource.
describe('#login()', function () { describe('.login()', function () {
var url = 'http://foo.com/app?__formid__=login'; var url = 'http://foo.com/app?__formid__=login';
it('should send an HTTP POST to the action', function () { it('should send an HTTP POST to the action', function () {
...@@ -127,7 +132,7 @@ describe('h:session', function () { ...@@ -127,7 +132,7 @@ describe('h:session', function () {
}); });
}); });
describe('#load()', function () { describe('.load()', function () {
var url = 'http://foo.com/app'; var url = 'http://foo.com/app';
it('should fetch the session data', function () { it('should fetch the session data', function () {
...@@ -157,7 +162,7 @@ describe('h:session', function () { ...@@ -157,7 +162,7 @@ describe('h:session', function () {
}); });
}); });
describe('#update()', function () { describe('.update()', function () {
it('broadcasts SESSION_CHANGED when the session changes', function () { it('broadcasts SESSION_CHANGED when the session changes', function () {
var sessionChangeCallback = sinon.stub(); var sessionChangeCallback = sinon.stub();
...@@ -206,5 +211,15 @@ describe('h:session', function () { ...@@ -206,5 +211,15 @@ describe('h:session', function () {
}); });
assert.calledOnce(userChangeCallback); assert.calledOnce(userChangeCallback);
}); });
it('updates the user ID for Sentry error reports', function () {
session.update({
userid: 'anne',
csrf: 'dummytoken',
});
assert.calledWith(fakeRaven.setUserInfo, {
id: 'anne',
});
});
}); });
}); });
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
"node-uuid": "^1.4.3", "node-uuid": "^1.4.3",
"postcss": "^5.0.6", "postcss": "^5.0.6",
"raf": "^3.1.0", "raf": "^3.1.0",
"raven-js": "^2.0.2",
"retry": "^0.8.0", "retry": "^0.8.0",
"scroll-into-view": "^1.3.1", "scroll-into-view": "^1.3.1",
"showdown": "^1.2.1", "showdown": "^1.2.1",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment