@honeybadger-io/core
Version:
JavaScript error notifier for Honeybadger.io
655 lines • 23.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logDeprecatedMethod = exports.globalThisOrWindow = exports.isBrowserConfig = exports.clone = exports.formatCGIData = exports.filterUrl = exports.filter = exports.generateStackTrace = exports.endpoint = exports.instrumentConsole = exports.instrument = exports.isErrorObject = exports.makeNotice = exports.logger = exports.sanitize = exports.shallowClone = exports.runAfterNotifyHandlers = exports.runBeforeNotifyHandlers = exports.getSourceForBacktrace = exports.getCauses = exports.calculateBacktraceShift = exports.DEFAULT_BACKTRACE_SHIFT = exports.makeBacktrace = exports.objectIsExtensible = exports.objectIsEmpty = exports.mergeNotice = exports.merge = void 0;
/* eslint-disable prefer-rest-params */
var stackTraceParser = __importStar(require("stacktrace-parser"));
function merge(obj1, obj2) {
var result = {};
for (var k in obj1) {
result[k] = obj1[k];
}
for (var k in obj2) {
result[k] = obj2[k];
}
return result;
}
exports.merge = merge;
function mergeNotice(notice1, notice2) {
var result = merge(notice1, notice2);
if (notice1.context && notice2.context) {
result.context = merge(notice1.context, notice2.context);
}
return result;
}
exports.mergeNotice = mergeNotice;
function objectIsEmpty(obj) {
for (var k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
return false;
}
}
return true;
}
exports.objectIsEmpty = objectIsEmpty;
function objectIsExtensible(obj) {
if (typeof Object.isExtensible !== 'function') {
return true;
}
return Object.isExtensible(obj);
}
exports.objectIsExtensible = objectIsExtensible;
function makeBacktrace(stack, filterHbSourceCode, logger) {
if (filterHbSourceCode === void 0) { filterHbSourceCode = false; }
if (logger === void 0) { logger = console; }
if (!stack) {
return [];
}
try {
var backtrace = stackTraceParser
.parse(stack)
.map(function (line) {
return {
file: line.file,
method: line.methodName,
number: line.lineNumber,
column: line.column
};
});
if (filterHbSourceCode) {
backtrace.splice(0, calculateBacktraceShift(backtrace));
}
return backtrace;
}
catch (err) {
logger.debug(err);
return [];
}
}
exports.makeBacktrace = makeBacktrace;
function isFrameFromHbSourceCode(frame) {
var hasHbFile = false;
var hasHbMethod = false;
if (frame.file) {
hasHbFile = frame.file.toLowerCase().indexOf('@honeybadger-io') > -1;
}
if (frame.method) {
hasHbMethod = frame.method.toLowerCase().indexOf('@honeybadger-io') > -1;
}
return hasHbFile || hasHbMethod;
}
exports.DEFAULT_BACKTRACE_SHIFT = 3;
/**
* If {@link generateStackTrace} is used, we want to exclude frames that come from
* Honeybadger's source code.
*
* Logic:
* - For each frame, increment the shift if source code is from Honeybadger
* - If a frame from an <anonymous> file is encountered increment the shift ONLY if between Honeybadger source code
* (i.e. previous and next frames are from Honeybadger)
* - Exit when frame encountered is not from Honeybadger source code
*
* Note: this will not always work, especially in browser versions where code
* is minified, uglified and bundled.
* For those cases we default to 3:
* - generateStackTrace
* - makeNotice
* - notify
*/
function calculateBacktraceShift(backtrace) {
var shift = 0;
for (var i = 0; i < backtrace.length; i++) {
var frame = backtrace[i];
if (isFrameFromHbSourceCode(frame)) {
shift++;
continue;
}
if (!frame.file || frame.file === '<anonymous>') {
var nextFrame = backtrace[i + 1];
if (nextFrame && isFrameFromHbSourceCode(nextFrame)) {
shift++;
continue;
}
}
break;
}
return shift || exports.DEFAULT_BACKTRACE_SHIFT;
}
exports.calculateBacktraceShift = calculateBacktraceShift;
function getCauses(notice, logger) {
if (notice.cause) {
var causes = [];
var cause = notice;
// @ts-ignore this throws an error if tsconfig.json has strict: true
while (causes.length < 3 && (cause = cause.cause)) {
causes.push({
class: cause.name,
message: cause.message,
backtrace: typeof cause.stack == 'string' ? makeBacktrace(cause.stack, false, logger) : null
});
}
return causes;
}
return [];
}
exports.getCauses = getCauses;
function getSourceForBacktrace(backtrace, getSourceFileHandler) {
return __awaiter(this, void 0, void 0, function () {
var result, index, trace, fileContent;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
result = [];
if (!getSourceFileHandler || !backtrace || !backtrace.length) {
return [2 /*return*/, result];
}
index = 0;
_a.label = 1;
case 1:
if (!backtrace.length) return [3 /*break*/, 3];
trace = backtrace.splice(0)[index];
return [4 /*yield*/, getSourceFileHandler(trace.file)];
case 2:
fileContent = _a.sent();
result[index] = getSourceCodeSnippet(fileContent, trace.number, trace.column, 2);
index++;
return [3 /*break*/, 1];
case 3: return [2 /*return*/, result];
}
});
});
}
exports.getSourceForBacktrace = getSourceForBacktrace;
function runBeforeNotifyHandlers(notice, handlers) {
var results = [];
var result = true;
for (var i = 0, len = handlers.length; i < len; i++) {
var handler = handlers[i];
var handlerResult = handler(notice);
if (handlerResult === false) {
result = false;
}
results.push(handlerResult);
}
return {
results: results,
result: result
};
}
exports.runBeforeNotifyHandlers = runBeforeNotifyHandlers;
function runAfterNotifyHandlers(notice, handlers, error) {
if (notice && notice.afterNotify) {
notice.afterNotify(error, notice);
}
for (var i = 0, len = handlers.length; i < len; i++) {
handlers[i](error, notice);
}
return true;
}
exports.runAfterNotifyHandlers = runAfterNotifyHandlers;
// Returns a new object with properties from other object.
function shallowClone(obj) {
if (typeof (obj) !== 'object' || obj === null) {
return {};
}
var result = {};
for (var k in obj) {
result[k] = obj[k];
}
return result;
}
exports.shallowClone = shallowClone;
function sanitize(obj, maxDepth) {
if (maxDepth === void 0) { maxDepth = 8; }
var seenObjects = [];
function seen(obj) {
if (!obj || typeof (obj) !== 'object') {
return false;
}
for (var i = 0; i < seenObjects.length; i++) {
var value = seenObjects[i];
if (value === obj) {
return true;
}
}
seenObjects.push(obj);
return false;
}
function canSerialize(obj) {
var typeOfObj = typeof obj;
// Functions are TMI
if (/function/.test(typeOfObj)) {
// Let special toJSON method pass as it's used by JSON.stringify (#722)
return obj.name === 'toJSON';
}
// Symbols can't convert to strings.
if (/symbol/.test(typeOfObj)) {
return false;
}
if (obj === null) {
return false;
}
// No prototype, likely created with `Object.create(null)`.
if (typeof obj === 'object' && typeof obj.hasOwnProperty === 'undefined') {
return false;
}
return true;
}
function serialize(obj, depth) {
if (depth === void 0) { depth = 0; }
if (depth >= maxDepth) {
return '[DEPTH]';
}
// Inspect invalid types
if (!canSerialize(obj)) {
return Object.prototype.toString.call(obj);
}
// Halt circular references
if (seen(obj)) {
return '[RECURSION]';
}
// Serialize inside arrays
if (Array.isArray(obj)) {
return obj.map(function (o) { return safeSerialize(o, depth + 1); });
}
// Serialize inside objects
if (typeof (obj) === 'object') {
var ret = {};
for (var k in obj) {
var v = obj[k];
if (Object.prototype.hasOwnProperty.call(obj, k) && (k != null) && (v != null)) {
ret[k] = safeSerialize(v, depth + 1);
}
}
return ret;
}
// Return everything else untouched
return obj;
}
function safeSerialize(obj, depth) {
if (depth === void 0) { depth = 0; }
try {
return serialize(obj, depth);
}
catch (e) {
return "[ERROR] ".concat(e);
}
}
return safeSerialize(obj);
}
exports.sanitize = sanitize;
function logger(client) {
var log = function (method) {
return function () {
var _a;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (method === 'debug') {
if (!client.config.debug) {
return;
}
// Log at default level so that you don't need to also enable verbose
// logging in Chrome.
method = 'log';
}
args.unshift('[Honeybadger]');
(_a = client.config.logger)[method].apply(_a, args);
};
};
return {
log: log('log'),
info: log('info'),
debug: log('debug'),
warn: log('warn'),
error: log('error')
};
}
exports.logger = logger;
/**
* Converts any object into a notice object (which at minimum has the same
* properties as Error, but supports additional Honeybadger properties.)
*/
function makeNotice(thing) {
var notice;
if (!thing) {
notice = {};
}
else if (isErrorObject(thing)) {
var e = thing;
notice = merge(thing, { name: e.name, message: e.message, stack: e.stack, cause: e.cause });
}
else if (typeof thing === 'object') {
notice = shallowClone(thing);
}
else {
var m = String(thing);
notice = { message: m };
}
return notice;
}
exports.makeNotice = makeNotice;
function isErrorObject(thing) {
return thing instanceof Error
|| Object.prototype.toString.call(thing) === '[object Error]'; // Important for cross-realm objects
}
exports.isErrorObject = isErrorObject;
/**
* Instrument an existing function inside an object (usually global).
* @param {!Object} object
* @param {!String} name
* @param {!Function} replacement
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function instrument(object, name, replacement) {
if (!object || !name || !replacement || !(name in object)) {
return;
}
try {
var original = object[name];
while (original && original.__hb_original) {
original = original.__hb_original;
}
object[name] = replacement(original);
object[name].__hb_original = original;
}
catch (_e) {
// Ignores errors where "original" is a restricted object (see #1001)
// Uncaught Error: Permission denied to access property "__hb_original"
// Also ignores:
// Error: TypeError: Cannot set property onunhandledrejection of [object Object] which has only a getter
// User-Agent: Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-G960F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/12.1 Chrome/79.0.3945.136 Mobile Safari/537.36
}
}
exports.instrument = instrument;
var _consoleAlreadyInstrumented = false;
var listeners = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function instrumentConsole(_window, handler) {
if (!_window || !_window.console || !handler) {
return;
}
listeners.push(handler);
if (_consoleAlreadyInstrumented) {
return;
}
_consoleAlreadyInstrumented = true;
['debug', 'info', 'warn', 'error', 'log'].forEach(function (level) {
instrument(_window.console, level, function hbLogger(original) {
return function () {
var args = Array.prototype.slice.call(arguments);
listeners.forEach(function (listener) {
try {
listener(level, args);
}
catch (_e) {
// ignore
// should never reach here because instrument method already wraps with try/catch block
}
});
if (typeof original === 'function') {
Function.prototype.apply.call(original, _window.console, arguments);
}
};
});
});
}
exports.instrumentConsole = instrumentConsole;
function endpoint(base, path) {
var endpoint = base.trim().replace(/\/$/, '');
path = path.trim().replace(/(^\/|\/$)/g, '');
return "".concat(endpoint, "/").concat(path);
}
exports.endpoint = endpoint;
function generateStackTrace() {
try {
throw new Error('');
}
catch (e) {
if (e.stack) {
return e.stack;
}
}
var maxStackSize = 10;
var stack = [];
var curr = arguments.callee;
while (curr && stack.length < maxStackSize) {
if (/function(?:\s+([\w$]+))+\s*\(/.test(curr.toString())) {
stack.push(RegExp.$1 || '<anonymous>');
}
else {
stack.push('<anonymous>');
}
try {
curr = curr.caller;
}
catch (e) {
break;
}
}
return stack.join('\n');
}
exports.generateStackTrace = generateStackTrace;
function filter(obj, filters) {
if (!is('Object', obj)) {
return;
}
if (!is('Array', filters)) {
filters = [];
}
var seen = [];
function filter(obj) {
var k, newObj;
if (is('Object', obj) || is('Array', obj)) {
if (seen.indexOf(obj) !== -1) {
return '[CIRCULAR DATA STRUCTURE]';
}
seen.push(obj);
}
if (is('Object', obj)) {
newObj = {};
for (k in obj) {
if (filterMatch(k, filters)) {
newObj[k] = '[FILTERED]';
}
else {
newObj[k] = filter(obj[k]);
}
}
return newObj;
}
if (is('Array', obj)) {
return obj.map(function (v) {
return filter(v);
});
}
if (is('Function', obj)) {
return '[FUNC]';
}
return obj;
}
return filter(obj);
}
exports.filter = filter;
function filterMatch(key, filters) {
for (var i = 0; i < filters.length; i++) {
if (key.toLowerCase().indexOf(filters[i].toLowerCase()) !== -1) {
return true;
}
}
return false;
}
function is(type, obj) {
var klass = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && klass === type;
}
function filterUrl(url, filters) {
if (!filters) {
return url;
}
if (typeof url !== 'string') {
return url;
}
var query = url.split(/\?/, 2)[1];
if (!query) {
return url;
}
var result = url;
query.split(/[&]\s?/).forEach(function (pair) {
var _a = pair.split('=', 2), key = _a[0], value = _a[1];
if (filterMatch(key, filters)) {
result = result.replace("".concat(key, "=").concat(value), "".concat(key, "=[FILTERED]"));
}
});
return result;
}
exports.filterUrl = filterUrl;
function formatCGIData(vars, prefix) {
if (prefix === void 0) { prefix = ''; }
var formattedVars = {};
Object.keys(vars).forEach(function (key) {
var formattedKey = prefix + key.replace(/\W/g, '_').toUpperCase();
formattedVars[formattedKey] = vars[key];
});
return formattedVars;
}
exports.formatCGIData = formatCGIData;
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
exports.clone = clone;
var THRESHOLD_COLUMN_NUMBER = 10000;
var THRESHOLD_LINE_LENGTH = 10000;
var THRESHOLD_FILE_SIZE = 200000; // 200KB threshold
function getThresholdExceededSnippet(lineNumber) {
var _a;
return _a = {}, _a[lineNumber] = 'SOURCE_SIZE_TOO_LARGE', _a;
}
function getSourceCodeSnippet(fileData, lineNumber, columnNumber, sourceRadius) {
if (sourceRadius === void 0) { sourceRadius = 2; }
if (!fileData) {
return null;
}
// If column number is provided and very high, it's likely a bundled/minified file
if (columnNumber && columnNumber > THRESHOLD_COLUMN_NUMBER) {
return getThresholdExceededSnippet(lineNumber);
}
// If file is very large, it's likely bundled
if (fileData.length > THRESHOLD_FILE_SIZE) {
return getThresholdExceededSnippet(lineNumber);
}
var lines = fileData.split('\n');
// add one empty line because array index starts from 0, but error line number is counted from 1
lines.unshift('');
// Check if the target line is extremely long
var targetLine = lines[lineNumber];
if (targetLine && targetLine.length > THRESHOLD_LINE_LENGTH) {
return getThresholdExceededSnippet(lineNumber);
}
var start = lineNumber - sourceRadius;
var end = lineNumber + sourceRadius;
var result = {};
for (var i = start; i <= end; i++) {
var line = lines[i];
if (typeof line === 'string') {
result[i] = line;
}
}
return result;
}
function isBrowserConfig(config) {
return config.async !== undefined;
}
exports.isBrowserConfig = isBrowserConfig;
/** globalThis has fairly good support. But just in case, lets check its defined.
* @see {https://caniuse.com/?search=globalThis}
*/
function globalThisOrWindow() {
if (typeof globalThis !== 'undefined') {
return globalThis;
}
if (typeof self !== 'undefined') {
return self;
}
return window;
}
exports.globalThisOrWindow = globalThisOrWindow;
var _deprecatedMethodCalls = {};
/**
* Logs a deprecation warning, every X calls to the method.
*/
function logDeprecatedMethod(logger, oldMethod, newMethod, callCountThreshold) {
if (callCountThreshold === void 0) { callCountThreshold = 100; }
var key = "".concat(oldMethod, "-").concat(newMethod);
if (typeof _deprecatedMethodCalls[key] === 'undefined') {
_deprecatedMethodCalls[key] = 0;
}
if (_deprecatedMethodCalls[key] % callCountThreshold !== 0) {
_deprecatedMethodCalls[key]++;
return;
}
var msg = "Deprecation warning: ".concat(oldMethod, " has been deprecated; please use ").concat(newMethod, " instead.");
logger.warn(msg);
_deprecatedMethodCalls[key]++;
}
exports.logDeprecatedMethod = logDeprecatedMethod;
//# sourceMappingURL=util.js.map