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