diff --git a/components/error-boundary.js b/components/error-boundary.js index 2b496ec7..0fe6554c 100644 --- a/components/error-boundary.js +++ b/components/error-boundary.js @@ -6,7 +6,7 @@ import copy from 'clipboard-copy' import { LoggerContext } from './logger' import Button from 'react-bootstrap/Button' import { useToast } from './toast' - +import { decodeMinifiedStackTrace } from '@/lib/stacktrace' class ErrorBoundary extends Component { constructor (props) { super(props) @@ -27,7 +27,7 @@ class ErrorBoundary extends Component { getErrorDetails () { let details = this.state.error.stack if (this.state.errorInfo?.componentStack) { - details += `\n\nComponent stack:${this.state.errorInfo.componentStack}` + details += `\n\nComponent stack:\n ${this.state.errorInfo.componentStack}` } return details } @@ -69,7 +69,8 @@ const CopyErrorButton = ({ errorDetails }) => { const toaster = useToast() const onClick = async () => { try { - await copy(errorDetails) + const decodedDetails = await decodeMinifiedStackTrace(errorDetails) + await copy(decodedDetails) toaster?.success?.('copied') } catch (err) { console.error(err) diff --git a/lib/stacktrace.js b/lib/stacktrace.js new file mode 100644 index 00000000..e265ccda --- /dev/null +++ b/lib/stacktrace.js @@ -0,0 +1,55 @@ +import { SourceMapConsumer } from 'source-map' + +// FUN@FILE:LINE:COLUMN +const STACK_TRACE_LINE_REGEX = /^([A-Za-z0-9]*)@(.*):([0-9]+):([0-9]+)/ + +/** + * Decode a minified stack trace using source maps + * @param {string} stack - the minified stack trace + * @param {Object} [sourceMaps] - an object used to cache source maps + * @returns {Promise} Decoded stack trace + */ +export async function decodeMinifiedStackTrace (stack, sourceMaps = {}) { + let decodedStack = '' + let decoded = false + for (const line of stack.split('\n')) { + try { + const stackLine = line.trim() + const stackLineParts = stackLine?.match(STACK_TRACE_LINE_REGEX) + if (stackLineParts) { + const [stackFile, stackLine, stackColumn] = stackLineParts.slice(2) + if (!stackFile || !stackLine || !stackColumn) throw new Error('Unsupported stack line') + if ( + ( + !stackFile.startsWith(process.env.NEXT_PUBLIC_ASSET_PREFIX) && + !stackFile.startsWith(process.env.NEXT_PUBLIC_URL) + ) || + !stackFile.endsWith('.js') + ) throw new Error('Unsupported file url ' + stackFile) + const sourceMapUrl = stackFile + '.map' + if (!sourceMaps[sourceMapUrl]) { + sourceMaps[sourceMapUrl] = await new SourceMapConsumer(await fetch(sourceMapUrl).then(res => res.text())) + } + const sourceMapper = sourceMaps[sourceMapUrl] + const map = sourceMapper.originalPositionFor({ + line: parseInt(stackLine), + column: parseInt(stackColumn) + }) + const { source, name, line, column } = map + if (!source || line === undefined) throw new Error('Unsupported stack line') + decodedStack += `${name || ''}@${source}:${line}:${column}\n` + decoded = true + continue + } + } catch (e) { + console.error('Cannot decode stack line', e) + } + decodedStack += `${line}\n` + } + + if (decoded) { + decodedStack = `Decoded stacktrace:\n${decodedStack}\n\nOriginal stack trace:\n${stack}` + } + + return decodedStack +} diff --git a/package-lock.json b/package-lock.json index d4dd4c9f..3326e9a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,6 +86,7 @@ "remove-markdown": "^0.5.5", "sass": "^1.79.5", "serviceworker-storage": "^0.1.0", + "source-map": "^0.8.0-beta.0", "textarea-caret": "^3.1.0", "tldts": "^6.1.51", "tsx": "^4.19.1", @@ -18307,6 +18308,7 @@ "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" }, @@ -18343,6 +18345,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "license": "MIT", "dependencies": { "punycode": "^2.1.0" } @@ -18350,12 +18353,14 @@ "node_modules/source-map/node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause" }, "node_modules/source-map/node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", diff --git a/package.json b/package.json index 9ea6c48f..d4fa4d11 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "remove-markdown": "^0.5.5", "sass": "^1.79.5", "serviceworker-storage": "^0.1.0", + "source-map": "^0.8.0-beta.0", "textarea-caret": "^3.1.0", "tldts": "^6.1.51", "tsx": "^4.19.1",