UNPKG

@mtdt.temp/browser-core

Version:
183 lines 7.96 kB
/** * Cross-browser stack trace computation. * * Reference implementation: https://github.com/csnover/TraceKit/blob/04530298073c3823de72deb0b97e7b38ca7bcb59/tracekit.js */ const UNKNOWN_FUNCTION = '?'; export function computeStackTrace(ex) { var _a, _b; const stack = []; let stackProperty = tryToGetString(ex, 'stack'); const exString = String(ex); if (stackProperty && stackProperty.startsWith(exString)) { stackProperty = stackProperty.slice(exString.length); } if (stackProperty) { stackProperty.split('\n').forEach((line) => { const stackFrame = parseChromeLine(line) || parseChromeAnonymousLine(line) || parseWinLine(line) || parseGeckoLine(line); if (stackFrame) { if (!stackFrame.func && stackFrame.line) { stackFrame.func = UNKNOWN_FUNCTION; } stack.push(stackFrame); } }); } if (stack.length > 0 && isWronglyReportingCustomErrors() && ex instanceof Error) { // if we are wrongly reporting custom errors const constructors = []; // go through each inherited constructor let currentPrototype = ex; while ((currentPrototype = Object.getPrototypeOf(currentPrototype)) && isNonNativeClassPrototype(currentPrototype)) { const constructorName = ((_a = currentPrototype.constructor) === null || _a === void 0 ? void 0 : _a.name) || UNKNOWN_FUNCTION; constructors.push(constructorName); } // traverse the stacktrace in reverse order because the stacktrace starts with the last inherited constructor // we check constructor names to ensure we remove the correct frame (and there isn't a weird unsupported environment behavior) for (let i = constructors.length - 1; i >= 0 && ((_b = stack[0]) === null || _b === void 0 ? void 0 : _b.func) === constructors[i]; i--) { // if the first stack frame is the custom error constructor // null stack frames may represent frames that failed to be parsed because the error class did not have a constructor stack.shift(); // remove it } } return { message: tryToGetString(ex, 'message'), name: tryToGetString(ex, 'name'), stack, }; } const fileUrl = '((?:file|https?|blob|chrome-extension|electron|native|eval|webpack|snippet|<anonymous>|\\w+\\.|\\/).*?)'; const filePosition = '(?::(\\d+))'; const CHROME_LINE_RE = new RegExp(`^\\s*at (.*?) ?\\(${fileUrl}${filePosition}?${filePosition}?\\)?\\s*$`, 'i'); const CHROME_EVAL_RE = new RegExp(`\\((\\S*)${filePosition}${filePosition}\\)`); function parseChromeLine(line) { const parts = CHROME_LINE_RE.exec(line); if (!parts) { return; } const isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line const isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line const submatch = CHROME_EVAL_RE.exec(parts[2]); if (isEval && submatch) { // throw out eval line/column and use top-most line/column number parts[2] = submatch[1]; // url parts[3] = submatch[2]; // line parts[4] = submatch[3]; // column } return { args: isNative ? [parts[2]] : [], column: parts[4] ? +parts[4] : undefined, func: parts[1] || UNKNOWN_FUNCTION, line: parts[3] ? +parts[3] : undefined, url: !isNative ? parts[2] : undefined, }; } const CHROME_ANONYMOUS_FUNCTION_RE = new RegExp(`^\\s*at ?${fileUrl}${filePosition}?${filePosition}??\\s*$`, 'i'); function parseChromeAnonymousLine(line) { const parts = CHROME_ANONYMOUS_FUNCTION_RE.exec(line); if (!parts) { return; } return { args: [], column: parts[3] ? +parts[3] : undefined, func: UNKNOWN_FUNCTION, line: parts[2] ? +parts[2] : undefined, url: parts[1], }; } const WINJS_LINE_RE = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i; function parseWinLine(line) { const parts = WINJS_LINE_RE.exec(line); if (!parts) { return; } return { args: [], column: parts[4] ? +parts[4] : undefined, func: parts[1] || UNKNOWN_FUNCTION, line: +parts[3], url: parts[2], }; } const GECKO_LINE_RE = /^\s*(.*?)(?:\((.*?)\))?(?:(?:(?:^|@)((?:file|https?|blob|chrome|webpack|resource|capacitor|\[native).*?|[^@]*bundle|\[wasm code\])(?::(\d+))?(?::(\d+))?)|@)\s*$/i; const GECKO_EVAL_RE = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; function parseGeckoLine(line) { const parts = GECKO_LINE_RE.exec(line); if (!parts) { return; } const isEval = parts[3] && parts[3].indexOf(' > eval') > -1; const submatch = GECKO_EVAL_RE.exec(parts[3]); if (isEval && submatch) { // throw out eval line/column and use top-most line number parts[3] = submatch[1]; parts[4] = submatch[2]; parts[5] = undefined; // no column when eval } return { args: parts[2] ? parts[2].split(',') : [], column: parts[5] ? +parts[5] : undefined, func: parts[1] || UNKNOWN_FUNCTION, line: parts[4] ? +parts[4] : undefined, url: parts[3], }; } function tryToGetString(candidate, property) { if (typeof candidate !== 'object' || !candidate || !(property in candidate)) { return undefined; } const value = candidate[property]; return typeof value === 'string' ? value : undefined; } export function computeStackTraceFromOnErrorMessage(messageObj, url, line, column) { if (url === undefined) { return; } const { name, message } = tryToParseMessage(messageObj); return { name, message, stack: [{ url, column, line }], }; } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types const ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?([\s\S]*)$/; function tryToParseMessage(messageObj) { let name; let message; if ({}.toString.call(messageObj) === '[object String]') { ; [, name, message] = ERROR_TYPES_RE.exec(messageObj); } return { name, message }; } // Custom error stacktrace fix // Some browsers (safari/firefox) add the error constructor as a frame in the stacktrace // In order to normalize the stacktrace, we need to remove it function isNonNativeClassPrototype(prototype) { return String(prototype.constructor).startsWith('class '); } let isWronglyReportingCustomErrorsCache; function isWronglyReportingCustomErrors() { if (isWronglyReportingCustomErrorsCache !== undefined) { return isWronglyReportingCustomErrorsCache; } /* eslint-disable no-restricted-syntax */ class YourCompanyTestCustomError extends Error { constructor() { super(); this.name = 'Error'; // set name to Error so that no browser would default to the constructor name } } const [customError, nativeError] = [YourCompanyTestCustomError, Error].map((errConstructor) => new errConstructor()); // so that both errors should exactly have the same stacktrace isWronglyReportingCustomErrorsCache = // If customError is not a class, it means that this was built with ES5 as target, converting the class to a normal object. // Thus, error constructors will be reported on all browsers, which is the expected behavior. isNonNativeClassPrototype(Object.getPrototypeOf(customError)) && // If the browser is correctly reporting the stacktrace, the normal error stacktrace should be the same as the custom error stacktrace nativeError.stack !== customError.stack; return isWronglyReportingCustomErrorsCache; } //# sourceMappingURL=computeStackTrace.js.map