(function(){ // Raven.js // // Originally based on the Arecibo JavaScript client. // // Requires: // * TraceKit (included in the full and minified distribution files) 'use strict'; // First, check for JSON support // If there is no JSON, we no-op the core features of Raven // since JSON is required to encode the payload var _Raven = window.Raven, hasJSON = !isUndefined(window.JSON), globalServer, globalUser, globalKey, globalProject, globalOptions = { logger: 'javascript', ignoreErrors: [], ignoreUrls: [] }; var TK = TraceKit.noConflict(); // Disable Tracekit's remote fetching by default TK.remoteFetching = false; /* * The core Raven object */ var Raven = { VERSION: '@VERSION', /* * Raven.noConflict() * * Allow multiple versions of Raven to be installed. */ noConflict: function() { window.Raven = _Raven; return Raven; }, /* * Raven.config() * * Configure raven with a DSN and extra options */ config: function(dsn, options) { var uri = parseUri(dsn), lastSlash = uri.path.lastIndexOf('/'), path = uri.path.substr(1, lastSlash); // merge in options if (options) { each(options, function(key, value){ globalOptions[key] = value; }); } // "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. globalOptions.ignoreErrors.push('Script error.'); globalKey = uri.user; globalProject = ~~uri.path.substr(lastSlash + 1); // assemble the endpoint from the uri pieces globalServer = uri.protocol + '://' + uri.host + (uri.port ? ':' + uri.port : '') + '/' + path + 'api/' + globalProject + '/store/'; if (globalOptions.fetchContext) { TK.remoteFetching = true; } // return for chaining return Raven; }, /* * Raven.install() * * Installs a global window.onerror error handler * to capture and report uncaught exceptions. */ install: function() { if (!isSetup()) return; TK.report.subscribe(handleStackInfo); return Raven; }, /* * Raven.context() * * Wrap code within a context so Raven can capture errors * reliably across domains that is executed immediately. */ context: function(options, func, args) { if (isFunction(options)) { args = func; func = options; options = undefined; } Raven.wrap(options, func).apply(this, args); }, /* Raven.wrap() * * Wrap code within a context and returns back a new function to be executed */ wrap: function(options, func) { // options is optional if (isFunction(options)) { func = options; options = undefined; } return function() { try { func.apply(this, arguments); } catch(e) { Raven.captureException(e, options); } }; }, /* * Raven.uninstall() * * Uninstalls the global error handler. */ uninstall: function() { TK.report.unsubscribe(handleStackInfo); return Raven; }, /* * Raven.captureException() * * Manually capture an exception and send it over to Sentry */ captureException: function(ex, options) { // TraceKit.report will re-raise any exception passed to it, // which means you have to wrap it in try/catch. Instead, we // can wrap it here and only re-raise if TraceKit.report // raises an exception different from the one we asked to // report on. try { TK.report(ex, options); } catch(ex1) { if(ex !== ex1) { throw ex1; } } return Raven; }, /* * Raven.captureMessage() * * Manually send a message to Sentry */ captureMessage: function(msg, options) { // Fire away! send( arrayMerge({ message: msg }, options) ); return Raven; }, /* * Raven.setUser() * * Set/clear a user to be sent along with the payload. */ setUser: function(user) { globalUser = user; return Raven; } }; var uriKeys = 'source protocol authority userInfo user password host port relative path directory file query anchor'.split(' '), uriPattern = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; /**** Private functions ****/ function parseUri(str) { var m = uriPattern.exec(str), uri = {}, i = 14; while (i--) uri[uriKeys[i]] = m[i] || ''; return uri; } function isUndefined(what) { return typeof what === 'undefined'; } function isFunction(what) { return typeof what === 'function'; } function each(obj, callback) { var i, j; if (obj.length === undefined) { for (i in obj) { if (obj.hasOwnProperty(i)) { callback.call(null, i, obj[i]); } } } else { for (i = 0, j = obj.length; i < j; i++) { callback.call(null, i, obj[i]); } } } var cachedAuth; function getAuthQueryString() { if (cachedAuth) return cachedAuth; var qs = [ 'sentry_version=2.0', 'sentry_client=raven-js/' + Raven.VERSION ]; if (globalKey) { qs.push('sentry_key=' + globalKey); } cachedAuth = '?' + qs.join('&'); return cachedAuth; } function handleStackInfo(stackInfo, options) { var frames = [], i = 0, j = stackInfo.stack && stackInfo.stack.length || 0, frame; for (; i < j; i++) { frame = normalizeFrame(stackInfo.stack[i]); if (frame) { frames.push(frame); } } processException( stackInfo.name, stackInfo.message, stackInfo.url, stackInfo.lineno, frames, options ); } function normalizeFrame(frame) { if (!frame.url) return; // normalize the frames data var normalized = { filename: frame.url, lineno: frame.line, colno: frame.column, 'function': frame.func || '?' }, context = extractContextFromFrame(frame); if (context) { var i = 3, keys = ['pre_context', 'context_line', 'post_context']; while (i--) normalized[keys[i]] = context[i]; } normalized.in_app = !/(Raven|TraceKit)\./.test(normalized['function']); return normalized; } function extractContextFromFrame(frame) { // immediately check if we should even attempt to parse a context if (!frame.context || !globalOptions.fetchContext) return; var context = frame.context, pivot = ~~(context.length / 2), i = context.length, isMinified = false; while (i--) { // We're making a guess to see if the source is minified or not. // To do that, we make the assumption if *any* of the lines passed // in are greater than 300 characters long, we bail. // Sentry will see that there isn't a context if (context[i].length > 300) { isMinified = true; break; } } if (isMinified) { // The source is minified and we don't know which column. Fuck it. if (isUndefined(frame.column)) return; // If the source is minified and has a frame column // we take a chunk of the offending line to hopefully shed some light return [ [], // no pre_context context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column [] // no post_context ]; } return [ context.slice(0, pivot), // pre_context context[pivot], // context_line context.slice(pivot + 1) // post_context ]; } function processException(type, message, fileurl, lineno, frames, options) { var stacktrace, label, i; // IE8 really doesn't have Array.prototype.indexOf // Filter out a message that matches our ignore list i = globalOptions.ignoreErrors.length; while (i--) { if (message === globalOptions.ignoreErrors[i]) { return; } } if (frames && frames.length) { stacktrace = {frames: frames}; fileurl = fileurl || frames[0].filename; } else if (fileurl) { stacktrace = { frames: [{ filename: fileurl, lineno: lineno }] }; } i = globalOptions.ignoreUrls.length; while (i--) { if (globalOptions.ignoreUrls[i].test(fileurl)) { return; } } label = lineno ? message + ' at ' + lineno : message; // Fire away! send( arrayMerge({ 'sentry.interfaces.Exception': { type: type, value: message }, 'sentry.interfaces.Stacktrace': stacktrace, culprit: fileurl, message: label }, options) ); } function arrayMerge(arr1, arr2) { if (!arr2) { return arr1; } each(arr2, function(key, value){ arr1[key] = value; }); return arr1; } function getHttpData() { var http = { url: window.location.href, headers: { 'User-Agent': navigator.userAgent } }; if (window.document.referrer) { http.headers.Referer = window.document.referrer; } return http; } function send(data) { if (!isSetup()) return; data = arrayMerge({ project: globalProject, logger: globalOptions.logger, site: globalOptions.site, platform: 'javascript', 'sentry.interfaces.Http': getHttpData() }, data ); if (globalUser) data['sentry.interfaces.User'] = globalUser; if (isFunction(globalOptions.dataCallback)) { data = globalOptions.dataCallback(data); } makeRequest(data); } function makeRequest(data) { new Image().src = globalServer + getAuthQueryString() + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); } function isSetup() { if (!hasJSON) return false; // needs JSON support if (!globalServer) { console.error("Error: Raven has not been configured."); return false; } return true; } window.Raven = Raven; })();