import fs from 'fs'; import StackTracey from 'stacktracey'; import { SourceMapConsumer } from 'source-map'; const nixSlashes = (x) => x.replace(/\\/g, '/'); const sourcemapCatch = {}; function stacktracey(stacktrace, opts) { const parseStack = []; const stack = opts.preset.parseStacktrace(stacktrace); stack.items.forEach((item, index) => { const fn = () => { const { line = 0, column = 0, file, fileName, fileRelative } = item; try { return opts.preset .getSourceMapContent(file, fileName, fileRelative) .then((content) => { if (content) { return getConsumer(content).then((consumer) => { const sourceMapContent = parseSourceMapContent(consumer, { line, column, }); if (sourceMapContent) { const { source, sourcePath, sourceLine, sourceColumn, fileName = '', } = sourceMapContent; stack.items[index] = Object.assign({}, item, { file: source, line: sourceLine, column: sourceColumn, fileShort: sourcePath, fileRelative: sourcePath, fileName, }); } }); } }); } catch (error) { return Promise.resolve(); } }; parseStack.push(fn()); }); return new Promise((resolve, reject) => { Promise.all(parseStack) .then(() => { const parseError = opts.preset.asTableStacktrace({ stack, maxColumnWidths: { callee: 999, file: 999, sourceLine: 999, }, stacktrace, }); resolve(parseError); }) .catch(() => { resolve(stacktrace); }); }); } function getConsumer(content) { return new Promise((resolve, reject) => { if (SourceMapConsumer.with) { SourceMapConsumer.with(content, null, (consumer) => { resolve(consumer); }); } else { // @ts-ignore resolve(SourceMapConsumer(content)); } }); } function getSourceMapContent(sourcemapUrl) { try { return (sourcemapCatch[sourcemapUrl] || (sourcemapCatch[sourcemapUrl] = new Promise((resolve, reject) => { try { if (/^[http|https]+:/i.test(sourcemapUrl)) { uni.request({ url: sourcemapUrl, success: (res) => { sourcemapCatch[sourcemapUrl] = res.data; resolve(sourcemapCatch[sourcemapUrl]); }, }); } else { sourcemapCatch[sourcemapUrl] = fs.readFileSync(sourcemapUrl, 'utf-8'); resolve(sourcemapCatch[sourcemapUrl]); } } catch (error) { resolve(''); } }))); } catch (error) { return ''; } } function parseSourceMapContent(consumer, obj) { // source -> 'uni-app:///node_modules/@sentry/browser/esm/helpers.js' const { source, line: sourceLine, column: sourceColumn, } = consumer.originalPositionFor(obj); if (source) { const sourcePathSplit = source.split('/'); const sourcePath = sourcePathSplit.slice(3).join('/'); const fileName = sourcePathSplit.pop(); return { source, sourcePath, sourceLine: sourceLine === null ? 0 : sourceLine, sourceColumn: sourceColumn === null ? 0 : sourceColumn, fileName, }; } } function uniStracktraceyPreset(opts) { const { base, sourceRoot } = opts; return { parseSourceMapUrl(file, fileName, fileRelative) { // 组合 sourceMapUrl if (fileRelative.indexOf('(') !== -1) fileRelative = fileRelative.match(/\((.*)/)[1]; if (!base || !fileRelative) return ''; if (sourceRoot) { return `${fileRelative.replace(sourceRoot, base + '/')}.map`; } return `${base}/${fileRelative}.map`; }, getSourceMapContent(file, fileName, fileRelative) { return Promise.resolve(getSourceMapContent(this.parseSourceMapUrl(file, fileName, fileRelative))); }, parseStacktrace(stacktrace) { return new StackTracey(stacktrace); }, asTableStacktrace({ maxColumnWidths, stacktrace, stack }) { const errorName = stacktrace.split('\n')[0]; return ((errorName.indexOf('at') === -1 ? `${errorName}\n` : '') + (stack.asTable ? stack.asTable({ maxColumnWidths }) : '')); }, }; } function utsStracktraceyPreset(opts) { const { base, sourceRoot } = opts; let errStack = []; return { parseSourceMapUrl(file, fileName, fileRelative) { // 组合 sourceMapUrl if (sourceRoot) { return `${file.replace(sourceRoot, base + '/')}.map`; } return `${base}/${file}.map`; }, getSourceMapContent(file, fileName, fileRelative) { // 根据 base,filename 组合 sourceMapUrl return Promise.resolve(getSourceMapContent(this.parseSourceMapUrl(file, fileName, fileRelative))); }, parseStacktrace(str) { const lines = (str || '').split('\n'); const entries = lines .map((line, index) => { line = line.trim(); let callee, fileLineColumn = [], planA, planB; if ((planA = line.match(/e: (.+\.kt)(.+\))*:\s*(.+)*/))) { errStack.push('%StacktraceyItem%'); callee = 'e: '; fileLineColumn = (planA[2].match(/.*:.*\((\d+).+?(\d+)\)/) || []).slice(1); } else { errStack.push(line); return undefined; } const fileName = planA[1] ? (planB = planA[1].match(/(.*)*\/(.+)/) || [])[2] || '' : ''; return { beforeParse: line, callee: callee || '', index: false, native: false, file: nixSlashes(planA[1] || ''), line: parseInt(fileLineColumn[0] || '', 10) || undefined, column: parseInt(fileLineColumn[1] || '', 10) || undefined, fileName, fileShort: planB ? planB[1] : '', errMsg: planA[3] || '', calleeShort: '', fileRelative: '', thirdParty: false, }; }) .filter((x) => x !== undefined); return { items: entries, }; }, asTableStacktrace({ maxColumnWidths, stacktrace, stack }) { return errStack .map((item) => { if (item === '%StacktraceyItem%') { const _stack = stack.items.shift(); if (_stack) return `${_stack.callee}${_stack.file}: (${_stack.line}, ${_stack.column}): ${_stack.errMsg}`; } return item; }) .join('\n'); }, }; } export { stacktracey, uniStracktraceyPreset, utsStracktraceyPreset };