Skip to content

Instantly share code, notes, and snippets.

@hellsan631
Last active July 16, 2021 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hellsan631/d6fee6b168548a037f63d65461d83c64 to your computer and use it in GitHub Desktop.
Save hellsan631/d6fee6b168548a037f63d65461d83c64 to your computer and use it in GitHub Desktop.
Raygun Angular.js and Express/Loopback integrations... done right, and documented!

Raygun Integration

Featuring

  • AngularJS
  • Loopback (express)

I've solved some big issues with getting Raygun to work for tracking the javascript in the mean stack. There were several issues with angular and loopback, and the documentation is pretty scarce. So here is my best attempt at outlining my solution. Keep in mind, this may not work if you download and run this, but its working in my web bot project, which uses the same tech.

<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="utf-8"/>
<title>Raygun Angular Example</title>
<!-- Bower and NPM script injection is broken. You should use the CDN -->
<script type="text/javascript">
!function(a,b,c,d,e,f,g,h){a.RaygunObject=e,a[e]=a[e]||function(){
(a[e].o=a[e].o||[]).push(arguments)},f=b.createElement(c),g=b.getElementsByTagName(c)[0],
f.async=1,f.src=d,g.parentNode.insertBefore(f,g),h=a.onerror,a.onerror=function(b,c,d,f,g){
h&&h(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[],a[e].q.push({
e:g})}}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js");
</script>
</head>
<body>
<!-- inject:js -->
<script src="vendor/angular.js"></script>
<script src="raygun.config.js"></script>
<script src="exception-handler.provider.js"></script>
<!-- endinject -->
</body>
</html>
(function() {
'use strict';
/* global Raygun: false */
angular
.module('app', ['blocks.exception'])
.run(raygunConfig);
/* @ngInject */
function raygunConfig($interval) {
// Waits until raygun is done loading (async) and then initializes it.
var raygunCheck = $interval(function() {
if (Raygun) {
/**
* The raygun api v2 also works here, but we use v1 because we are going to need
* to use the Raygun.send method inside our error handler
*/
Raygun.init('API_KEY_HERE');
Raygun.attach();
$interval.cancel(raygunCheck);
}
}, 50);
}
})();
// Structure Shamelessly taken from JohnPapa and one of his projects
(function() {
'use strict';
angular
.module('blocks.exception', [])
.provider('exceptionHandler', exceptionHandlerProvider)
.config(config);
/**
* Must configure the exception handling
* @return {[type]}
*/
function exceptionHandlerProvider() {
/* jshint validthis:true */
this.config = {
appErrorPrefix: undefined
};
this.configure = function handlePrefix(appErrorPrefix) {
this.config.appErrorPrefix = appErrorPrefix;
};
this.$get = function returnConfig() {
return {config: this.config};
};
}
/**
* Configure by setting an optional string value for appErrorPrefix.
* Accessible via config.appErrorPrefix (via config value).
* @param {[type]} $provide
* @return {[type]}
* @ngInject
*/
/* @ngInject */
function config($provide) {
$provide.decorator('$exceptionHandler', extendExceptionHandler);
}
/**
* Extend the $exceptionHandler service to also display a toast.
* @param {Object} $delegate
* @param {Object} exceptionHandler
* @param {Object} logger
* @return {Function} the decorated $exceptionHandler service
*/
/* @ngInject */
function extendExceptionHandler($delegate, exceptionHandler, logger) {
return function(exception, cause) {
var errorData = {exception: exception, cause: cause};
/* global Raygun: false */
// This is the meat and potatoes of the exception raygun is going to get
Raygun.send(new Error(exception.message), errorData);
console.error(exception.message, errorData);
$delegate(exception, cause);
};
}
})();
const loopback = require('loopback');
const boot = require('loopback-boot');
const path = require('path');
const raygun = require('raygun');
const env = process.env.NODE_ENV || 'development';
const app = module.exports = loopback();
app.start = function() {
// start the web server
return app.listen(function() {
var baseUrl = app.get('url').replace(/\/$/, '');
var angularPath = './';
var explorerPath;
app.emit('started');
console.log('Web server listening at: %s', baseUrl);
if (env !== 'production' &&
app.get('loopback-component-explorer')) {
explorerPath = app.get('loopback-component-explorer').mountPath;
console.log(`Browse your REST API at ${baseUrl}${explorerPath}`);
}
mountAngular(angularPath);
});
};
// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
if (err) throw err;
// start the server if `$ node server.js`
if (require.main === module)
app.start();
});
function mountAngular(mountPath) {
const staticPath = path.resolve(__dirname, mountPath);
const raygunClient = new raygun.Client().init({
apiKey: 'API_KEY_HERE'
});
app.use(loopback.static(staticPath));
// have angular function in HTML5 mode
app.use('/*', function(req, res, next) {
if (req.originalUrl.includes('api')) {
return next();
}
if (req.originalUrl.includes('js.map')) {
return next();
}
res.sendFile(staticPath + '/index.html');
});
// This is how we add the error handler 'catcher' for raygun
app.get('remoting').errorHandler = {
handler: function(error, req, res, next) {
raygunClient.send(error);
//delegate the task down the line
next();
},
disableStackTrace: env !== 'production'
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment