diff --git a/package.json b/package.json index 16e7f943f1..b6ec44ebd0 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#9cf17f63b7c0b0ec5f31df27da0f82f7238dc594", "resize-observer-polyfill": "^1.5.0", "sanitize-html": "^1.18.4", + "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.1", "url": "^0.11.0", "velocity-animate": "^1.5.2", diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 42c87172b8..9b08e41a8c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -418,6 +418,7 @@ "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", + "Downloading report": "Downloading report", "Waiting for response from server": "Waiting for response from server", "Messages containing my display name": "Messages containing my display name", "Messages containing my username": "Messages containing my username", diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js index ed5a9e5946..1d765dd273 100644 --- a/src/rageshake/submit-rageshake.js +++ b/src/rageshake/submit-rageshake.js @@ -21,6 +21,7 @@ import pako from 'pako'; import {MatrixClientPeg} from '../MatrixClientPeg'; import PlatformPeg from '../PlatformPeg'; import { _t } from '../languageHandler'; +import Tar from "tar-js"; import * as rageshake from './rageshake'; @@ -33,26 +34,7 @@ if (!TextEncoder) { TextEncoder = TextEncodingUtf8.TextEncoder; } -/** - * Send a bug report. - * - * @param {string} bugReportEndpoint HTTP url to send the report to - * - * @param {object} opts optional dictionary of options - * - * @param {string} opts.userText Any additional user input. - * - * @param {boolean} opts.sendLogs True to send logs - * - * @param {function(string)} opts.progressCallback Callback to call with progress updates - * - * @return {Promise} Resolved when the bug report is sent. - */ -export default async function sendBugReport(bugReportEndpoint, opts) { - if (!bugReportEndpoint) { - throw new Error("No bug report endpoint has been set."); - } - +async function collectBugReport(opts) { opts = opts || {}; const progressCallback = opts.progressCallback || (() => {}); @@ -106,10 +88,96 @@ export default async function sendBugReport(bugReportEndpoint, opts) { } } + return body; +} + +/** + * Send a bug report. + * + * @param {string} bugReportEndpoint HTTP url to send the report to + * + * @param {object} opts optional dictionary of options + * + * @param {string} opts.userText Any additional user input. + * + * @param {boolean} opts.sendLogs True to send logs + * + * @param {function(string)} opts.progressCallback Callback to call with progress updates + * + * @return {Promise} Resolved when the bug report is sent. + */ +export default async function sendBugReport(bugReportEndpoint, opts) { + if (!bugReportEndpoint) { + throw new Error("No bug report endpoint has been set."); + } + + opts = opts || {}; + const progressCallback = opts.progressCallback || (() => {}); + const body = await collectBugReport(opts); + progressCallback(_t("Uploading report")); await _submitReport(bugReportEndpoint, body, progressCallback); } +/** + * Downloads the files from a bug report. This is the same as sendBugReport, + * but instead causes the browser to download the files locally. + * + * @param {object} opts optional dictionary of options + * + * @param {string} opts.userText Any additional user input. + * + * @param {boolean} opts.sendLogs True to send logs + * + * @param {function(string)} opts.progressCallback Callback to call with progress updates + * + * @return {Promise} Resolved when the bug report is downloaded (or started). + */ +export async function downloadBugReport(opts) { + opts = opts || {}; + const progressCallback = opts.progressCallback || (() => {}); + const body = await collectBugReport(opts); + + progressCallback(_t("Downloading report")); + let metadata = ""; + const tape = new Tar(); + let i = 0; + for (const e of body.entries()) { + if (e[0] === 'compressed-log') { + await new Promise((resolve => { + const reader = new FileReader(); + reader.addEventListener('loadend', ev => { + tape.append(`log-${i++}.log`, pako.ungzip(ev.target.result)); + resolve(); + }); + reader.readAsArrayBuffer(e[1]); + })) + } else { + metadata += `${e[0]} = ${e[1]}\n`; + } + } + tape.append('issue.txt', metadata); + + // We have to create a new anchor to download if we want a filename. Otherwise we could + // just use window.open. + const dl = document.createElement('a'); + dl.href = `data:application/octet-stream;base64,${btoa(uint8ToString(tape.out))}`; + dl.download = 'rageshake.tar'; + document.body.appendChild(dl); + dl.click(); + document.body.removeChild(dl); +} + +// Source: https://github.com/beatgammit/tar-js/blob/master/examples/main.js +function uint8ToString(buf) { + let i, length, out = ''; + for (i = 0, length = buf.length; i < length; i += 1) { + out += String.fromCharCode(buf[i]); + } + + return out; +} + function _submitReport(endpoint, body, progressCallback) { return new Promise((resolve, reject) => { const req = new XMLHttpRequest(); diff --git a/yarn.lock b/yarn.lock index d2135f7aa6..18bbf44f4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8251,6 +8251,11 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tar-js@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tar-js/-/tar-js-0.3.0.tgz#6949aabfb0ba18bb1562ae51a439fd0f30183a17" + integrity sha1-aUmqv7C6GLsVYq5RpDn9DzAYOhc= + tar@^4: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"