diff --git a/package-lock.json b/package-lock.json index 9c7879e4..b05880f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4761,6 +4761,14 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "^1.1.1" + } + }, "es-abstract": { "version": "1.17.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", @@ -14120,11 +14128,50 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, + "stack-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "requires": { + "stackframe": "^1.1.1" + } + }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" }, + "stackframe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz", + "integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==" + }, + "stacktrace-gps": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", + "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", + "requires": { + "source-map": "0.5.6", + "stackframe": "^1.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + } + } + }, + "stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "requires": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", diff --git a/package.json b/package.json index 6640f408..297f6416 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "react": "16.12.0", "react-dom": "16.12.0", "react-scripts": "3.4.0", - "roughjs": "4.0.4" + "roughjs": "4.0.4", + "stacktrace-js": "2.0.2" }, "description": "", "devDependencies": { diff --git a/src/index.tsx b/src/index.tsx index cdea56e9..8a8c4c2d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2322,20 +2322,70 @@ export class App extends React.Component { const rootElement = document.getElementById("root"); -class TopErrorBoundary extends React.Component { - state = { hasError: false, stack: "", localStorage: "" }; +interface TopErrorBoundaryState { + unresolvedError: Error[] | null; + hasError: boolean; + stack: string; + localStorage: string; +} - static getDerivedStateFromError(error: any) { - console.error(error); - return { +class TopErrorBoundary extends React.Component { + state: TopErrorBoundaryState = { + unresolvedError: null, + hasError: false, + stack: "", + localStorage: "", + }; + + componentDidCatch(error: Error) { + const _localStorage: any = {}; + for (const [key, value] of Object.entries({ ...localStorage })) { + try { + _localStorage[key] = JSON.parse(value); + } catch (err) { + _localStorage[key] = value; + } + } + this.setState(state => ({ hasError: true, - localStorage: JSON.stringify({ ...localStorage }), - stack: error.stack, - }; + unresolvedError: state.unresolvedError + ? state.unresolvedError.concat(error) + : [error], + localStorage: JSON.stringify(_localStorage), + })); + } + + async componentDidUpdate() { + if (this.state.unresolvedError !== null) { + let stack = ""; + for (const error of this.state.unresolvedError) { + if (stack) { + stack += `\n\n================\n\n`; + } + stack += `${error.message}:\n\n`; + try { + const StackTrace = await import("stacktrace-js"); + stack += (await StackTrace.fromError(error)).join("\n"); + } catch (err) { + console.error(err); + stack += error.stack || ""; + } + } + + this.setState(state => ({ + unresolvedError: null, + stack: `${ + state.stack ? `${state.stack}\n\n================\n\n${stack}` : stack + }`, + })); + } } private selectTextArea(event: React.MouseEvent) { - (event.target as HTMLTextAreaElement).select(); + if (event.target !== document.activeElement) { + event.preventDefault(); + (event.target as HTMLTextAreaElement).select(); + } } private async createGithubIssue() { @@ -2391,14 +2441,20 @@ class TopErrorBoundary extends React.Component {