diff --git a/src/components/crash-message/crash-message.jsx b/src/components/crash-message/crash-message.jsx
index aa4a3a9b1ac..67b003e2e87 100644
--- a/src/components/crash-message/crash-message.jsx
+++ b/src/components/crash-message/crash-message.jsx
@@ -5,6 +5,7 @@ import {FormattedMessage} from 'react-intl';
import styles from './crash-message.css';
import reloadIcon from './reload.svg';
+import { downloadLogs } from '../../lib/pm-log-capture.js';
const CrashMessage = props => (
@@ -25,7 +26,7 @@ const CrashMessage = props => (
defaultMessage={'We are so sorry, but it looks like the page has crashed.' +
' Please refresh your page to try' +
' again.' +
- ' If the problem persists, please report this error to our Discord.'}
+ ' If the problem persists, please report the downloadable error below to our Discord.'}
description="Message to inform the user that page has crashed."
id="tw.gui.crashMessage.description"
/>
@@ -57,6 +58,12 @@ const CrashMessage = props => (
id="gui.crashMessage.reload"
/>
+
);
diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx
index 7a504d204c7..7c8d216a1c0 100644
--- a/src/components/menu-bar/menu-bar.jsx
+++ b/src/components/menu-bar/menu-bar.jsx
@@ -85,7 +85,7 @@ import sharedMessages from '../../lib/shared-messages';
import SeeInsideButton from './tw-see-inside.jsx';
import { notScratchDesktop } from '../../lib/isScratchDesktop.js';
-//import { consoleLogs } from '../../lib/pm-log-capture.js';
+import { downloadLogs } from '../../lib/pm-log-capture.js';
const ariaMessages = defineMessages({
language: {
@@ -207,6 +207,7 @@ class MenuBar extends React.Component {
'handleClickPackager',
'handleClickRestorePoints',
'handleClickSeeCommunity',
+ 'handleClickDownloadLogs',
'handleClickShare',
'handleKeyPress',
'handleLanguageMouseUp',
@@ -422,21 +423,7 @@ class MenuBar extends React.Component {
this.props.onRequestCloseAbout();
};
}
- /*
- - hidden until this is actually helpful for developers
- - unhide when a solution is found for not blocking error tracking/using 3rd parties
- handleClickDownloadLogs() {
- const str = JSON.stringify(consoleLogs);
- const a = document.createElement('a');
- a.style.display = 'none';
- document.body.append(a);
- const url = window.URL.createObjectURL(new Blob([str]));
- a.href = url;
- a.download = 'pm-log-trace.json';
- a.click();
- window.URL.revokeObjectURL(url);
- a.remove();
- }*/
+ handleClickDownloadLogs() { downloadLogs(); }
render() {
const saveNowMessage = (
+
diff --git a/src/lib/analytics.js b/src/lib/analytics.js
index b123c673c1f..5b795e69a52 100644
--- a/src/lib/analytics.js
+++ b/src/lib/analytics.js
@@ -4,3 +4,4 @@ const GoogleAnalytics = {
};
export default GoogleAnalytics;
+
\ No newline at end of file
diff --git a/src/lib/pm-log-capture.js b/src/lib/pm-log-capture.js
index e22189b3da5..2e59c21baa8 100644
--- a/src/lib/pm-log-capture.js
+++ b/src/lib/pm-log-capture.js
@@ -1,3 +1,6 @@
+import JSZip from 'jszip';
+import uid from './uid';
+
/**
* String.prototype.indexOf, but it returns NaN not -1 on failure
* @param {string} str The string to check in
@@ -56,7 +59,7 @@ const push = (type, message, trace) => {
trace
});
};
-const _parseFirefoxStack = stack => stack.split('\n')
+const _parseFirefoxStack = stack => stack.split('\n').slice(1)
.map(line => {
const at = line.indexOf('@');
const secondCol = line.lastIndexOf(':');
@@ -87,7 +90,7 @@ const _parseFirefoxStack = stack => stack.split('\n')
origin
};
});
-const _parseChromeStack = stack => stack.split('\n').slice(1)
+const _parseChromeStack = stack => stack.split('\n').slice(2)
.map(line => {
// we have no use for the human readable fluff
line = line.slice(7);
@@ -142,18 +145,83 @@ const parseStack = (stack, url, line, column) => {
if (stack.split('\n', 2)[0].includes('@')) return _parseFirefoxStack(stack);
return _parseChromeStack(stack);
};
+const downloadLogs = async () => {
+ const files = new JSZip();
+ files.file('logs.json', JSON.stringify(consoleLogs));
+ const index = {};
+ // get files
+ // sadly, this may just dead ass fail to get files due to blob lifecycle
+ // and i dont want to make these files get stored at runtime, cause poopy doo doo ram
+ for (const log of consoleLogs) {
+ for (const trace of log.trace) {
+ if (index[trace.url]) continue;
+ const id = uid();
+ const content = await fetch(trace.url)
+ .then(res => res.ok ? res.text() : null)
+ .catch(() => {});
+ if (!content) continue;
+ files.file(id, content);
+ index[trace.url] = id;
+ }
+ }
+ files.file('index.json', JSON.stringify(index));
+ let blob = await files.generateAsync({ type: 'blob', compression: 'DEFLATE' });
+ let filename = 'pm-error-download.pml';
+ /* actually, this is a bad idea
+ // if we can, include the project
+ if (vm) {
+ filename = 'pm-error-download.pmp';
+ const archive = vm._saveProjectZip();
+ archive.file('logs.json', blob);
+ blob = await archive.generateAsync({
+ type: 'blob',
+ mimeType: 'application/x.scratch.sb3',
+ compression: 'DEFLATE'
+ });
+ }
+ */
+ const a = document.createElement('a');
+ a.style.display = 'none';
+ document.body.append(a);
+ const url = window.URL.createObjectURL(blob);
+ a.href = url;
+ a.download = filename;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ a.remove();
+};
+window.downloadLogs = downloadLogs;
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);
- };
+class StackTrace extends Error {
+ constructor() {
+ super('');
+ if (this.stack.split('\n', 2)[0].includes('@'))
+ this.stack = this.stack
+ .split('\n')
+ .slice(2, 3)
+ .join('\n');
+ else {
+ // chrome is weird ngl
+ const lines = this.stack
+ .split('\n')
+ .slice(0, 3);
+ lines.splice(1, 2);
+ this.stack = lines.join('\n');
+ }
+ }
}
-
-export { consoleLogs, parseStack, push };
+if (!String(window.location.href).startsWith(`http://localhost:`)) {
+ 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.call(console, ...args, new StackTrace());
+ };
+ }
+}
+export { consoleLogs, parseStack, push, downloadLogs };
diff --git a/src/lib/uid.js b/src/lib/uid.js
index 187ab3b50e4..2c8d093841b 100644
--- a/src/lib/uid.js
+++ b/src/lib/uid.js
@@ -6,10 +6,10 @@
* Legal characters for the unique ID.
* Should be all on a US keyboard. No XML special characters or control codes.
* Removed $ due to issue 251.
+ * Removed all symbols due to use in files
* @private
*/
-const soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' +
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+const soup_ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
/**
* Generate a unique ID, from Blockly. This should be globally unique.