├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Makefile ├── bower.json ├── dist ├── angular-bugsnag.js └── angular-bugsnag.min.js ├── gulpfile.js ├── karma.conf.js ├── package.json ├── readme.md ├── src ├── .jshintrc └── scripts │ └── angular-bugsnag.js └── test ├── app ├── index.html ├── scripts │ └── app.js └── styles │ └── main.scss └── specs ├── .jshintrc └── angular-bugsnag-spec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "test/app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .idea 3 | node_modules 4 | test/.tmp 5 | test/app/bower_components 6 | reports 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - npm install gulp -g 6 | - npm install bower -g 7 | - bower install 8 | after_script: 9 | - npm run coveralls 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=$(grep 'version' package.json | sed 's/.*\"\(.*\)\".*/\1/') 2 | 3 | install: 4 | npm install # Install node modules 5 | bower install # Install bower components 6 | bower update # Update bower components 7 | gulp build # Build client app 8 | 9 | release: 10 | gulp bump 11 | make install 12 | git add dist bower.json package.json 13 | git commit -m "Bumped version to $(value VERSION)" 14 | git tag -a $(value VERSION) -m "v$(value VERSION)" 15 | 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bugsnag", 3 | "version": "0.2.1", 4 | "author": "Luke Bunselmeyer ", 5 | "homepage": "https://github.com/wmluke/angular-bugsnag", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/wmluke/angular-bugsnag.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/wmluke/angular-bugsnag/issues" 12 | }, 13 | "main": [ 14 | "dist/angular-bugsnag.js" 15 | ], 16 | "ignore": [ 17 | "test", 18 | "src/.jshintrc", 19 | ".jshintrc", 20 | ".travis.yml", 21 | "karma.conf.js", 22 | "Gruntfile.js", 23 | "Makefile" 24 | ], 25 | "dependencies": { 26 | "angular": "1.x", 27 | "bugsnag": "3.x" 28 | }, 29 | "devDependencies": { 30 | "jquery": "~2.1.1", 31 | "bootstrap-sass": "~3.0.2", 32 | "angular": "=1.3.13", 33 | "angular-mocks": "=1.3.13" 34 | }, 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /dist/angular-bugsnag.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * @license angular-bugsnag v0.2.1 3 | * Copyright (c) 2013 Luke Bunselmeyer . https://github.com/wmluke/angular-bugsnag 4 | * License: MIT 5 | */ 6 | (function () { 7 | 'use strict'; 8 | 9 | var _bugsnag; 10 | 11 | angular.module('angular-bugsnag', []) 12 | .config(['$provide', function ($provide) { 13 | $provide.provider({ 14 | bugsnag: function () { 15 | 16 | // if a script blocker blocks the bugsnag library Bugsnag will be undefined at this point, so we initialize it to an object 17 | // with methods that do nothing but are declared and won't throw errors later by the angular-bugsnag 18 | // module calling them 19 | var Bugsnag = window.Bugsnag || { 20 | notifyException: function () {}, 21 | notify: function () {}, 22 | noConflict: function () {} 23 | }; 24 | 25 | _bugsnag = Bugsnag; 26 | var _self = this; 27 | var _beforeNotify; 28 | 29 | this.noConflict = function () { 30 | _bugsnag = Bugsnag.noConflict(); 31 | return _self; 32 | }; 33 | 34 | this.apiKey = function (apiKey) { 35 | _bugsnag.apiKey = apiKey; 36 | return _self; 37 | }; 38 | 39 | this.releaseStage = function (releaseStage) { 40 | _bugsnag.releaseStage = releaseStage; 41 | return _self; 42 | }; 43 | 44 | this.notifyReleaseStages = function (notifyReleaseStages) { 45 | _bugsnag.notifyReleaseStages = notifyReleaseStages; 46 | return _self; 47 | }; 48 | 49 | this.appVersion = function (appVersion) { 50 | _bugsnag.appVersion = appVersion; 51 | return _self; 52 | }; 53 | 54 | this.user = function (user) { 55 | _bugsnag.user = user; 56 | return _self; 57 | }; 58 | 59 | this.projectRoot = function (projectRoot) { 60 | _bugsnag.projectRoot = projectRoot; 61 | return _self; 62 | }; 63 | 64 | this.endpoint = function (endpoint) { 65 | _bugsnag.endpoint = endpoint; 66 | return _self; 67 | }; 68 | 69 | this.metaData = function (metaData) { 70 | _bugsnag.metaData = metaData; 71 | return _self; 72 | }; 73 | 74 | this.autoNotify = function (autoNotify) { 75 | _bugsnag.autoNotify = autoNotify; 76 | return _self; 77 | }; 78 | 79 | this.beforeNotify = function (beforeNotify) { 80 | _beforeNotify = beforeNotify; 81 | return _self; 82 | }; 83 | 84 | this._testRequest = function (testRequest) { 85 | _bugsnag.testRequest = testRequest; 86 | return _self; 87 | }; 88 | 89 | this.$get = ['$injector', function ($injector) { 90 | if (_beforeNotify) { 91 | _bugsnag.beforeNotify = angular.isString(_beforeNotify) ? $injector.get(_beforeNotify) : $injector.invoke(_beforeNotify); 92 | } 93 | return _bugsnag; 94 | }]; 95 | 96 | }, 97 | $exceptionHandler: function () { 98 | this.$get = ['$log', 'bugsnag', function ($log, bugsnag) { 99 | return function (exception, cause) { 100 | $log.error.apply($log, arguments); 101 | try { 102 | bugsnag.fixContext(); 103 | if (angular.isString(exception)) { 104 | bugsnag.notify(exception); 105 | } else { 106 | bugsnag.notifyException(exception); 107 | } 108 | } catch (e) { 109 | $log.error(e); 110 | } 111 | }; 112 | }]; 113 | } 114 | }); 115 | }]) 116 | .run(['bugsnag', '$location', '$rootScope', function (bugsnag, $location, $rootScope) { 117 | // Set the context from $location.url(). We cannot do this above b/c injecting $location creates a circular dependency. 118 | bugsnag.fixContext = function () { 119 | bugsnag.context = $location.url(); 120 | }; 121 | // refresh the rate-limit 122 | $rootScope.$on('$locationChangeSuccess', bugsnag.refresh || angular.noop); 123 | }]); 124 | }()); 125 | -------------------------------------------------------------------------------- /dist/angular-bugsnag.min.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * @license angular-bugsnag v0.2.1 3 | * Copyright (c) 2013 Luke Bunselmeyer . https://github.com/wmluke/angular-bugsnag 4 | * License: MIT 5 | */ 6 | !function(){"use strict";var t;angular.module("angular-bugsnag",[]).config(["$provide",function(n){n.provider({bugsnag:function(){var n=window.Bugsnag||{notifyException:function(){},notify:function(){},noConflict:function(){}};t=n;var o,e=this;this.noConflict=function(){return t=n.noConflict(),e},this.apiKey=function(n){return t.apiKey=n,e},this.releaseStage=function(n){return t.releaseStage=n,e},this.notifyReleaseStages=function(n){return t.notifyReleaseStages=n,e},this.appVersion=function(n){return t.appVersion=n,e},this.user=function(n){return t.user=n,e},this.projectRoot=function(n){return t.projectRoot=n,e},this.endpoint=function(n){return t.endpoint=n,e},this.metaData=function(n){return t.metaData=n,e},this.autoNotify=function(n){return t.autoNotify=n,e},this.beforeNotify=function(t){return o=t,e},this._testRequest=function(n){return t.testRequest=n,e},this.$get=["$injector",function(n){return o&&(t.beforeNotify=angular.isString(o)?n.get(o):n.invoke(o)),t}]},$exceptionHandler:function(){this.$get=["$log","bugsnag",function(t,n){return function(o,e){t.error.apply(t,arguments);try{n.fixContext(),angular.isString(o)?n.notify(o):n.notifyException(o)}catch(i){t.error(i)}}}]}})}]).run(["bugsnag","$location","$rootScope",function(t,n,o){t.fixContext=function(){t.context=n.url()},o.$on("$locationChangeSuccess",t.refresh||angular.noop)}])}(); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var gutil = require('gulp-util'); 5 | var karma = require('karma').server; 6 | var path = require('path'); 7 | var $ = require('gulp-load-plugins')(); 8 | var pkg = require('./bower.json'); 9 | var serveStatic = require('serve-static'); 10 | var serveIndex = require('serve-index'); 11 | 12 | 13 | var folders = { 14 | bower: 'test/app/bower_components', 15 | src: 'src', 16 | dist: 'dist', 17 | app: 'test/app', 18 | tmp: 'test/.tmp' 19 | }; 20 | 21 | var files = { 22 | src: { 23 | scripts: [ 24 | 'src/*.js', 25 | 'src/**/*.js' 26 | ] 27 | }, 28 | test: { 29 | specs: [ 30 | 'test/specs/*-spec.js', 31 | 'test/specs/**/*-spec.js' 32 | ] 33 | }, 34 | app: { 35 | watch: [ 36 | 'test/app/*.html', 37 | 'test/app/views/*.html', 38 | 'test/app/views/**/*.html', 39 | 'test/app/scripts/*.js', 40 | 'test/app/scripts/**/*.js', 41 | 'test/app/styles/*.css', 42 | 'test/app/styles/**/*.css', 43 | 'test/.tmp/styles/*.css', 44 | 'test/.tmp/styles/**/*.css' 45 | ], 46 | sass: [ 47 | 'test/app/styles/**/*.scss' 48 | ] 49 | } 50 | }; 51 | 52 | var banner = [ 53 | '/**! ', 54 | ' * @license <%= pkg.name %> v<%= pkg.version %>', 55 | ' * Copyright (c) 2013 <%= pkg.author %>. <%= pkg.homepage %>', 56 | ' * License: MIT', 57 | ' */\n' 58 | ].join('\n'); 59 | 60 | 61 | gulp.task('test', function () { 62 | karma.start({ 63 | configFile: path.resolve('karma.conf.js'), 64 | browsers: ['PhantomJS'], 65 | singleRun: true 66 | }, function (exitCode) { 67 | gutil.log('Karma has exited with ' + exitCode); 68 | process.exit(exitCode); 69 | }); 70 | }); 71 | 72 | gulp.task('test-debug', function () { 73 | karma.start({ 74 | configFile: path.resolve('karma.conf.js'), 75 | singleRun: false, 76 | reporters: ['dots', 'progress', 'junit'] 77 | }, function (exitCode) { 78 | gutil.log('Karma has exited with ' + exitCode); 79 | process.exit(exitCode); 80 | }); 81 | }); 82 | 83 | gulp.task('sass', function () { 84 | return gulp.src(files.app.sass) 85 | .pipe($.rubySass({ 86 | style: 'expanded', 87 | compass: true, 88 | loadPath: folders.app 89 | })) 90 | .pipe($.autoprefixer('last 1 version')) 91 | .pipe(gulp.dest(folders.tmp + '/styles')) 92 | .pipe($.size()); 93 | }); 94 | 95 | gulp.task('scripts', function () { 96 | return gulp.src(files.src.scripts) 97 | .pipe($.jshint()) 98 | .pipe($.jshint.reporter($.jshintStylish)) 99 | .pipe($.header(banner, { pkg: pkg })) 100 | .pipe($.concat(pkg.name + '.js')) 101 | .pipe(gulp.dest(folders.dist)) 102 | .pipe($.uglify({ preserveComments: 'some' })) 103 | .pipe($.rename(pkg.name + '.min.js')) 104 | .pipe(gulp.dest(folders.dist)) 105 | .pipe($.size()); 106 | }); 107 | 108 | gulp.task('bump', function () { 109 | gulp.src(['./bower.json', './package.json']) 110 | .pipe($.bump({ indent: 4 })) 111 | .pipe(gulp.dest('./')); 112 | }); 113 | 114 | gulp.task('clean', function () { 115 | return gulp.src([folders.tmp, folders.dist], { read: false }) 116 | .pipe($.clean()); 117 | }); 118 | 119 | gulp.task('build', ['clean', 'test', 'scripts']); 120 | 121 | gulp.task('default', ['build']); 122 | 123 | gulp.task('connect', function () { 124 | var connect = require('connect'); 125 | var app = connect() 126 | .use(require('connect-livereload')({ port: 35729 })) 127 | .use(serveStatic(folders.src)) 128 | .use(serveStatic(folders.app)) 129 | .use(serveStatic(folders.tmp)) 130 | .use(serveIndex(folders.app)); 131 | 132 | require('http').createServer(app) 133 | .listen(9000) 134 | .on('listening', function () { 135 | console.log('Started connect web server on http://localhost:9000'); 136 | }); 137 | }); 138 | 139 | gulp.task('serve', ['connect', 'sass'], function () { 140 | require('opn')('http://localhost:9000'); 141 | }); 142 | 143 | gulp.task('watch', ['serve'], function () { 144 | var server = $.livereload(); 145 | 146 | // watch for changes 147 | 148 | gulp.watch(files.app.watch).on('change', function (file) { 149 | server.changed(file.path); 150 | }); 151 | gulp.watch(files.src.scripts).on('change', function (file) { 152 | server.changed(file.path); 153 | }); 154 | 155 | gulp.watch(files.app.sass, ['sass']); 156 | gulp.watch(files.src.scripts, ['scripts']); 157 | }); 158 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | 'use strict'; 3 | 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // frameworks to use 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/app/bower_components/bugsnag/src/bugsnag.js', 15 | 'test/app/bower_components/angular/angular.js', 16 | 'test/app/bower_components/angular-mocks/angular-mocks.js', 17 | 18 | 'src/scripts/*.js', 19 | 20 | 'test/specs/*-spec.js', 21 | 'test/specs/**/*-spec.js' 22 | ], 23 | 24 | // list of files to exclude 25 | exclude: [ 26 | ], 27 | 28 | // test results reporter to use 29 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 30 | reporters: ['dots', 'progress', 'junit', 'coverage'], 31 | 32 | preprocessors: { 33 | // source files, that you wanna generate coverage for 34 | // do not include tests or libraries 35 | // (these files will be instrumented by Istanbul) 36 | 'src/scripts/*.js': ['coverage'] 37 | }, 38 | 39 | coverageReporter: { 40 | type: 'lcov', // lcov format supported by Coveralls 41 | dir: 'reports/coverage' 42 | }, 43 | 44 | junitReporter: { 45 | outputFile: 'reports/test/unit-test-results.xml' 46 | }, 47 | 48 | // web server port 49 | port: 9876, 50 | 51 | runnerPort: 9100, 52 | 53 | // enable / disable colors in the output (reporters and logs) 54 | colors: true, 55 | 56 | // level of logging 57 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 58 | logLevel: config.LOG_INFO, 59 | 60 | // enable / disable watching file and executing tests whenever any file changes 61 | autoWatch: false, 62 | 63 | // Start these browsers, currently available: 64 | // - Chrome 65 | // - ChromeCanary 66 | // - Firefox 67 | // - Opera 68 | // - Safari (only Mac) 69 | // - PhantomJS 70 | // - IE (only Windows) 71 | browsers: ['Chrome'], 72 | 73 | // If browser does not capture in given timeout [ms], kill it 74 | captureTimeout: 5000, 75 | 76 | // Continuous Integration mode 77 | // if true, it capture browsers, run tests and exit 78 | singleRun: false 79 | }); 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bugsnag", 3 | "version": "0.2.1", 4 | "scripts": { 5 | "test": "gulp test", 6 | "coveralls": "cat ./reports/coverage/*/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 7 | }, 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "underscore.string": "~3.0.3", 11 | "istanbul": "~0.3.6", 12 | "matchdep": "~0.3.0", 13 | "coveralls": "~2.11.2", 14 | "connect": "^3.3.4", 15 | "connect-livereload": "^0.5.3", 16 | "gulp": "^3.8.11", 17 | "gulp-autoprefixer": "^2.1.0", 18 | "gulp-bower-files": "^0.2.7", 19 | "gulp-bump": "^0.1.13", 20 | "gulp-cache": "^0.2.6", 21 | "gulp-clean": "^0.3.1", 22 | "gulp-concat": "^2.5.1", 23 | "gulp-csso": "^1.0.0", 24 | "gulp-header": "^1.2.2", 25 | "gulp-filter": "^2.0.2", 26 | "gulp-flatten": "^0.0.4", 27 | "gulp-imagemin": "^2.2.0", 28 | "gulp-jshint": "^1.9.2", 29 | "gulp-karma": "0.0.4", 30 | "gulp-livereload": "^3.7.0", 31 | "gulp-load-plugins": "^0.8.0", 32 | "gulp-rename": "^1.2.0", 33 | "gulp-ruby-sass": "^0.4.3", 34 | "gulp-size": "^1.2.0", 35 | "gulp-uglify": "^1.1.0", 36 | "gulp-useref": "^1.1.1", 37 | "gulp-util": "^3.0.3", 38 | "jshint-stylish": "^1.0.0", 39 | "karma": "^0.12.31", 40 | "karma-chrome-launcher": "^0.1.7", 41 | "karma-coverage": "^0.2.7", 42 | "karma-jasmine": "^0.3.5", 43 | "karma-junit-reporter": "^0.2.2", 44 | "karma-phantomjs-launcher": "^0.1.4", 45 | "lodash": "^3.2.0", 46 | "opn": "^1.0.1", 47 | "serve-static": "~1.9.1", 48 | "serve-index": "~1.6.2", 49 | "wiredep": "^2.2.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # angular-bugsnag 2 | 3 | Angular wrapper for [Bugsnag](https://github.com/bugsnag/bugsnag-js). 4 | 5 | [![Build Status](https://travis-ci.org/wmluke/angular-bugsnag.svg)](https://travis-ci.org/wmluke/angular-bugsnag) 6 | [![Coverage Status](https://coveralls.io/repos/wmluke/angular-bugsnag/badge.png)](https://coveralls.io/r/wmluke/angular-bugsnag) 7 | 8 | Specifically, `angular-bugsnag` does the following... 9 | 10 | * Provides `bugsnagProvider` to configure the `bugsnag` client and also to inject `bugsnag` as needed 11 | * Overrides the default angular `$exceptionHandler` to send uncaught exceptions to Bugsnag 12 | 13 | ## Installation 14 | 15 | Download [angular-bugsnag.js](https://raw.githubusercontent.com/wmluke/angular-bugsnag/master/dist/angular-bugsnag.js) or install with bower. 16 | 17 | ```bash 18 | $ bower install angular-bugsnag --save 19 | ``` 20 | 21 | Load the `angular-bugsnag` module into your app... 22 | 23 | ```javascript 24 | angular.module('app', ['angular-bugsnag']) 25 | ``` 26 | 27 | ## `bugsnagProvider` configuration 28 | 29 | The `bugsnagProvider` has pretty much the same [configuration](https://github.com/bugsnag/bugsnag-js#configuration) options as `bugsnag`. 30 | 31 | The main difference is that `bugsnagProvider` uses chainable setter methods instead of properties. 32 | 33 | ### `noConflict` 34 | Call `noConflict()` **before** other settings to remove `bugsnag` from `window`. 35 | 36 | ### `apiKey` 37 | 38 | ### `releaseStage` 39 | 40 | ### `notifyReleaseStages` 41 | 42 | ### `appVersion` 43 | 44 | ### `user` 45 | 46 | ### `projectRoot` 47 | 48 | ### `endpoint` 49 | 50 | ### `metaData` 51 | 52 | ### `autoNotify` 53 | 54 | ### `beforeNotify` 55 | 56 | Takes an angular `providerFunction` or service name that should return a [beforeNotify](https://github.com/bugsnag/bugsnag-js#beforenotify) callback used by `bugsnag`. 57 | 58 | #### Examples 59 | 60 | Log notifications with `$log`: 61 | 62 | ```js 63 | bugsnagProvider 64 | .beforeNotify(['$log', function ($log) { 65 | return function (error, metaData) { 66 | $log.debug(error.name); 67 | return true; 68 | }; 69 | }]) 70 | ``` 71 | 72 | `beforeNotify` can also take a service name defined elsewhere: 73 | 74 | ```js 75 | 76 | module 77 | .factory('bugsnagNotificationInterceptor', ['$log', function ($log) { 78 | return function (error, metaData) { 79 | $log.debug(error.name); 80 | return true; 81 | }; 82 | }]) 83 | 84 | bugsnagProvider 85 | .beforeNotify('bugsnagNotificationInterceptor') 86 | ``` 87 | 88 | ### Example Usage 89 | 90 | ```javascript 91 | angular.module('demo-app', ['angular-bugsnag']) 92 | .config(['bugsnagProvider', function (bugsnagProvider) { 93 | bugsnagProvider 94 | .noConflict() 95 | .apiKey('[replace me]') 96 | .releaseStage('development') 97 | .user({ 98 | id: 123, 99 | name: 'Jon Doe', 100 | email: 'jon.doe@gmail.com' 101 | 102 | }) 103 | .appVersion('0.1.0') 104 | .beforeNotify(['$log', function ($log) { 105 | return function (error, metaData) { 106 | $log.debug(error.name); 107 | return true; 108 | }; 109 | }]); 110 | }]) 111 | .controller('MainCtrl', ['$rootScope', 'bugsnag', function ($scope, bugsnag) { 112 | 113 | this.throwError = function (err) { 114 | throw err; 115 | }; 116 | 117 | this.notifyError = function (err) { 118 | bugsnag.notify(err); 119 | }; 120 | 121 | this.brokenUndefined = function () { 122 | $scope.foo.bar(); 123 | }; 124 | 125 | }]); 126 | ``` 127 | 128 | ## Contributing 129 | 130 | PR's are welcome. Just make sure the tests pass. 131 | 132 | ```bash 133 | $ make 134 | $ gulp test 135 | ``` 136 | 137 | Additionally, use `gulp serve` or `gulp watch` to run the test app. Just insert a Bugsnag API Key [here](https://github.com/wmluke/angular-bugsnag/blob/master/test/app/scripts/app.js#L8-8). Remember, don't commit your key! 138 | 139 | ## License 140 | MIT 141 | -------------------------------------------------------------------------------- /src/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": false, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "white": true, 22 | "globals": { 23 | "angular": false, 24 | "Bugsnag": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/scripts/angular-bugsnag.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var _bugsnag; 5 | 6 | angular.module('angular-bugsnag', []) 7 | .config(['$provide', function ($provide) { 8 | $provide.provider({ 9 | bugsnag: function () { 10 | 11 | // if a script blocker blocks the bugsnag library Bugsnag will be undefined at this point, so we initialize it to an object 12 | // with methods that do nothing but are declared and won't throw errors later by the angular-bugsnag 13 | // module calling them 14 | var Bugsnag = window.Bugsnag || { 15 | notifyException: function () {}, 16 | notify: function () {}, 17 | noConflict: function () {} 18 | }; 19 | 20 | _bugsnag = Bugsnag; 21 | var _self = this; 22 | var _beforeNotify; 23 | 24 | this.noConflict = function () { 25 | _bugsnag = Bugsnag.noConflict(); 26 | return _self; 27 | }; 28 | 29 | this.apiKey = function (apiKey) { 30 | _bugsnag.apiKey = apiKey; 31 | return _self; 32 | }; 33 | 34 | this.releaseStage = function (releaseStage) { 35 | _bugsnag.releaseStage = releaseStage; 36 | return _self; 37 | }; 38 | 39 | this.notifyReleaseStages = function (notifyReleaseStages) { 40 | _bugsnag.notifyReleaseStages = notifyReleaseStages; 41 | return _self; 42 | }; 43 | 44 | this.appVersion = function (appVersion) { 45 | _bugsnag.appVersion = appVersion; 46 | return _self; 47 | }; 48 | 49 | this.user = function (user) { 50 | _bugsnag.user = user; 51 | return _self; 52 | }; 53 | 54 | this.projectRoot = function (projectRoot) { 55 | _bugsnag.projectRoot = projectRoot; 56 | return _self; 57 | }; 58 | 59 | this.endpoint = function (endpoint) { 60 | _bugsnag.endpoint = endpoint; 61 | return _self; 62 | }; 63 | 64 | this.metaData = function (metaData) { 65 | _bugsnag.metaData = metaData; 66 | return _self; 67 | }; 68 | 69 | this.autoNotify = function (autoNotify) { 70 | _bugsnag.autoNotify = autoNotify; 71 | return _self; 72 | }; 73 | 74 | this.beforeNotify = function (beforeNotify) { 75 | _beforeNotify = beforeNotify; 76 | return _self; 77 | }; 78 | 79 | this._testRequest = function (testRequest) { 80 | _bugsnag.testRequest = testRequest; 81 | return _self; 82 | }; 83 | 84 | this.$get = ['$injector', function ($injector) { 85 | if (_beforeNotify) { 86 | _bugsnag.beforeNotify = angular.isString(_beforeNotify) ? $injector.get(_beforeNotify) : $injector.invoke(_beforeNotify); 87 | } 88 | return _bugsnag; 89 | }]; 90 | 91 | }, 92 | $exceptionHandler: function () { 93 | this.$get = ['$log', 'bugsnag', function ($log, bugsnag) { 94 | return function (exception, cause) { 95 | $log.error.apply($log, arguments); 96 | try { 97 | bugsnag.fixContext(); 98 | if (angular.isString(exception)) { 99 | bugsnag.notify(exception); 100 | } else { 101 | bugsnag.notifyException(exception); 102 | } 103 | } catch (e) { 104 | $log.error(e); 105 | } 106 | }; 107 | }]; 108 | } 109 | }); 110 | }]) 111 | .run(['bugsnag', '$location', '$rootScope', function (bugsnag, $location, $rootScope) { 112 | // Set the context from $location.url(). We cannot do this above b/c injecting $location creates a circular dependency. 113 | bugsnag.fixContext = function () { 114 | bugsnag.context = $location.url(); 115 | }; 116 | // refresh the rate-limit 117 | $rootScope.$on('$locationChangeSuccess', bugsnag.refresh || angular.noop); 118 | }]); 119 | }()); 120 | -------------------------------------------------------------------------------- /test/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

angular-bugsnag

24 |
25 | 26 |
27 |
28 | 33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('demo-app', ['angular-bugsnag']) 5 | .config(['bugsnagProvider', function (bugsnagProvider) { 6 | bugsnagProvider 7 | //.noConflict() 8 | .apiKey('[replace me]') 9 | .releaseStage('development') 10 | .user({ 11 | id: 123, 12 | name: 'Jon Doe', 13 | email: 'jon.doe@gmail.com' 14 | 15 | }) 16 | .appVersion('0.1.0'); 17 | }]) 18 | .controller('MainCtrl', ['$rootScope', 'bugsnag', function ($scope, bugsnag) { 19 | 20 | this.throwError = function (err) { 21 | throw err; 22 | }; 23 | 24 | this.notifyError = function (err) { 25 | bugsnag.notify(err); 26 | }; 27 | 28 | this.brokenUndefined = function () { 29 | $scope.foo.bar(); 30 | }; 31 | 32 | 33 | }]) 34 | 35 | }()); 36 | -------------------------------------------------------------------------------- /test/app/styles/main.scss: -------------------------------------------------------------------------------- 1 | //@import "bootstrap-sass/vendor/assets/stylesheets/bootstrap/bootstrap.scss"; 2 | -------------------------------------------------------------------------------- /test/specs/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": false, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "white": true, 22 | "globals": { 23 | "describe": false, 24 | "beforeEach": false, 25 | "afterEach": false, 26 | "it": false, 27 | "expect": false, 28 | "jasmine": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/specs/angular-bugsnag-spec.js: -------------------------------------------------------------------------------- 1 | describe('angular-bugsnag', function () { 2 | 3 | window.BUGSNAG_TESTING = true; 4 | 5 | var bugsnag, testRequest; 6 | 7 | beforeEach(function () { 8 | jasmine.addMatchers({ 9 | startWith: function () { 10 | return { 11 | compare: function (actual, expected) { 12 | return { 13 | pass: actual.lastIndexOf(expected, 0) === 0, 14 | message: 'Expected `' + actual + '` to start with `' + expected + '`' 15 | } 16 | } 17 | 18 | }; 19 | } 20 | }); 21 | }); 22 | 23 | beforeEach(module('angular-bugsnag')); 24 | 25 | beforeEach(module(function (bugsnagProvider, $provide) { 26 | testRequest = jasmine.createSpy('testRequest'); 27 | 28 | bugsnagProvider 29 | .apiKey('11111111111111111111111111111111') 30 | .releaseStage('development') 31 | .user({ 32 | id: 123, 33 | name: 'Jon Doe', 34 | email: 'jon.doe@gmail.com' 35 | }) 36 | .appVersion('0.1.0') 37 | .metaData({ 38 | aaa: 'bbb' 39 | }) 40 | .notifyReleaseStages(['development']) 41 | .beforeNotify(['$log', function ($log) { 42 | return function (error, metaData) { 43 | $log.debug(error.name); 44 | return true; 45 | }; 46 | }]) 47 | ._testRequest(testRequest); 48 | 49 | $provide.service('dummyService', function () { 50 | 51 | this.brokenUndefined = function () { 52 | return this.foo.bar(); 53 | }; 54 | }); 55 | 56 | })); 57 | 58 | beforeEach(inject(function (_bugsnag_) { 59 | bugsnag = _bugsnag_; 60 | })); 61 | 62 | 63 | it('should be configured with bugsnagProvider', inject(function ($log, $location) { 64 | var actual = {}; 65 | 66 | spyOn($location, 'url').and.callFake(function () { 67 | return '/foo/bar' 68 | }); 69 | 70 | testRequest.and.callFake(function (url, params) { 71 | actual.url = url; 72 | actual.params = params; 73 | }); 74 | 75 | bugsnag.fixContext(); 76 | bugsnag.notify('fail'); 77 | 78 | expect(actual.url).startWith('https://notify.bugsnag.com/js'); 79 | expect(actual.params.apiKey).toBe('11111111111111111111111111111111'); 80 | expect(actual.params.user).toEqual({id: 123, name: 'Jon Doe', email: 'jon.doe@gmail.com'}); 81 | expect(actual.params.releaseStage).toBe('development'); 82 | expect(actual.params.appVersion).toBe('0.1.0'); 83 | expect(actual.params.name).toBe('fail'); 84 | expect(actual.params.metaData.aaa).toBe('bbb'); 85 | expect(actual.params.context).toBe('/foo/bar'); 86 | 87 | expect($log.debug.logs[0][0]).toBe('fail'); 88 | })); 89 | 90 | it('should report uncaught exceptions', inject(function ($rootScope, dummyService, $location) { 91 | 92 | spyOn($location, 'url').and.callFake(function () { 93 | return '/aaa/bbb' 94 | }); 95 | 96 | var actual = {}; 97 | 98 | testRequest.and.callFake(function (url, params) { 99 | actual.url = url; 100 | actual.params = params; 101 | }); 102 | 103 | $rootScope.$apply(function () { 104 | dummyService.brokenUndefined(); 105 | }); 106 | 107 | 108 | expect(actual.url).startWith('https://notify.bugsnag.com/js'); 109 | expect(actual.params.name).toBe('TypeError'); 110 | expect(actual.params.message).toBe('\'undefined\' is not an object (evaluating \'this.foo.bar\')'); 111 | expect(actual.params.context).toBe('/aaa/bbb'); 112 | 113 | })); 114 | 115 | }); 116 | --------------------------------------------------------------------------------