Spaces:
Runtime error
Runtime error
| /** | |
| * String.prototype.indexOf, but it returns NaN not -1 on failure | |
| * @param {string} str The string to check in | |
| * @param {string} key The vaue to search for | |
| * @param {number?} offset The offset into the string to start from | |
| * @returns {number} The index of the key, or NaN if no instances where found | |
| */ | |
| const _lastIndexNaN = (str, key, offset = Infinity) => { | |
| if (!str) return NaN; | |
| const val = str.lastIndexOf(key, offset); | |
| if (val === -1) return NaN; | |
| return val; | |
| }; | |
| const browserHasStack = !!new Error().stack; | |
| /** | |
| * @typedef {'anonymous'|'eval'|'Function'|'GeneratorFunction'|'AsyncFunction'|'AsyncGeneratorFunction'} EvalName | |
| * Set to anonymous in any browser context that does not supply any of the other valid types | |
| */ | |
| /** | |
| * @typedef {'log'|'warn'|'error'|'debug'|'info'|'promiseError'} LogType | |
| */ | |
| /** | |
| * @typedef {Object} StackTrace | |
| * @param {string} name Name/path of the function | |
| * @param {string} url The url on which this error exists | |
| * @param {[number,number]} evalOrigin The line/column inside an eval call. | |
| * Is null for none-eval-shaped calls. | |
| * @param {EvalName?} evalType The type of eval this was ran with, such as 'eval', null or 'Function' | |
| * @param {[number,number]} origin The line/column that this call is from, any one of these | |
| * values can be NaN to stand in for N/A. | |
| */ | |
| /** | |
| * @typedef {Object} LogEntry | |
| * @prop {number} time The time stamp at which this log was pushed | |
| * @prop {LogType} type The type of this error | |
| * @prop {string|any[]} message The error message/log arguments | |
| * @prop {StackTrace[]} trace The stack trace leading to this log | |
| */ | |
| /** @type {LogEntry[]} */ | |
| const consoleLogs = []; | |
| /** | |
| * Pushes a message into the console log list | |
| * @param {LogType} type The type of message to log | |
| * @param {string} message The error, literally what else is there to say | |
| * @param {StackTrace[]} trace The stack trace of this log | |
| */ | |
| const push = (type, message, trace) => { | |
| // try to keep logs temporaly relevant, as long lengths of run time could make this array over flow | |
| while (consoleLogs.length > 10000) | |
| consoleLogs.shift(); | |
| consoleLogs.push({ | |
| time: Date.now(), | |
| type, | |
| message, | |
| trace | |
| }); | |
| }; | |
| const _parseFirefoxStack = stack => stack.split('\n') | |
| .map(line => { | |
| const at = line.indexOf('@'); | |
| const secondCol = line.lastIndexOf(':'); | |
| const firstCol = line.lastIndexOf(':', secondCol -1); | |
| const endLine = line.length; | |
| const name = line.slice(0, at); | |
| let url = line.slice(at +1, firstCol); | |
| let evalType = null; | |
| let origin = [ | |
| Number(line.slice(firstCol +1, secondCol)), | |
| Number(line.slice(secondCol +1, endLine)) | |
| ]; | |
| let evalOrigin = null; | |
| /** @type {RegExpMatchArray} */ | |
| let match; | |
| if ((match = url.match(/^ line ([0-9]+) > /))) { | |
| url = line.slice(at, match.index); | |
| evalOrigin = origin; | |
| evalType = line.slice(match.index + match[0].length, firstCol); | |
| origin = [Number(match[1]), NaN]; | |
| } | |
| return { | |
| name, | |
| url, | |
| evalOrigin, | |
| evalType, | |
| origin | |
| }; | |
| }); | |
| const _parseChromeStack = stack => stack.split('\n').slice(1) | |
| .map(line => { | |
| // we have no use for the human readable fluff | |
| line = line.slice(7); | |
| const firstOpenParen = line.indexOf('('); | |
| const secondOpenParen = line.indexOf('(', firstOpenParen +1); | |
| const firstCloseParen = line.indexOf(')'); | |
| const secondCloseParen = line.indexOf(')', firstCloseParen +1); | |
| let fourthCol = line.lastIndexOf(':'); | |
| let thirdCol = line.lastIndexOf(':', (fourthCol || line.length) -1); | |
| let secondCol = _lastIndexNaN(line, ':', (thirdCol || line.length) -1); | |
| let firstCol = _lastIndexNaN(line, ':', (secondCol || line.length) -1); | |
| if (secondOpenParen === -1) { | |
| secondCol = fourthCol; | |
| firstCol = thirdCol; | |
| fourthCol = NaN; | |
| thirdCol = NaN; | |
| } | |
| const name = line.slice(0, firstOpenParen -1); | |
| const origin = [ | |
| Number(line.slice(firstCol +1, secondCol)), | |
| Number(line.slice(secondCol +1, thirdCol || firstCloseParen)) | |
| ]; | |
| let url = line.slice(firstOpenParen +1, firstCol); | |
| let evalType = null; | |
| let evalOrigin = null; | |
| if (secondOpenParen !== -1) { | |
| url = line.slice(secondOpenParen +1, firstCol); | |
| evalType = 'anonymous'; | |
| evalOrigin = [ | |
| Number(line.slice(thirdCol +1, fourthCol)), | |
| Number(line.slice(fourthCol +1, secondCloseParen)) | |
| ]; | |
| } | |
| return { | |
| name, | |
| url, | |
| evalOrigin, | |
| evalType, | |
| origin | |
| }; | |
| }); | |
| const parseStack = (stack, url, line, column) => { | |
| if (!browserHasStack || !stack) { | |
| return [{ | |
| name: '<inaccessible>', | |
| url, | |
| origin: [line, column] | |
| }]; | |
| } | |
| // firefox has a *completely* different style rule compared to chrome | |
| if (stack.split('\n', 2)[0].includes('@')) return _parseFirefoxStack(stack); | |
| return _parseChromeStack(stack); | |
| }; | |
| window.addEventListener('error', e => | |
| push('error', e.message, parseStack(e.error.stack, e.filename, e.lineno, e.colno))); | |
| window.addEventListener('unhandledrejection', e => push('promiseError', e.reason, [])); | |
| for (const name of ['log', 'warn', 'error', 'debug', 'info']) { | |
| const item = window.console[name]; | |
| window.console[name] = (...args) => { | |
| let stack = []; | |
| if (browserHasStack) stack = parseStack(new Error().stack); | |
| push(name, args, stack); | |
| item(...args); | |
| }; | |
| } | |
| export { consoleLogs, parseStack, push }; |