vendor/assets/javascripts/raven.js in ravenjs-gem-1.0.7.0 vs vendor/assets/javascripts/raven.js in ravenjs-gem-1.1.14

- old
+ new

@@ -1,57 +1,37 @@ -/*! Raven.js 1.0.7 | github.com/getsentry/raven-js */ +/*! Raven.js 1.1.14 (f9803bd) | github.com/getsentry/raven-js */ /* * Includes TraceKit * https://github.com/getsentry/TraceKit * - * Copyright 2013 Matt Robenolt and other contributors + * Copyright 2014 Matt Robenolt and other contributors * Released under the BSD license * https://github.com/getsentry/raven-js/blob/master/LICENSE * */ +;(function(window, undefined){ +'use strict'; + /* TraceKit - Cross brower stack traces - github.com/occ/TraceKit MIT license */ -;(function(window, undefined) { +var TraceKit = { + remoteFetching: false, + collectWindowErrors: true, + // 3 lines before, the offending line, 3 lines after + linesOfContext: 7 +}; - -var TraceKit = {}; -var _oldTraceKit = window.TraceKit; - // global reference to slice var _slice = [].slice; var UNKNOWN_FUNCTION = '?'; /** - * _has, a better form of hasOwnProperty - * Example: _has(MainHostObject, property) === true/false - * - * @param {Object} host object to check property - * @param {string} key to check - */ -function _has(object, key) { - return Object.prototype.hasOwnProperty.call(object, key); -} - -function _isUndefined(what) { - return typeof what === 'undefined'; -} - -/** - * TraceKit.noConflict: Export TraceKit out to another variable - * Example: var TK = TraceKit.noConflict() - */ -TraceKit.noConflict = function noConflict() { - window.TraceKit = _oldTraceKit; - return TraceKit; -}; - -/** * TraceKit.wrap: Wrap any function in a TraceKit reporter * Example: func = TraceKit.wrap(func); * * @param {Function} func Function to be wrapped * @return {Function} The wrapped func @@ -107,18 +87,20 @@ * Handlers receive a stackInfo object as described in the * TraceKit.computeStackTrace docs. */ TraceKit.report = (function reportModuleWrapper() { var handlers = [], + lastArgs = null, lastException = null, lastExceptionStack = null; /** * Add a crash handler. * @param {Function} handler */ function subscribe(handler) { + installGlobalHandler(); handlers.push(handler); } /** * Remove a crash handler. @@ -131,20 +113,28 @@ } } } /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** * Dispatch stack information to all handlers. * @param {Object.<string, *>} stack */ - function notifyHandlers(stack, windowError) { + function notifyHandlers(stack, isWindowError) { var exception = null; - if (windowError && !TraceKit.collectWindowErrors) { + if (isWindowError && !TraceKit.collectWindowErrors) { return; } for (var i in handlers) { - if (_has(handlers, i)) { + if (hasKey(handlers, i)) { try { handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); } catch (inner) { exception = inner; } @@ -154,91 +144,127 @@ if (exception) { throw exception; } } - var _oldOnerrorHandler = window.onerror; + var _oldOnerrorHandler, _onErrorHandlerInstalled; /** * Ensures all global unhandled exceptions are recorded. * Supported by Gecko and IE. * @param {string} message Error message. * @param {string} url URL of script that generated the exception. * @param {(number|string)} lineNo The line number at which the error * occurred. + * @param {?(number|string)} colNo The column number at which the error + * occurred. + * @param {?Error} ex The actual Error object. */ - window.onerror = function traceKitWindowOnError(message, url, lineNo) { + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { var stack = null; if (lastExceptionStack) { TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); - stack = lastExceptionStack; - lastExceptionStack = null; - lastException = null; + processLastException(); + } else if (ex) { + // New chrome and blink send along a real error object + // Let's just report that like a normal error. + // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror + stack = TraceKit.computeStackTrace(ex); + notifyHandlers(stack, true); } else { var location = { 'url': url, - 'line': lineNo + 'line': lineNo, + 'column': colNo }; location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); stack = { - 'mode': 'onerror', 'message': message, 'url': document.location.href, - 'stack': [location], - 'useragent': navigator.userAgent + 'stack': [location] }; + notifyHandlers(stack, true); } - notifyHandlers(stack, 'from window.onerror'); - if (_oldOnerrorHandler) { return _oldOnerrorHandler.apply(this, arguments); } return false; - }; + } + function installGlobalHandler () + { + if (_onErrorHandlerInstalled) { + return; + } + _oldOnerrorHandler = window.onerror; + window.onerror = traceKitWindowOnError; + _onErrorHandlerInstalled = true; + } + + function uninstallGlobalHandler () + { + if (!_onErrorHandlerInstalled) { + return; + } + window.onerror = _oldOnerrorHandler; + _onErrorHandlerInstalled = false; + _oldOnerrorHandler = undefined; + } + + function processLastException() { + var _lastExceptionStack = lastExceptionStack, + _lastArgs = lastArgs; + lastArgs = null; + lastExceptionStack = null; + lastException = null; + notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); + } + /** * Reports an unhandled Error to TraceKit. * @param {Error} ex + * @param {?boolean} rethrow If false, do not re-throw the exception. + * Only used for window.onerror to not cause an infinite loop of + * rethrowing. */ - function report(ex) { + function report(ex, rethrow) { var args = _slice.call(arguments, 1); if (lastExceptionStack) { if (lastException === ex) { return; // already caught by an inner catch block, ignore } else { - var s = lastExceptionStack; - lastExceptionStack = null; - lastException = null; - notifyHandlers.apply(null, [s, null].concat(args)); + processLastException(); } } var stack = TraceKit.computeStackTrace(ex); lastExceptionStack = stack; lastException = ex; + lastArgs = args; // If the stack trace is incomplete, wait for 2 seconds for // slow slow IE to see if onerror occurs or not before reporting // this exception; otherwise, we will end up with an incomplete // stack trace window.setTimeout(function () { if (lastException === ex) { - lastExceptionStack = null; - lastException = null; - notifyHandlers.apply(null, [stack, null].concat(args)); + processLastException(); } }, (stack.incomplete ? 2000 : 0)); - throw ex; // re-throw to propagate to the top level (and cause window.onerror) + if (rethrow !== false) { + throw ex; // re-throw to propagate to the top level (and cause window.onerror) + } } report.subscribe = subscribe; report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; return report; }()); /** * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript @@ -253,11 +279,10 @@ * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) * s.stack[i].args - arguments passed to the function, if known * s.stack[i].line - line number, if known * s.stack[i].column - column number, if known * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# - * s.mode - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace * * Supports: * - Firefox: full stack trace with line numbers and unreliable column * number on top frame * - Opera 10: full stack trace with line and column numbers @@ -320,18 +345,18 @@ function loadSource(url) { if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. return ''; } try { - function getXHR() { + var getXHR = function() { try { return new window.XMLHttpRequest(); } catch (e) { // explicitly bubble up the exception if not found return new window.ActiveXObject('Microsoft.XMLHTTP'); } - } + }; var request = getXHR(); request.open('GET', url, false); request.send(''); return request.responseText; @@ -344,11 +369,12 @@ * Retrieves source code from the source code cache. * @param {string} url URL of source code. * @return {Array.<string>} Source contents. */ function getSource(url) { - if (!_has(sourceCache, url)) { + if (!isString(url)) return []; + if (!hasKey(sourceCache, url)) { // URL needs to be able to fetched within the acceptable domain. Otherwise, // cross-domain errors will be triggered. var source = ''; if (url.indexOf(document.domain) !== -1) { source = loadSource(url); @@ -382,11 +408,11 @@ // Walk backwards from the first line in the function until we find the line which // matches the pattern above, which is the function definition for (var i = 0; i < maxLines; ++i) { line = source[lineNo - i] + line; - if (!_isUndefined(line)) { + if (!isUndefined(line)) { if ((m = reGuessFunction.exec(line))) { return m[1]; } else if ((m = reFunctionArgNames.exec(line))) { return m[1]; } @@ -421,11 +447,11 @@ end = Math.min(source.length, line + linesAfter - 1); line -= 1; // convert to 0-based index for (var i = start; i < end; ++i) { - if (!_isUndefined(source[i])) { + if (!isUndefined(source[i])) { context.push(source[i]); } } return context.length > 0 ? context : null; @@ -531,11 +557,11 @@ if (!(parts = codeRE.exec(code))) { re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); } - // not sure if this is really necessary, but I don’t have a test + // not sure if this is really necessary, but I don’t have a test // corpus large enough to confirm that and it was in the original. else { var name = parts[1] ? '\\s+' + parts[1] : '', args = parts[2].split(',').join('\\s*,\\s*'); @@ -585,10 +611,11 @@ // // FIREFOX: // ex.message = qq is not defined // ex.fileName = http://... // ex.lineNumber = 59 + // ex.columnNumber = 69 // ex.stack = ...stack trace... (see the example below) // ex.name = ReferenceError // // CHROME: // ex.message = qq is not defined @@ -616,12 +643,12 @@ function computeStackTraceFromStackProp(ex) { if (!ex.stack) { return null; } - var chrome = /^\s*at (?:((?:\[object object\])?\S+) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i, - gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i, + var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|https?):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|https?).*?):(\d+)(?::(\d+))?\s*$/i, lines = ex.stack.split('\n'), stack = [], parts, element, reference = /^(.*) is undefined$/.exec(ex.message); @@ -655,25 +682,28 @@ } stack.push(element); } - if (stack[0] && stack[0].line && !stack[0].column && reference) { - stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); - } - if (!stack.length) { return null; } + if (stack[0].line && !stack[0].column && reference) { + stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); + } else if (!stack[0].column && !isUndefined(ex.columnNumber)) { + // FireFox uses this awesome columnNumber property for its top frame + // Also note, Firefox's column number is 0-based and everything else expects 1-based, + // so adding 1 + stack[0].column = ex.columnNumber + 1; + } + return { - 'mode': 'stack', 'name': ex.name, 'message': ex.message, 'url': document.location.href, - 'stack': stack, - 'useragent': navigator.userAgent + 'stack': stack }; } /** * Computes stack trace information from the stacktrace property. @@ -722,16 +752,14 @@ if (!stack.length) { return null; } return { - 'mode': 'stacktrace', 'name': ex.name, 'message': ex.message, 'url': document.location.href, - 'stack': stack, - 'useragent': navigator.userAgent + 'stack': stack }; } /** * NOT TESTED. @@ -760,23 +788,23 @@ var lines = ex.message.split('\n'); if (lines.length < 4) { return null; } - var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i, - lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|http|https)\S+)(?:: in function (\S+))?\s*$/i, + var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i, + lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?)\S+)(?:: in function (\S+))?\s*$/i, lineRE3 = /^\s*Line (\d+) of function script\s*$/i, stack = [], scripts = document.getElementsByTagName('script'), inlineScriptBlocks = [], parts, i, len, source; for (i in scripts) { - if (_has(scripts, i) && !scripts[i].src) { + if (hasKey(scripts, i) && !scripts[i].src) { inlineScriptBlocks.push(scripts[i]); } } for (i = 2, len = lines.length; i < len; i += 2) { @@ -834,16 +862,14 @@ if (!stack.length) { return null; // could not parse multiline exception message as Opera stack trace } return { - 'mode': 'multiline', 'name': ex.name, 'message': lines[0], 'url': document.location.href, - 'stack': stack, - 'useragent': navigator.userAgent + 'stack': stack }; } /** * Adds information about the first frame to incomplete stack traces. @@ -967,16 +993,14 @@ // console.log('stack is ' + stack.length); stack.splice(0, depth); } var result = { - 'mode': 'callers', 'name': ex.name, 'message': ex.message, 'url': document.location.href, - 'stack': stack, - 'useragent': navigator.userAgent + 'stack': stack }; augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description); return result; } @@ -1034,13 +1058,11 @@ if (debug) { throw e; } } - return { - 'mode': 'failed' - }; + return {}; } /** * Logs a stacktrace starting from the previous call and working down. * @param {(number|string)=} depth How many frames deep to trace. @@ -1051,171 +1073,52 @@ try { throw new Error(); } catch (ex) { return computeStackTrace(ex, depth + 1); } - - return null; } computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; computeStackTrace.guessFunctionName = guessFunctionName; computeStackTrace.gatherContext = gatherContext; computeStackTrace.ofCaller = computeStackTraceOfCaller; return computeStackTrace; }()); -/** - * Extends support for global error handling for asynchronous browser - * functions. Adopted from Closure Library's errorhandler.js - */ -(function extendToAsynchronousCallbacks() { - var _helper = function _helper(fnName) { - var originalFn = window[fnName]; - window[fnName] = function traceKitAsyncExtension() { - // Make a copy of the arguments - var args = _slice.call(arguments); - var originalCallback = args[0]; - if (typeof (originalCallback) === 'function') { - args[0] = TraceKit.wrap(originalCallback); - } - // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it - // also only supports 2 argument and doesn't care what "this" is, so we - // can just call the original function directly. - if (originalFn.apply) { - return originalFn.apply(this, args); - } else { - return originalFn(args[0], args[1]); - } - }; - }; - - _helper('setTimeout'); - _helper('setInterval'); -}()); - -/** - * Extended support for backtraces and global error handling for most - * asynchronous jQuery functions. - */ -(function traceKitAsyncForjQuery($) { - - // quit if jQuery isn't on the page - if (!$) { - return; - } - - var _oldEventAdd = $.event.add; - $.event.add = function traceKitEventAdd(elem, types, handler, data, selector) { - var _handler; - - if (handler.handler) { - _handler = handler.handler; - handler.handler = TraceKit.wrap(handler.handler); - } else { - _handler = handler; - handler = TraceKit.wrap(handler); - } - - // If the handler we are attaching doesn’t have the same guid as - // the original, it will never be removed when someone tries to - // unbind the original function later. Technically as a result of - // this our guids are no longer globally unique, but whatever, that - // never hurt anybody RIGHT?! - if (_handler.guid) { - handler.guid = _handler.guid; - } else { - handler.guid = _handler.guid = $.guid++; - } - - return _oldEventAdd.call(this, elem, types, handler, data, selector); - }; - - var _oldReady = $.fn.ready; - $.fn.ready = function traceKitjQueryReadyWrapper(fn) { - return _oldReady.call(this, TraceKit.wrap(fn)); - }; - - var _oldAjax = $.ajax; - $.ajax = function traceKitAjaxWrapper(url, options) { - var keys = ['complete', 'error', 'success'], key; - - // Taken from https://github.com/jquery/jquery/blob/eee2eaf1d7a189d99106423a4206c224ebd5b848/src/ajax.js#L311-L318 - // If url is an object, simulate pre-1.5 signature - if (typeof url === 'object') { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - while(key = keys.pop()) { - if ($.isFunction(options[key])) { - options[key] = TraceKit.wrap(options[key]); - } - } - - try { - return _oldAjax.call(this, url, options); - } catch (e) { - TraceKit.report(e); - throw e; - } - }; - -}(window.jQuery)); - -//Default options: -if (!TraceKit.remoteFetching) { - TraceKit.remoteFetching = true; -} -if (!TraceKit.collectWindowErrors) { - TraceKit.collectWindowErrors = true; -} -if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) { - // 5 lines before, the offending line, 5 lines after - TraceKit.linesOfContext = 11; -} - - - -// Export to global object -window.TraceKit = TraceKit; - -}(window)); -;(function(window, undefined){ '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), + hasJSON = !!(window.JSON && window.JSON.stringify), + lastCapturedException, + lastEventId, globalServer, globalUser, globalKey, globalProject, globalOptions = { logger: 'javascript', ignoreErrors: [], - ignoreUrls: [] - }; + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + collectWindowErrors: true, + tags: {}, + extra: {} + }, + authQueryString; -var TK = TraceKit.noConflict(); - -// Disable Tracekit's remote fetching by default -TK.remoteFetching = false; - /* * The core Raven singleton * * @this {Raven} */ var Raven = { - VERSION: '1.0.7', + VERSION: '1.1.14', /* * Allow multiple versions of Raven to be installed. * Strip Raven from the global context and returns the instance. * @@ -1232,31 +1135,36 @@ * @param {string} dsn The public Sentry DSN * @param {object} options Optional set of of global options [optional] * @return {Raven} */ config: function(dsn, options) { - var uri = parseUri(dsn), + if (!dsn) return Raven; + + var uri = parseDSN(dsn), lastSlash = uri.path.lastIndexOf('/'), path = uri.path.substr(1, lastSlash); - if (options && options.ignoreErrors && window.console && console.warn) { - console.warn('DeprecationWarning: `ignoreErrors` is going to be removed soon.'); - } - // 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.'); + globalOptions.ignoreErrors.push('Script error'); + // join regexp rules into one big rule + globalOptions.ignoreErrors = joinRegExp(globalOptions.ignoreErrors); + globalOptions.ignoreUrls = globalOptions.ignoreUrls.length ? joinRegExp(globalOptions.ignoreUrls) : false; + globalOptions.whitelistUrls = globalOptions.whitelistUrls.length ? joinRegExp(globalOptions.whitelistUrls) : false; + globalOptions.includePaths = joinRegExp(globalOptions.includePaths); + globalKey = uri.user; - globalProject = ~~uri.path.substr(lastSlash + 1); + globalProject = uri.path.substr(lastSlash + 1); // assemble the endpoint from the uri pieces globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '') + '/' + path + 'api/' + globalProject + '/store/'; @@ -1264,13 +1172,21 @@ if (uri.protocol) { globalServer = uri.protocol + ':' + globalServer; } if (globalOptions.fetchContext) { - TK.remoteFetching = true; + TraceKit.remoteFetching = true; } + if (globalOptions.linesOfContext) { + TraceKit.linesOfContext = globalOptions.linesOfContext; + } + + TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors; + + setAuthQueryString(); + // return for chaining return Raven; }, /* @@ -1280,14 +1196,14 @@ * to the way TraceKit is set up. * * @return {Raven} */ install: function() { - if (!isSetup()) return; + if (isSetup()) { + TraceKit.report.subscribe(handleStackInfo); + } - TK.report.subscribe(handleStackInfo); - return Raven; }, /* * Wrap code within a context so Raven can capture errors @@ -1302,44 +1218,83 @@ args = func || []; func = options; options = undefined; } - Raven.wrap(options, func).apply(this, args); + return Raven.wrap(options, func).apply(this, args); }, /* * Wrap code within a context and returns back a new function to be executed * * @param {object} options A specific set of options for this context [optional] * @param {function} func The function to be wrapped in a new context * @return {function} The newly wrapped functions with a context */ wrap: function(options, func) { + // 1 argument has been passed, and it's not a function + // so just return it + if (isUndefined(func) && !isFunction(options)) { + return options; + } + // options is optional if (isFunction(options)) { func = options; options = undefined; } - return function() { + // At this point, we've passed along 2 arguments, and the second one + // is not a function either, so we'll just return the second argument. + if (!isFunction(func)) { + return func; + } + + // We don't wanna wrap it twice! + if (func.__raven__) { + return func; + } + + function wrapped() { + var args = [], i = arguments.length, + deep = !options || options && options.deep !== false; + // Recursively wrap all of a function's arguments that are + // functions themselves. + + while(i--) args[i] = deep ? Raven.wrap(options, arguments[i]) : arguments[i]; + try { - func.apply(this, arguments); + /*jshint -W040*/ + return func.apply(this, args); } catch(e) { Raven.captureException(e, options); throw e; } - }; + } + + // copy over properties of the old function + for (var property in func) { + if (func.hasOwnProperty(property)) { + wrapped[property] = func[property]; + } + } + + // Signal that this function has been wrapped already + // for both debugging and to prevent it to being wrapped twice + wrapped.__raven__ = true; + wrapped.__inner__ = func; + + return wrapped; }, /* * Uninstalls the global error handler. * * @return {Raven} */ uninstall: function() { - TK.report.unsubscribe(handleStackInfo); + TraceKit.report.uninstall(); return Raven; }, /* @@ -1349,21 +1304,22 @@ * @param {object} options A specific set of options for this error [optional] * @return {Raven} */ captureException: function(ex, options) { // If a string is passed through, recall as a message - if (typeof ex === 'string') { - return Raven.captureMessage(ex, options); - } + if (isString(ex)) return Raven.captureMessage(ex, options); + // Store the raw exception object for potential debugging and introspection + lastCapturedException = ex; + // 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); + TraceKit.report(ex, options); } catch(ex1) { if(ex !== ex1) { throw ex1; } } @@ -1379,11 +1335,11 @@ * @return {Raven} */ captureMessage: function(msg, options) { // Fire away! send( - arrayMerge({ + objectMerge({ message: msg }, options) ); return Raven; @@ -1397,82 +1353,163 @@ */ setUser: function(user) { globalUser = user; return Raven; + }, + + /* + * Get the latest raw exception that was captured by Raven. + * + * @return {error} + */ + lastException: function() { + return lastCapturedException; + }, + + /* + * Get the last event id + * + * @return {string} + */ + lastEventId: function() { + return lastEventId; } }; -var uriKeys = 'source protocol authority userInfo user password host port relative path directory file query anchor'.split(' '), - uriPattern = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; +function triggerEvent(eventType, options) { + var event, key; + options = options || {}; + + eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1); + + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventType, true, true); + } else { + event = document.createEventObject(); + event.eventType = eventType; + } + + for (key in options) if (options.hasOwnProperty(key)) { + event[key] = options[key]; + } + + if (document.createEvent) { + // IE9 if standards + document.dispatchEvent(event); + } else { + // IE8 regardless of Quirks or Standards + // IE9 if quirks + try { + document.fireEvent('on' + event.eventType.toLowerCase(), event); + } catch(e) {} + } +} + +var dsnKeys = 'source protocol user pass host port path'.split(' '), + dsnPattern = /^(?:(\w+):)?\/\/(\w+)(:\w+)?@([\w\.-]+)(?::(\d+))?(\/.*)/; + +function RavenConfigError(message) { + this.name = 'RavenConfigError'; + this.message = message; +} +RavenConfigError.prototype = new Error(); +RavenConfigError.prototype.constructor = RavenConfigError; + /**** Private functions ****/ -function parseUri(str) { - var m = uriPattern.exec(str), - uri = {}, - i = 14; +function parseDSN(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; - while (i--) uri[uriKeys[i]] = m[i] || ''; + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch(e) { + throw new RavenConfigError('Invalid DSN: ' + str); + } - return uri; + if (dsn.pass) + throw new RavenConfigError('Do not specify your private key in the DSN!'); + + return dsn; } function isUndefined(what) { return typeof what === 'undefined'; } function isFunction(what) { return typeof what === 'function'; } +function isString(what) { + return typeof what === 'string'; +} + +function isEmptyObject(what) { + for (var k in what) return false; + return true; +} + +/** + * hasKey, a better form of hasOwnProperty + * Example: hasKey(MainHostObject, property) === true/false + * + * @param {Object} host object to check property + * @param {string} key to check + */ +function hasKey(object, key) { + return Object.prototype.hasOwnProperty.call(object, key); +} + function each(obj, callback) { var i, j; if (isUndefined(obj.length)) { 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]); + j = obj.length; + if (j) { + for (i = 0; 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 setAuthQueryString() { + authQueryString = + '?sentry_version=4' + + '&sentry_client=raven-js/' + Raven.VERSION + + '&sentry_key=' + globalKey; } + function handleStackInfo(stackInfo, options) { - var frames = [], - i = 0, - j, frame; + var frames = []; - if (stackInfo.stack && (j = stackInfo.stack.length)) { - for (; i < j; i++) { - frame = normalizeFrame(stackInfo.stack[i]); + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = normalizeFrame(stack); if (frame) { frames.push(frame); } - } + }); } + triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); + processException( stackInfo.name, stackInfo.message, stackInfo.url, stackInfo.lineno, @@ -1488,18 +1525,26 @@ var normalized = { filename: frame.url, lineno: frame.line, colno: frame.column, 'function': frame.func || '?' - }, context = extractContextFromFrame(frame); + }, context = extractContextFromFrame(frame), i; if (context) { - var i = 3, keys = ['pre_context', 'context_line', 'post_context']; + var keys = ['pre_context', 'context_line', 'post_context']; + i = 3; while (i--) normalized[keys[i]] = context[i]; } - normalized.in_app = !/(Raven|TraceKit)\./.test(normalized['function']); + normalized.in_app = !( // determine if an exception came from outside of our app + // first we check the global includePaths list. + !globalOptions.includePaths.test(normalized.filename) || + // Now we check for fun, if the function name is Raven or TraceKit + /(Raven|TraceKit)\./.test(normalized['function']) || + // finally, we do a last ditch effort and check for raven.min.js + /raven\.(min\.)js$/.test(normalized.filename) + ); return normalized; } function extractContextFromFrame(frame) { @@ -1542,101 +1587,148 @@ } 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; - } - } + // Sometimes an exception is getting logged in Sentry as + // <no message value> + // This can only mean that the message was falsey since this value + // is hardcoded into Sentry itself. + // At this point, if the message is falsey, we bail since it's useless + if (type === 'Error' && !message) return; + if (globalOptions.ignoreErrors.test(message)) return; + if (frames && frames.length) { + fileurl = frames[0].filename || fileurl; + // Sentry expects frames oldest to newest + // and JS sends them as newest to oldest + frames.reverse(); stacktrace = {frames: frames}; - fileurl = fileurl || frames[0].filename; } else if (fileurl) { stacktrace = { frames: [{ filename: fileurl, - lineno: lineno + lineno: lineno, + in_app: true }] }; } - i = globalOptions.ignoreUrls.length; - while (i--) { - if (globalOptions.ignoreUrls[i].test(fileurl)) { - return; - } - } + // Truncate the message to a max of characters + message = truncate(message, 100); + if (globalOptions.ignoreUrls && globalOptions.ignoreUrls.test(fileurl)) return; + if (globalOptions.whitelistUrls && !globalOptions.whitelistUrls.test(fileurl)) return; + label = lineno ? message + ' at ' + lineno : message; // Fire away! send( - arrayMerge({ - 'sentry.interfaces.Exception': { + objectMerge({ + // sentry.interfaces.Exception + exception: { type: type, value: message }, - 'sentry.interfaces.Stacktrace': stacktrace, + // sentry.interfaces.Stacktrace + stacktrace: stacktrace, culprit: fileurl, message: label }, options) ); } -function arrayMerge(arr1, arr2) { - if (!arr2) { - return arr1; +function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; } - each(arr2, function(key, value){ - arr1[key] = value; + each(obj2, function(key, value){ + obj1[key] = value; }); - return arr1; + return obj1; } +function truncate(str, max) { + return str.length <= max ? str : str.substr(0, max) + '\u2026'; +} + function getHttpData() { var http = { - url: window.location.href, + url: document.location.href, headers: { 'User-Agent': navigator.userAgent } }; - if (window.document.referrer) { - http.headers.Referer = window.document.referrer; + if (document.referrer) { + http.headers.Referer = document.referrer; } return http; } function send(data) { if (!isSetup()) return; - data = arrayMerge({ + data = objectMerge({ project: globalProject, logger: globalOptions.logger, site: globalOptions.site, platform: 'javascript', - 'sentry.interfaces.Http': getHttpData() + // sentry.interfaces.Http + request: getHttpData() }, data); - if (globalUser) data['sentry.interfaces.User'] = globalUser; + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge(globalOptions.tags, data.tags); + data.extra = objectMerge(globalOptions.extra, data.extra); + // If there are no tags/extra, strip the key from the payload alltogther. + if (isEmptyObject(data.tags)) delete data.tags; + if (isEmptyObject(data.extra)) delete data.extra; + + if (globalUser) { + // sentry.interfaces.User + data.user = globalUser; + } + if (isFunction(globalOptions.dataCallback)) { data = globalOptions.dataCallback(data); } + // Check if the request should be filtered or not + if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { + return; + } + + // Send along an event_id if not explicitly passed. + // This event_id can be used to reference the error within Sentry itself. + // Set lastEventId after we know the error should actually be sent + lastEventId = data.event_id || (data.event_id = uuid4()); + makeRequest(data); } + function makeRequest(data) { - new Image().src = globalServer + getAuthQueryString() + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); + var img = new Image(), + src = globalServer + authQueryString + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); + + img.onload = function success() { + triggerEvent('success', { + data: data, + src: src + }); + }; + img.onerror = img.onabort = function failure() { + triggerEvent('failure', { + data: data, + src: src + }); + }; + img.src = src; } function isSetup() { if (!hasJSON) return false; // needs JSON support if (!globalServer) { @@ -1646,14 +1738,88 @@ return false; } return true; } +function joinRegExp(patterns) { + // Combine an array of regular expressions and strings into one large regexp + // Be mad. + var sources = [], + i = 0, len = patterns.length, + pattern; + + for (; i < len; i++) { + pattern = patterns[i]; + if (isString(pattern)) { + // If it's a string, we need to escape it + // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1")); + } else if (pattern && pattern.source) { + // If it's a regexp already, we want to extract the source + sources.push(pattern.source); + } + // Intentionally skip other cases + } + return new RegExp(sources.join('|'), 'i'); +} + +// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 +function uuid4() { + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, + v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); +} + +function afterLoad() { + // Attempt to initialize Raven on load + var RavenConfig = window.RavenConfig; + if (RavenConfig) { + Raven.config(RavenConfig.dsn, RavenConfig.config).install(); + } +} +afterLoad(); + // Expose Raven to the world window.Raven = Raven; // AMD if (typeof define === 'function' && define.amd) { - define(function() { return Raven; }); + define('raven', [], function() { return Raven; }); } -})(window); +})(this); + +/** + * native plugin + * + * Extends support for global error handling for asynchronous browser + * functions. Adopted from Closure Library's errorhandler.js + */ +;(function extendToAsynchronousCallbacks(window, Raven) { +"use strict"; + +var _helper = function _helper(fnName) { + var originalFn = window[fnName]; + window[fnName] = function ravenAsyncExtension() { + // Make a copy of the arguments + var args = [].slice.call(arguments); + var originalCallback = args[0]; + if (typeof (originalCallback) === 'function') { + args[0] = Raven.wrap(originalCallback); + } + // IE < 9 doesn't support .call/.apply on setInterval/etTimeout, but it + // also only supports 2 argument and doesn't care what this" is, so we + // can just call the original function directly. + if (originalFn.apply) { + return originalFn.apply(this, args); + } else { + return originalFn(args[0], args[1]); + } + }; +}; + +_helper('setTimeout'); +_helper('setInterval'); + +}(this, Raven));