diff --git a/assets/javascripts/vendor/raven.js b/assets/javascripts/vendor/raven.js index 78c47292..d99c6f1c 100755 --- a/assets/javascripts/vendor/raven.js +++ b/assets/javascripts/vendor/raven.js @@ -1,2055 +1,2435 @@ -/*! Raven.js 1.3.0 (768fdca) | github.com/getsentry/raven-js */ +/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */ /* * Includes TraceKit * https://github.com/getsentry/TraceKit * - * Copyright 2015 Matt Robenolt and other contributors + * Copyright 2016 Matt Robenolt and other contributors * Released under the BSD license * https://github.com/getsentry/raven-js/blob/master/LICENSE * */ -;(function(window, undefined){ + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0; --i) { - if (handlers[i] === handler) { - handlers.splice(i, 1); - } - } - } + debug: false, - /** - * Remove all crash handlers. - */ - function unsubscribeAll() { - uninstallGlobalHandler(); - handlers = []; - } + TraceKit: TraceKit, // alias to TraceKit - /** - * Dispatch stack information to all handlers. - * @param {Object.} stack + /* + * 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} */ - function notifyHandlers(stack, isWindowError) { - var exception = null; - if (isWindowError && !TraceKit.collectWindowErrors) { - return; + config: function(dsn, options) { + var self = this; + + if (this._globalServer) { + this._logDebug('error', 'Error: Raven has already been configured'); + return this; } - 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 (!dsn) return this; + + // merge in options + if (options) { + each(options, function(key, value){ + // tags and extra are special and need to be put into context + if (key === 'tags' || key === 'extra') { + self._globalContext[key] = value; + } else { + self._globalOptions[key] = value; } - } + }); } - if (exception) { - throw exception; - } - } + var uri = this._parseDSN(dsn), + lastSlash = uri.path.lastIndexOf('/'), + path = uri.path.substr(1, lastSlash); - var _oldOnerrorHandler, _onErrorHandlerInstalled; + this._dsn = dsn; - /** - * 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; + // "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. + this._globalOptions.ignoreErrors.push(/^Script error\.?$/); + this._globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/); - 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': getLocationHref(), - 'stack': [location] - }; - notifyHandlers(stack, true); - } + // join regexp rules into one big rule + this._globalOptions.ignoreErrors = joinRegExp(this._globalOptions.ignoreErrors); + this._globalOptions.ignoreUrls = this._globalOptions.ignoreUrls.length ? joinRegExp(this._globalOptions.ignoreUrls) : false; + this._globalOptions.whitelistUrls = this._globalOptions.whitelistUrls.length ? joinRegExp(this._globalOptions.whitelistUrls) : false; + this._globalOptions.includePaths = joinRegExp(this._globalOptions.includePaths); - if (_oldOnerrorHandler) { - return _oldOnerrorHandler.apply(this, arguments); - } + this._globalKey = uri.user; + this._globalSecret = uri.pass && uri.pass.substr(1); + this._globalProject = uri.path.substr(lastSlash + 1); - return false; - } + this._globalServer = this._getGlobalServer(uri); - function installGlobalHandler () - { - if (_onErrorHandlerInstalled) { - return; + this._globalEndpoint = this._globalServer + + '/' + path + 'api/' + this._globalProject + '/store/'; + + if (this._globalOptions.fetchContext) { + TraceKit.remoteFetching = true; } - _oldOnerrorHandler = window.onerror; - window.onerror = traceKitWindowOnError; - _onErrorHandlerInstalled = true; - } - function uninstallGlobalHandler () - { - if (!_onErrorHandlerInstalled) { - return; + if (this._globalOptions.linesOfContext) { + TraceKit.linesOfContext = this._globalOptions.linesOfContext; } - 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)); - } + TraceKit.collectWindowErrors = !!this._globalOptions.collectWindowErrors; - /** - * 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. + // return for chaining + return this; + }, + + /* + * 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} */ - 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(); - } + install: function() { + var self = this; + if (this.isSetup() && !this._isRavenInstalled) { + TraceKit.report.subscribe(function () { + self._handleOnErrorStackInfo.apply(self, arguments); + }); + this._wrapBuiltIns(); + + // Install all of the plugins + this._drainPlugins(); + + this._isRavenInstalled = true; } - 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)); + Error.stackTraceLimit = this._globalOptions.stackTraceLimit; + return this; + }, - if (rethrow !== false) { - throw ex; // re-throw to propagate to the top level (and cause window.onerror) + /* + * 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 || []; + func = options; + options = undefined; } - } - - report.subscribe = subscribe; - report.unsubscribe = unsubscribe; - report.uninstall = unsubscribeAll; - return report; -}()); -/** - * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript - * - * Syntax: - * 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). - * - */ -TraceKit.computeStackTrace = (function computeStackTraceWrapper() { - var sourceCache = {}; + return this.wrap(options, func).apply(this, args); + }, - /** - * 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. + /* + * 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 */ - function loadSource(url) { - if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. - return ''; + wrap: function(options, func) { + var self = this; + + // 1 argument has been passed, and it's not a function + // so just return it + if (isUndefined(func) && !isFunction(options)) { + return options; } - 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 ''; + // options is optional + if (isFunction(options)) { + func = options; + options = undefined; } - } - /** - * Retrieves source code from the source code cache. - * @param {string} url URL of source code. - * @return {Array.} 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 = ''; - var domain = ''; - try { domain = document.domain; } catch (e) {} - if (url.indexOf(domain) !== -1) { - source = loadSource(url); + // 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! + try { + if (func.__raven__) { + return func; } - sourceCache[url] = source ? source.split('\n') : []; + } catch (e) { + // Just accessing the __raven__ prop in some Selenium environments + // can cause a "Permission denied" exception (see raven-js#495). + // Bail on wrapping and return the function as-is (defers to window.onerror). + return func; } - return sourceCache[url]; - } + // If this has already been wrapped in the past, return that + if (func.__raven_wrapper__ ){ + return func.__raven_wrapper__ ; + } - /** - * 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; + 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. - if (!source.length) { - return UNKNOWN_FUNCTION; - } + while(i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i]; - // 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; + try { + return func.apply(this, args); + } catch(e) { + self._ignoreNextOnError(); + self.captureException(e, options); + throw e; + } + } - if (!isUndefined(line)) { - if ((m = reGuessFunction.exec(line))) { - return m[1]; - } else if ((m = reFunctionArgNames.exec(line))) { - return m[1]; - } + // copy over properties of the old function + for (var property in func) { + if (hasKey(func, property)) { + wrapped[property] = func[property]; } } + func.__raven_wrapper__ = wrapped; - return UNKNOWN_FUNCTION; - } + wrapped.prototype = func.prototype; - /** - * 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.} Lines of source code. - */ - function gatherContext(url, line) { - var source = getSource(url); + // 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; - if (!source.length) { - return null; - } + return wrapped; + }, - 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); + /* + * Uninstalls the global error handler. + * + * @return {Raven} + */ + uninstall: function() { + TraceKit.report.uninstall(); - line -= 1; // convert to 0-based index + this._restoreBuiltIns(); - for (var i = start; i < end; ++i) { - if (!isUndefined(source[i])) { - context.push(source[i]); - } - } + Error.stackTraceLimit = this._originalErrorStackTraceLimit; + this._isRavenInstalled = false; - return context.length > 0 ? context : null; - } + return this; + }, - /** - * 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. + /* + * 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} */ - function escapeCodeAsRegExpForMatchingInsideHTML(body) { - return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); - } + captureException: function(ex, options) { + // If not an Error is passed through, recall as a message instead + if (!isError(ex)) return this.captureMessage(ex, options); - /** - * Determines where a code fragment occurs in the source code. - * @param {RegExp} re The function definition. - * @param {Array.} urls A list of URLs to search. - * @return {?Object.} 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]); + // Store the raw exception object for potential debugging and introspection + this._lastCapturedException = ex; - return { - 'url': urls[i], - 'line': source.substring(0, m.index).split('\n').length, - 'column': m.index - source.lastIndexOf('\n', m.index) - 1 - }; - } + // 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 { + var stack = TraceKit.computeStackTrace(ex); + this._handleStackInfo(stack, options); + } catch(ex1) { + if(ex !== ex1) { + throw ex1; } } - // console.log('no match'); - - return null; - } + return this; + }, - /** - * 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. + /* + * 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} */ - function findSourceInLine(fragment, url, line) { - var source = getSource(url), - re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), - m; + captureMessage: function(msg, options) { + // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an + // early call; we'll error on the side of logging anything called before configuration since it's + // probably something you should see: + if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(msg)) { + return; + } - line -= 1; + // Fire away! + this._send( + objectMerge({ + message: msg + '' // Make sure it's actually a string + }, options) + ); - if (source && source.length > line && (m = re.exec(source[line]))) { - return m.index; + return this; + }, + + addPlugin: function(plugin /*arg1, arg2, ... argN*/) { + var pluginArgs = Array.prototype.slice.call(arguments, 1); + + this._plugins.push([plugin, pluginArgs]); + if (this._isRavenInstalled) { + this._drainPlugins(); } - return null; - } + return this; + }, + + /* + * Set/clear a user to be sent along with the payload. + * + * @param {object} user An object representing user data [optional] + * @return {Raven} + */ + setUserContext: function(user) { + // Intentionally do not merge here since that's an unexpected behavior. + this._globalContext.user = user; + + return this; + }, + + /* + * Merge extra attributes to be sent along with the payload. + * + * @param {object} extra An object representing extra data [optional] + * @return {Raven} + */ + setExtraContext: function(extra) { + this._mergeContext('extra', extra); + + return this; + }, + + /* + * Merge tags to be sent along with the payload. + * + * @param {object} tags An object representing tags [optional] + * @return {Raven} + */ + setTagsContext: function(tags) { + this._mergeContext('tags', tags); + + return this; + }, + + /* + * Clear all of the context. + * + * @return {Raven} + */ + clearContext: function() { + this._globalContext = {}; + + return this; + }, + + /* + * Get a copy of the current context. This cannot be mutated. + * + * @return {object} copy of context + */ + getContext: function() { + // lol javascript + return JSON.parse(JSON.stringify(this._globalContext)); + }, + + /* + * Set release version of application + * + * @param {string} release Typically something like a git SHA to identify version + * @return {Raven} + */ + setRelease: function(release) { + this._globalOptions.release = release; + + return this; + }, + + /* + * Set the dataCallback option + * + * @param {function} callback The callback to run which allows the + * data blob to be mutated before sending + * @return {Raven} + */ + setDataCallback: function(callback) { + this._globalOptions.dataCallback = callback; + + return this; + }, + + /* + * Set the shouldSendCallback option + * + * @param {function} callback The callback to run which allows + * introspecting the blob before sending + * @return {Raven} + */ + setShouldSendCallback: function(callback) { + this._globalOptions.shouldSendCallback = callback; + + return this; + }, /** - * Determines where a function was defined within the source code. - * @param {(Function|string)} func A function reference or serialized - * function definition. - * @return {?Object.} An object containing - * the url, line, and column number of the defined function. + * Override the default HTTP transport mechanism that transmits data + * to the Sentry server. + * + * @param {function} transport Function invoked instead of the default + * `makeRequest` handler. + * + * @return {Raven} */ - function findSourceByFunctionBody(func) { - if (typeof document === 'undefined') - return; + setTransport: function(transport) { + this._globalOptions.transport = transport; - 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; + return this; + }, - for (var i = 0; i < scripts.length; ++i) { - var script = scripts[i]; - if (script.src) { - urls.push(script.src); + /* + * Get the latest raw exception that was captured by Raven. + * + * @return {error} + */ + lastException: function() { + return this._lastCapturedException; + }, + + /* + * Get the last event id + * + * @return {string} + */ + lastEventId: function() { + return this._lastEventId; + }, + + /* + * Determine if Raven is setup and ready to go. + * + * @return {boolean} + */ + isSetup: function() { + if (!this._hasJSON) return false; // needs JSON support + if (!this._globalServer) { + if (!this.ravenNotConfiguredError) { + this.ravenNotConfiguredError = true; + this._logDebug('error', 'Error: Raven has not been configured.'); } + return false; } + return true; + }, - if (!(parts = codeRE.exec(code))) { - re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); + afterLoad: function () { + // TODO: remove window dependence? + + // Attempt to initialize Raven on load + var RavenConfig = window.RavenConfig; + if (RavenConfig) { + this.config(RavenConfig.dsn, RavenConfig.config).install(); } + }, - // 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*'); + showReportDialog: function (options) { + if (!window.document) // doesn't work without a document (React native) + return; - 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*}'); + options = options || {}; + + var lastEventId = options.eventId || this.lastEventId(); + if (!lastEventId) { + throw new RavenConfigError('Missing eventId'); } - // look for a normal function definition - if ((result = findSourceInUrls(re, urls))) { - return result; + var dsn = options.dsn || this._dsn; + if (!dsn) { + throw new RavenConfigError('Missing DSN'); } - // look for an old-school event handler function - if ((parts = eventRE.exec(code))) { - var event = parts[1]; - body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); + var encode = encodeURIComponent; + var qs = ''; + qs += '?eventId=' + encode(lastEventId); + qs += '&dsn=' + encode(dsn); - // look for a function defined in HTML as an onXXX handler - re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); + var user = options.user || this._globalContext.user; + if (user) { + if (user.name) qs += '&name=' + encode(user.name); + if (user.email) qs += '&email=' + encode(user.email); + } - if ((result = findSourceInUrls(re, urls[0]))) { - return result; - } + var globalServer = this._getGlobalServer(this._parseDSN(dsn)); - // look for ??? - re = new RegExp(body); + var script = document.createElement('script'); + script.async = true; + script.src = globalServer + '/api/embed/error-page/' + qs; + (document.head || document.body).appendChild(script); + }, - if ((result = findSourceInUrls(re, urls))) { - return result; - } + /**** Private functions ****/ + _ignoreNextOnError: function () { + var self = this; + this._ignoreOnError += 1; + setTimeout(function () { + // onerror should trigger before setTimeout + self._ignoreOnError -= 1; + }); + }, + + _triggerEvent: function(eventType, options) { + // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it + var evt, key; + + if (!this._hasDocument) + return; + + options = options || {}; + + eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1); + + if (document.createEvent) { + evt = document.createEvent('HTMLEvents'); + evt.initEvent(eventType, true, true); + } else { + evt = document.createEventObject(); + evt.eventType = eventType; } - return null; - } + for (key in options) if (hasKey(options, key)) { + evt[key] = options[key]; + } - // 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' + if (document.createEvent) { + // IE9 if standards + document.dispatchEvent(evt); + } else { + // IE8 regardless of Quirks or Standards + // IE9 if quirks + try { + document.fireEvent('on' + evt.eventType.toLowerCase(), evt); + } catch(e) { + // Do nothing + } + } + }, /** - * Computes stack trace information from the stack property. - * Chrome and Gecko use this property. - * @param {Error} ex - * @return {?Object.} Stack trace information. + * Install any queued plugins */ - function computeStackTraceFromStackProp(ex) { - if (isUndefined(ex.stack) || !ex.stack) return; + _wrapBuiltIns: function() { + var self = this; + + function fill(obj, name, replacement, noUndo) { + var orig = obj[name]; + obj[name] = replacement(orig); + if (!noUndo) { + self._wrappedBuiltIns.push([obj, name, orig]); + } + } - var chrome = /^\s*at (.*?) ?\(?((?:(?:file|https?|chrome-extension):.*?)|):(\d+)(?::(\d+))?\)?\s*$/i, - gecko = /^\s*(.*?)(?:\((.*?)\))?@((?:file|https?|chrome).*?):(\d+)(?::(\d+))?\s*$/i, - winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i, - lines = ex.stack.split('\n'), - stack = [], - parts, - element, - reference = /^(.*) is undefined$/.exec(ex.message); + function wrapTimeFn(orig) { + return function (fn, t) { // preserve arity + // Make a copy of the arguments + var args = [].slice.call(arguments); + var originalCallback = args[0]; + if (isFunction(originalCallback)) { + args[0] = self.wrap(originalCallback); + } - 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 + // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it + // also supports only two arguments and doesn't care what this is, so we + // can just call the original function directly. + if (orig.apply) { + return orig.apply(this, args); + } else { + return orig(args[0], args[1]); + } + }; + } + + fill(window, 'setTimeout', wrapTimeFn); + fill(window, 'setInterval', wrapTimeFn); + if (window.requestAnimationFrame) { + fill(window, 'requestAnimationFrame', function (orig) { + return function (cb) { + return orig(self.wrap(cb)); }; - } 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 + }); + } + + // event targets borrowed from bugsnag-js: + // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666 + 'EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload'.replace(/\w+/g, function (global) { + var proto = window[global] && window[global].prototype; + if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) { + fill(proto, 'addEventListener', function(orig) { + return function (evt, fn, capture, secure) { // preserve arity + try { + if (fn && fn.handleEvent) { + fn.handleEvent = self.wrap(fn.handleEvent); + } + } catch (err) { + // can sometimes get 'Permission denied to access property "handle Event' + } + return orig.call(this, evt, self.wrap(fn), capture, secure); + }; + }); + fill(proto, 'removeEventListener', function (orig) { + return function (evt, fn, capture, secure) { + fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn); + return orig.call(this, evt, fn, capture, secure); + }; + }); + } + }); + + if ('XMLHttpRequest' in window) { + fill(XMLHttpRequest.prototype, 'send', function(origSend) { + return function (data) { // preserve arity + var xhr = this; + 'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) { + if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') { + fill(xhr, prop, function (orig) { + return self.wrap(orig); + }, true /* noUndo */); // don't track filled methods on XHR instances + } + }); + return origSend.apply(this, arguments); }; - } else if ((parts = winjs.exec(lines[i]))) { - element = { - 'url': parts[2], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'line': +parts[3], - 'column': parts[4] ? +parts[4] : null + }); + } + + var $ = window.jQuery || window.$; + if ($ && $.fn && $.fn.ready) { + fill($.fn, 'ready', function (orig) { + return function (fn) { + return orig.call(this, self.wrap(fn)); }; - } else { - continue; - } + }); + } + }, - if (!element.func && element.line) { - element.func = guessFunctionName(element.url, element.line); - } + _restoreBuiltIns: function () { + // restore any wrapped builtins + var builtin; + while (this._wrappedBuiltIns.length) { + builtin = this._wrappedBuiltIns.shift(); - if (element.line) { - element.context = gatherContext(element.url, element.line); - } + var obj = builtin[0], + name = builtin[1], + orig = builtin[2]; - stack.push(element); + obj[name] = orig; } + }, - if (!stack.length) { - return null; + _drainPlugins: function() { + var self = this; + + // FIX ME TODO + each(this._plugins, function(_, plugin) { + var installer = plugin[0]; + var args = plugin[1]; + installer.apply(self, [self].concat(args)); + }); + }, + + _parseDSN: function(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; + + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch(e) { + throw new RavenConfigError('Invalid DSN: ' + str); } - 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; + if (dsn.pass && !this._globalOptions.allowSecretKey) { + throw new RavenConfigError('Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key'); } - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } + return dsn; + }, - /** - * Computes stack trace information from the stacktrace property. - * Opera 10 uses this property. - * @param {Error} ex - * @return {?Object.} 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; - if (isUndefined(ex.stacktrace) || !ex.stacktrace) return; + _getGlobalServer: function(uri) { + // assemble the endpoint from the uri pieces + var globalServer = '//' + uri.host + + (uri.port ? ':' + uri.port : ''); - var testRE = / line (\d+), column (\d+) in (?:]+)>|([^\)]+))\((.*)\) in (.*):\s*$/i, - lines = stacktrace.split('\n'), - stack = [], - parts; + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; + } + return globalServer; + }, - 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] - }; + _handleOnErrorStackInfo: function() { + // if we are intentionally ignoring errors via onerror, bail out + if (!this._ignoreOnError) { + this._handleStackInfo.apply(this, arguments); + } + }, - 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) {} - } + _handleStackInfo: function(stackInfo, options) { + var self = this; + var frames = []; - if (!element.context) { - element.context = [lines[i + 1]]; + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = self._normalizeFrame(stack); + if (frame) { + frames.push(frame); } + }); + } - stack.push(element); + this._triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); + + this._processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames.slice(0, this._globalOptions.stackTraceLimit), + options + ); + }, + + _normalizeFrame: function(frame) { + if (!frame.url) return; + + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + 'function': frame.func || '?' + }, context = this._extractContextFromFrame(frame), i; + + if (context) { + var keys = ['pre_context', 'context_line', 'post_context']; + i = 3; + while (i--) normalized[keys[i]] = context[i]; + } + + normalized.in_app = !( // determine if an exception came from outside of our app + // first we check the global includePaths list. + !!this._globalOptions.includePaths.test && !this._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; + }, + + _extractContextFromFrame: function(frame) { + // immediately check if we should even attempt to parse a context + if (!frame.context || !this._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 (!stack.length) { - return null; + 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 { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } + return [ + context.slice(0, pivot), // pre_context + context[pivot], // context_line + context.slice(pivot + 1) // post_context + ]; + }, - /** - * 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.} 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); } - // ... + _processException: function(type, message, fileurl, lineno, frames, options) { + var stacktrace, fullMessage; + + if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return; + + message += ''; + message = truncate(message, this._globalOptions.maxMessageLength); + + fullMessage = (type ? type + ': ' : '') + message; + fullMessage = truncate(fullMessage, this._globalOptions.maxMessageLength); + + 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}; + } else if (fileurl) { + stacktrace = { + frames: [{ + filename: fileurl, + lineno: lineno, + in_app: true + }] + }; + } - var lines = ex.message.split('\n'); - if (lines.length < 4) { - return null; + if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return; + if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return; + + var data = objectMerge({ + // sentry.interfaces.Exception + exception: { + values: [{ + type: type, + value: message, + stacktrace: stacktrace + }] + }, + culprit: fileurl, + message: fullMessage + }, options); + + // Fire away! + this._send(data); + }, + + _trimPacket: function(data) { + // For now, we only want to truncate the two different messages + // but this could/should be expanded to just trim everything + var max = this._globalOptions.maxMessageLength; + data.message = truncate(data.message, max); + if (data.exception) { + var exception = data.exception.values[0]; + exception.value = truncate(exception.value, max); } - 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; + return data; + }, - for (i in scripts) { - if (hasKey(scripts, i) && !scripts[i].src) { - inlineScriptBlocks.push(scripts[i]); - } + _getHttpData: function() { + if (!this._hasDocument || !document.location || !document.location.href) { + return; } - 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