UNPKG

raven-js

Version:
653 lines (560 loc) 16.5 kB
var stringify = require('../vendor/json-stringify-safe/stringify'); var _window = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : 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 (Object.prototype.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 Object.prototype.toString.call(value) === '[object ErrorEvent]'; } function isDOMError(value) { return Object.prototype.toString.call(value) === '[object DOMError]'; } function isDOMException(value) { return Object.prototype.toString.call(value) === '[object DOMException]'; } 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 supportsDOMError() { try { new DOMError(''); // eslint-disable-line no-new return true; } catch (e) { return false; } } function supportsDOMException() { try { new DOMException(''); // 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; } } // Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default // https://caniuse.com/#feat=referrer-policy // It doesn't. And it throw exception instead of ignoring this parameter... // REF: https://github.com/getsentry/raven-js/issues/1233 function supportsReferrerPolicy() { if (!supportsFetch()) return false; try { // eslint-disable-next-line no-new new Request('pickleRick', { referrerPolicy: 'origin' }); return true; } catch (e) { return false; } } function supportsPromiseRejectionEvent() { return typeof PromiseRejectionEvent === 'function'; } function wrappedCallback(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) { if (typeof max !== 'number') { throw new Error('2nd argument to `truncate` function should be a number'); } if (typeof str !== 'string' || max === 0) { return str; } return 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(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 stacktrace is malformed if (frames1 === undefined || frames2 === undefined) return false; // 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) { if (obj == null) return; 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); } // Default Node.js REPL depth var MAX_SERIALIZE_EXCEPTION_DEPTH = 3; // 50kB, as 100kB is max payload size, so half sounds reasonable var MAX_SERIALIZE_EXCEPTION_SIZE = 50 * 1024; var MAX_SERIALIZE_KEYS_LENGTH = 40; function utf8Length(value) { return ~-encodeURI(value).split(/%..|./).length; } function jsonSize(value) { return utf8Length(JSON.stringify(value)); } function serializeValue(value) { if (typeof value === 'string') { var maxLength = 40; return truncate(value, maxLength); } else if ( typeof value === 'number' || typeof value === 'boolean' || typeof value === 'undefined' ) { return value; } var type = Object.prototype.toString.call(value); // Node.js REPL notation if (type === '[object Object]') return '[Object]'; if (type === '[object Array]') return '[Array]'; if (type === '[object Function]') return value.name ? '[Function: ' + value.name + ']' : '[Function]'; return value; } function serializeObject(value, depth) { if (depth === 0) return serializeValue(value); if (isPlainObject(value)) { return Object.keys(value).reduce(function(acc, key) { acc[key] = serializeObject(value[key], depth - 1); return acc; }, {}); } else if (Array.isArray(value)) { return value.map(function(val) { return serializeObject(val, depth - 1); }); } return serializeValue(value); } function serializeException(ex, depth, maxSize) { if (!isPlainObject(ex)) return ex; depth = typeof depth !== 'number' ? MAX_SERIALIZE_EXCEPTION_DEPTH : depth; maxSize = typeof depth !== 'number' ? MAX_SERIALIZE_EXCEPTION_SIZE : maxSize; var serialized = serializeObject(ex, depth); if (jsonSize(stringify(serialized)) > maxSize) { return serializeException(ex, depth - 1); } return serialized; } function serializeKeysForMessage(keys, maxLength) { if (typeof keys === 'number' || typeof keys === 'string') return keys.toString(); if (!Array.isArray(keys)) return ''; keys = keys.filter(function(key) { return typeof key === 'string'; }); if (keys.length === 0) return '[object has no keys]'; maxLength = typeof maxLength !== 'number' ? MAX_SERIALIZE_KEYS_LENGTH : maxLength; if (keys[0].length >= maxLength) return keys[0]; for (var usedKeys = keys.length; usedKeys > 0; usedKeys--) { var serialized = keys.slice(0, usedKeys).join(', '); if (serialized.length > maxLength) continue; if (usedKeys === keys.length) return serialized; return serialized + '\u2026'; } return ''; } function sanitize(input, sanitizeKeys) { if (!isArray(sanitizeKeys) || (isArray(sanitizeKeys) && sanitizeKeys.length === 0)) return input; var sanitizeRegExp = joinRegExp(sanitizeKeys); var sanitizeMask = '********'; var safeInput; try { safeInput = JSON.parse(stringify(input)); } catch (o_O) { return input; } function sanitizeWorker(workerInput) { if (isArray(workerInput)) { return workerInput.map(function(val) { return sanitizeWorker(val); }); } if (isPlainObject(workerInput)) { return Object.keys(workerInput).reduce(function(acc, k) { if (sanitizeRegExp.test(k)) { acc[k] = sanitizeMask; } else { acc[k] = sanitizeWorker(workerInput[k]); } return acc; }, {}); } return workerInput; } return sanitizeWorker(safeInput); } module.exports = { isObject: isObject, isError: isError, isErrorEvent: isErrorEvent, isDOMError: isDOMError, isDOMException: isDOMException, isUndefined: isUndefined, isFunction: isFunction, isPlainObject: isPlainObject, isString: isString, isArray: isArray, isEmptyObject: isEmptyObject, supportsErrorEvent: supportsErrorEvent, supportsDOMError: supportsDOMError, supportsDOMException: supportsDOMException, supportsFetch: supportsFetch, supportsReferrerPolicy: supportsReferrerPolicy, supportsPromiseRejectionEvent: supportsPromiseRejectionEvent, wrappedCallback: wrappedCallback, 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, serializeException: serializeException, serializeKeysForMessage: serializeKeysForMessage, sanitize: sanitize };