diff --git a/src/raven.js b/src/raven.js
index 461598667aa6..7af6c67f6a6f 100644
--- a/src/raven.js
+++ b/src/raven.js
@@ -1,12 +1,31 @@
-/*global XDomainRequest:false, __DEV__:false*/
+/*global XDomainRequest:false */
var TraceKit = require('../vendor/TraceKit/tracekit');
var stringify = require('../vendor/json-stringify-safe/stringify');
var RavenConfigError = require('./configError');
-var utils = require('./utils');
-var isError = utils.isError,
- isObject = utils.isObject;
+var utils = require('./utils');
+var isError = utils.isError;
+var isObject = utils.isObject;
+var isObject = utils.isObject;
+var isError = utils.isError;
+var isUndefined = utils.isUndefined;
+var isFunction = utils.isFunction;
+var isString = utils.isString;
+var isEmptyObject = utils.isEmptyObject;
+var each = utils.each;
+var objectMerge = utils.objectMerge;
+var truncate = utils.truncate;
+var objectFrozen = utils.objectFrozen;
+var hasKey = utils.hasKey;
+var joinRegExp = utils.joinRegExp;
+var urlencode = utils.urlencode;
+var uuid4 = utils.uuid4;
+var htmlTreeAsString = utils.htmlTreeAsString;
+var isSameException = utils.isSameException;
+var isSameStacktrace = utils.isSameStacktrace;
+var parseUrl = utils.parseUrl;
+var fill = utils.fill;
var wrapConsoleMethod = require('./console').wrapMethod;
@@ -1891,347 +1910,6 @@ Raven.prototype = {
}
};
-/*------------------------------------------------
- * utils
- *
- * conditionally exported for test via Raven.utils
- =================================================
- */
-var objectPrototype = Object.prototype;
-
-function isUndefined(what) {
- return what === void 0;
-}
-
-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 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 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
- // 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 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;
-
- 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);
- 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;
diff --git a/src/utils.js b/src/utils.js
index 3657386c912d..11644c4b2b07 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,3 +1,8 @@
+var _window =
+ typeof window !== 'undefined'
+ ? window
+ : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
function isObject(what) {
return typeof what === 'object' && what !== null;
}
@@ -17,6 +22,23 @@ function isError(value) {
}
}
+function isUndefined(what) {
+ return what === void 0;
+}
+
+function isFunction(what) {
+ return typeof what === 'function';
+}
+
+function isString(what) {
+ return Object.prototype.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 wrappedCallback(callback) {
function dataCallback(data, original) {
var normalizedData = callback(data) || data;
@@ -29,8 +51,320 @@ function wrappedCallback(callback) {
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 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;
+ }
+
+ 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 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;
+
+ 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);
+ if (track) {
+ track.push([obj, name, orig]);
+ }
+}
+
module.exports = {
isObject: isObject,
isError: isError,
- wrappedCallback: wrappedCallback
+ isUndefined: isUndefined,
+ isFunction: isFunction,
+ isString: isString,
+ isEmptyObject: isEmptyObject,
+ 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
};
diff --git a/test/raven.test.js b/test/raven.test.js
index f077ceba60b7..226b3d54f2ab 100644
--- a/test/raven.test.js
+++ b/test/raven.test.js
@@ -16,7 +16,7 @@ _Raven.prototype._getUuid = function() {
return 'abc123';
};
-var joinRegExp = _Raven.utils.joinRegExp;
+var joinRegExp = require('../src/utils').joinRegExp;
// window.console must be stubbed in for browsers that don't have it
if (typeof window.console === 'undefined') {
diff --git a/test/utils.test.js b/test/utils.test.js
index a7812a4d902f..6229e762111f 100644
--- a/test/utils.test.js
+++ b/test/utils.test.js
@@ -2,17 +2,16 @@
/*global assert:false, console:true*/
'use strict';
-var Raven = require('../src/raven');
-var wrappedCallback = require('../src/utils').wrappedCallback;
-var utils = Raven.utils;
var RavenConfigError = require('../src/configError');
+var utils = require('../src/utils');
var isUndefined = utils.isUndefined;
var isFunction = utils.isFunction;
var isString = utils.isString;
var isObject = utils.isObject;
var isEmptyObject = utils.isEmptyObject;
var isError = utils.isError;
+var wrappedCallback = utils.wrappedCallback;
var joinRegExp = utils.joinRegExp;
var objectMerge = utils.objectMerge;
var truncate = utils.truncate;