UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

109 lines (108 loc) 4.72 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useState } from 'react'; import CloseIcon from '@mui/icons-material/Close'; import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, } from '@mui/material'; import { SourceMapConsumer } from 'source-map-js'; import ErrorMessageStackTraceContents from "./ErrorMessageStackTraceContents.js"; import LoadingEllipses from "./LoadingEllipses.js"; async function myfetchtext(uri) { const res = await fetch(uri); if (!res.ok) { throw new Error(`HTTP ${res.status} fetching ${uri}: ${await res.text()}`); } return res.text(); } async function myfetchjson(uri) { return JSON.parse(await myfetchtext(uri)); } const sourceMaps = {}; const sourceMappingUrlRe = /\/\/# sourceMappingURL=(.*)/; const stackLineRe = /(.*)((?:https?|file):\/\/.*):(\d+):(\d+)/; async function getSourceMapFromUri(uri) { if (sourceMaps[uri]) { return sourceMaps[uri]; } const uriQuery = new URL(uri).search; const currentScriptContent = await myfetchtext(uri); const mapUri = sourceMappingUrlRe.exec(currentScriptContent)?.[1] ?? ''; const data = await myfetchjson(new URL(mapUri, uri).href + uriQuery); const map = new SourceMapConsumer(data); sourceMaps[uri] = map; return map; } async function mapStackTrace(stack) { const mappedStack = []; for (const line of stack.split('\n')) { const match = stackLineRe.exec(line); if (!match) { mappedStack.push(line); continue; } const uri = match[2]; const consumer = await getSourceMapFromUri(uri); const pos = consumer.originalPositionFor({ line: +match[3], column: +match[4], }); if (!pos.source || !pos.line || !pos.column) { mappedStack.push(line); continue; } mappedStack.push(`${pos.source}:${pos.line}:${pos.column + 1} (${match[1].trim()})`); } return mappedStack.join('\n'); } const MAX_ERR_LEN = 10_000; function stripMessage(trace, error) { return trace.startsWith('Error:') ? trace.slice(`${error}`.length) : trace; } function getStackTrace(error) { return typeof error === 'object' && error !== null && 'stack' in error ? `${error.stack}` : ''; } export default function ErrorMessageStackTraceDialog({ error, onClose, extra, }) { const [mappedStackTrace, setMappedStackTrace] = useState(); const [secondaryError, setSecondaryError] = useState(); const [clicked, setClicked] = useState(false); const errorText = error ? `${error}` : ''; const stackTrace = stripMessage(getStackTrace(error), errorText); useEffect(() => { ; (async () => { try { setMappedStackTrace(await mapStackTrace(stackTrace)); } catch (e) { console.error(e); setMappedStackTrace(stackTrace); setSecondaryError(e); } })(); }, [stackTrace]); const version = window.JBrowseSession?.version; const errorBoxText = [ secondaryError ? 'Error loading source map, showing raw stack trace below:' : '', errorText.length > MAX_ERR_LEN ? `${errorText.slice(0, MAX_ERR_LEN)}...` : errorText, mappedStackTrace || 'No stack trace available', version ? `JBrowse ${version}` : '', ] .filter(Boolean) .join('\n'); return (_jsxs(Dialog, { open: true, onClose: onClose, maxWidth: "xl", children: [_jsxs(DialogTitle, { children: ["Stack trace", _jsx(IconButton, { onClick: onClose, sx: { position: 'absolute', right: 8, top: 8, }, children: _jsx(CloseIcon, {}) })] }), _jsx(DialogContent, { children: mappedStackTrace === undefined ? (_jsx(LoadingEllipses, { variant: "h6" })) : (_jsx(ErrorMessageStackTraceContents, { text: errorBoxText, extra: extra })) }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "contained", color: "secondary", onClick: async () => { const { default: copy } = await import('copy-to-clipboard'); copy(errorBoxText); setClicked(true); setTimeout(() => { setClicked(false); }, 1000); }, children: clicked ? 'Copied!' : 'Copy stack trace to clipboard' }), _jsx(Button, { variant: "contained", color: "primary", onClick: onClose, children: "Close" })] })] })); }