rollbar
Version:
Effortlessly track and debug errors in your JavaScript applications with Rollbar. This package includes advanced error tracking features and an intuitive interface to help you identify and fix issues more quickly.
976 lines (890 loc) • 22.8 kB
JavaScript
/*
* isType - Given a Javascript value and a string, returns true if the type of the value matches the
* given string.
*
* @param x - any value
* @param t - a lowercase string containing one of the following type names:
* - undefined
* - null
* - error
* - number
* - boolean
* - string
* - symbol
* - function
* - object
* - array
* @returns true if x is of type t, otherwise false
*/
function isType(x, t) {
return t === typeName(x);
}
/*
* typeName - Given a Javascript value, returns the type of the object as a string
*/
function typeName(x) {
var name = typeof x;
if (name !== 'object') {
return name;
}
if (!x) {
return 'null';
}
if (x instanceof Error) {
return 'error';
}
return {}.toString
.call(x)
.match(/\s([a-zA-Z]+)/)[1]
.toLowerCase();
}
/* isFunction - a convenience function for checking if a value is a function
*
* @param f - any value
* @returns true if f is a function, otherwise false
*/
function isFunction(f) {
return isType(f, 'function');
}
/* isNativeFunction - a convenience function for checking if a value is a native JS function
*
* @param f - any value
* @returns true if f is a native JS function, otherwise false
*/
function isNativeFunction(f) {
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
var funcMatchString = Function.prototype.toString
.call(Object.prototype.hasOwnProperty)
.replace(reRegExpChar, '\\$&')
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?');
var reIsNative = RegExp('^' + funcMatchString + '$');
return isObject(f) && reIsNative.test(f);
}
/* isObject - Checks if the argument is an object
*
* @param value - any value
* @returns true is value is an object function is an object)
*/
function isObject(value) {
return (
value != null && (typeof value == 'object' || typeof value == 'function')
);
}
/* hasOwn - safe helper around Object.hasOwnProperty */
function hasOwn(obj, prop) {
if (obj == null) {
return false;
}
if (Object.hasOwn) {
return Object.hasOwn(obj, prop);
}
return Object.prototype.hasOwnProperty.call(obj, prop);
}
/* isString - Checks if the argument is a string
*
* @param value - any value
* @returns true if value is a string
*/
function isString(value) {
return typeof value === 'string' || value instanceof String;
}
/**
* isFiniteNumber - determines whether the passed value is a finite number
*
* @param {*} n - any value
* @returns true if value is a finite number
*/
function isFiniteNumber(n) {
return Number.isFinite(n);
}
/*
* isIterable - convenience function for checking if a value can be iterated, essentially
* whether it is an object or an array.
*
* @param i - any value
* @returns true if i is an object or an array as determined by `typeName`
*/
function isIterable(i) {
var type = typeName(i);
return type === 'object' || type === 'array';
}
/*
* isError - convenience function for checking if a value is of an error type
*
* @param e - any value
* @returns true if e is an error
*/
function isError(e) {
// Detect both Error and Firefox Exception type
return isType(e, 'error') || isType(e, 'exception');
}
/* isPromise - a convenience function for checking if a value is a promise
*
* @param p - any value
* @returns true if f is a function, otherwise false
*/
function isPromise(p) {
return isObject(p) && isType(p.then, 'function');
}
/**
* isBrowser - a convenience function for checking if the code is running in a browser
*
* @returns true if the code is running in a browser environment
*/
function isBrowser() {
return typeof window !== 'undefined';
}
function isRequestObject(input) {
return typeof Request !== 'undefined' && input instanceof Request;
}
function redact() {
return '********';
}
// from http://stackoverflow.com/a/8809472/1138191
function uuid4() {
var d = now();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
function (c) {
var r = ((d + Math.random() * 16) % 16) | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x7) | 0x8).toString(16);
},
);
return uuid;
}
var LEVELS = {
debug: 0,
info: 1,
warning: 2,
error: 3,
critical: 4,
};
function sanitizeHref(url) {
try {
const urlObject = new URL(url);
if (urlObject.password) {
urlObject.password = redact();
}
if (urlObject.search) {
urlObject.search = redact();
}
return urlObject.toString();
} catch (_) {
return url; // Return original URL if parsing fails
}
}
function sanitizeUrl(url) {
var baseUrlParts = parseUri(url);
if (!baseUrlParts) {
return '(unknown)';
}
// remove a trailing # if there is no anchor
if (baseUrlParts.anchor === '') {
baseUrlParts.source = baseUrlParts.source.replace('#', '');
}
url = baseUrlParts.source.replace('?' + baseUrlParts.query, '');
return url;
}
var parseUriOptions = {
strictMode: false,
key: [
'source',
'protocol',
'authority',
'userInfo',
'user',
'password',
'host',
'port',
'relative',
'path',
'directory',
'file',
'query',
'anchor',
],
q: {
name: 'queryKey',
parser: /(?:^|&)([^&=]*)=?([^&]*)/g,
},
parser: {
strict:
/^(?:([^:/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:/?#]*)(?::(\d*))?))?((((?:[^?#/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose:
/^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#/]*\.[^?#/.]+(?:[?#]|$)))*\/?)?([^?#/]*))(?:\?([^#]*))?(?:#(.*))?)/,
},
};
function parseUri(str) {
if (!isType(str, 'string')) {
return undefined;
}
var o = parseUriOptions;
var m = o.parser[o.strictMode ? 'strict' : 'loose'].exec(str);
var uri = {};
for (var i = 0, l = o.key.length; i < l; ++i) {
uri[o.key[i]] = m[i] || '';
}
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) {
uri[o.q.name][$1] = $2;
}
});
return uri;
}
function addParamsAndAccessTokenToPath(accessToken, options, params) {
params = params || {};
params.access_token = accessToken;
var paramsArray = [];
var k;
for (k in params) {
if (Object.prototype.hasOwnProperty.call(params, k)) {
paramsArray.push([k, params[k]].join('='));
}
}
var query = '?' + paramsArray.sort().join('&');
options = options || {};
options.path = options.path || '';
var qs = options.path.indexOf('?');
var h = options.path.indexOf('#');
var p;
if (qs !== -1 && (h === -1 || h > qs)) {
p = options.path;
options.path = p.substring(0, qs) + query + '&' + p.substring(qs + 1);
} else {
if (h !== -1) {
p = options.path;
options.path = p.substring(0, h) + query + p.substring(h);
} else {
options.path = options.path + query;
}
}
}
function formatUrl(u, protocol) {
protocol = protocol || u.protocol;
if (!protocol && u.port) {
if (u.port === 80) {
protocol = 'http:';
} else if (u.port === 443) {
protocol = 'https:';
}
}
protocol = protocol || 'https:';
if (!u.hostname) {
return null;
}
var result = protocol + '//' + u.hostname;
if (u.port) {
result = result + ':' + u.port;
}
if (u.path) {
result = result + u.path;
}
return result;
}
function stringify(obj, backup) {
var value, error;
try {
value = JSON.stringify(obj);
} catch (jsonError) {
if (backup && isFunction(backup)) {
try {
value = backup(obj);
} catch (backupError) {
error = backupError;
}
} else {
error = jsonError;
}
}
return { error: error, value: value };
}
function maxByteSize(string) {
// The transport will use utf-8, so assume utf-8 encoding.
//
// This minimal implementation will accurately count bytes for all UCS-2 and
// single code point UTF-16. If presented with multi code point UTF-16,
// which should be rare, it will safely overcount, not undercount.
//
// While robust utf-8 encoders exist, this is far smaller and far more performant.
// For quickly counting payload size for truncation, smaller is better.
var count = 0;
var length = string.length;
for (var i = 0; i < length; i++) {
var code = string.charCodeAt(i);
if (code < 128) {
// up to 7 bits
count = count + 1;
} else if (code < 2048) {
// up to 11 bits
count = count + 2;
} else if (code < 65536) {
// up to 16 bits
count = count + 3;
}
}
return count;
}
function jsonParse(s) {
var value, error;
try {
value = JSON.parse(s);
} catch (e) {
error = e;
}
return { error, value };
}
function makeUnhandledStackInfo(
message,
url,
lineno,
colno,
error,
mode,
backupMessage,
errorParser,
) {
var location = {
url: url || '',
line: lineno,
column: colno,
};
location.func = errorParser.guessFunctionName(location.url, location.line);
location.context = errorParser.gatherContext(location.url, location.line);
var href =
typeof document !== 'undefined' &&
document &&
document.location &&
document.location.href;
var useragent =
typeof window !== 'undefined' &&
window &&
window.navigator &&
window.navigator.userAgent;
return {
mode: mode,
message: error ? String(error) : message || backupMessage,
url: href,
stack: [location],
useragent: useragent,
};
}
function wrapCallback(logger, f) {
return function (err, resp) {
try {
f(err, resp);
} catch (e) {
logger.error(e);
}
};
}
function nonCircularClone(obj) {
var seen = [obj];
function clone(obj, seen) {
var value,
name,
newSeen,
result = {};
try {
for (name in obj) {
value = obj[name];
if (value && (isType(value, 'object') || isType(value, 'array'))) {
if (seen.includes(value)) {
result[name] = 'Removed circular reference: ' + typeName(value);
} else {
newSeen = seen.slice();
newSeen.push(value);
result[name] = clone(value, newSeen);
}
continue;
}
result[name] = value;
}
} catch (e) {
result = 'Failed cloning custom data: ' + e.message;
}
return result;
}
return clone(obj, seen);
}
function createItem(args, logger, notifier, requestKeys, lambdaContext) {
var message, err, custom, callback, request;
var arg;
var extraArgs = [];
var diagnostic = {};
var argTypes = [];
for (var i = 0, l = args.length; i < l; ++i) {
arg = args[i];
var typ = typeName(arg);
argTypes.push(typ);
switch (typ) {
case 'undefined':
break;
case 'string':
if (message) {
extraArgs.push(arg);
} else {
message = arg;
}
break;
case 'function':
callback = wrapCallback(logger, arg);
break;
case 'date':
extraArgs.push(arg);
break;
case 'error':
case 'domexception':
case 'exception': // Firefox Exception type
if (err) {
extraArgs.push(arg);
} else {
err = arg;
}
break;
case 'object':
case 'array':
if (
arg instanceof Error ||
(typeof DOMException !== 'undefined' && arg instanceof DOMException)
) {
if (err) {
extraArgs.push(arg);
} else {
err = arg;
}
break;
}
if (requestKeys && typ === 'object' && !request) {
for (var j = 0, len = requestKeys.length; j < len; ++j) {
if (arg[requestKeys[j]] !== undefined) {
request = arg;
break;
}
}
if (request) {
break;
}
}
if (custom) {
extraArgs.push(arg);
} else {
custom = arg;
}
break;
default:
if (
arg instanceof Error ||
(typeof DOMException !== 'undefined' && arg instanceof DOMException)
) {
if (err) {
extraArgs.push(arg);
} else {
err = arg;
}
break;
}
extraArgs.push(arg);
}
}
// if custom is an array this turns it into an object with integer keys
if (custom) custom = nonCircularClone(custom);
if (extraArgs.length > 0) {
if (!custom) custom = nonCircularClone({});
custom.extraArgs = nonCircularClone(extraArgs);
}
var item = {
message: message,
err: err,
custom: custom,
timestamp: now(),
callback: callback,
notifier: notifier,
diagnostic: diagnostic,
uuid: uuid4(),
};
item.data = item.data || {};
setCustomItemKeys(item, custom);
if (requestKeys && request) {
item.request = request;
}
if (lambdaContext) {
item.lambdaContext = lambdaContext;
}
item._originalArgs = args;
item.diagnostic.original_arg_types = argTypes;
return item;
}
function setCustomItemKeys(item, custom) {
if (custom && custom.level !== undefined) {
item.level = custom.level;
delete custom.level;
}
if (custom && custom.skipFrames !== undefined) {
item.skipFrames = custom.skipFrames;
delete custom.skipFrames;
}
}
function addErrorContext(item, errors) {
var custom = item.data.custom || {};
var contextAdded = false;
try {
for (const error of errors) {
if (hasOwn(error, 'rollbarContext')) {
custom = merge(custom, nonCircularClone(error.rollbarContext));
contextAdded = true;
}
}
// Avoid adding an empty object to the data.
if (contextAdded) {
item.data.custom = custom;
}
} catch (e) {
item.diagnostic.error_context = 'Failed: ' + e.message;
}
}
var TELEMETRY_TYPES = [
'log',
'network',
'dom',
'navigation',
'error',
'manual',
];
var TELEMETRY_LEVELS = ['critical', 'error', 'warning', 'info', 'debug'];
function arrayIncludes(arr, val) {
for (const entry of arr) {
if (entry === val) {
return true;
}
}
return false;
}
function createTelemetryEvent(args) {
var type, metadata, level;
var arg;
for (var i = 0, l = args.length; i < l; ++i) {
arg = args[i];
var typ = typeName(arg);
switch (typ) {
case 'string':
if (!type && arrayIncludes(TELEMETRY_TYPES, arg)) {
type = arg;
} else if (!level && arrayIncludes(TELEMETRY_LEVELS, arg)) {
level = arg;
}
break;
case 'object':
metadata = arg;
break;
default:
break;
}
}
var event = {
type: type || 'manual',
metadata: metadata || {},
level: level,
};
return event;
}
function addItemAttributes(itemData, attributes) {
itemData.attributes = itemData.attributes || [];
for (const a of attributes) {
if (a.value === undefined) {
continue;
}
itemData.attributes.push(a);
}
}
/*
* get - given an obj/array and a keypath, return the value at that keypath or
* undefined if not possible.
*
* @param obj - an object or array
* @param path - a string of keys separated by '.' such as 'plugin.jquery.0.message'
* which would correspond to 42 in `{plugin: {jquery: [{message: 42}]}}`
*/
function get(obj, path) {
if (!obj) {
return undefined;
}
var keys = path.split('.');
var result = obj;
try {
for (var i = 0, len = keys.length; i < len; ++i) {
result = result[keys[i]];
}
} catch (_e) {
result = undefined;
}
return result;
}
function set(obj, path, value) {
if (!obj) {
return;
}
// Prevent prototype pollution by setting the prototype to null.
Object.setPrototypeOf(obj, null);
var keys = path.split('.');
var len = keys.length;
if (len < 1) {
return;
}
if (len === 1) {
obj[keys[0]] = value;
return;
}
try {
var temp = obj[keys[0]] || {};
var replacement = temp;
for (var i = 1; i < len - 1; ++i) {
temp[keys[i]] = temp[keys[i]] || {};
temp = temp[keys[i]];
}
temp[keys[len - 1]] = value;
obj[keys[0]] = replacement;
} catch (_e) {
return;
}
}
function formatArgsAsString(args) {
var i, len, arg;
var result = [];
for (i = 0, len = args.length; i < len; ++i) {
arg = args[i];
switch (typeName(arg)) {
case 'object':
arg = stringify(arg);
arg = arg.error || arg.value;
if (arg.length > 500) {
arg = arg.substr(0, 497) + '...';
}
break;
case 'null':
arg = 'null';
break;
case 'undefined':
arg = 'undefined';
break;
case 'symbol':
arg = arg.toString();
break;
}
result.push(arg);
}
return result.join(' ');
}
function now() {
if (Date.now) {
return Date.now();
}
return Number(new Date());
}
function filterIp(requestData, captureIp) {
if (!requestData || !requestData['user_ip'] || captureIp === true) {
return;
}
var newIp = requestData['user_ip'];
if (!captureIp) {
newIp = null;
} else {
try {
var parts;
if (newIp.indexOf('.') !== -1) {
parts = newIp.split('.');
parts.pop();
parts.push('0');
newIp = parts.join('.');
} else if (newIp.indexOf(':') !== -1) {
parts = newIp.split(':');
if (parts.length > 2) {
var beginning = parts.slice(0, 3);
var slashIdx = beginning[2].indexOf('/');
if (slashIdx !== -1) {
beginning[2] = beginning[2].substring(0, slashIdx);
}
var terminal = '0000:0000:0000:0000:0000';
newIp = beginning.concat(terminal).join(':');
}
} else {
newIp = null;
}
} catch (_e) {
newIp = null;
}
}
requestData['user_ip'] = newIp;
}
function handleOptions(current, input, payload, logger) {
var result = merge(current, input, payload);
result = updateDeprecatedOptions(result, logger);
if (!input || input.overwriteScrubFields) {
return result;
}
if (input.scrubFields) {
result.scrubFields = (current.scrubFields || []).concat(input.scrubFields);
}
return result;
}
function updateDeprecatedOptions(options, logger) {
if (options.hostWhiteList && !options.hostSafeList) {
options.hostSafeList = options.hostWhiteList;
options.hostWhiteList = undefined;
logger && logger.log('hostWhiteList is deprecated. Use hostSafeList.');
}
if (options.hostBlackList && !options.hostBlockList) {
options.hostBlockList = options.hostBlackList;
options.hostBlackList = undefined;
logger && logger.log('hostBlackList is deprecated. Use hostBlockList.');
}
return options;
}
function merge() {
function isPlainObject(obj) {
if (!obj || Object.prototype.toString.call(obj) !== '[object Object]') {
return false;
}
var hasOwnConstructor = hasOwn(obj, 'constructor');
var hasIsPrototypeOf =
obj.constructor &&
obj.constructor.prototype &&
hasOwn(obj.constructor.prototype, 'isPrototypeOf');
// Not own constructor property must be Object
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) {
/**/
}
return typeof key === 'undefined' || hasOwn(obj, key);
}
var i,
src,
copy,
clone,
name,
result = Object.create(null), // no prototype pollution on Object
current = null,
length = arguments.length;
for (i = 0; i < length; i++) {
current = arguments[i];
if (current === null || current === undefined) {
continue;
}
for (name in current) {
src = result[name];
copy = current[name];
if (result !== copy) {
if (copy && isPlainObject(copy)) {
clone = src && isPlainObject(src) ? src : {};
result[name] = merge(clone, copy);
} else if (typeof copy !== 'undefined') {
result[name] = copy;
}
}
}
}
return result;
}
function shouldAddBaggageHeader(options, tracing, url) {
if (!tracing?.sessionId || !url) {
return false;
}
const propagation = options?.tracing?.propagation;
const enabledHeaders = propagation?.enabledHeaders;
if (!Array.isArray(enabledHeaders) || !enabledHeaders.includes('baggage')) {
return false;
}
const enabledCorsUrls = propagation?.enabledCorsUrls;
if (!Array.isArray(enabledCorsUrls) || enabledCorsUrls.length === 0) {
return false;
}
return enabledCorsUrls.some((pattern) => {
if (isType(pattern, 'string')) {
return url === pattern;
}
if (isType(pattern, 'regexp')) {
return pattern.test(url);
}
return false;
});
}
function addHeadersToFetch(args, newHeaders) {
// Headers may be in the request object or the init object.
// If present in both places, the init object must be used.
//
let init = args[1];
const initHeaders = init?.headers;
const reqHeaders = isRequestObject(args[0]) && args[0].headers;
let headers = initHeaders || reqHeaders;
// If headers are not present in either place, they are added to the init object.
// If there is no init object, one must be created and added to args.
if (!headers) {
if (!init) {
args[1] = init = {};
}
headers = init.headers = {};
}
// `headers` may be a Headers object or a plain object.
if (headers instanceof Headers) {
for (const key of Object.keys(newHeaders)) {
headers.append(key, newHeaders[key]);
}
} else if (isObject(headers)) {
for (const key of Object.keys(newHeaders)) {
headers[key] = newHeaders[key];
}
}
}
function getSessionIdFromAsyncLocalStorage(client) {
const storage = client.asyncLocalStorage;
if (!storage || typeof storage.getStore !== 'function') {
return null;
}
const store = storage.getStore();
return store?.sessionId || null;
}
export {
addParamsAndAccessTokenToPath,
createItem,
addErrorContext,
createTelemetryEvent,
addItemAttributes,
filterIp,
formatArgsAsString,
formatUrl,
get,
handleOptions,
isError,
isFiniteNumber,
isFunction,
hasOwn,
isIterable,
isNativeFunction,
isObject,
isString,
isType,
isPromise,
isBrowser,
jsonParse,
LEVELS,
makeUnhandledStackInfo,
merge,
now,
redact,
sanitizeHref,
sanitizeUrl,
set,
stringify,
maxByteSize,
typeName,
uuid4,
shouldAddBaggageHeader,
addHeadersToFetch,
getSessionIdFromAsyncLocalStorage,
};