575 lines
14 KiB
JavaScript
575 lines
14 KiB
JavaScript
/*! Raven.js 3.22.2 (1b6187b) | github.com/getsentry/raven-js */
|
|
|
|
/*
|
|
* Includes TraceKit
|
|
* https://github.com/getsentry/TraceKit
|
|
*
|
|
* Copyright 2018 Matt Robenolt and other contributors
|
|
* Released under the BSD license
|
|
* https://github.com/getsentry/raven-js/blob/master/LICENSE
|
|
*
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global.Raven = global.Raven || {}, global.Raven.Plugins = global.Raven.Plugins || {}, global.Raven.Plugins.Angular = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
function _typeof(obj) {
|
|
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
_typeof = function (obj) {
|
|
return typeof obj;
|
|
};
|
|
} else {
|
|
_typeof = function (obj) {
|
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
};
|
|
}
|
|
|
|
return _typeof(obj);
|
|
}
|
|
|
|
var _sPO = Object.setPrototypeOf || function _sPO(o, p) {
|
|
o.__proto__ = p;
|
|
return o;
|
|
};
|
|
|
|
var _construct = typeof Reflect === "object" && Reflect.construct || function _construct(Parent, args, Class) {
|
|
var Constructor,
|
|
a = [null];
|
|
a.push.apply(a, args);
|
|
Constructor = Parent.bind.apply(Parent, a);
|
|
return _sPO(new Constructor(), Class.prototype);
|
|
};
|
|
|
|
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
|
|
var _window = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {};
|
|
|
|
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 isErrorEvent(value) {
|
|
return supportsErrorEvent() && {}.toString.call(value) === '[object ErrorEvent]';
|
|
}
|
|
|
|
function isUndefined(what) {
|
|
return what === void 0;
|
|
}
|
|
|
|
function isFunction(what) {
|
|
return typeof what === 'function';
|
|
}
|
|
|
|
function isPlainObject(what) {
|
|
return Object.prototype.toString.call(what) === '[object Object]';
|
|
}
|
|
|
|
function isString(what) {
|
|
return Object.prototype.toString.call(what) === '[object String]';
|
|
}
|
|
|
|
function isArray(what) {
|
|
return Object.prototype.toString.call(what) === '[object Array]';
|
|
}
|
|
|
|
function isEmptyObject(what) {
|
|
if (!isPlainObject(what)) return false;
|
|
|
|
for (var _ in what) {
|
|
if (what.hasOwnProperty(_)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function supportsErrorEvent() {
|
|
try {
|
|
new ErrorEvent(''); // eslint-disable-line no-new
|
|
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function supportsFetch() {
|
|
if (!('fetch' in _window)) return false;
|
|
|
|
try {
|
|
new Headers(); // eslint-disable-line no-new
|
|
|
|
new Request(''); // eslint-disable-line no-new
|
|
|
|
new Response(); // eslint-disable-line no-new
|
|
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function wrappedCallback$1(callback) {
|
|
function dataCallback(data, original) {
|
|
var normalizedData = callback(data) || data;
|
|
|
|
if (original) {
|
|
return original(normalizedData) || normalizedData;
|
|
}
|
|
|
|
return normalizedData;
|
|
}
|
|
|
|
return dataCallback;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
* So it's ok that objectFrozen returns false if Object.isFrozen is not
|
|
* 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 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 Object.prototype.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 <a/> href parsing trick because React Native and other
|
|
// environments where DOM might not be available
|
|
|
|
|
|
function parseUrl(url) {
|
|
if (typeof url !== 'string') return {};
|
|
var match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/); // 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 pad(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;
|
|
}
|
|
|
|
out.push(nextStr);
|
|
len += nextStr.length;
|
|
elem = elem.parentNode;
|
|
}
|
|
|
|
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('');
|
|
}
|
|
/**
|
|
* Returns true if either a OR b is truthy, but not both
|
|
*/
|
|
|
|
|
|
function isOnlyOneTruthy(a, b) {
|
|
return !!(!!a ^ !!b);
|
|
}
|
|
/**
|
|
* Returns true if both parameters are undefined
|
|
*/
|
|
|
|
|
|
function isBothUndefined(a, b) {
|
|
return isUndefined(a) && isUndefined(b);
|
|
}
|
|
/**
|
|
* Returns true if the two input exception interfaces have the same content
|
|
*/
|
|
|
|
|
|
function isSameException(ex1, ex2) {
|
|
if (isOnlyOneTruthy(ex1, ex2)) return false;
|
|
ex1 = ex1.values[0];
|
|
ex2 = ex2.values[0];
|
|
if (ex1.type !== ex2.type || ex1.value !== ex2.value) return false; // in case both stacktraces are undefined, we can't decide so default to false
|
|
|
|
if (isBothUndefined(ex1.stacktrace, ex2.stacktrace)) return false;
|
|
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;
|
|
}
|
|
/**
|
|
* 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);
|
|
obj[name].__raven__ = true;
|
|
obj[name].__orig__ = orig;
|
|
|
|
if (track) {
|
|
track.push([obj, name, orig]);
|
|
}
|
|
}
|
|
/**
|
|
* Join values in array
|
|
* @param input array of values to be joined together
|
|
* @param delimiter string to be placed in-between values
|
|
* @returns {string}
|
|
*/
|
|
|
|
|
|
function safeJoin(input, delimiter) {
|
|
if (!isArray(input)) return '';
|
|
var output = [];
|
|
|
|
for (var i = 0; i < input.length; i++) {
|
|
try {
|
|
output.push(String(input[i]));
|
|
} catch (e) {
|
|
output.push('[value cannot be serialized]');
|
|
}
|
|
}
|
|
|
|
return output.join(delimiter);
|
|
}
|
|
|
|
var utils = {
|
|
isObject: isObject,
|
|
isError: isError,
|
|
isErrorEvent: isErrorEvent,
|
|
isUndefined: isUndefined,
|
|
isFunction: isFunction,
|
|
isPlainObject: isPlainObject,
|
|
isString: isString,
|
|
isArray: isArray,
|
|
isEmptyObject: isEmptyObject,
|
|
supportsErrorEvent: supportsErrorEvent,
|
|
supportsFetch: supportsFetch,
|
|
wrappedCallback: wrappedCallback$1,
|
|
each: each,
|
|
objectMerge: objectMerge,
|
|
truncate: truncate,
|
|
objectFrozen: objectFrozen,
|
|
hasKey: hasKey,
|
|
joinRegExp: joinRegExp,
|
|
urlencode: urlencode,
|
|
uuid4: uuid4,
|
|
htmlTreeAsString: htmlTreeAsString,
|
|
htmlElementAsString: htmlElementAsString,
|
|
isSameException: isSameException,
|
|
isSameStacktrace: isSameStacktrace,
|
|
parseUrl: parseUrl,
|
|
fill: fill,
|
|
safeJoin: safeJoin
|
|
};
|
|
|
|
/**
|
|
* Angular.js plugin
|
|
*
|
|
* Provides an $exceptionHandler for Angular.js
|
|
*/
|
|
|
|
var wrappedCallback = utils.wrappedCallback; // See https://github.com/angular/angular.js/blob/v1.4.7/src/minErr.js
|
|
|
|
var angularPattern = /^\[((?:[$a-zA-Z0-9]+:)?(?:[$a-zA-Z0-9]+))\] (.*?)\n?(\S+)$/;
|
|
var moduleName = 'ngRaven';
|
|
|
|
function angularPlugin(Raven, angular) {
|
|
angular = angular || window.angular;
|
|
if (!angular) return;
|
|
|
|
function RavenProvider() {
|
|
this.$get = ['$window', function ($window) {
|
|
return Raven;
|
|
}];
|
|
}
|
|
|
|
function ExceptionHandlerProvider($provide) {
|
|
$provide.decorator('$exceptionHandler', ['Raven', '$delegate', exceptionHandler]);
|
|
}
|
|
|
|
function exceptionHandler(R, $delegate) {
|
|
return function (ex, cause) {
|
|
R.captureException(ex, {
|
|
extra: {
|
|
cause: cause
|
|
}
|
|
});
|
|
$delegate(ex, cause);
|
|
};
|
|
}
|
|
|
|
angular.module(moduleName, []).provider('Raven', RavenProvider).config(['$provide', ExceptionHandlerProvider]);
|
|
Raven.setDataCallback(wrappedCallback(function (data) {
|
|
return angularPlugin._normalizeData(data);
|
|
}));
|
|
}
|
|
|
|
angularPlugin._normalizeData = function (data) {
|
|
// We only care about mutating an exception
|
|
var exception = data.exception;
|
|
|
|
if (exception) {
|
|
exception = exception.values[0];
|
|
var matches = angularPattern.exec(exception.value);
|
|
|
|
if (matches) {
|
|
// This type now becomes something like: $rootScope:inprog
|
|
exception.type = matches[1];
|
|
exception.value = matches[2];
|
|
data.message = exception.type + ': ' + exception.value; // auto set a new tag specifically for the angular error url
|
|
|
|
data.extra.angularDocs = matches[3].substr(0, 250);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
};
|
|
|
|
angularPlugin.moduleName = moduleName;
|
|
var angular = angularPlugin;
|
|
|
|
return angular;
|
|
|
|
})));
|