diff --git a/assets/javascripts/vendor/raven.js b/assets/javascripts/vendor/raven.js index b453c3e0..591b6767 100644 --- a/assets/javascripts/vendor/raven.js +++ b/assets/javascripts/vendor/raven.js @@ -1,4 +1,4 @@ -/*! Raven.js 3.17.0 (6384830) | github.com/getsentry/raven-js */ +/*! Raven.js 3.18.0 (b771bb2) | github.com/getsentry/raven-js */ /* * Includes TraceKit @@ -10,243 +10,322 @@ * */ -(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 this._globalOptions.maxBreadcrumbs) { - this._breadcrumbs.shift(); - } - return this; - }, + this._breadcrumbs.push(crumb); + if (this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs) { + this._breadcrumbs.shift(); + } + return this; + }, - addPlugin: function(plugin /*arg1, arg2, ... argN*/) { - var pluginArgs = [].slice.call(arguments, 1); + addPlugin: function(plugin /*arg1, arg2, ... argN*/) { + var pluginArgs = [].slice.call(arguments, 1); - this._plugins.push([plugin, pluginArgs]); - if (this._isRavenInstalled) { - this._drainPlugins(); - } + this._plugins.push([plugin, pluginArgs]); + if (this._isRavenInstalled) { + this._drainPlugins(); + } - return this; - }, + 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; + setUserContext: function(user) { + // Intentionally do not merge here since that's an unexpected behavior. + this._globalContext.user = user; - return this; - }, + 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); + setExtraContext: function(extra) { + this._mergeContext('extra', extra); - return this; - }, + 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); + setTagsContext: function(tags) { + this._mergeContext('tags', tags); - return this; - }, + return this; + }, - /* + /* * Clear all of the context. * * @return {Raven} */ - clearContext: function() { - this._globalContext = {}; + clearContext: function() { + this._globalContext = {}; - return this; - }, + 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(stringify(this._globalContext)); - }, - + getContext: function() { + // lol javascript + return JSON.parse(stringify(this._globalContext)); + }, - /* + /* * Set environment of application * * @param {string} environment Typically something like 'production'. * @return {Raven} */ - setEnvironment: function(environment) { - this._globalOptions.environment = environment; + setEnvironment: function(environment) { + this._globalOptions.environment = environment; - return this; - }, + return this; + }, - /* + /* * 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; + setRelease: function(release) { + this._globalOptions.release = release; - return this; - }, + 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) { - var original = this._globalOptions.dataCallback; - this._globalOptions.dataCallback = - keepOriginalCallback(original, callback); - return this; - }, + setDataCallback: function(callback) { + var original = this._globalOptions.dataCallback; + this._globalOptions.dataCallback = keepOriginalCallback( + original, + callback + ); + return this; + }, - /* + /* * Set the breadcrumbCallback option * * @param {function} callback The callback to run which allows filtering * or mutating breadcrumbs * @return {Raven} */ - setBreadcrumbCallback: function(callback) { - var original = this._globalOptions.breadcrumbCallback; - this._globalOptions.breadcrumbCallback = - keepOriginalCallback(original, callback); - return this; - }, + setBreadcrumbCallback: function(callback) { + var original = this._globalOptions.breadcrumbCallback; + this._globalOptions.breadcrumbCallback = keepOriginalCallback( + original, + 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) { - var original = this._globalOptions.shouldSendCallback; - this._globalOptions.shouldSendCallback = - keepOriginalCallback(original, callback); - return this; - }, + setShouldSendCallback: function(callback) { + var original = this._globalOptions.shouldSendCallback; + this._globalOptions.shouldSendCallback = keepOriginalCallback( + original, + callback + ); + return this; + }, - /** + /** * Override the default HTTP transport mechanism that transmits data * to the Sentry server. * @@ -687,368 +798,438 @@ Raven.prototype = { * * @return {Raven} */ - setTransport: function(transport) { - this._globalOptions.transport = transport; + setTransport: function(transport) { + this._globalOptions.transport = transport; - return this; - }, + return this; + }, - /* + /* * Get the latest raw exception that was captured by Raven. * * @return {error} */ - lastException: function() { - return this._lastCapturedException; - }, + lastException: function() { + return this._lastCapturedException; + }, - /* + /* * Get the last event id * * @return {string} */ - lastEventId: function() { - return this._lastEventId; - }, + 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; - }, - - afterLoad: function () { - // TODO: remove window dependence? + 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; + }, - // Attempt to initialize Raven on load - var RavenConfig = _window.RavenConfig; - if (RavenConfig) { - this.config(RavenConfig.dsn, RavenConfig.config).install(); - } - }, + afterLoad: function() { + // TODO: remove window dependence? - showReportDialog: function (options) { - if (!_document) // doesn't work without a document (React native) - return; + // Attempt to initialize Raven on load + var RavenConfig = _window.RavenConfig; + if (RavenConfig) { + this.config(RavenConfig.dsn, RavenConfig.config).install(); + } + }, - options = options || {}; + showReportDialog: function(options) { + if ( + !_document // doesn't work without a document (React native) + ) + return; - var lastEventId = options.eventId || this.lastEventId(); - if (!lastEventId) { - throw new RavenConfigError('Missing eventId'); - } + options = options || {}; - var dsn = options.dsn || this._dsn; - if (!dsn) { - throw new RavenConfigError('Missing DSN'); - } - - var encode = encodeURIComponent; - var qs = ''; - qs += '?eventId=' + encode(lastEventId); - qs += '&dsn=' + encode(dsn); + var lastEventId = options.eventId || this.lastEventId(); + if (!lastEventId) { + throw new RavenConfigError('Missing eventId'); + } - 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); - } + var dsn = options.dsn || this._dsn; + if (!dsn) { + throw new RavenConfigError('Missing DSN'); + } - var globalServer = this._getGlobalServer(this._parseDSN(dsn)); + var encode = encodeURIComponent; + var qs = ''; + qs += '?eventId=' + encode(lastEventId); + qs += '&dsn=' + encode(dsn); - var script = _document.createElement('script'); - script.async = true; - script.src = globalServer + '/api/embed/error-page/' + qs; - (_document.head || _document.body).appendChild(script); - }, + 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); + } - /**** Private functions ****/ - _ignoreNextOnError: function () { - var self = this; - this._ignoreOnError += 1; - setTimeout(function () { - // onerror should trigger before setTimeout - self._ignoreOnError -= 1; - }); - }, + var globalServer = this._getGlobalServer(this._parseDSN(dsn)); + + var script = _document.createElement('script'); + script.async = true; + script.src = globalServer + '/api/embed/error-page/' + qs; + (_document.head || _document.body).appendChild(script); + }, + + /**** 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; + _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; + if (!this._hasDocument) return; - options = options || {}; + options = options || {}; - eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1); + 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; - } + if (_document.createEvent) { + evt = _document.createEvent('HTMLEvents'); + evt.initEvent(eventType, true, true); + } else { + evt = _document.createEventObject(); + evt.eventType = eventType; + } - for (key in options) if (hasKey(options, key)) { - evt[key] = options[key]; - } + for (key in options) + if (hasKey(options, key)) { + evt[key] = options[key]; + } - 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 - } - } - }, + 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 + } + } + }, - /** + /** * Wraps addEventListener to capture UI breadcrumbs * @param evtName the event name (e.g. "click") * @returns {Function} * @private */ - _breadcrumbEventHandler: function(evtName) { - var self = this; - return function (evt) { - // reset keypress timeout; e.g. triggering a 'click' after - // a 'keypress' will reset the keypress debounce so that a new - // set of keypresses can be recorded - self._keypressTimeout = null; - - // It's possible this handler might trigger multiple times for the same - // event (e.g. event propagation through node ancestors). Ignore if we've - // already captured the event. - if (self._lastCapturedEvent === evt) - return; - - self._lastCapturedEvent = evt; - - // try/catch both: - // - accessing evt.target (see getsentry/raven-js#838, #768) - // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly - // can throw an exception in some circumstances. - var target; - try { - target = htmlTreeAsString(evt.target); - } catch (e) { - target = ''; - } - - self.captureBreadcrumb({ - category: 'ui.' + evtName, // e.g. ui.click, ui.input - message: target - }); - }; - }, + _breadcrumbEventHandler: function(evtName) { + var self = this; + return function(evt) { + // reset keypress timeout; e.g. triggering a 'click' after + // a 'keypress' will reset the keypress debounce so that a new + // set of keypresses can be recorded + self._keypressTimeout = null; + + // It's possible this handler might trigger multiple times for the same + // event (e.g. event propagation through node ancestors). Ignore if we've + // already captured the event. + if (self._lastCapturedEvent === evt) return; + + self._lastCapturedEvent = evt; + + // try/catch both: + // - accessing evt.target (see getsentry/raven-js#838, #768) + // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly + // can throw an exception in some circumstances. + var target; + try { + target = htmlTreeAsString(evt.target); + } catch (e) { + target = ''; + } + + self.captureBreadcrumb({ + category: 'ui.' + evtName, // e.g. ui.click, ui.input + message: target + }); + }; + }, - /** + /** * Wraps addEventListener to capture keypress UI events * @returns {Function} * @private */ - _keypressEventHandler: function() { - var self = this, - debounceDuration = 1000; // milliseconds - - // TODO: if somehow user switches keypress target before - // debounce timeout is triggered, we will only capture - // a single breadcrumb from the FIRST target (acceptable?) - return function (evt) { - var target; - try { - target = evt.target; - } catch (e) { - // just accessing event properties can throw an exception in some rare circumstances - // see: https://github.com/getsentry/raven-js/issues/838 - return; - } - var tagName = target && target.tagName; - - // only consider keypress events on actual input elements - // this will disregard keypresses targeting body (e.g. tabbing - // through elements, hotkeys, etc) - if (!tagName || tagName !== 'INPUT' && tagName !== 'TEXTAREA' && !target.isContentEditable) - return; - - // record first keypress in a series, but ignore subsequent - // keypresses until debounce clears - var timeout = self._keypressTimeout; - if (!timeout) { - self._breadcrumbEventHandler('input')(evt); - } - clearTimeout(timeout); - self._keypressTimeout = setTimeout(function () { - self._keypressTimeout = null; - }, debounceDuration); - }; - }, + _keypressEventHandler: function() { + var self = this, + debounceDuration = 1000; // milliseconds + + // TODO: if somehow user switches keypress target before + // debounce timeout is triggered, we will only capture + // a single breadcrumb from the FIRST target (acceptable?) + return function(evt) { + var target; + try { + target = evt.target; + } catch (e) { + // just accessing event properties can throw an exception in some rare circumstances + // see: https://github.com/getsentry/raven-js/issues/838 + return; + } + var tagName = target && target.tagName; + + // only consider keypress events on actual input elements + // this will disregard keypresses targeting body (e.g. tabbing + // through elements, hotkeys, etc) + if ( + !tagName || + (tagName !== 'INPUT' && + tagName !== 'TEXTAREA' && + !target.isContentEditable) + ) + return; + + // record first keypress in a series, but ignore subsequent + // keypresses until debounce clears + var timeout = self._keypressTimeout; + if (!timeout) { + self._breadcrumbEventHandler('input')(evt); + } + clearTimeout(timeout); + self._keypressTimeout = setTimeout(function() { + self._keypressTimeout = null; + }, debounceDuration); + }; + }, - /** + /** * Captures a breadcrumb of type "navigation", normalizing input URLs * @param to the originating URL * @param from the target URL * @private */ - _captureUrlChange: function(from, to) { - var parsedLoc = parseUrl(this._location.href); - var parsedTo = parseUrl(to); - var parsedFrom = parseUrl(from); - - // because onpopstate only tells you the "new" (to) value of location.href, and - // not the previous (from) value, we need to track the value of the current URL - // state ourselves - this._lastHref = to; - - // Use only the path component of the URL if the URL matches the current - // document (almost all the time when using pushState) - if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) - to = parsedTo.relative; - if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) - from = parsedFrom.relative; - - this.captureBreadcrumb({ - category: 'navigation', - data: { - to: to, - from: from - } - }); - }, + _captureUrlChange: function(from, to) { + var parsedLoc = parseUrl(this._location.href); + var parsedTo = parseUrl(to); + var parsedFrom = parseUrl(from); + + // because onpopstate only tells you the "new" (to) value of location.href, and + // not the previous (from) value, we need to track the value of the current URL + // state ourselves + this._lastHref = to; + + // Use only the path component of the URL if the URL matches the current + // document (almost all the time when using pushState) + if ( + parsedLoc.protocol === parsedTo.protocol && + parsedLoc.host === parsedTo.host + ) + to = parsedTo.relative; + if ( + parsedLoc.protocol === parsedFrom.protocol && + parsedLoc.host === parsedFrom.host + ) + from = parsedFrom.relative; + + this.captureBreadcrumb({ + category: 'navigation', + data: { + to: to, + from: from + } + }); + }, - /** + /** * Wrap timer functions and event targets to catch errors and provide * better metadata. */ - _instrumentTryCatch: function() { - var self = this; + _instrumentTryCatch: function() { + var self = this; - var wrappedBuiltIns = self._wrappedBuiltIns; + var wrappedBuiltIns = self._wrappedBuiltIns; - function wrapTimeFn(orig) { - return function (fn, t) { // preserve arity - // Make a copy of the arguments to prevent deoptimization - // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments - var args = new Array(arguments.length); - for(var i = 0; i < args.length; ++i) { - args[i] = arguments[i]; - } - var originalCallback = args[0]; - if (isFunction(originalCallback)) { - args[0] = self.wrap(originalCallback); - } + function wrapTimeFn(orig) { + return function(fn, t) { + // preserve arity + // Make a copy of the arguments to prevent deoptimization + // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + var originalCallback = args[0]; + if (isFunction(originalCallback)) { + args[0] = self.wrap(originalCallback); + } - // 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]); + // 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]); + } + }; } - }; - } - var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; - - function wrapEventTarget(global) { - var proto = _window[global] && _window[global].prototype; - if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) { - fill(proto, 'addEventListener', function(orig) { - return function (evtName, fn, capture, secure) { // preserve arity - try { + var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; + + function wrapEventTarget(global) { + var proto = _window[global] && _window[global].prototype; + if ( + proto && + proto.hasOwnProperty && + proto.hasOwnProperty('addEventListener') + ) { + fill( + proto, + 'addEventListener', + function(orig) { + return function(evtName, fn, capture, secure) { + // preserve arity + try { if (fn && fn.handleEvent) { - fn.handleEvent = self.wrap(fn.handleEvent); + fn.handleEvent = self.wrap(fn.handleEvent); } - } catch (err) { + } catch (err) { // can sometimes get 'Permission denied to access property "handle Event' - } + } - // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs` - // so that we don't have more than one wrapper function - var before, - clickHandler, - keypressHandler; + // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs` + // so that we don't have more than one wrapper function + var before, clickHandler, keypressHandler; - if (autoBreadcrumbs && autoBreadcrumbs.dom && (global === 'EventTarget' || global === 'Node')) { + if ( + autoBreadcrumbs && + autoBreadcrumbs.dom && + (global === 'EventTarget' || global === 'Node') + ) { // NOTE: generating multiple handlers per addEventListener invocation, should // revisit and verify we can just use one (almost certainly) clickHandler = self._breadcrumbEventHandler('click'); keypressHandler = self._keypressEventHandler(); - before = function (evt) { - // need to intercept every DOM event in `before` argument, in case that - // same wrapped method is re-used for different events (e.g. mousemove THEN click) - // see #724 - if (!evt) return; - - var eventType; - try { - eventType = evt.type - } catch (e) { - // just accessing event properties can throw an exception in some rare circumstances - // see: https://github.com/getsentry/raven-js/issues/838 - return; - } - if (eventType === 'click') - return clickHandler(evt); - else if (eventType === 'keypress') - return keypressHandler(evt); + before = function(evt) { + // need to intercept every DOM event in `before` argument, in case that + // same wrapped method is re-used for different events (e.g. mousemove THEN click) + // see #724 + if (!evt) return; + + var eventType; + try { + eventType = evt.type; + } catch (e) { + // just accessing event properties can throw an exception in some rare circumstances + // see: https://github.com/getsentry/raven-js/issues/838 + return; + } + if (eventType === 'click') return clickHandler(evt); + else if (eventType === 'keypress') + return keypressHandler(evt); }; - } - return orig.call(this, evtName, self.wrap(fn, undefined, before), capture, secure); - }; - }, wrappedBuiltIns); - fill(proto, 'removeEventListener', function (orig) { - return function (evt, fn, capture, secure) { - try { - fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn); - } catch (e) { + } + return orig.call( + this, + evtName, + self.wrap(fn, undefined, before), + capture, + secure + ); + }; + }, + wrappedBuiltIns + ); + fill( + proto, + 'removeEventListener', + function(orig) { + return function(evt, fn, capture, secure) { + try { + fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn); + } catch (e) { // ignore, accessing __raven_wrapper__ will throw in some Selenium environments - } - return orig.call(this, evt, fn, capture, secure); - }; - }, wrappedBuiltIns); - } - } - - fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns); - fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns); - if (_window.requestAnimationFrame) { - fill(_window, 'requestAnimationFrame', function (orig) { - return function (cb) { - return orig(self.wrap(cb)); - }; - }, wrappedBuiltIns); - } + } + return orig.call(this, evt, fn, capture, secure); + }; + }, + wrappedBuiltIns + ); + } + } - // event targets borrowed from bugsnag-js: - // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666 - var eventTargets = ['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']; - for (var i = 0; i < eventTargets.length; i++) { - wrapEventTarget(eventTargets[i]); - } - }, + fill(_window, 'setTimeout', wrapTimeFn, wrappedBuiltIns); + fill(_window, 'setInterval', wrapTimeFn, wrappedBuiltIns); + if (_window.requestAnimationFrame) { + fill( + _window, + 'requestAnimationFrame', + function(orig) { + return function(cb) { + return orig(self.wrap(cb)); + }; + }, + wrappedBuiltIns + ); + } + // event targets borrowed from bugsnag-js: + // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666 + var eventTargets = [ + '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' + ]; + for (var i = 0; i < eventTargets.length; i++) { + wrapEventTarget(eventTargets[i]); + } + }, - /** + /** * Instrument browser built-ins w/ breadcrumb capturing * - XMLHttpRequests * - DOM interactions (click/typing) @@ -1057,795 +1238,910 @@ Raven.prototype = { * * Can be disabled or individually configured via the `autoBreadcrumbs` config option */ - _instrumentBreadcrumbs: function () { - var self = this; - var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; - - var wrappedBuiltIns = self._wrappedBuiltIns; - - function wrapProp(prop, xhr) { - if (prop in xhr && isFunction(xhr[prop])) { - fill(xhr, prop, function (orig) { - return self.wrap(orig); - }); // intentionally don't track filled methods on XHR instances - } - } - - if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) { - var xhrproto = XMLHttpRequest.prototype; - fill(xhrproto, 'open', function(origOpen) { - return function (method, url) { // preserve arity + _instrumentBreadcrumbs: function() { + var self = this; + var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs; + + var wrappedBuiltIns = self._wrappedBuiltIns; + + function wrapProp(prop, xhr) { + if (prop in xhr && isFunction(xhr[prop])) { + fill(xhr, prop, function(orig) { + return self.wrap(orig); + }); // intentionally don't track filled methods on XHR instances + } + } - // if Sentry key appears in URL, don't capture - if (isString(url) && url.indexOf(self._globalKey) === -1) { - this.__raven_xhr = { + if (autoBreadcrumbs.xhr && 'XMLHttpRequest' in _window) { + var xhrproto = XMLHttpRequest.prototype; + fill( + xhrproto, + 'open', + function(origOpen) { + return function(method, url) { + // preserve arity + + // if Sentry key appears in URL, don't capture + if (isString(url) && url.indexOf(self._globalKey) === -1) { + this.__raven_xhr = { method: method, url: url, status_code: null - }; - } - - return origOpen.apply(this, arguments); - }; - }, wrappedBuiltIns); - - fill(xhrproto, 'send', function(origSend) { - return function (data) { // preserve arity - var xhr = this; + }; + } - function onreadystatechangeHandler() { - if (xhr.__raven_xhr && (xhr.readyState === 1 || xhr.readyState === 4)) { + return origOpen.apply(this, arguments); + }; + }, + wrappedBuiltIns + ); + + fill( + xhrproto, + 'send', + function(origSend) { + return function(data) { + // preserve arity + var xhr = this; + + function onreadystatechangeHandler() { + if (xhr.__raven_xhr && xhr.readyState === 4) { try { - // touching statusCode in some platforms throws - // an exception - xhr.__raven_xhr.status_code = xhr.status; - } catch (e) { /* do nothing */ } + // touching statusCode in some platforms throws + // an exception + xhr.__raven_xhr.status_code = xhr.status; + } catch (e) { + /* do nothing */ + } + self.captureBreadcrumb({ - type: 'http', - category: 'xhr', - data: xhr.__raven_xhr + type: 'http', + category: 'xhr', + data: xhr.__raven_xhr }); + } } - } - var props = ['onload', 'onerror', 'onprogress']; - for (var j = 0; j < props.length; j++) { - wrapProp(props[j], xhr); - } + var props = ['onload', 'onerror', 'onprogress']; + for (var j = 0; j < props.length; j++) { + wrapProp(props[j], xhr); + } - if ('onreadystatechange' in xhr && isFunction(xhr.onreadystatechange)) { - fill(xhr, 'onreadystatechange', function (orig) { - return self.wrap(orig, undefined, onreadystatechangeHandler); - } /* intentionally don't track this instrumentation */); - } else { - // if onreadystatechange wasn't actually set by the page on this xhr, we - // are free to set our own and capture the breadcrumb - xhr.onreadystatechange = onreadystatechangeHandler; - } + if ( + 'onreadystatechange' in xhr && + isFunction(xhr.onreadystatechange) + ) { + fill( + xhr, + 'onreadystatechange', + function(orig) { + return self.wrap( + orig, + undefined, + onreadystatechangeHandler + ); + } /* intentionally don't track this instrumentation */ + ); + } else { + // if onreadystatechange wasn't actually set by the page on this xhr, we + // are free to set our own and capture the breadcrumb + xhr.onreadystatechange = onreadystatechangeHandler; + } - return origSend.apply(this, arguments); - }; - }, wrappedBuiltIns); - } + return origSend.apply(this, arguments); + }; + }, + wrappedBuiltIns + ); + } - if (autoBreadcrumbs.xhr && 'fetch' in _window) { - fill(_window, 'fetch', function(origFetch) { - return function (fn, t) { // preserve arity - // Make a copy of the arguments to prevent deoptimization - // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments - var args = new Array(arguments.length); - for (var i = 0; i < args.length; ++i) { - args[i] = arguments[i]; - } + if (autoBreadcrumbs.xhr && 'fetch' in _window) { + fill( + _window, + 'fetch', + function(origFetch) { + return function(fn, t) { + // preserve arity + // Make a copy of the arguments to prevent deoptimization + // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } - var fetchInput = args[0]; - var method = 'GET'; - var url; + var fetchInput = args[0]; + var method = 'GET'; + var url; - if (typeof fetchInput === 'string') { - url = fetchInput; - } else { - url = fetchInput.url; - if (fetchInput.method) { + if (typeof fetchInput === 'string') { + url = fetchInput; + } else { + url = fetchInput.url; + if (fetchInput.method) { method = fetchInput.method; + } } - } - - if (args[1] && args[1].method) { - method = args[1].method; - } - - var fetchData = { - method: method, - url: url, - status_code: null - }; - - self.captureBreadcrumb({ - type: 'http', - category: 'fetch', - data: fetchData - }); - return origFetch.apply(this, args).then(function (response) { - fetchData.status_code = response.status; - - return response; - }); - }; - }, wrappedBuiltIns); - } + if (args[1] && args[1].method) { + method = args[1].method; + } - // Capture breadcrumbs from any click that is unhandled / bubbled up all the way - // to the document. Do this before we instrument addEventListener. - if (autoBreadcrumbs.dom && this._hasDocument) { - if (_document.addEventListener) { - _document.addEventListener('click', self._breadcrumbEventHandler('click'), false); - _document.addEventListener('keypress', self._keypressEventHandler(), false); - } - else { - // IE8 Compatibility - _document.attachEvent('onclick', self._breadcrumbEventHandler('click')); - _document.attachEvent('onkeypress', self._keypressEventHandler()); - } - } + var fetchData = { + method: method, + url: url, + status_code: null + }; - // record navigation (URL) changes - // NOTE: in Chrome App environment, touching history.pushState, *even inside - // a try/catch block*, will cause Chrome to output an error to console.error - // borrowed from: https://github.com/angular/angular.js/pull/13945/files - var chrome = _window.chrome; - var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; - var hasPushState = !isChromePackagedApp && _window.history && history.pushState; - if (autoBreadcrumbs.location && hasPushState) { - // TODO: remove onpopstate handler on uninstall() - var oldOnPopState = _window.onpopstate; - _window.onpopstate = function () { - var currentHref = self._location.href; - self._captureUrlChange(self._lastHref, currentHref); - - if (oldOnPopState) { - return oldOnPopState.apply(this, arguments); + self.captureBreadcrumb({ + type: 'http', + category: 'fetch', + data: fetchData + }); + + return origFetch.apply(this, args).then(function(response) { + fetchData.status_code = response.status; + + return response; + }); + }; + }, + wrappedBuiltIns + ); } - }; - fill(history, 'pushState', function (origPushState) { - // note history.pushState.length is 0; intentionally not declaring - // params to preserve 0 arity - return function (/* state, title, url */) { - var url = arguments.length > 2 ? arguments[2] : undefined; + // Capture breadcrumbs from any click that is unhandled / bubbled up all the way + // to the document. Do this before we instrument addEventListener. + if (autoBreadcrumbs.dom && this._hasDocument) { + if (_document.addEventListener) { + _document.addEventListener( + 'click', + self._breadcrumbEventHandler('click'), + false + ); + _document.addEventListener( + 'keypress', + self._keypressEventHandler(), + false + ); + } else { + // IE8 Compatibility + _document.attachEvent( + 'onclick', + self._breadcrumbEventHandler('click') + ); + _document.attachEvent('onkeypress', self._keypressEventHandler()); + } + } - // url argument is optional - if (url) { - // coerce to string (this is what pushState does) - self._captureUrlChange(self._lastHref, url + ''); + // record navigation (URL) changes + // NOTE: in Chrome App environment, touching history.pushState, *even inside + // a try/catch block*, will cause Chrome to output an error to console.error + // borrowed from: https://github.com/angular/angular.js/pull/13945/files + var chrome = _window.chrome; + var isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; + var hasPushState = + !isChromePackagedApp && _window.history && history.pushState; + if (autoBreadcrumbs.location && hasPushState) { + // TODO: remove onpopstate handler on uninstall() + var oldOnPopState = _window.onpopstate; + _window.onpopstate = function() { + var currentHref = self._location.href; + self._captureUrlChange(self._lastHref, currentHref); + + if (oldOnPopState) { + return oldOnPopState.apply(this, arguments); } + }; + + fill( + history, + 'pushState', + function(origPushState) { + // note history.pushState.length is 0; intentionally not declaring + // params to preserve 0 arity + return function(/* state, title, url */) { + var url = arguments.length > 2 ? arguments[2] : undefined; + + // url argument is optional + if (url) { + // coerce to string (this is what pushState does) + self._captureUrlChange(self._lastHref, url + ''); + } - return origPushState.apply(this, arguments); - }; - }, wrappedBuiltIns); - } - - if (autoBreadcrumbs.console && 'console' in _window && console.log) { - // console - var consoleMethodCallback = function (msg, data) { - self.captureBreadcrumb({ - message: msg, - level: data.level, - category: 'console' - }); - }; - - each(['debug', 'info', 'warn', 'error', 'log'], function (_, level) { - wrapConsoleMethod(console, level, consoleMethodCallback); - }); - } - - }, - - _restoreBuiltIns: function () { - // restore any wrapped builtins - var builtin; - while (this._wrappedBuiltIns.length) { - builtin = this._wrappedBuiltIns.shift(); + return origPushState.apply(this, arguments); + }; + }, + wrappedBuiltIns + ); + } - var obj = builtin[0], - name = builtin[1], - orig = builtin[2]; + if (autoBreadcrumbs.console && 'console' in _window && console.log) { + // console + var consoleMethodCallback = function(msg, data) { + self.captureBreadcrumb({ + message: msg, + level: data.level, + category: 'console' + }); + }; - obj[name] = orig; - } - }, + each(['debug', 'info', 'warn', 'error', 'log'], function(_, level) { + wrapConsoleMethod(console, level, consoleMethodCallback); + }); + } + }, - _drainPlugins: function() { - var self = this; + _restoreBuiltIns: function() { + // restore any wrapped builtins + var builtin; + while (this._wrappedBuiltIns.length) { + builtin = this._wrappedBuiltIns.shift(); - // FIX ME TODO - each(this._plugins, function(_, plugin) { - var installer = plugin[0]; - var args = plugin[1]; - installer.apply(self, [self].concat(args)); - }); - }, + var obj = builtin[0], + name = builtin[1], + orig = builtin[2]; - _parseDSN: function(str) { - var m = dsnPattern.exec(str), - dsn = {}, - i = 7; + obj[name] = orig; + } + }, - try { - while (i--) dsn[dsnKeys[i]] = m[i] || ''; - } catch(e) { - throw new RavenConfigError('Invalid DSN: ' + str); - } + _drainPlugins: function() { + var self = this; - 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'); - } + // FIX ME TODO + each(this._plugins, function(_, plugin) { + var installer = plugin[0]; + var args = plugin[1]; + installer.apply(self, [self].concat(args)); + }); + }, - return dsn; - }, + _parseDSN: function(str) { + var m = dsnPattern.exec(str), + dsn = {}, + i = 7; - _getGlobalServer: function(uri) { - // assemble the endpoint from the uri pieces - var globalServer = '//' + uri.host + - (uri.port ? ':' + uri.port : ''); + try { + while (i--) dsn[dsnKeys[i]] = m[i] || ''; + } catch (e) { + throw new RavenConfigError('Invalid DSN: ' + str); + } - if (uri.protocol) { - globalServer = uri.protocol + ':' + globalServer; - } - return globalServer; - }, + 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' + ); + } - _handleOnErrorStackInfo: function() { - // if we are intentionally ignoring errors via onerror, bail out - if (!this._ignoreOnError) { - this._handleStackInfo.apply(this, arguments); - } - }, + return dsn; + }, - _handleStackInfo: function(stackInfo, options) { - var frames = this._prepareFrames(stackInfo, options); - - this._triggerEvent('handle', { - stackInfo: stackInfo, - options: options - }); - - this._processException( - stackInfo.name, - stackInfo.message, - stackInfo.url, - stackInfo.lineno, - frames, - options - ); - }, + _getGlobalServer: function(uri) { + // assemble the endpoint from the uri pieces + var globalServer = '//' + uri.host + (uri.port ? ':' + uri.port : ''); - _prepareFrames: function(stackInfo, options) { - var self = this; - var frames = []; - if (stackInfo.stack && stackInfo.stack.length) { - each(stackInfo.stack, function(i, stack) { - var frame = self._normalizeFrame(stack); - if (frame) { - frames.push(frame); + if (uri.protocol) { + globalServer = uri.protocol + ':' + globalServer; } - }); + return globalServer; + }, - // e.g. frames captured via captureMessage throw - if (options && options.trimHeadFrames) { - for (var j = 0; j < options.trimHeadFrames && j < frames.length; j++) { - frames[j].in_app = false; + _handleOnErrorStackInfo: function() { + // if we are intentionally ignoring errors via onerror, bail out + if (!this._ignoreOnError) { + this._handleStackInfo.apply(this, arguments); } - } - } - frames = frames.slice(0, this._globalOptions.stackTraceLimit); - return frames; - }, + }, + _handleStackInfo: function(stackInfo, options) { + var frames = this._prepareFrames(stackInfo, 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 || '?' - }; - - 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) - ); + this._triggerEvent('handle', { + stackInfo: stackInfo, + options: options + }); - return normalized; - }, + this._processException( + stackInfo.name, + stackInfo.message, + stackInfo.url, + stackInfo.lineno, + frames, + options + ); + }, + + _prepareFrames: function(stackInfo, options) { + var self = this; + var frames = []; + if (stackInfo.stack && stackInfo.stack.length) { + each(stackInfo.stack, function(i, stack) { + var frame = self._normalizeFrame(stack, stackInfo.url); + if (frame) { + frames.push(frame); + } + }); + + // e.g. frames captured via captureMessage throw + if (options && options.trimHeadFrames) { + for ( + var j = 0; + j < options.trimHeadFrames && j < frames.length; + j++ + ) { + frames[j].in_app = false; + } + } + } + frames = frames.slice(0, this._globalOptions.stackTraceLimit); + return frames; + }, + + _normalizeFrame: function(frame, stackInfoUrl) { + // normalize the frames data + var normalized = { + filename: frame.url, + lineno: frame.line, + colno: frame.column, + function: frame.func || '?' + }; - _processException: function(type, message, fileurl, lineno, frames, options) { - var stacktrace; - if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return; - - message += ''; - - 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 - }] - }; - } + // Case when we don't have any information about the error + // E.g. throwing a string or raw object, instead of an `Error` in Firefox + // Generating synthetic error doesn't add any value here + // + // We should probably somehow let a user know that they should fix their code + if (!frame.url) { + normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler + } - 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 - }, options); - - // Fire away! - this._send(data); - }, + 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; + }, + + _processException: function( + type, + message, + fileurl, + lineno, + frames, + options + ) { + var testString = (type || '') + ': ' + (message || ''); + + if ( + !!this._globalOptions.ignoreErrors.test && + this._globalOptions.ignoreErrors.test(testString) + ) + return; + + var stacktrace; + + 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 + } + ] + }; + } - _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; - if (data.message) { - data.message = truncate(data.message, max); - } - if (data.exception) { - var exception = data.exception.values[0]; - exception.value = truncate(exception.value, max); - } + 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 + }, + 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; + if (data.message) { + data.message = truncate(data.message, max); + } + if (data.exception) { + var exception = data.exception.values[0]; + exception.value = truncate(exception.value, max); + } - var request = data.request; - if (request) { - if (request.url) { - request.url = truncate(request.url, this._globalOptions.maxUrlLength); - } - if (request.Referer) { - request.Referer = truncate(request.Referer, this._globalOptions.maxUrlLength); - } - } + var request = data.request; + if (request) { + if (request.url) { + request.url = truncate(request.url, this._globalOptions.maxUrlLength); + } + if (request.Referer) { + request.Referer = truncate( + request.Referer, + this._globalOptions.maxUrlLength + ); + } + } - if (data.breadcrumbs && data.breadcrumbs.values) - this._trimBreadcrumbs(data.breadcrumbs); + if (data.breadcrumbs && data.breadcrumbs.values) + this._trimBreadcrumbs(data.breadcrumbs); - return data; - }, + return data; + }, - /** + /** * Truncate breadcrumb values (right now just URLs) */ - _trimBreadcrumbs: function (breadcrumbs) { - // known breadcrumb properties with urls - // TODO: also consider arbitrary prop values that start with (https?)?:// - var urlProps = ['to', 'from', 'url'], - urlProp, - crumb, - data; - - for (var i = 0; i < breadcrumbs.values.length; ++i) { - crumb = breadcrumbs.values[i]; - if (!crumb.hasOwnProperty('data') || !isObject(crumb.data) || objectFrozen(crumb.data)) - continue; - - data = objectMerge({}, crumb.data); - for (var j = 0; j < urlProps.length; ++j) { - urlProp = urlProps[j]; - if (data.hasOwnProperty(urlProp)) { - data[urlProp] = truncate(data[urlProp], this._globalOptions.maxUrlLength); + _trimBreadcrumbs: function(breadcrumbs) { + // known breadcrumb properties with urls + // TODO: also consider arbitrary prop values that start with (https?)?:// + var urlProps = ['to', 'from', 'url'], + urlProp, + crumb, + data; + + for (var i = 0; i < breadcrumbs.values.length; ++i) { + crumb = breadcrumbs.values[i]; + if ( + !crumb.hasOwnProperty('data') || + !isObject(crumb.data) || + objectFrozen(crumb.data) + ) + continue; + + data = objectMerge({}, crumb.data); + for (var j = 0; j < urlProps.length; ++j) { + urlProp = urlProps[j]; + if (data.hasOwnProperty(urlProp) && data[urlProp]) { + data[urlProp] = truncate( + data[urlProp], + this._globalOptions.maxUrlLength + ); + } + } + breadcrumbs.values[i].data = data; } - } - breadcrumbs.values[i].data = data; - } - }, - - _getHttpData: function() { - if (!this._hasNavigator && !this._hasDocument) return; - var httpData = {}; - - if (this._hasNavigator && _navigator.userAgent) { - httpData.headers = { - 'User-Agent': navigator.userAgent - }; - } - - if (this._hasDocument) { - if (_document.location && _document.location.href) { - httpData.url = _document.location.href; - } - if (_document.referrer) { - if (!httpData.headers) httpData.headers = {}; - httpData.headers.Referer = _document.referrer; - } - } - - return httpData; - }, + }, - _resetBackoff: function() { - this._backoffDuration = 0; - this._backoffStart = null; - }, + _getHttpData: function() { + if (!this._hasNavigator && !this._hasDocument) return; + var httpData = {}; - _shouldBackoff: function() { - return this._backoffDuration && now() - this._backoffStart < this._backoffDuration; - }, - - /** - * Returns true if the in-process data payload matches the signature - * of the previously-sent data - * - * NOTE: This has to be done at this level because TraceKit can generate - * data from window.onerror WITHOUT an exception object (IE8, IE9, - * other old browsers). This can take the form of an "exception" - * data object with a single frame (derived from the onerror args). - */ - _isRepeatData: function (current) { - var last = this._lastData; - - if (!last || - current.message !== last.message || // defined for captureMessage - current.culprit !== last.culprit) // defined for captureException/onerror - return false; + if (this._hasNavigator && _navigator.userAgent) { + httpData.headers = { + 'User-Agent': navigator.userAgent + }; + } - // Stacktrace interface (i.e. from captureMessage) - if (current.stacktrace || last.stacktrace) { - return isSameStacktrace(current.stacktrace, last.stacktrace); - } - // Exception interface (i.e. from captureException/onerror) - else if (current.exception || last.exception) { - return isSameException(current.exception, last.exception); - } + if (this._hasDocument) { + if (_document.location && _document.location.href) { + httpData.url = _document.location.href; + } + if (_document.referrer) { + if (!httpData.headers) httpData.headers = {}; + httpData.headers.Referer = _document.referrer; + } + } - return true; - }, + return httpData; + }, - _setBackoffState: function(request) { - // If we are already in a backoff state, don't change anything - if (this._shouldBackoff()) { - return; - } + _resetBackoff: function() { + this._backoffDuration = 0; + this._backoffStart = null; + }, - var status = request.status; - - // 400 - project_id doesn't exist or some other fatal - // 401 - invalid/revoked dsn - // 429 - too many requests - if (!(status === 400 || status === 401 || status === 429)) - return; - - var retry; - try { - // If Retry-After is not in Access-Control-Expose-Headers, most - // browsers will throw an exception trying to access it - retry = request.getResponseHeader('Retry-After'); - retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds - } catch (e) { - /* eslint no-empty:0 */ - } + _shouldBackoff: function() { + return ( + this._backoffDuration && + now() - this._backoffStart < this._backoffDuration + ); + }, + /** + * Returns true if the in-process data payload matches the signature + * of the previously-sent data + * + * NOTE: This has to be done at this level because TraceKit can generate + * data from window.onerror WITHOUT an exception object (IE8, IE9, + * other old browsers). This can take the form of an "exception" + * data object with a single frame (derived from the onerror args). + */ + _isRepeatData: function(current) { + var last = this._lastData; + + if ( + !last || + current.message !== last.message || // defined for captureMessage + current.culprit !== last.culprit // defined for captureException/onerror + ) + return false; + + // Stacktrace interface (i.e. from captureMessage) + if (current.stacktrace || last.stacktrace) { + return isSameStacktrace(current.stacktrace, last.stacktrace); + } else if (current.exception || last.exception) { + // Exception interface (i.e. from captureException/onerror) + return isSameException(current.exception, last.exception); + } - this._backoffDuration = retry - // If Sentry server returned a Retry-After value, use it - ? retry - // Otherwise, double the last backoff duration (starts at 1 sec) - : this._backoffDuration * 2 || 1000; + return true; + }, - this._backoffStart = now(); - }, + _setBackoffState: function(request) { + // If we are already in a backoff state, don't change anything + if (this._shouldBackoff()) { + return; + } - _send: function(data) { - var globalOptions = this._globalOptions; + var status = request.status; - var baseData = { - project: this._globalProject, - logger: globalOptions.logger, - platform: 'javascript' - }, httpData = this._getHttpData(); + // 400 - project_id doesn't exist or some other fatal + // 401 - invalid/revoked dsn + // 429 - too many requests + if (!(status === 400 || status === 401 || status === 429)) return; - if (httpData) { - baseData.request = httpData; - } + var retry; + try { + // If Retry-After is not in Access-Control-Expose-Headers, most + // browsers will throw an exception trying to access it + retry = request.getResponseHeader('Retry-After'); + retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds + } catch (e) { + /* eslint no-empty:0 */ + } - // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload - if (data.trimHeadFrames) delete data.trimHeadFrames; + this._backoffDuration = retry + ? // If Sentry server returned a Retry-After value, use it + retry + : // Otherwise, double the last backoff duration (starts at 1 sec) + this._backoffDuration * 2 || 1000; - data = objectMerge(baseData, data); + this._backoffStart = now(); + }, - // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge - data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags); - data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra); + _send: function(data) { + var globalOptions = this._globalOptions; - // Send along our own collected metadata with extra - data.extra['session:duration'] = now() - this._startTime; + var baseData = { + project: this._globalProject, + logger: globalOptions.logger, + platform: 'javascript' + }, + httpData = this._getHttpData(); - if (this._breadcrumbs && this._breadcrumbs.length > 0) { - // intentionally make shallow copy so that additions - // to breadcrumbs aren't accidentally sent in this request - data.breadcrumbs = { - values: [].slice.call(this._breadcrumbs, 0) - }; - } + if (httpData) { + baseData.request = httpData; + } - // If there are no tags/extra, strip the key from the payload alltogther. - if (isEmptyObject(data.tags)) delete data.tags; + // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload + if (data.trimHeadFrames) delete data.trimHeadFrames; + + data = objectMerge(baseData, data); + + // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge + data.tags = objectMerge( + objectMerge({}, this._globalContext.tags), + data.tags + ); + data.extra = objectMerge( + objectMerge({}, this._globalContext.extra), + data.extra + ); + + // Send along our own collected metadata with extra + data.extra['session:duration'] = now() - this._startTime; + + if (this._breadcrumbs && this._breadcrumbs.length > 0) { + // intentionally make shallow copy so that additions + // to breadcrumbs aren't accidentally sent in this request + data.breadcrumbs = { + values: [].slice.call(this._breadcrumbs, 0) + }; + } - if (this._globalContext.user) { - // sentry.interfaces.User - data.user = this._globalContext.user; - } + // If there are no tags/extra, strip the key from the payload alltogther. + if (isEmptyObject(data.tags)) delete data.tags; - // Include the environment if it's defined in globalOptions - if (globalOptions.environment) data.environment = globalOptions.environment; + if (this._globalContext.user) { + // sentry.interfaces.User + data.user = this._globalContext.user; + } - // Include the release if it's defined in globalOptions - if (globalOptions.release) data.release = globalOptions.release; + // Include the environment if it's defined in globalOptions + if (globalOptions.environment) + data.environment = globalOptions.environment; - // Include server_name if it's defined in globalOptions - if (globalOptions.serverName) data.server_name = globalOptions.serverName; + // Include the release if it's defined in globalOptions + if (globalOptions.release) data.release = globalOptions.release; - if (isFunction(globalOptions.dataCallback)) { - data = globalOptions.dataCallback(data) || data; - } + // Include server_name if it's defined in globalOptions + if (globalOptions.serverName) data.server_name = globalOptions.serverName; - // Why?????????? - if (!data || isEmptyObject(data)) { - return; - } + if (isFunction(globalOptions.dataCallback)) { + data = globalOptions.dataCallback(data) || data; + } - // Check if the request should be filtered or not - if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { - return; - } + // Why?????????? + if (!data || isEmptyObject(data)) { + return; + } - // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests), - // so drop requests until "cool-off" period has elapsed. - if (this._shouldBackoff()) { - this._logDebug('warn', 'Raven dropped error due to backoff: ', data); - return; - } + // Check if the request should be filtered or not + if ( + isFunction(globalOptions.shouldSendCallback) && + !globalOptions.shouldSendCallback(data) + ) { + return; + } - if (typeof globalOptions.sampleRate === 'number') { - if (Math.random() < globalOptions.sampleRate) { - this._sendProcessedPayload(data); - } - } else { - this._sendProcessedPayload(data); - } - }, + // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests), + // so drop requests until "cool-off" period has elapsed. + if (this._shouldBackoff()) { + this._logDebug('warn', 'Raven dropped error due to backoff: ', data); + return; + } - _getUuid: function () { - return uuid4(); - }, + if (typeof globalOptions.sampleRate === 'number') { + if (Math.random() < globalOptions.sampleRate) { + this._sendProcessedPayload(data); + } + } else { + this._sendProcessedPayload(data); + } + }, - _sendProcessedPayload: function(data, callback) { - var self = this; - var globalOptions = this._globalOptions; + _getUuid: function() { + return uuid4(); + }, - if (!this.isSetup()) return; + _sendProcessedPayload: function(data, callback) { + var self = this; + var globalOptions = this._globalOptions; - // 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 - this._lastEventId = data.event_id || (data.event_id = this._getUuid()); + if (!this.isSetup()) return; - // Try and clean up the packet before sending by truncating long values - data = this._trimPacket(data); + // Try and clean up the packet before sending by truncating long values + data = this._trimPacket(data); - // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback, - // but this would require copying an un-truncated copy of the data packet, which can be - // arbitrarily deep (extra_data) -- could be worthwhile? will revisit - if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) { - this._logDebug('warn', 'Raven dropped repeat event: ', data); - return; - } + // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback, + // but this would require copying an un-truncated copy of the data packet, which can be + // arbitrarily deep (extra_data) -- could be worthwhile? will revisit + if (!this._globalOptions.allowDuplicates && this._isRepeatData(data)) { + this._logDebug('warn', 'Raven dropped repeat event: ', data); + return; + } - // Store outbound payload after trim - this._lastData = data; + // 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 + this._lastEventId = data.event_id || (data.event_id = this._getUuid()); - this._logDebug('debug', 'Raven about to send:', data); + // Store outbound payload after trim + this._lastData = data; - var auth = { - sentry_version: '7', - sentry_client: 'raven-js/' + this.VERSION, - sentry_key: this._globalKey - }; - if (this._globalSecret) { - auth.sentry_secret = this._globalSecret; - } + this._logDebug('debug', 'Raven about to send:', data); - var exception = data.exception && data.exception.values[0]; - this.captureBreadcrumb({ - category: 'sentry', - message: exception - ? (exception.type ? exception.type + ': ' : '') + exception.value - : data.message, - event_id: data.event_id, - level: data.level || 'error' // presume error unless specified - }); - - var url = this._globalEndpoint; - (globalOptions.transport || this._makeRequest).call(this, { - url: url, - auth: auth, - data: data, - options: globalOptions, - onSuccess: function success() { - self._resetBackoff(); - - self._triggerEvent('success', { - data: data, - src: url - }); - callback && callback(); - }, - onError: function failure(error) { - self._logDebug('error', 'Raven transport failed to send: ', error); + var auth = { + sentry_version: '7', + sentry_client: 'raven-js/' + this.VERSION, + sentry_key: this._globalKey + }; - if (error.request) { - self._setBackoffState(error.request); + if (this._globalSecret) { + auth.sentry_secret = this._globalSecret; } - self._triggerEvent('failure', { - data: data, - src: url + var exception = data.exception && data.exception.values[0]; + this.captureBreadcrumb({ + category: 'sentry', + message: exception + ? (exception.type ? exception.type + ': ' : '') + exception.value + : data.message, + event_id: data.event_id, + level: data.level || 'error' // presume error unless specified }); - error = error || new Error('Raven send failed (no additional details provided)'); - callback && callback(error); - } - }); - }, - _makeRequest: function(opts) { - var request = new XMLHttpRequest(); + var url = this._globalEndpoint; + (globalOptions.transport || this._makeRequest).call(this, { + url: url, + auth: auth, + data: data, + options: globalOptions, + onSuccess: function success() { + self._resetBackoff(); + + self._triggerEvent('success', { + data: data, + src: url + }); + callback && callback(); + }, + onError: function failure(error) { + self._logDebug('error', 'Raven transport failed to send: ', error); - // if browser doesn't support CORS (e.g. IE7), we are out of luck - var hasCORS = - 'withCredentials' in request || - typeof XDomainRequest !== 'undefined'; + if (error.request) { + self._setBackoffState(error.request); + } - if (!hasCORS) return; + self._triggerEvent('failure', { + data: data, + src: url + }); + error = + error || + new Error('Raven send failed (no additional details provided)'); + callback && callback(error); + } + }); + }, + + _makeRequest: function(opts) { + var request = _window.XMLHttpRequest && new _window.XMLHttpRequest(); + if (!request) return; + + // if browser doesn't support CORS (e.g. IE7), we are out of luck + var hasCORS = + 'withCredentials' in request || typeof XDomainRequest !== 'undefined'; + + if (!hasCORS) return; + + var url = opts.url; + + if ('withCredentials' in request) { + request.onreadystatechange = function() { + if (request.readyState !== 4) { + return; + } else if (request.status === 200) { + opts.onSuccess && opts.onSuccess(); + } else if (opts.onError) { + var err = new Error('Sentry error code: ' + request.status); + err.request = request; + opts.onError(err); + } + }; + } else { + request = new XDomainRequest(); + // xdomainrequest cannot go http -> https (or vice versa), + // so always use protocol relative + url = url.replace(/^https?:/, ''); + + // onreadystatechange not supported by XDomainRequest + if (opts.onSuccess) { + request.onload = opts.onSuccess; + } + if (opts.onError) { + request.onerror = function() { + var err = new Error('Sentry error code: XDomainRequest'); + err.request = request; + opts.onError(err); + }; + } + } - var url = opts.url; + // NOTE: auth is intentionally sent as part of query string (NOT as custom + // HTTP header) so as to avoid preflight CORS requests + request.open('POST', url + '?' + urlencode(opts.auth)); + request.send(stringify(opts.data)); + }, + + _logDebug: function(level) { + if (this._originalConsoleMethods[level] && this.debug) { + // In IE<10 console methods do not have their own 'apply' method + Function.prototype.apply.call( + this._originalConsoleMethods[level], + this._originalConsole, + [].slice.call(arguments, 1) + ); + } + }, - if ('withCredentials' in request) { - request.onreadystatechange = function () { - if (request.readyState !== 4) { - return; - } else if (request.status === 200) { - opts.onSuccess && opts.onSuccess(); - } else if (opts.onError) { - var err = new Error('Sentry error code: ' + request.status); - err.request = request; - opts.onError(err); + _mergeContext: function(key, context) { + if (isUndefined(context)) { + delete this._globalContext[key]; + } else { + this._globalContext[key] = objectMerge( + this._globalContext[key] || {}, + context + ); } + } }; - } else { - request = new XDomainRequest(); - // xdomainrequest cannot go http -> https (or vice versa), - // so always use protocol relative - url = url.replace(/^https?:/, ''); - - // onreadystatechange not supported by XDomainRequest - if (opts.onSuccess) { - request.onload = opts.onSuccess; - } - if (opts.onError) { - request.onerror = function () { - var err = new Error('Sentry error code: XDomainRequest'); - err.request = request; - opts.onError(err); - } - } - } - - // NOTE: auth is intentionally sent as part of query string (NOT as custom - // HTTP header) so as to avoid preflight CORS requests - request.open('POST', url + '?' + urlencode(opts.auth)); - request.send(stringify(opts.data)); - }, - - _logDebug: function(level) { - if (this._originalConsoleMethods[level] && this.debug) { - // In IE<10 console methods do not have their own 'apply' method - Function.prototype.apply.call( - this._originalConsoleMethods[level], - this._originalConsole, - [].slice.call(arguments, 1) - ); - } - }, - - _mergeContext: function(key, context) { - if (isUndefined(context)) { - delete this._globalContext[key]; - } else { - this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context); - } - } -}; -/*------------------------------------------------ + /*------------------------------------------------ * utils * * conditionally exported for test via Raven.utils ================================================= */ -var objectPrototype = Object.prototype; + var objectPrototype = Object.prototype; -function isUndefined(what) { - return what === void 0; -} - -function isFunction(what) { - return typeof what === 'function'; -} + function isUndefined(what) { + return what === void 0; + } -function isString(what) { - return objectPrototype.toString.call(what) === '[object String]'; -} + function isFunction(what) { + return typeof what === 'function'; + } + function isString(what) { + return objectPrototype.toString.call(what) === '[object String]'; + } -function isEmptyObject(what) { - for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars - return true; -} + function isEmptyObject(what) { + for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars + return true; + } -function each(obj, callback) { - var i, j; + function each(obj, callback) { + var i, j; - if (isUndefined(obj.length)) { - for (i in obj) { - if (hasKey(obj, i)) { - callback.call(null, i, obj[i]); - } - } - } else { - j = obj.length; - if (j) { - for (i = 0; i < j; i++) { - callback.call(null, i, obj[i]); + if (isUndefined(obj.length)) { + for (i in obj) { + if (hasKey(obj, i)) { + callback.call(null, i, obj[i]); + } + } + } else { + j = obj.length; + if (j) { + for (i = 0; i < j; i++) { + callback.call(null, i, obj[i]); + } + } + } } - } - } -} -function objectMerge(obj1, obj2) { - if (!obj2) { - return obj1; - } - each(obj2, function(key, value){ - obj1[key] = value; - }); - return obj1; -} + function objectMerge(obj1, obj2) { + if (!obj2) { + return obj1; + } + each(obj2, function(key, value) { + obj1[key] = value; + }); + return obj1; + } -/** + /** * This function is only used for react-native. * react-native freezes object that have already been sent over the * js bridge. We need this function in order to check if the object is frozen. @@ -1853,363 +2149,398 @@ function objectMerge(obj1, obj2) { * supported because it's not relevant for other "platforms". See related issue: * https://github.com/getsentry/react-native-sentry/issues/57 */ -function objectFrozen(obj) { - if (!Object.isFrozen) { - return false; - } - return Object.isFrozen(obj); -} + function objectFrozen(obj) { + if (!Object.isFrozen) { + return false; + } + return Object.isFrozen(obj); + } -function truncate(str, max) { - return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; -} + function truncate(str, max) { + return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; + } -/** + /** * 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 objectPrototype.hasOwnProperty.call(object, key); -} - -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'); -} - -function urlencode(o) { - var pairs = []; - each(o, function(key, value) { - pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }); - return pairs.join('&'); -} - -// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B -// intentionally using regex and not href parsing trick because React Native and other -// environments where DOM might not be available -function parseUrl(url) { - var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/); - if (!match) return {}; - - // coerce to undefined values to empty string so we don't get 'undefined' - var query = match[6] || ''; - var fragment = match[8] || ''; - return { - protocol: match[2], - host: match[4], - path: match[5], - relative: match[5] + query + fragment // everything minus origin - }; -} -function uuid4() { - var crypto = _window.crypto || _window.msCrypto; - - if (!isUndefined(crypto) && crypto.getRandomValues) { - // Use window.crypto API if available - var arr = new Uint16Array(8); - crypto.getRandomValues(arr); - - // set 4 in byte 7 - arr[3] = arr[3] & 0xFFF | 0x4000; - // set 2 most significant bits of byte 9 to '10' - arr[4] = arr[4] & 0x3FFF | 0x8000; - - var pad = function(num) { - var v = num.toString(16); - while (v.length < 4) { - v = '0' + v; + function hasKey(object, key) { + return objectPrototype.hasOwnProperty.call(object, key); } - return v; - }; - return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) + - pad(arr[5]) + pad(arr[6]) + pad(arr[7]); - } else { - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 - 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 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'); + } + + function urlencode(o) { + var pairs = []; + each(o, function(key, value) { + pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }); + return pairs.join('&'); + } + + // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B + // intentionally using regex and not href parsing trick because React Native and other + // environments where DOM might not be available + function parseUrl(url) { + var match = url.match( + /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/ + ); + if (!match) return {}; + + // coerce to undefined values to empty string so we don't get 'undefined' + var query = match[6] || ''; + var fragment = match[8] || ''; + return { + protocol: match[2], + host: match[4], + path: match[5], + relative: match[5] + query + fragment // everything minus origin + }; + } + function uuid4() { + var crypto = _window.crypto || _window.msCrypto; + + if (!isUndefined(crypto) && crypto.getRandomValues) { + // Use window.crypto API if available + // eslint-disable-next-line no-undef + var arr = new Uint16Array(8); + crypto.getRandomValues(arr); + + // set 4 in byte 7 + arr[3] = (arr[3] & 0xfff) | 0x4000; + // set 2 most significant bits of byte 9 to '10' + arr[4] = (arr[4] & 0x3fff) | 0x8000; + + var pad = function(num) { + var v = num.toString(16); + while (v.length < 4) { + v = '0' + v; + } + return v; + }; + + return ( + pad(arr[0]) + + pad(arr[1]) + + pad(arr[2]) + + pad(arr[3]) + + pad(arr[4]) + + pad(arr[5]) + + pad(arr[6]) + + pad(arr[7]) + ); + } else { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = (Math.random() * 16) | 0, + v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); + } + } -/** + /** * Given a child DOM element, returns a query-selector statement describing that * and its ancestors * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] * @param elem * @returns {string} */ -function htmlTreeAsString(elem) { - /* eslint no-extra-parens:0*/ - var MAX_TRAVERSE_HEIGHT = 5, - MAX_OUTPUT_LEN = 80, - out = [], - height = 0, - len = 0, - separator = ' > ', - sepLength = separator.length, - nextStr; - - while (elem && height++ < MAX_TRAVERSE_HEIGHT) { - - nextStr = htmlElementAsString(elem); - // bail out if - // - nextStr is the 'html' element - // - the length of the string that would be created exceeds MAX_OUTPUT_LEN - // (ignore this limit if we are on the first iteration) - if (nextStr === 'html' || height > 1 && len + (out.length * sepLength) + nextStr.length >= MAX_OUTPUT_LEN) { - break; - } + function htmlTreeAsString(elem) { + /* eslint no-extra-parens:0*/ + var MAX_TRAVERSE_HEIGHT = 5, + MAX_OUTPUT_LEN = 80, + out = [], + height = 0, + len = 0, + separator = ' > ', + sepLength = separator.length, + nextStr; + + while (elem && height++ < MAX_TRAVERSE_HEIGHT) { + nextStr = htmlElementAsString(elem); + // bail out if + // - nextStr is the 'html' element + // - the length of the string that would be created exceeds MAX_OUTPUT_LEN + // (ignore this limit if we are on the first iteration) + if ( + nextStr === 'html' || + (height > 1 && + len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN) + ) { + break; + } - out.push(nextStr); + out.push(nextStr); - len += nextStr.length; - elem = elem.parentNode; - } + len += nextStr.length; + elem = elem.parentNode; + } - return out.reverse().join(separator); -} + return out.reverse().join(separator); + } -/** + /** * Returns a simple, query-selector representation of a DOM element * e.g. [HTMLElement] => input#foo.btn[name=baz] * @param HTMLElement * @returns {string} */ -function htmlElementAsString(elem) { - var out = [], - className, - classes, - key, - attr, - i; - - if (!elem || !elem.tagName) { - return ''; - } - - out.push(elem.tagName.toLowerCase()); - if (elem.id) { - out.push('#' + elem.id); - } - - className = elem.className; - if (className && isString(className)) { - classes = className.split(/\s+/); - for (i = 0; i < classes.length; i++) { - out.push('.' + classes[i]); - } - } - var attrWhitelist = ['type', 'name', 'title', 'alt']; - for (i = 0; i < attrWhitelist.length; i++) { - key = attrWhitelist[i]; - attr = elem.getAttribute(key); - if (attr) { - out.push('[' + key + '="' + attr + '"]'); - } - } - return out.join(''); -} + function htmlElementAsString(elem) { + var out = [], + className, + classes, + key, + attr, + i; + + if (!elem || !elem.tagName) { + return ''; + } + + out.push(elem.tagName.toLowerCase()); + if (elem.id) { + out.push('#' + elem.id); + } + + className = elem.className; + if (className && isString(className)) { + classes = className.split(/\s+/); + for (i = 0; i < classes.length; i++) { + out.push('.' + classes[i]); + } + } + var attrWhitelist = ['type', 'name', 'title', 'alt']; + for (i = 0; i < attrWhitelist.length; i++) { + key = attrWhitelist[i]; + attr = elem.getAttribute(key); + if (attr) { + out.push('[' + key + '="' + attr + '"]'); + } + } + return out.join(''); + } -/** + /** * Returns true if either a OR b is truthy, but not both */ -function isOnlyOneTruthy(a, b) { - return !!(!!a ^ !!b); -} + function isOnlyOneTruthy(a, b) { + return !!(!!a ^ !!b); + } -/** + /** * Returns true if the two input exception interfaces have the same content */ -function isSameException(ex1, ex2) { - if (isOnlyOneTruthy(ex1, ex2)) - return false; + function isSameException(ex1, ex2) { + if (isOnlyOneTruthy(ex1, ex2)) return false; - ex1 = ex1.values[0]; - ex2 = ex2.values[0]; + ex1 = ex1.values[0]; + ex2 = ex2.values[0]; - if (ex1.type !== ex2.type || - ex1.value !== ex2.value) - return false; + if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false; - return isSameStacktrace(ex1.stacktrace, ex2.stacktrace); -} + return isSameStacktrace(ex1.stacktrace, ex2.stacktrace); + } -/** + /** * Returns true if the two input stack trace interfaces have the same content */ -function isSameStacktrace(stack1, stack2) { - if (isOnlyOneTruthy(stack1, stack2)) - return false; - - var frames1 = stack1.frames; - var frames2 = stack2.frames; - - // Exit early if frame count differs - if (frames1.length !== frames2.length) - return false; - - // Iterate through every frame; bail out if anything differs - var a, b; - for (var i = 0; i < frames1.length; i++) { - a = frames1[i]; - b = frames2[i]; - if (a.filename !== b.filename || - a.lineno !== b.lineno || - a.colno !== b.colno || - a['function'] !== b['function']) - return false; - } - return true; -} + function isSameStacktrace(stack1, stack2) { + if (isOnlyOneTruthy(stack1, stack2)) return false; + + var frames1 = stack1.frames; + var frames2 = stack2.frames; + + // Exit early if frame count differs + if (frames1.length !== frames2.length) return false; + + // Iterate through every frame; bail out if anything differs + var a, b; + for (var i = 0; i < frames1.length; i++) { + a = frames1[i]; + b = frames2[i]; + if ( + a.filename !== b.filename || + a.lineno !== b.lineno || + a.colno !== b.colno || + a['function'] !== b['function'] + ) + return false; + } + return true; + } -/** + /** * Polyfill a method * @param obj object e.g. `document` * @param name method name present on object e.g. `addEventListener` * @param replacement replacement function * @param track {optional} record instrumentation to an array */ -function fill(obj, name, replacement, track) { - var orig = obj[name]; - obj[name] = replacement(orig); - if (track) { - track.push([obj, name, orig]); - } -} - -if (typeof __DEV__ !== 'undefined' && __DEV__) { - Raven.utils = { - isUndefined: isUndefined, - isFunction: isFunction, - isString: isString, - isObject: isObject, - isEmptyObject: isEmptyObject, - isError: isError, - each: each, - objectMerge: objectMerge, - truncate: truncate, - hasKey: hasKey, - joinRegExp: joinRegExp, - urlencode: urlencode, - uuid4: uuid4, - htmlTreeAsString: htmlTreeAsString, - htmlElementAsString: htmlElementAsString, - parseUrl: parseUrl, - fill: fill - }; -}; - -// Deprecations -Raven.prototype.setUser = Raven.prototype.setUserContext; -Raven.prototype.setReleaseContext = Raven.prototype.setRelease; - -module.exports = Raven; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"1":1,"2":2,"5":5,"6":6,"7":7}],4:[function(_dereq_,module,exports){ -(function (global){ -/** + function fill(obj, name, replacement, track) { + var orig = obj[name]; + obj[name] = replacement(orig); + if (track) { + track.push([obj, name, orig]); + } + } + + if (typeof __DEV__ !== 'undefined' && __DEV__) { + Raven.utils = { + isUndefined: isUndefined, + isFunction: isFunction, + isString: isString, + isObject: isObject, + isEmptyObject: isEmptyObject, + isError: isError, + each: each, + objectMerge: objectMerge, + truncate: truncate, + hasKey: hasKey, + joinRegExp: joinRegExp, + urlencode: urlencode, + uuid4: uuid4, + htmlTreeAsString: htmlTreeAsString, + htmlElementAsString: htmlElementAsString, + parseUrl: parseUrl, + fill: fill + }; + } + + // Deprecations + Raven.prototype.setUser = Raven.prototype.setUserContext; + Raven.prototype.setReleaseContext = Raven.prototype.setRelease; + + module.exports = Raven; + }.call( + this, + typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' ? window : {} + )); + }, + {'1': 1, '2': 2, '5': 5, '6': 6, '7': 7} + ], + 4: [ + function(_dereq_, module, exports) { + (function(global) { + /** * Enforces a single instance of the Raven client, and the * main entry point for Raven. If you are a consumer of the * Raven library, you SHOULD load this file (vs raven.js). **/ -'use strict'; - -var RavenConstructor = _dereq_(3); + var RavenConstructor = _dereq_(3); -// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) -var _window = typeof window !== 'undefined' ? window - : typeof global !== 'undefined' ? global - : typeof self !== 'undefined' ? self - : {}; -var _Raven = _window.Raven; + // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) + var _window = + typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' ? self : {}; + var _Raven = _window.Raven; -var Raven = new RavenConstructor(); + var Raven = new RavenConstructor(); -/* + /* * Allow multiple versions of Raven to be installed. * Strip Raven from the global context and returns the instance. * * @return {Raven} */ -Raven.noConflict = function () { - _window.Raven = _Raven; - return Raven; -}; - -Raven.afterLoad(); - -module.exports = Raven; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"3":3}],5:[function(_dereq_,module,exports){ -'use strict'; - -function isObject(what) { - return typeof what === 'object' && what !== null; -} - -// Yanked from https://git.io/vS8DV re-used under CC0 -// with some tiny modifications -function isError(value) { - switch ({}.toString.call(value)) { - case '[object Error]': return true; - case '[object Exception]': return true; - case '[object DOMException]': return true; - default: return value instanceof Error; - } -} - -function wrappedCallback(callback) { - function dataCallback(data, original) { - var normalizedData = callback(data) || data; - if (original) { - return original(normalizedData) || normalizedData; - } - return normalizedData; - } - - return dataCallback; -} - -module.exports = { - isObject: isObject, - isError: isError, - wrappedCallback: wrappedCallback -}; - -},{}],6:[function(_dereq_,module,exports){ -(function (global){ -'use strict'; + Raven.noConflict = function() { + _window.Raven = _Raven; + return Raven; + }; -var utils = _dereq_(5); + Raven.afterLoad(); + + module.exports = Raven; + }.call( + this, + typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' ? window : {} + )); + }, + {'3': 3} + ], + 5: [ + function(_dereq_, module, exports) { + function isObject(what) { + return typeof what === 'object' && what !== null; + } + + // Yanked from https://git.io/vS8DV re-used under CC0 + // with some tiny modifications + function isError(value) { + switch ({}.toString.call(value)) { + case '[object Error]': + return true; + case '[object Exception]': + return true; + case '[object DOMException]': + return true; + default: + return value instanceof Error; + } + } + + function wrappedCallback(callback) { + function dataCallback(data, original) { + var normalizedData = callback(data) || data; + if (original) { + return original(normalizedData) || normalizedData; + } + return normalizedData; + } -/* + return dataCallback; + } + + module.exports = { + isObject: isObject, + isError: isError, + wrappedCallback: wrappedCallback + }; + }, + {} + ], + 6: [ + function(_dereq_, module, exports) { + (function(global) { + var utils = _dereq_(5); + + /* TraceKit - Cross brower stack traces This was originally forked from github.com/occ/TraceKit, but has since been @@ -2219,33 +2550,33 @@ var utils = _dereq_(5); MIT license */ -var TraceKit = { - collectWindowErrors: true, - debug: false -}; - -// This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) -var _window = typeof window !== 'undefined' ? window - : typeof global !== 'undefined' ? global - : typeof self !== 'undefined' ? self - : {}; + var TraceKit = { + collectWindowErrors: true, + debug: false + }; -// global reference to slice -var _slice = [].slice; -var UNKNOWN_FUNCTION = '?'; + // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785) + var _window = + typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' ? self : {}; -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types -var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; + // global reference to slice + var _slice = [].slice; + var UNKNOWN_FUNCTION = '?'; -function getLocationHref() { - if (typeof document === 'undefined' || typeof document.location === 'undefined') - return ''; + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types + var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; - return document.location.href; -} + function getLocationHref() { + if (typeof document === 'undefined' || document.location == null) return ''; + return document.location.href; + } -/** + /** * TraceKit.report: cross-browser processing of unhandled exceptions * * Syntax: @@ -2284,68 +2615,68 @@ function getLocationHref() { * Handlers receive a stackInfo object as described in the * TraceKit.computeStackTrace docs. */ -TraceKit.report = (function reportModuleWrapper() { - var handlers = [], - lastArgs = null, - lastException = null, - lastExceptionStack = null; + 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); - } + 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); - } - } - } + 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 = []; - } + function unsubscribeAll() { + uninstallGlobalHandler(); + handlers = []; + } - /** + /** * Dispatch stack information to all handlers. * @param {Object.} stack */ - function notifyHandlers(stack, isWindowError) { - var exception = null; - if (isWindowError && !TraceKit.collectWindowErrors) { - return; - } - for (var i in handlers) { - if (handlers.hasOwnProperty(i)) { - try { - handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); - } catch (inner) { - exception = inner; + function notifyHandlers(stack, isWindowError) { + var exception = null; + if (isWindowError && !TraceKit.collectWindowErrors) { + return; + } + for (var i in handlers) { + if (handlers.hasOwnProperty(i)) { + try { + handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); + } catch (inner) { + exception = inner; + } + } } - } - } - if (exception) { - throw exception; - } - } + if (exception) { + throw exception; + } + } - var _oldOnerrorHandler, _onErrorHandlerInstalled; + var _oldOnerrorHandler, _onErrorHandlerInstalled; - /** + /** * Ensures all global unhandled exceptions are recorded. * Supported by Gecko and IE. * @param {string} message Error message. @@ -2356,129 +2687,135 @@ TraceKit.report = (function reportModuleWrapper() { * 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 && utils.isError(ex)) { - // non-string `ex` arg; attempt to extract stack trace - - // 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 - }; - - var name = undefined; - var msg = message; // must be new var or will modify original `arguments` - var groups; - if ({}.toString.call(message) === '[object String]') { - var groups = message.match(ERROR_TYPES_RE); - if (groups) { - name = groups[1]; - msg = groups[2]; - } - } - - location.func = UNKNOWN_FUNCTION; - - stack = { - 'name': name, - 'message': msg, - 'url': getLocationHref(), - 'stack': [location] - }; - notifyHandlers(stack, true); - } - - if (_oldOnerrorHandler) { - return _oldOnerrorHandler.apply(this, arguments); - } + function traceKitWindowOnError(message, url, lineNo, colNo, ex) { + var stack = null; + + if (lastExceptionStack) { + TraceKit.computeStackTrace.augmentStackTraceWithInitialElement( + lastExceptionStack, + url, + lineNo, + message + ); + processLastException(); + } else if (ex && utils.isError(ex)) { + // non-string `ex` arg; attempt to extract stack trace + + // 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 + }; + + var name = undefined; + var msg = message; // must be new var or will modify original `arguments` + var groups; + if ({}.toString.call(message) === '[object String]') { + var groups = message.match(ERROR_TYPES_RE); + if (groups) { + name = groups[1]; + msg = groups[2]; + } + } - return false; - } + location.func = UNKNOWN_FUNCTION; - function installGlobalHandler () - { - if (_onErrorHandlerInstalled) { - return; - } - _oldOnerrorHandler = _window.onerror; - _window.onerror = traceKitWindowOnError; - _onErrorHandlerInstalled = true; - } + stack = { + name: name, + message: msg, + url: getLocationHref(), + stack: [location] + }; + notifyHandlers(stack, true); + } - function uninstallGlobalHandler () - { - if (!_onErrorHandlerInstalled) { - return; - } - _window.onerror = _oldOnerrorHandler; - _onErrorHandlerInstalled = false; - _oldOnerrorHandler = undefined; - } + if (_oldOnerrorHandler) { + return _oldOnerrorHandler.apply(this, arguments); + } - function processLastException() { - var _lastExceptionStack = lastExceptionStack, - _lastArgs = lastArgs; - lastArgs = null; - lastExceptionStack = null; - lastException = null; - notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); - } + 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 - setTimeout(function () { - if (lastException === ex) { - processLastException(); - } - }, (stack.incomplete ? 2000 : 0)); + 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(); + } + } - if (rethrow !== false) { - throw ex; // re-throw to propagate to the top level (and cause window.onerror) - } - } + 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 + 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; -}()); + report.subscribe = subscribe; + report.unsubscribe = unsubscribe; + report.uninstall = unsubscribeAll; + return report; + })(); -/** + /** * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript * * Syntax: @@ -2529,138 +2866,140 @@ TraceKit.report = (function reportModuleWrapper() { * inner function that actually caused the exception). * */ -TraceKit.computeStackTrace = (function computeStackTraceWrapper() { - // 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' - - /** + TraceKit.computeStackTrace = (function computeStackTraceWrapper() { + // 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.} Stack trace information. */ - function computeStackTraceFromStackProp(ex) { - if (typeof ex.stack === 'undefined' || !ex.stack) return; - - var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, - gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i, - winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, - - // Used to additionally parse URL/line/column from eval frames - geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i, - chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/, - - lines = ex.stack.split('\n'), - stack = [], - submatch, - parts, - element, - reference = /^(.*) is undefined$/.exec(ex.message); - - for (var i = 0, j = lines.length; i < j; ++i) { - if ((parts = chrome.exec(lines[i]))) { - var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line - var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line - if (isEval && (submatch = chromeEval.exec(parts[2]))) { - // throw out eval line/column and use top-most line/column number - parts[2] = submatch[1]; // url - parts[3] = submatch[2]; // line - parts[4] = submatch[3]; // column - } - element = { - 'url': !isNative ? parts[2] : null, - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': isNative ? [parts[2]] : [], - 'line': parts[3] ? +parts[3] : null, - 'column': parts[4] ? +parts[4] : null - }; - } else if ( parts = winjs.exec(lines[i]) ) { - element = { - 'url': parts[2], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': [], - 'line': +parts[3], - 'column': parts[4] ? +parts[4] : null - }; - } else if ((parts = gecko.exec(lines[i]))) { - var isEval = parts[3] && parts[3].indexOf(' > eval') > -1; - if (isEval && (submatch = geckoEval.exec(parts[3]))) { - // throw out eval line/column and use top-most line number - parts[3] = submatch[1]; - parts[4] = submatch[2]; - parts[5] = null; // no column when eval - } else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') { - // 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 - // NOTE: this hack doesn't work if top-most frame is eval - stack[0].column = ex.columnNumber + 1; - } - element = { - 'url': parts[3], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': parts[2] ? parts[2].split(',') : [], - 'line': parts[4] ? +parts[4] : null, - 'column': parts[5] ? +parts[5] : null - }; - } else { - continue; - } + function computeStackTraceFromStackProp(ex) { + if (typeof ex.stack === 'undefined' || !ex.stack) return; + + var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, + gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i, + winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + // Used to additionally parse URL/line/column from eval frames + geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i, + chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/, + lines = ex.stack.split('\n'), + stack = [], + submatch, + parts, + element, + reference = /^(.*) is undefined$/.exec(ex.message); + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = chrome.exec(lines[i]))) { + var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line + var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line + if (isEval && (submatch = chromeEval.exec(parts[2]))) { + // throw out eval line/column and use top-most line/column number + parts[2] = submatch[1]; // url + parts[3] = submatch[2]; // line + parts[4] = submatch[3]; // column + } + element = { + url: !isNative ? parts[2] : null, + func: parts[1] || UNKNOWN_FUNCTION, + args: isNative ? [parts[2]] : [], + line: parts[3] ? +parts[3] : null, + column: parts[4] ? +parts[4] : null + }; + } else if ((parts = winjs.exec(lines[i]))) { + element = { + url: parts[2], + func: parts[1] || UNKNOWN_FUNCTION, + args: [], + line: +parts[3], + column: parts[4] ? +parts[4] : null + }; + } else if ((parts = gecko.exec(lines[i]))) { + var isEval = parts[3] && parts[3].indexOf(' > eval') > -1; + if (isEval && (submatch = geckoEval.exec(parts[3]))) { + // throw out eval line/column and use top-most line number + parts[3] = submatch[1]; + parts[4] = submatch[2]; + parts[5] = null; // no column when eval + } else if ( + i === 0 && + !parts[5] && + typeof ex.columnNumber !== 'undefined' + ) { + // 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 + // NOTE: this hack doesn't work if top-most frame is eval + stack[0].column = ex.columnNumber + 1; + } + element = { + url: parts[3], + func: parts[1] || UNKNOWN_FUNCTION, + args: parts[2] ? parts[2].split(',') : [], + line: parts[4] ? +parts[4] : null, + column: parts[5] ? +parts[5] : null + }; + } else { + continue; + } - if (!element.func && element.line) { - element.func = UNKNOWN_FUNCTION; - } + if (!element.func && element.line) { + element.func = UNKNOWN_FUNCTION; + } - stack.push(element); - } + stack.push(element); + } - if (!stack.length) { - return null; - } + if (!stack.length) { + return null; + } - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } + return { + name: ex.name, + message: ex.message, + url: getLocationHref(), + 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.} stackInfo Stack trace information from @@ -2673,41 +3012,49 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() { * @return {boolean} Whether or not the stack information was * augmented. */ - function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) { - var initial = { - 'url': url, - 'line': lineNo - }; + function augmentStackTraceWithInitialElement( + stackInfo, + url, + lineNo, + message + ) { + var initial = { + url: url, + line: lineNo + }; - if (initial.url && initial.line) { - stackInfo.incomplete = false; + if (initial.url && initial.line) { + stackInfo.incomplete = false; - if (!initial.func) { - initial.func = UNKNOWN_FUNCTION; - } + if (!initial.func) { + initial.func = UNKNOWN_FUNCTION; + } - if (stackInfo.stack.length > 0) { - if (stackInfo.stack[0].url === initial.url) { - if (stackInfo.stack[0].line === 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) { + } else if ( + !stackInfo.stack[0].line && + stackInfo.stack[0].func === initial.func + ) { stackInfo.stack[0].line = initial.line; return false; + } } - } - } + } - stackInfo.stack.unshift(initial); - stackInfo.partial = true; - return true; - } else { - stackInfo.incomplete = true; - } + stackInfo.stack.unshift(initial); + stackInfo.partial = true; + return true; + } else { + stackInfo.incomplete = true; + } - return false; - } + 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 @@ -2716,159 +3063,212 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() { * @param {Error} ex * @return {?Object.} 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 (typeof item.func === 'undefined') { - try { - item.func = parts.input.substring(0, parts.input.indexOf('{')); - } catch (e) { } - } - - 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); - } + 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 (typeof item.func === 'undefined') { + try { + item.func = parts.input.substring(0, parts.input.indexOf('{')); + } catch (e) {} + } + + if (funcs['' + curr]) { + recursion = true; + } else { + funcs['' + curr] = true; + } + + stack.push(item); + } - var result = { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description); - return result; - } + 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: getLocationHref(), + 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 { - stack = computeStackTraceFromStackProp(ex); - if (stack) { - return stack; - } - } catch (e) { - if (TraceKit.debug) { - throw e; - } - } - - try { - stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); - if (stack) { - return stack; - } - } catch (e) { - if (TraceKit.debug) { - throw e; - } - } - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref() - }; - } - - computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; - computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; - - return computeStackTrace; -}()); + function computeStackTrace(ex, depth) { + var stack = null; + depth = depth == null ? 0 : +depth; -module.exports = TraceKit; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"5":5}],7:[function(_dereq_,module,exports){ -'use strict'; + try { + stack = computeStackTraceFromStackProp(ex); + if (stack) { + return stack; + } + } catch (e) { + if (TraceKit.debug) { + throw e; + } + } -/* + try { + stack = computeStackTraceByWalkingCallerChain(ex, depth + 1); + if (stack) { + return stack; + } + } catch (e) { + if (TraceKit.debug) { + throw e; + } + } + return { + name: ex.name, + message: ex.message, + url: getLocationHref() + }; + } + + computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement; + computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp; + + return computeStackTrace; + })(); + + module.exports = TraceKit; + }.call( + this, + typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' ? window : {} + )); + }, + {'5': 5} + ], + 7: [ + function(_dereq_, module, exports) { + /* json-stringify-safe Like JSON.stringify, but doesn't throw on circular references. Originally forked from https://github.com/isaacs/json-stringify-safe - version 5.0.1 on 3/8/2017 and modified for IE8 compatibility. - Tests for this are in test/vendor. + version 5.0.1 on 3/8/2017 and modified to handle Errors serialization + and IE8 compatibility. Tests for this are in test/vendor. ISC license: https://github.com/isaacs/json-stringify-safe/blob/master/LICENSE */ -exports = module.exports = stringify -exports.getSerialize = serializer + exports = module.exports = stringify; + exports.getSerialize = serializer; -function indexOf(haystack, needle) { - for (var i = 0; i < haystack.length; ++i) { - if (haystack[i] === needle) return i; - } - return -1; -} + function indexOf(haystack, needle) { + for (var i = 0; i < haystack.length; ++i) { + if (haystack[i] === needle) return i; + } + return -1; + } + + function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces); + } + + // https://github.com/ftlabs/js-abbreviate/blob/fa709e5f139e7770a71827b1893f22418097fbda/index.js#L95-L106 + function stringifyError(value) { + var err = { + // These properties are implemented as magical getters and don't show up in for in + stack: value.stack, + message: value.message, + name: value.name + }; -function stringify(obj, replacer, spaces, cycleReplacer) { - return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) -} + for (var i in value) { + if (Object.prototype.hasOwnProperty.call(value, i)) { + err[i] = value[i]; + } + } -function serializer(replacer, cycleReplacer) { - var stack = [], keys = [] + return err; + } - if (cycleReplacer == null) cycleReplacer = function(key, value) { - if (stack[0] === value) return '[Circular ~]' - return '[Circular ~.' + keys.slice(0, indexOf(stack, value)).join('.') + ']' - } + function serializer(replacer, cycleReplacer) { + var stack = []; + var keys = []; - return function(key, value) { - if (stack.length > 0) { - var thisPos = indexOf(stack, this); - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) - if (~indexOf(stack, value)) value = cycleReplacer.call(this, key, value) - } - else stack.push(value) + if (cycleReplacer == null) { + cycleReplacer = function(key, value) { + if (stack[0] === value) { + return '[Circular ~]'; + } + return ( + '[Circular ~.' + keys.slice(0, indexOf(stack, value)).join('.') + ']' + ); + }; + } - return replacer == null ? value : replacer.call(this, key, value) - } -} + return function(key, value) { + if (stack.length > 0) { + var thisPos = indexOf(stack, this); + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); -},{}]},{},[4])(4) -}); \ No newline at end of file + if (~indexOf(stack, value)) { + value = cycleReplacer.call(this, key, value); + } + } else { + stack.push(value); + } + + return replacer == null + ? value instanceof Error ? stringifyError(value) : value + : replacer.call(this, key, value); + }; + } + }, + {} + ] + }, + {}, + [4] + )(4); +});