commit/galaxy-central: carlfeberhard: UI: update Raven.js to 1.1.16 (463f68f)
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/d7b26e59a22e/ Changeset: d7b26e59a22e User: carlfeberhard Date: 2015-01-22 16:29:07+00:00 Summary: UI: update Raven.js to 1.1.16 (463f68f) Affected #: 4 files diff -r f6e5390c830084b629ec1a5efb95fe29fff03ceb -r d7b26e59a22e01288135f22bde90ca5259647837 client/GruntFile.js --- a/client/GruntFile.js +++ b/client/GruntFile.js @@ -71,7 +71,7 @@ "jquery-migrate-official": [ "index.js", "jquery/jquery.migrate.js" ], "traceKit": [ "tracekit.js", "tracekit.js" ], - //"ravenjs": [ "dist/raven.js", "raven.js" ], + "ravenjs": [ "dist/raven.js", "raven.js" ], //"underscore": [ "underscore.js", "underscore.js" ], //"backbone": [ "backbone.js", "backbone/backbone.js" ], diff -r f6e5390c830084b629ec1a5efb95fe29fff03ceb -r d7b26e59a22e01288135f22bde90ca5259647837 client/galaxy/scripts/libs/raven.js --- a/client/galaxy/scripts/libs/raven.js +++ b/client/galaxy/scripts/libs/raven.js @@ -1,11 +1,1089 @@ -(function(){ +/*! Raven.js 1.1.16 (463f68f) | github.com/getsentry/raven-js */ -// Raven.js -// -// Originally based on the Arecibo JavaScript client. -// -// Requires: -// * TraceKit (included in the full and minified distribution files) +/* + * Includes TraceKit + * https://github.com/getsentry/TraceKit + * + * 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 +*/ + +var TraceKit = { + remoteFetching: false, + collectWindowErrors: true, + // 3 lines before, the offending line, 3 lines after + linesOfContext: 7 +}; + +// global reference to slice +var _slice = [].slice; +var UNKNOWN_FUNCTION = '?'; + + +/** + * 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 + */ +TraceKit.wrap = function traceKitWrapper(func) { + function wrapped() { + try { + return func.apply(this, arguments); + } catch (e) { + TraceKit.report(e); + throw e; + } + } + return wrapped; +}; + +/** + * TraceKit.report: cross-browser processing of unhandled exceptions + * + * Syntax: + * TraceKit.report.subscribe(function(stackInfo) { ... }) + * TraceKit.report.unsubscribe(function(stackInfo) { ... }) + * TraceKit.report(exception) + * try { ...code... } catch(ex) { TraceKit.report(ex); } + * + * Supports: + * - Firefox: full stack trace with line numbers, plus column number + * on top frame; column number is not guaranteed + * - Opera: full stack trace with line and column numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * - IE: line and column number for the top frame only; some frames + * may be missing, and column number is not guaranteed + * + * In theory, TraceKit should work on all of the following versions: + * - IE5.5+ (only 8.0 tested) + * - Firefox 0.9+ (only 3.5+ tested) + * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require + * Exceptions Have Stacktrace to be enabled in opera:config) + * - Safari 3+ (only 4+ tested) + * - Chrome 1+ (only 5+ tested) + * - Konqueror 3.5+ (untested) + * + * Requires TraceKit.computeStackTrace. + * + * Tries to catch all unhandled exceptions and report them to the + * subscribed handlers. Please note that TraceKit.report will rethrow the + * exception. This is REQUIRED in order to get a useful stack trace in IE. + * If the exception does not reach the top of the browser, you will only + * get a stack trace from the point where TraceKit.report was called. + * + * 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. + * @param {Function} handler + */ + function unsubscribe(handler) { + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + } + } + } + + /** + * Remove all crash handlers. + */ + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } + + /** + * Dispatch stack information to all handlers. + * @param {Object.<string, *>} stack + */ + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (hasKey(handlers, i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } + } + + if (exception) { + throw exception; + } + } + + 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. + */ + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { + var stack = null; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); + 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, + 'column': colNo + }; + location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); + location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); + stack = { + 'message': message, + 'url': document.location.href, + 'stack': [location] + }; + notifyHandlers(stack, true); + } + + 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, rethrow) { + var args = _slice.call(arguments, 1); + if (lastExceptionStack) { + if (lastException === ex) { + return; // already caught by an inner catch block, ignore + } else { + 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) { + processLastException(); + } + }, (stack.incomplete ? 2000 : 0)); + + 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 + * + * Syntax: + * s = TraceKit.computeStackTrace.ofCaller([depth]) + * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) + * Returns: + * s.name - exception name + * s.message - exception message + * s.stack[i].url - JavaScript or HTML file URL + * 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# + * + * 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 + * - Opera 9-: full stack trace with line numbers + * - Chrome: full stack trace with line and column numbers + * - Safari: line and column number for the topmost stacktrace element + * only + * - IE: no line numbers whatsoever + * + * Tries to guess names of anonymous functions by looking for assignments + * in the source code. In IE and Safari, we have to guess source file names + * by searching for function bodies inside all page scripts. This will not + * work for scripts that are loaded cross-domain. + * Here be dragons: some function names may be guessed incorrectly, and + * duplicate functions may be mismatched. + * + * TraceKit.computeStackTrace should only be used for tracing purposes. + * Logging of unhandled exceptions should be done with TraceKit.report, + * which builds on top of TraceKit.computeStackTrace and provides better + * IE support by utilizing the window.onerror event to retrieve information + * about the top of the stack. + * + * Note: In IE and Safari, no stack trace is recorded on the Error object, + * so computeStackTrace instead walks its *own* chain of callers. + * This means that: + * * in Safari, some methods may be missing from the stack trace; + * * in IE, the topmost function in the stack trace will always be the + * caller of computeStackTrace. + * + * This is okay for tracing (because you are likely to be calling + * computeStackTrace from the function you want to be the topmost element + * of the stack trace anyway), but not okay for logging unhandled + * exceptions (because your catch block will likely be far away from the + * inner function that actually caused the exception). + * + * Tracing example: + * function trace(message) { + * var stackInfo = TraceKit.computeStackTrace.ofCaller(); + * var data = message + "\n"; + * for(var i in stackInfo.stack) { + * var item = stackInfo.stack[i]; + * data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n"; + * } + * if (window.console) + * console.info(data); + * else + * alert(data); + * } + */ +TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + var debug = false, + sourceCache = {}; + + /** + * Attempts to retrieve source code via XMLHttpRequest, which is used + * to look up anonymous function names. + * @param {string} url URL of source code. + * @return {string} Source contents. + */ + function loadSource(url) { + if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. + return ''; + } + try { + 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; + } catch (e) { + return ''; + } + } + + /** + * Retrieves source code from the source code cache. + * @param {string} url URL of source code. + * @return {Array.<string>} Source contents. + */ + function getSource(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); + } + sourceCache[url] = source ? source.split('\n') : []; + } + + return sourceCache[url]; + } + + /** + * Tries to use an externally loaded copy of source code to determine + * the name of a function by looking at the name of the variable it was + * assigned to, if any. + * @param {string} url URL of source code. + * @param {(string|number)} lineNo Line number in source code. + * @return {string} The function name, if discoverable. + */ + function guessFunctionName(url, lineNo) { + var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/, + reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/, + line = '', + maxLines = 10, + source = getSource(url), + m; + + if (!source.length) { + return UNKNOWN_FUNCTION; + } + + // 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 ((m = reGuessFunction.exec(line))) { + return m[1]; + } else if ((m = reFunctionArgNames.exec(line))) { + return m[1]; + } + } + } + + return UNKNOWN_FUNCTION; + } + + /** + * Retrieves the surrounding lines from where an exception occurred. + * @param {string} url URL of source code. + * @param {(string|number)} line Line number in source code to centre + * around for context. + * @return {?Array.<string>} Lines of source code. + */ + function gatherContext(url, line) { + var source = getSource(url); + + if (!source.length) { + return null; + } + + var context = [], + // linesBefore & linesAfter are inclusive with the offending line. + // if linesOfContext is even, there will be one extra line + // *before* the offending line. + linesBefore = Math.floor(TraceKit.linesOfContext / 2), + // Add one extra line if linesOfContext is odd + linesAfter = linesBefore + (TraceKit.linesOfContext % 2), + start = Math.max(0, line - linesBefore - 1), + 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])) { + context.push(source[i]); + } + } + + return context.length > 0 ? context : null; + } + + /** + * Escapes special characters, except for whitespace, in a string to be + * used inside a regular expression as a string literal. + * @param {string} text The string. + * @return {string} The escaped string literal. + */ + function escapeRegExp(text) { + return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); + } + + /** + * Escapes special characters in a string to be used inside a regular + * expression as a string literal. Also ensures that HTML entities will + * be matched the same as their literal friends. + * @param {string} body The string. + * @return {string} The escaped string. + */ + function escapeCodeAsRegExpForMatchingInsideHTML(body) { + return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); + } + + /** + * Determines where a code fragment occurs in the source code. + * @param {RegExp} re The function definition. + * @param {Array.<string>} urls A list of URLs to search. + * @return {?Object.<string, (string|number)>} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceInUrls(re, urls) { + var source, m; + for (var i = 0, j = urls.length; i < j; ++i) { + // console.log('searching', urls[i]); + if ((source = getSource(urls[i])).length) { + source = source.join('\n'); + if ((m = re.exec(source))) { + // console.log('Found function in ' + urls[i]); + + return { + 'url': urls[i], + 'line': source.substring(0, m.index).split('\n').length, + 'column': m.index - source.lastIndexOf('\n', m.index) - 1 + }; + } + } + } + + // console.log('no match'); + + return null; + } + + /** + * Determines at which column a code fragment occurs on a line of the + * source code. + * @param {string} fragment The code fragment. + * @param {string} url The URL to search. + * @param {(string|number)} line The line number to examine. + * @return {?number} The column number. + */ + function findSourceInLine(fragment, url, line) { + var source = getSource(url), + re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), + m; + + line -= 1; + + if (source && source.length > line && (m = re.exec(source[line]))) { + return m.index; + } + + return null; + } + + /** + * Determines where a function was defined within the source code. + * @param {(Function|string)} func A function reference or serialized + * function definition. + * @return {?Object.<string, (string|number)>} An object containing + * the url, line, and column number of the defined function. + */ + function findSourceByFunctionBody(func) { + var urls = [window.location.href], + scripts = document.getElementsByTagName('script'), + body, + code = '' + func, + codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, + re, + parts, + result; + + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + if (script.src) { + urls.push(script.src); + } + } + + 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 + // 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*'); + + body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+'); + re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}'); + } + + // look for a normal function definition + if ((result = findSourceInUrls(re, urls))) { + return result; + } + + // look for an old-school event handler function + if ((parts = eventRE.exec(code))) { + var event = parts[1]; + body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); + + // look for a function defined in HTML as an onXXX handler + re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); + + if ((result = findSourceInUrls(re, urls[0]))) { + return result; + } + + // look for ??? + re = new RegExp(body); + + if ((result = findSourceInUrls(re, urls))) { + return result; + } + } + + return null; + } + + // Contents of Exception in various browsers. + // + // SAFARI: + // ex.message = Can't find variable: qq + // ex.line = 59 + // ex.sourceId = 580238192 + // ex.sourceURL = http://... + // ex.expressionBeginOffset = 96 + // ex.expressionCaretOffset = 98 + // ex.expressionEndOffset = 98 + // ex.name = ReferenceError + // + // 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 + // ex.name = ReferenceError + // ex.type = not_defined + // ex.arguments = ['aa'] + // ex.stack = ...stack trace... + // + // INTERNET EXPLORER: + // ex.message = ... + // ex.name = ReferenceError + // + // OPERA: + // ex.message = ...message... (see the example below) + // ex.name = ReferenceError + // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) + // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' + + /** + * Computes stack trace information from the stack property. + * Chrome and Gecko use this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStackProp(ex) { + if (!ex.stack) { + return null; + } + + var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|https?|chrome-extension):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i, + lines = ex.stack.split('\n'), + stack = [], + parts, + element, + reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = gecko.exec(lines[i]))) { + element = { + 'url': parts[3], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'args': parts[2] ? parts[2].split(',') : '', + 'line': +parts[4], + 'column': parts[5] ? +parts[5] : null + }; + } else if ((parts = chrome.exec(lines[i]))) { + element = { + 'url': parts[2], + 'func': parts[1] || UNKNOWN_FUNCTION, + 'line': +parts[3], + 'column': parts[4] ? +parts[4] : null + }; + } else { + continue; + } + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + + if (element.line) { + element.context = gatherContext(element.url, element.line); + } + + stack.push(element); + } + + 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 { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * Computes stack trace information from the stacktrace property. + * Opera 10 uses this property. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceFromStacktraceProp(ex) { + // Access and store the stacktrace property before doing ANYTHING + // else to it because Opera is not very good at providing it + // reliably in other circumstances. + var stacktrace = ex.stacktrace; + + var testRE = / line (\d+), column (\d+) in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i, + lines = stacktrace.split('\n'), + stack = [], + parts; + + for (var i = 0, j = lines.length; i < j; i += 2) { + if ((parts = testRE.exec(lines[i]))) { + var element = { + 'line': +parts[1], + 'column': +parts[2], + 'func': parts[3] || parts[4], + 'args': parts[5] ? parts[5].split(',') : [], + 'url': parts[6] + }; + + if (!element.func && element.line) { + element.func = guessFunctionName(element.url, element.line); + } + if (element.line) { + try { + element.context = gatherContext(element.url, element.line); + } catch (exc) {} + } + + if (!element.context) { + element.context = [lines[i + 1]]; + } + + stack.push(element); + } + } + + if (!stack.length) { + return null; + } + + return { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * NOT TESTED. + * Computes stack trace information from an error message that includes + * the stack trace. + * Opera 9 and earlier use this method if the option to show stack + * traces is turned on in opera:config. + * @param {Error} ex + * @return {?Object.<string, *>} Stack information. + */ + function computeStackTraceFromOperaMultiLineMessage(ex) { + // Opera includes a stack trace into the exception message. An example is: + // + // Statement on line 3: Undefined variable: undefinedFunc + // Backtrace: + // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz + // undefinedFunc(a); + // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy + // zzz(x, y, z); + // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx + // yyy(a, a, a); + // Line 1 of function script + // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } + // ... + + var lines = ex.message.split('\n'); + if (lines.length < 4) { + return null; + } + + 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 (hasKey(scripts, i) && !scripts[i].src) { + inlineScriptBlocks.push(scripts[i]); + } + } + + for (i = 2, len = lines.length; i < len; i += 2) { + var item = null; + if ((parts = lineRE1.exec(lines[i]))) { + item = { + 'url': parts[2], + 'func': parts[3], + 'line': +parts[1] + }; + } else if ((parts = lineRE2.exec(lines[i]))) { + item = { + 'url': parts[3], + 'func': parts[4] + }; + var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block + var script = inlineScriptBlocks[parts[2] - 1]; + if (script) { + source = getSource(item.url); + if (source) { + source = source.join('\n'); + var pos = source.indexOf(script.innerText); + if (pos >= 0) { + item.line = relativeLine + source.substring(0, pos).split('\n').length; + } + } + } + } else if ((parts = lineRE3.exec(lines[i]))) { + var url = window.location.href.replace(/#.*$/, ''), + line = parts[1]; + var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[i + 1])); + source = findSourceInUrls(re, [url]); + item = { + 'url': url, + 'line': source ? source.line : line, + 'func': '' + }; + } + + if (item) { + if (!item.func) { + item.func = guessFunctionName(item.url, item.line); + } + var context = gatherContext(item.url, item.line); + var midline = (context ? context[Math.floor(context.length / 2)] : null); + if (context && midline.replace(/^\s*/, '') === lines[i + 1].replace(/^\s*/, '')) { + item.context = context; + } else { + // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url); + item.context = [lines[i + 1]]; + } + stack.push(item); + } + } + if (!stack.length) { + return null; // could not parse multiline exception message as Opera stack trace + } + + return { + 'name': ex.name, + 'message': lines[0], + 'url': document.location.href, + 'stack': stack + }; + } + + /** + * Adds information about the first frame to incomplete stack traces. + * Safari and IE require this to get complete data on the first frame. + * @param {Object.<string, *>} stackInfo Stack trace information from + * one of the compute* methods. + * @param {string} url The URL of the script that caused an error. + * @param {(number|string)} lineNo The line number of the script that + * caused an error. + * @param {string=} message The error generated by the browser, which + * hopefully contains the name of the object that caused the error. + * @return {boolean} Whether or not the stack information was + * augmented. + */ + function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { + var initial = { + 'url': url, + 'line': lineNo + }; + + if (initial.url && initial.line) { + stackInfo.incomplete = false; + + if (!initial.func) { + initial.func = guessFunctionName(initial.url, initial.line); + } + + if (!initial.context) { + initial.context = gatherContext(initial.url, initial.line); + } + + var reference = / '([^']+)' /.exec(message); + if (reference) { + initial.column = findSourceInLine(reference[1], initial.url, initial.line); + } + + if (stackInfo.stack.length > 0) { + if (stackInfo.stack[0].url === initial.url) { + if (stackInfo.stack[0].line === initial.line) { + return false; // already in stack trace + } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) { + stackInfo.stack[0].line = initial.line; + stackInfo.stack[0].context = initial.context; + return false; + } + } + } + + stackInfo.stack.unshift(initial); + stackInfo.partial = true; + return true; + } else { + stackInfo.incomplete = true; + } + + return false; + } + + /** + * Computes stack trace information by walking the arguments.caller + * chain at the time the exception occurred. This will cause earlier + * frames to be missed but is the only way to get any stack trace in + * Safari and IE. The top frame is restored by + * {@link augmentStackTraceWithInitialElement}. + * @param {Error} ex + * @return {?Object.<string, *>} Stack trace information. + */ + function computeStackTraceByWalkingCallerChain(ex, depth) { + var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i, + stack = [], + funcs = {}, + recursion = false, + parts, + item, + source; + + for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) { + if (curr === computeStackTrace || curr === TraceKit.report) { + // console.log('skipping internal function'); + continue; + } + + item = { + 'url': null, + 'func': UNKNOWN_FUNCTION, + 'line': null, + 'column': null + }; + + if (curr.name) { + item.func = curr.name; + } else if ((parts = functionName.exec(curr.toString()))) { + item.func = parts[1]; + } + + if ((source = findSourceByFunctionBody(curr))) { + item.url = source.url; + item.line = source.line; + + if (item.func === UNKNOWN_FUNCTION) { + item.func = guessFunctionName(item.url, item.line); + } + + var reference = / '([^']+)' /.exec(ex.message || ex.description); + if (reference) { + item.column = findSourceInLine(reference[1], source.url, source.line); + } + } + + if (funcs['' + curr]) { + recursion = true; + }else{ + funcs['' + curr] = true; + } + + stack.push(item); + } + + if (depth) { + // console.log('depth is ' + depth); + // console.log('stack is ' + stack.length); + stack.splice(0, depth); + } + + var result = { + 'name': ex.name, + 'message': ex.message, + 'url': document.location.href, + 'stack': stack + }; + augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description); + return result; + } + + /** + * Computes a stack trace for an exception. + * @param {Error} ex + * @param {(string|number)=} depth + */ + function computeStackTrace(ex, depth) { + var stack = null; + depth = (depth == null ? 0 : +depth); + + try { + // This must be tried first because Opera 10 *destroys* + // its stacktrace property if you try to access the stack + // property first!! + stack = computeStackTraceFromStacktraceProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceFromStackProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceFromOperaMultiLineMessage(ex); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + try { + stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { + return stack; + } + } catch (e) { + if (debug) { + throw e; + } + } + + return {}; + } + + /** + * Logs a stacktrace starting from the previous call and working down. + * @param {(number|string)=} depth How many frames deep to trace. + * @return {Object.<string, *>} Stack trace information. + */ + function computeStackTraceOfCaller(depth) { + depth = (depth == null ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame + try { + throw new Error(); + } catch (ex) { + return computeStackTrace(ex, depth + 1); + } + } + + computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; + computeStackTrace.guessFunctionName = guessFunctionName; + computeStackTrace.gatherContext = gatherContext; + computeStackTrace.ofCaller = computeStackTraceOfCaller; + + return computeStackTrace; +}()); 'use strict'; @@ -13,7 +1091,9 @@ // 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, @@ -21,24 +1101,31 @@ globalOptions = { logger: 'javascript', ignoreErrors: [], - ignoreUrls: [] - }; - -var TK = TraceKit.noConflict(); - -// Disable Tracekit's remote fetching by default -TK.remoteFetching = false; + ignoreUrls: [], + whitelistUrls: [], + includePaths: [], + collectWindowErrors: true, + tags: {}, + extra: {} + }, + authQueryString, + isRavenInstalled = false; /* - * The core Raven object + * The core Raven singleton + * + * @this {Raven} */ var Raven = { - VERSION: '@VERSION', + VERSION: '1.1.16', + + debug: true, /* - * Raven.noConflict() + * Allow multiple versions of Raven to be installed. + * Strip Raven from the global context and returns the instance. * - * Allow multiple versions of Raven to be installed. + * @return {Raven} */ noConflict: function() { window.Raven = _Raven; @@ -46,12 +1133,20 @@ }, /* - * Raven.config() + * Configure Raven with a DSN and extra options * - * Configure raven with a DSN and extra options + * @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 (globalServer) { + logDebug('error', 'Error: Raven has already been configured'); + return Raven; + } + if (!dsn) return Raven; + + var uri = parseDSN(dsn), lastSlash = uri.path.lastIndexOf('/'), path = uri.path.substr(1, lastSlash); @@ -65,97 +1160,177 @@ // "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'); + + // Other variants of external script errors: + globalOptions.ignoreErrors.push('Javascript error: Script error on line 0'); + globalOptions.ignoreErrors.push('Javascript error: Script error. on line 0'); + + // 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.protocol + '://' + uri.host + + globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : '') + '/' + path + 'api/' + globalProject + '/store/'; + 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; }, /* - * Raven.install() - * * Installs a global window.onerror error handler * to capture and report uncaught exceptions. + * At this point, install() is required to be called due + * to the way TraceKit is set up. + * + * @return {Raven} */ install: function() { - if (!isSetup()) return; - - TK.report.subscribe(handleStackInfo); + if (isSetup() && !isRavenInstalled) { + TraceKit.report.subscribe(handleStackInfo); + isRavenInstalled = true; + } return Raven; }, /* - * Raven.context() - * * Wrap code within a context so Raven can capture errors * reliably across domains that is executed immediately. + * + * @param {object} options A specific set of options for this context [optional] + * @param {function} func The callback to be immediately executed within the context + * @param {array} args An array of arguments to be called with the callback [optional] */ context: function(options, func, args) { if (isFunction(options)) { - args = func; + args = func || []; func = options; options = undefined; } - Raven.wrap(options, func).apply(this, args); + return Raven.wrap(options, func).apply(this, args); }, - /* Raven.wrap() + /* + * Wrap code within a context and returns back a new function to be executed * - * 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 (hasKey(func, 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; }, /* - * Raven.uninstall() + * Uninstalls the global error handler. * - * Uninstalls the global error handler. + * @return {Raven} */ uninstall: function() { - TK.report.unsubscribe(handleStackInfo); + TraceKit.report.uninstall(); + isRavenInstalled = false; return Raven; }, /* - * Raven.captureException() + * Manually capture an exception and send it over to Sentry * - * Manually capture an exception and send it over to Sentry + * @param {error} ex An exception to be logged + * @param {object} options A specific set of options for this error [optional] + * @return {Raven} */ captureException: function(ex, options) { + // If not an Error is passed through, recall as a message instead + if (!(ex instanceof Error)) 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; @@ -166,15 +1341,17 @@ }, /* - * Raven.captureMessage() + * Manually send a message to Sentry * - * Manually send a message to Sentry + * @param {string} msg A plain message to be captured in Sentry + * @param {object} options A specific set of options for this message [optional] + * @return {Raven} */ captureMessage: function(msg, options) { // Fire away! send( - arrayMerge({ - message: msg + objectMerge({ + message: msg + '' // Make sure it's actually a string }, options) ); @@ -182,29 +1359,119 @@ }, /* - * Raven.setUser() + * Set/clear a user to be sent along with the payload. * - * Set/clear a user to be sent along with the payload. + * @param {object} user An object representing user data [optional] + * @return {Raven} */ - setUser: function(user) { + setUserContext: function(user) { globalUser = user; return Raven; + }, + + /* + * Set extra attributes to be sent along with the payload. + * + * @param {object} extra An object representing extra data [optional] + * @return {Raven} + */ + setExtraContext: function(extra) { + globalOptions.extra = extra || {}; + + return Raven; + }, + + /* + * Set tags to be sent along with the payload. + * + * @param {object} tags An object representing tags [optional] + * @return {Raven} + */ + setTagsContext: function(tags) { + globalOptions.tags = tags || {}; + + 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*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; +Raven.setUser = Raven.setUserContext; // To be deprecated + +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 (hasKey(options, 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) { @@ -215,48 +1482,70 @@ 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 (obj.length === undefined) { + if (isUndefined(obj.length)) { for (i in obj) { - if (obj.hasOwnProperty(i)) { + if (hasKey(obj, 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; +function setAuthQueryString() { + authQueryString = + '?sentry_version=4' + + '&sentry_client=raven-js/' + Raven.VERSION + + '&sentry_key=' + globalKey; +} - var qs = [ - 'sentry_version=2.0', - 'sentry_client=raven-js/' + Raven.VERSION - ]; - if (globalKey) { - qs.push('sentry_key=' + globalKey); + +function handleStackInfo(stackInfo, options) { + var frames = []; + + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = normalizeFrame(stack); + if (frame) { + frames.push(frame); + } + }); } - 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); - } - } + triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); processException( stackInfo.name, @@ -277,14 +1566,22 @@ 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; } @@ -331,70 +1628,83 @@ 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; - } - } + // In some instances message is not actually a string, no idea why, + // so we want to always coerce it to one. + message += ''; + + // 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; @@ -403,36 +1713,126 @@ function send(data) { if (!isSetup()) return; - data = arrayMerge({ + data = objectMerge({ project: globalProject, logger: globalOptions.logger, site: globalOptions.site, platform: 'javascript', - 'sentry.interfaces.Http': getHttpData() - }, data ); + // 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) { - console.error("Error: Raven has not been configured."); + logDebug('error', 'Error: Raven has not been configured.'); 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_Expres... + 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-javas... +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 logDebug(level, message) { + if (window.console && console[level] && Raven.debug) { + console[level](message); + } +} + +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('raven', [], function() { return Raven; }); +} + +})(this); This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
commits-noreply@bitbucket.org