Skip to content

Commit ea4ab36

Browse files
authored
Downloadable logs (#152)
1 parent 186404b commit ea4ab36

File tree

5 files changed

+97
-31
lines changed

5 files changed

+97
-31
lines changed

src/components/crash-message/crash-message.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {FormattedMessage} from 'react-intl';
55

66
import styles from './crash-message.css';
77
import reloadIcon from './reload.svg';
8+
import { downloadLogs } from '../../lib/pm-log-capture.js';
89

910
const CrashMessage = props => (
1011
<div className={styles.crashWrapper}>
@@ -25,7 +26,7 @@ const CrashMessage = props => (
2526
defaultMessage={'We are so sorry, but it looks like the page has crashed.' +
2627
' Please refresh your page to try' +
2728
' again.' +
28-
' If the problem persists, please report this error to our Discord.'}
29+
' If the problem persists, please report the downloadable error below to our Discord.'}
2930
description="Message to inform the user that page has crashed."
3031
id="tw.gui.crashMessage.description"
3132
/>
@@ -57,6 +58,12 @@ const CrashMessage = props => (
5758
id="gui.crashMessage.reload"
5859
/>
5960
</button>
61+
<button
62+
className={styles.reloadButton}
63+
onClick={downloadLogs}
64+
>
65+
{'Download Error'}
66+
</button>
6067
</Box>
6168
</div>
6269
);

src/components/menu-bar/menu-bar.jsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import sharedMessages from '../../lib/shared-messages';
8585
import SeeInsideButton from './tw-see-inside.jsx';
8686
import { notScratchDesktop } from '../../lib/isScratchDesktop.js';
8787

88-
//import { consoleLogs } from '../../lib/pm-log-capture.js';
88+
import { downloadLogs } from '../../lib/pm-log-capture.js';
8989

9090
const ariaMessages = defineMessages({
9191
language: {
@@ -207,6 +207,7 @@ class MenuBar extends React.Component {
207207
'handleClickPackager',
208208
'handleClickRestorePoints',
209209
'handleClickSeeCommunity',
210+
'handleClickDownloadLogs',
210211
'handleClickShare',
211212
'handleKeyPress',
212213
'handleLanguageMouseUp',
@@ -422,21 +423,7 @@ class MenuBar extends React.Component {
422423
this.props.onRequestCloseAbout();
423424
};
424425
}
425-
/*
426-
- hidden until this is actually helpful for developers
427-
- unhide when a solution is found for not blocking error tracking/using 3rd parties
428-
handleClickDownloadLogs() {
429-
const str = JSON.stringify(consoleLogs);
430-
const a = document.createElement('a');
431-
a.style.display = 'none';
432-
document.body.append(a);
433-
const url = window.URL.createObjectURL(new Blob([str]));
434-
a.href = url;
435-
a.download = 'pm-log-trace.json';
436-
a.click();
437-
window.URL.revokeObjectURL(url);
438-
a.remove();
439-
}*/
426+
handleClickDownloadLogs() { downloadLogs(); }
440427
render() {
441428
const saveNowMessage = (
442429
<FormattedMessage
@@ -860,6 +847,9 @@ class MenuBar extends React.Component {
860847
id="pm.menuBar.moreSettings"
861848
/>
862849
</MenuItem>
850+
<MenuItem onClick={this.handleClickDownloadLogs}>
851+
Download Logs
852+
</MenuItem>
863853
</MenuSection>
864854
</MenuBarMenu>
865855
</div>

src/lib/analytics.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ const GoogleAnalytics = {
44
};
55

66
export default GoogleAnalytics;
7+

src/lib/pm-log-capture.js

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import JSZip from 'jszip';
2+
import uid from './uid';
3+
14
/**
25
* String.prototype.indexOf, but it returns NaN not -1 on failure
36
* @param {string} str The string to check in
@@ -56,7 +59,7 @@ const push = (type, message, trace) => {
5659
trace
5760
});
5861
};
59-
const _parseFirefoxStack = stack => stack.split('\n')
62+
const _parseFirefoxStack = stack => stack.split('\n').slice(1)
6063
.map(line => {
6164
const at = line.indexOf('@');
6265
const secondCol = line.lastIndexOf(':');
@@ -87,7 +90,7 @@ const _parseFirefoxStack = stack => stack.split('\n')
8790
origin
8891
};
8992
});
90-
const _parseChromeStack = stack => stack.split('\n').slice(1)
93+
const _parseChromeStack = stack => stack.split('\n').slice(2)
9194
.map(line => {
9295
// we have no use for the human readable fluff
9396
line = line.slice(7);
@@ -142,18 +145,83 @@ const parseStack = (stack, url, line, column) => {
142145
if (stack.split('\n', 2)[0].includes('@')) return _parseFirefoxStack(stack);
143146
return _parseChromeStack(stack);
144147
};
148+
const downloadLogs = async () => {
149+
const files = new JSZip();
150+
files.file('logs.json', JSON.stringify(consoleLogs));
151+
const index = {};
152+
// get files
153+
// sadly, this may just dead ass fail to get files due to blob lifecycle
154+
// and i dont want to make these files get stored at runtime, cause poopy doo doo ram
155+
for (const log of consoleLogs) {
156+
for (const trace of log.trace) {
157+
if (index[trace.url]) continue;
158+
const id = uid();
159+
const content = await fetch(trace.url)
160+
.then(res => res.ok ? res.text() : null)
161+
.catch(() => {});
162+
if (!content) continue;
163+
files.file(id, content);
164+
index[trace.url] = id;
165+
}
166+
}
167+
files.file('index.json', JSON.stringify(index));
168+
let blob = await files.generateAsync({ type: 'blob', compression: 'DEFLATE' });
169+
let filename = 'pm-error-download.pml';
170+
/* actually, this is a bad idea
171+
// if we can, include the project
172+
if (vm) {
173+
filename = 'pm-error-download.pmp';
174+
const archive = vm._saveProjectZip();
175+
archive.file('logs.json', blob);
176+
blob = await archive.generateAsync({
177+
type: 'blob',
178+
mimeType: 'application/x.scratch.sb3',
179+
compression: 'DEFLATE'
180+
});
181+
}
182+
*/
183+
const a = document.createElement('a');
184+
a.style.display = 'none';
185+
document.body.append(a);
186+
const url = window.URL.createObjectURL(blob);
187+
a.href = url;
188+
a.download = filename;
189+
a.click();
190+
window.URL.revokeObjectURL(url);
191+
a.remove();
192+
};
193+
window.downloadLogs = downloadLogs;
145194

146195
window.addEventListener('error', e =>
147196
push('error', e.message, parseStack(e.error.stack, e.filename, e.lineno, e.colno)));
148197
window.addEventListener('unhandledrejection', e => push('promiseError', e.reason, []));
149-
for (const name of ['log', 'warn', 'error', 'debug', 'info']) {
150-
const item = window.console[name];
151-
window.console[name] = (...args) => {
152-
let stack = [];
153-
if (browserHasStack) stack = parseStack(new Error().stack);
154-
push(name, args, stack);
155-
item(...args);
156-
};
198+
class StackTrace extends Error {
199+
constructor() {
200+
super('');
201+
if (this.stack.split('\n', 2)[0].includes('@'))
202+
this.stack = this.stack
203+
.split('\n')
204+
.slice(2, 3)
205+
.join('\n');
206+
else {
207+
// chrome is weird ngl
208+
const lines = this.stack
209+
.split('\n')
210+
.slice(0, 3);
211+
lines.splice(1, 2);
212+
this.stack = lines.join('\n');
213+
}
214+
}
157215
}
158-
159-
export { consoleLogs, parseStack, push };
216+
if (!String(window.location.href).startsWith(`http://localhost:`)) {
217+
for (const name of ['log', 'warn', 'error', 'debug', 'info']) {
218+
const item = window.console[name];
219+
window.console[name] = (...args) => {
220+
let stack = [];
221+
if (browserHasStack) stack = parseStack(new Error().stack);
222+
push(name, args, stack);
223+
item.call(console, ...args, new StackTrace());
224+
};
225+
}
226+
}
227+
export { consoleLogs, parseStack, push, downloadLogs };

src/lib/uid.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* Legal characters for the unique ID.
77
* Should be all on a US keyboard. No XML special characters or control codes.
88
* Removed $ due to issue 251.
9+
* Removed all symbols due to use in files
910
* @private
1011
*/
11-
const soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' +
12-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
12+
const soup_ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
1313

1414
/**
1515
* Generate a unique ID, from Blockly. This should be globally unique.

0 commit comments

Comments
 (0)