From e3a264756e63999d69355675d99895641a848a64 Mon Sep 17 00:00:00 2001 From: Monika Radaviciute Date: Wed, 10 Apr 2024 13:52:23 +0300 Subject: [PATCH 1/3] Identify download sessions by random ids to enable concurrent downloads from a single container --- .../src/common/socket.js | 19 +- .../wasm/js/crypt-post-downworker.js | 175 ++++++++++-------- 2 files changed, 108 insertions(+), 86 deletions(-) diff --git a/swift_browser_ui_frontend/src/common/socket.js b/swift_browser_ui_frontend/src/common/socket.js index 564714104..ded4863a8 100644 --- a/swift_browser_ui_frontend/src/common/socket.js +++ b/swift_browser_ui_frontend/src/common/socket.js @@ -102,6 +102,7 @@ export default class UploadSocket { } this.$store.commit("addDownload"); this.getHeaders( + e.data.id, e.data.container, e.data.files, e.data.pubkey, @@ -131,14 +132,14 @@ export default class UploadSocket { this.downloadFinished = false; if (e.data.archive) { let downloadUrl = new URL( - `/archive/${e.data.container}.tar`, + `/archive/${e.data.id}/${e.data.container}.tar`, document.location.origin, ); if (DEV) console.log(downloadUrl); window.open(downloadUrl, "_blank"); } else { let downloadUrl = new URL( - `/file/${e.data.container}/${e.data.path}`, + `/file/${e.data.id}/${e.data.container}/${e.data.path}`, document.location.origin, ); if (DEV) console.log(downloadUrl); @@ -221,7 +222,7 @@ export default class UploadSocket { } // Get headers for download - async getHeaders(container, fileList, pubkey, owner, ownerName) { + async getHeaders(id, container, fileList, pubkey, owner, ownerName) { let headers = {}; // Cache the container ID @@ -314,6 +315,7 @@ export default class UploadSocket { if (!this.useServiceWorker) { this.downWorker.postMessage({ command: "addHeaders", + id: id, container: container, headers: headers, }); @@ -321,6 +323,7 @@ export default class UploadSocket { navigator.serviceWorker.ready.then((reg) => { reg.active.postMessage({ command: "addHeaders", + id: id, container: container, headers: headers, }); @@ -395,12 +398,15 @@ export default class UploadSocket { objects, owner = "", ) { + + //get random id + const sessionId = Math.random().toString(36).slice(2, 8); + let ownerName = ""; if (owner) { let ids = await this.$store.state.client.projectCheckIDs(owner); ownerName = ids.name; } - let fileHandle = undefined; if (objects.length == 1) { // Download directly into the file if available. @@ -424,8 +430,10 @@ export default class UploadSocket { ]; } fileHandle = await window.showSaveFilePicker(opts); + this.downWorker.postMessage({ command: "downloadFile", + id: sessionId, container: container, file: objects[0], handle: fileHandle, @@ -439,6 +447,7 @@ export default class UploadSocket { navigator.serviceWorker.ready.then(reg => { reg.active.postMessage({ command: "downloadFile", + id: sessionId, container: container, file: objects[0], owner: owner, @@ -463,6 +472,7 @@ export default class UploadSocket { }); this.downWorker.postMessage({ command: "downloadFiles", + id: sessionId, container: container, files: objects.length < 1 ? [] : objects, handle: fileHandle, @@ -473,6 +483,7 @@ export default class UploadSocket { navigator.serviceWorker.ready.then(reg => { reg.active.postMessage({ command: "downloadFiles", + id: sessionId, container: container, files: objects.length < 1 ? [] : objects, owner: owner, diff --git a/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js b/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js index 085894f78..3eacad7f7 100644 --- a/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js +++ b/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js @@ -49,11 +49,12 @@ intermediary storage and the ServiceWorker doesn't. */ -// Example: https://devenv:8443/file/test-container/examplefile.txt.c4gh -const fileUrl = new RegExp("/file/[^/]*/.*$"); -// Example: https://devenv:8443/archive/test-container.tar -const archiveUrl = new RegExp("/archive/[^/]*\\.tar$"); -const fileUrlStart = new RegExp("/file/[^/]*/"); +// Example: https://devenv:8443/file/session-id/test-container/examplefile.txt.c4gh +const fileUrl = new RegExp("/file/[^/]*/[^/]*/.*$"); +// Example: https://devenv:8443/archive/session-id/test-container.tar +const archiveUrl = new RegExp("/archive/[^/]*/[^/]*\\.tar$"); +const fileUrlStart = new RegExp("/file/[^/]*/[^/]*/"); +const archiveUrlStart = new RegExp("/archive/[^/]*/"); if (inServiceWorker) { self.addEventListener("install", (event) => { @@ -62,10 +63,11 @@ if (inServiceWorker) { self.addEventListener("activate", (event) => { event.waitUntil(self.clients.claim()); }); + } // Create a download session -function createDownloadSession(container, handle, archive) { +function createDownloadSession(id, container, handle, archive) { aborted = false; //reset cancelled = false; @@ -83,12 +85,13 @@ function createDownloadSession(container, handle, archive) { [keypairPtr], ); - downloads[container] = { + downloads[id] = { keypair: keypairPtr, pubkey: new Uint8Array(HEAPU8.subarray(pubkeyPtr, pubkeyPtr + 32)), handle: handle, direct: !inServiceWorker, archive: archive, + container: container, files: {}, }; } @@ -102,7 +105,7 @@ function getFileSize(size, key) { } // Add a file to the download session -function createDownloadSessionFile(container, path, header, url, size) { +function createDownloadSessionFile(id, container, path, header, url, size) { if (checkPollutingName(path)) return; let headerPath = `header_${container}_` @@ -117,10 +120,10 @@ function createDownloadSessionFile(container, path, header, url, size) { "get_session_key_from_header", "number", ["number", "string"], - [downloads[container].keypair, headerPath], + [downloads[id].keypair, headerPath], ); - downloads[container].files[path] = { + downloads[id].files[path] = { key: sessionKeyPtr, url: url, size: getFileSize(size, sessionKeyPtr), @@ -132,7 +135,7 @@ function createDownloadSessionFile(container, path, header, url, size) { // Cache the header if no suitable key couldn't be found if (sessionKeyPtr <= 0) { - downloads[container].files[path].header = header; + downloads[id].files[path].header = header; } return sessionKeyPtr > 0; @@ -140,13 +143,13 @@ function createDownloadSessionFile(container, path, header, url, size) { // Decrypt a single chunk of a download -function decryptChunk(container, path, enChunk) { +function decryptChunk(id, path, enChunk) { let chunk = Module.ccall( "decrypt_chunk", "number", ["number", "array", "number"], [ - downloads[container].files[path].key, + downloads[id].files[path].key, enChunk, enChunk.length, ], @@ -183,12 +186,12 @@ function startProgressInterval() { class FileSlicer { constructor( output, - container, + id, path, ) { this.reader = undefined; this.output = output; - this.container = container; + this.id = id, this.path = path; this.chunk = undefined; this.done = false; @@ -208,9 +211,9 @@ class FileSlicer { let resp; // Don't separate smaller downloads (< 250 MiB) into ranges - if (downloads[this.container].files[this.path].realsize < DOWNLOAD_MAX_NONSEGMENTED_SIZE) { + if (downloads[this.id].files[this.path].realsize < DOWNLOAD_MAX_NONSEGMENTED_SIZE) { resp = await fetch( - downloads[this.container].files[this.path].url, + downloads[this.id].files[this.path].url, ).catch(() => {}); this.segmentOffset += DOWNLOAD_MAX_NONSEGMENTED_SIZE; this.reader = resp.body.getReader(); @@ -220,7 +223,7 @@ class FileSlicer { let end = this.segmentOffset + DOWNLOAD_SEGMENT_SIZE - 1; let range = `bytes=${this.segmentOffset}-${end}`; resp = await fetch( - downloads[this.container].files[this.path].url, + downloads[this.id].files[this.path].url, { headers: { "Range": range, @@ -257,7 +260,7 @@ class FileSlicer { } else { this.offset = 0; ({ value: this.chunk, done: this.done } = await this.reader.read()); - if (this.done && this.segmentOffset < downloads[this.container].files[this.path].realsize) { + if (this.done && this.segmentOffset < downloads[this.id].files[this.path].realsize) { await this.getNextSegment(); ({ value: this.chunk, done: this.done } = await this.reader.read()); } @@ -272,7 +275,7 @@ class FileSlicer { } async padFile() { - if (this.totalBytes % 512 > 0 && downloads[this.container].archive) { + if (this.totalBytes % 512 > 0 && downloads[this.id].archive) { let padding = "\x00".repeat(512 - this.totalBytes % 512); if (this.output instanceof WritableStream) { await this.output.write(enc.encode(padding)); @@ -286,9 +289,9 @@ class FileSlicer { // If the file can't be decrypted, add the header and concat the encrypted // file to the stream if (this.output instanceof WritableStream) { - await this.output.write(downloads[this.container].files[this.path].header); + await this.output.write(downloads[this.id].files[this.path].header); } else { - this.output.enqueue(downloads[this.container].files[this.path].header); + this.output.enqueue(downloads[this.id].files[this.path].header); } await this.getStart(); @@ -306,7 +309,7 @@ class FileSlicer { this.totalBytes += this.chunk.length; ({ value: this.chunk, done: this.done } = await this.reader.read()); - if (this.done && this.segmentOffset < downloads[this.container].files[this.path].realsize) { + if (this.done && this.segmentOffset < downloads[this.id].files[this.path].realsize) { await this.getNextSegment(); ({ value: this.chunk, done: this.done } = await this.reader.read()); } @@ -332,7 +335,7 @@ class FileSlicer { // downloading to File System if (this.bytes > 0) { await this.output.write(decryptChunk( - this.container, + this.id, this.path, this.enChunkBuf.subarray(0, this.bytes), )); @@ -345,7 +348,7 @@ class FileSlicer { } if (this.bytes > 0) { this.output.enqueue(new Uint8Array(decryptChunk( - this.container, + this.id, this.path, this.enChunkBuf.subarray(0, this.bytes), ))); @@ -361,7 +364,7 @@ class FileSlicer { "free_crypt4gh_session_key", undefined, ["number"], - [downloads[this.container].files[this.path].key], + [downloads[this.id].files[this.path].key], ); return true; } @@ -398,33 +401,33 @@ async function abortDownloads(direct, abortReason) { }); } clear(); - - for (let container in downloads) { - finishDownloadSession(container); + for (let id in downloads) { + finishDownloadSession(id); } } // Safely free and remove a download session -function finishDownloadSession(container) { +function finishDownloadSession(id) { Module.ccall( "free_keypair", undefined, ["number"], - [downloads[container].keypair], + [downloads[id].keypair], ); - delete downloads[container]; + delete downloads[id]; } async function addSessionFiles( + id, container, headers, ) { let undecryptable = false; for (const file in headers) { - if (!createDownloadSessionFile(container, file, headers[file].header, headers[file].url, headers[file].size)) { + if (!createDownloadSessionFile(id, container, file, headers[file].header, headers[file].url, headers[file].size)) { undecryptable = true; } } @@ -434,20 +437,20 @@ async function addSessionFiles( async function beginDownloadInSession( - container, + id, ) { - let fileHandle = downloads[container].handle; + let fileHandle = downloads[id].handle; let fileStream; - if (downloads[container].direct) { + if (downloads[id].direct) { fileStream = await fileHandle.createWritable(); } else { fileStream = fileHandle; } // Add the archive folder structure - if (downloads[container].archive) { - let folderPaths = Object.keys(downloads[container].files) + if (downloads[id].archive) { + let folderPaths = Object.keys(downloads[id].files) .map(path => path.split("/")) // split paths to items .map(path => path.slice(0, -1)) // remove the file names from paths .filter(path => path.length > 0) // remove empty paths (root level files) @@ -461,7 +464,7 @@ async function beginDownloadInSession( }, []); for (const path of folderPaths) { - if (downloads[container].direct) { + if (downloads[id].direct) { await fileStream.write( addTarFolder(path), ); @@ -471,38 +474,37 @@ async function beginDownloadInSession( } } - if (downloads[container].direct) { + if (downloads[id].direct) { //get total download size and periodically report download progress - for (const file in downloads[container].files) { - totalToDo += downloads[container].files[file].size; + for (const file in downloads[id].files) { + totalToDo += downloads[id].files[file].size; } if (!downProgressInterval) { downProgressInterval = startProgressInterval(); } } - for (const file in downloads[container].files) { + for (const file in downloads[id].files) { if (inServiceWorker) { self.clients.matchAll().then(clients => { clients.forEach(client => client.postMessage({ eventType: "downloadProgressing", - container: container, })); }); } let path = file.replace(".c4gh", ""); - if (downloads[container].archive) { - const size = downloads[container].files[file].size; + if (downloads[id].archive) { + const size = downloads[id].files[file].size; let fileHeader = addTarFile( - downloads[container].files[file].key != 0 ? path : file, + downloads[id].files[file].key != 0 ? path : file, size, ); - if (downloads[container].direct) { + if (downloads[id].direct) { await fileStream.write(fileHeader); } else { fileStream.enqueue(fileHeader); @@ -511,11 +513,11 @@ async function beginDownloadInSession( const slicer = new FileSlicer( fileStream, - container, + id, file); let res; - if (downloads[container].files[file].key <= 0) { + if (downloads[id].files[file].key <= 0) { res = await slicer.concatFile().catch(() => { return false; }); @@ -527,8 +529,8 @@ async function beginDownloadInSession( if (!res) { await slicer.abortStream().then(async() => { //remove temporary files - if (downloads[container].direct) { - await downloads[container].handle.remove(); + if (downloads[id].direct) { + await downloads[id].handle.remove(); } }); if (!aborted) { @@ -538,9 +540,9 @@ async function beginDownloadInSession( } } - if (downloads[container].archive) { + if (downloads[id].archive) { // Write the end of the archive - if (downloads[container].direct) { + if (downloads[id].direct) { await fileStream.write(enc.encode("\x00".repeat(1024))); } else { fileStream.enqueue(enc.encode("\x00".repeat(1024))); @@ -549,33 +551,31 @@ async function beginDownloadInSession( // Sync the file if downloading directly into file, otherwise finish // the fetch request. - if (downloads[container].direct) { + if (downloads[id].direct) { await fileStream.close(); - // downloads[container].handle.flush(); - // downloads[container].handle.close(); + } else { fileStream.close(); } - if (downloads[container].direct) { + if (downloads[id].direct) { // Direct downloads need no further action, the resulting archive is // already in the filesystem. postMessage({ eventType: "finished", - direct: true, - container: container, + container: downloads[id].container, }); } else { // Inform download with service worker finished self.clients.matchAll().then(clients => { clients.forEach(client => client.postMessage({ - eventType: "downloadProgressFinished", - container: container, + eventType: "finished", + container: downloads[id].container, })); }); } - finishDownloadSession(container); + finishDownloadSession(id); return; } @@ -586,13 +586,18 @@ if (inServiceWorker) { let fileName; let containerName; + let sessionId; if (fileUrl.test(url.pathname)) { fileName = url.pathname.replace(fileUrlStart, ""); - containerName = url.pathname.replace("/file/", "").replace(fileName, "").replace("/", ""); + [sessionId, containerName] = url.pathname + .replace("/file/", "").replace("/" + fileName, "").split("/"); } else if (archiveUrl.test(url.pathname)) { - fileName = url.pathname.replace("/archive/", ""); - containerName = fileName.replace(/\.tar$/, ""); + fileName = url.pathname.replace(archiveUrlStart, ""); + [sessionId, containerName] = url.pathname + .replace("/archive/", "") + .replace(/\.tar$/, "") + .split("/"); } else { return; } @@ -617,12 +622,12 @@ if (inServiceWorker) { ); // Map the streamController as the stream for the download - downloads[containerName].handle = streamController; + downloads[sessionId].handle = streamController; - // Start the decrypt slicer and respond, tell worker to stay open until - // stream is consumed + // Start the decrypt slicer and respond, tell worker to stay open + // until stream is consumed e.respondWith((() => { - e.waitUntil(beginDownloadInSession(containerName)); + e.waitUntil(beginDownloadInSession(sessionId)); return response; })()); } @@ -640,27 +645,30 @@ self.addEventListener("message", async (e) => { await timeout(250); } if (libinitDone) { - createDownloadSession(e.data.container, undefined, false); + createDownloadSession(e.data.id, e.data.container, undefined, false); e.source.postMessage({ eventType: "getHeaders", + id: e.data.id, container: e.data.container, files: [ e.data.file, ], - pubkey: downloads[e.data.container].pubkey, + pubkey: downloads[e.data.id].pubkey, owner: e.data.owner, ownerName: e.data.ownerName, }); } } else { - createDownloadSession(e.data.container, e.data.handle, false); + createDownloadSession( + e.data.id, e.data.container, e.data.handle, false); postMessage({ eventType: "getHeaders", + id: e.data.id, container: e.data.container, files: [ e.data.file, ], - pubkey: downloads[e.data.container].pubkey, + pubkey: downloads[e.data.id].pubkey, owner: e.data.owner, ownerName: e.data.ownerName, }); @@ -672,23 +680,25 @@ self.addEventListener("message", async (e) => { await timeout(250); } if (libinitDone) { - createDownloadSession(e.data.container, undefined, true); + createDownloadSession(e.data.id, e.data.container, undefined, true); e.source.postMessage({ eventType: "getHeaders", + id: e.data.id, container: e.data.container, files: e.data.files, - pubkey: downloads[e.data.container].pubkey, + pubkey: downloads[e.data.id].pubkey, owner: e.data.owner, ownerName: e.data.ownerName, }); } } else { - createDownloadSession(e.data.container, e.data.handle, true); + createDownloadSession(e.data.id, e.data.container, e.data.handle, true); postMessage({ eventType: "getHeaders", + id: e.data.id, container: e.data.container, files: e.data.files, - pubkey: downloads[e.data.container].pubkey, + pubkey: downloads[e.data.id].pubkey, owner: e.data.owner, ownerName: e.data.ownerName, }); @@ -696,7 +706,7 @@ self.addEventListener("message", async (e) => { break; case "addHeaders": if (aborted) return; - addSessionFiles(e.data.container, e.data.headers).then(ret => { + addSessionFiles(e.data.id, e.data.container, e.data.headers).then(ret => { if (ret && inServiceWorker) { e.source.postMessage({ eventType: "notDecryptable", @@ -712,13 +722,14 @@ self.addEventListener("message", async (e) => { if (inServiceWorker) { e.source.postMessage({ eventType: "downloadStarted", + id: e.data.id, container: e.data.container, - archive: downloads[e.data.container].archive, - path: downloads[e.data.container].archive ? undefined + archive: downloads[e.data.id].archive, + path: downloads[e.data.id].archive ? undefined : Object.keys(e.data.headers)[0], }); } else { - beginDownloadInSession(e.data.container); + beginDownloadInSession(e.data.id); postMessage({ eventType: "downloadStarted", container: e.data.container, From d7acc2ab3d6bfcf54ba5bc9f702b03cc2d9904d3 Mon Sep 17 00:00:00 2001 From: Monika Radaviciute Date: Wed, 10 Apr 2024 14:24:06 +0300 Subject: [PATCH 2/3] Fix download progress resetting to 0 when starting another direct download --- swift_browser_ui_frontend/src/common/socket.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/swift_browser_ui_frontend/src/common/socket.js b/swift_browser_ui_frontend/src/common/socket.js index ded4863a8..7e0594575 100644 --- a/swift_browser_ui_frontend/src/common/socket.js +++ b/swift_browser_ui_frontend/src/common/socket.js @@ -113,7 +113,9 @@ export default class UploadSocket { this.$store.commit("removeDownload"); this.$store.commit("toggleDownloadNotification", false); } else { - this.$store.commit("updateDownloadProgress", 0); + if (this.$store.state.downloadProgress === undefined) { + this.$store.commit("updateDownloadProgress", 0); + } } if (DEV) { console.log( From 89161b9d5ae5902b23b77b31aa6c9f4886c2f69c Mon Sep 17 00:00:00 2001 From: Monika Radaviciute Date: Mon, 15 Apr 2024 16:31:05 +0300 Subject: [PATCH 3/3] Refactor download abort to fix download temp files staying when cancelling before download begins --- .../src/common/socket.js | 4 +- .../wasm/js/crypt-post-downworker.js | 51 +++++++++---------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/swift_browser_ui_frontend/src/common/socket.js b/swift_browser_ui_frontend/src/common/socket.js index 7e0594575..0859481cf 100644 --- a/swift_browser_ui_frontend/src/common/socket.js +++ b/swift_browser_ui_frontend/src/common/socket.js @@ -122,6 +122,8 @@ export default class UploadSocket { `Got headers for download in container ${e.data.container}`, ); } + }).catch(() => { + this.downWorker.postMessage({ command: "abort", reason: "error" }); }); break; case "downloadStarted": @@ -360,7 +362,7 @@ export default class UploadSocket { } cancelDownload() { - this.downWorker.postMessage({ command: "cancel" }); + this.downWorker.postMessage({ command: "abort", reason: "cancel" }); if (DEV) console.log("Cancel direct downloads"); } diff --git a/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js b/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js index 3eacad7f7..a468c0f4f 100644 --- a/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js +++ b/swift_browser_ui_frontend/wasm/js/crypt-post-downworker.js @@ -25,7 +25,6 @@ let downProgressInterval = undefined; let totalDone = 0; let totalToDo = 0; let aborted = false; -let cancelled = false; // Use a 50 MiB segment when downloading const DOWNLOAD_SEGMENT_SIZE = 1024 * 1024 * 50; @@ -69,7 +68,6 @@ if (inServiceWorker) { // Create a download session function createDownloadSession(id, container, handle, archive) { aborted = false; //reset - cancelled = false; let keypairPtr = Module.ccall( "create_keypair", @@ -297,7 +295,7 @@ class FileSlicer { await this.getStart(); while (!this.done) { - if (cancelled) return; + if (aborted) return; if (this.output instanceof WritableStream) { await this.output.write(this.chunk); } else { @@ -318,7 +316,7 @@ class FileSlicer { // Round up to a multiple of 512, because tar await this.padFile(); - return; + return true; } async sliceFile() { @@ -327,7 +325,7 @@ class FileSlicer { // Slice the file and write decrypted content to output while (!this.done) { - if (cancelled) return; + if (aborted) return; await this.getSlice(); if (this.output instanceof WritableStream) { @@ -368,13 +366,6 @@ class FileSlicer { ); return true; } - - async abortStream() { - if (this.output instanceof WritableStream) { - //stop writing to stream - await this.output.abort(); - } - } } function clear() { @@ -386,7 +377,7 @@ function clear() { totalToDo = 0; } -async function abortDownloads(direct, abortReason) { +function startAbort(direct, abortReason) { aborted = true; const msg = { eventType: "abort", @@ -401,11 +392,16 @@ async function abortDownloads(direct, abortReason) { }); } clear(); - for (let id in downloads) { - finishDownloadSession(id); - } } +async function abortDownload(id, stream = null) { + if (downloads[id].direct) { + //remove temp files + if (stream) await stream.abort(); + await downloads[id].handle.remove(); + } + finishDownloadSession(id); +} // Safely free and remove a download session function finishDownloadSession(id) { @@ -485,6 +481,10 @@ async function beginDownloadInSession( } for (const file in downloads[id].files) { + if (aborted) { + await abortDownload(id, fileStream); + return; + } if (inServiceWorker) { self.clients.matchAll().then(clients => { clients.forEach(client => @@ -527,15 +527,8 @@ async function beginDownloadInSession( }); } if (!res) { - await slicer.abortStream().then(async() => { - //remove temporary files - if (downloads[id].direct) { - await downloads[id].handle.remove(); - } - }); - if (!aborted) { - await abortDownloads(!inServiceWorker, cancelled ? "cancel" : "error"); - } + if (!aborted) startAbort(!inServiceWorker, "error"); + await abortDownload(id, fileStream); return; } } @@ -705,7 +698,6 @@ self.addEventListener("message", async (e) => { } break; case "addHeaders": - if (aborted) return; addSessionFiles(e.data.id, e.data.container, e.data.headers).then(ret => { if (ret && inServiceWorker) { e.source.postMessage({ @@ -718,6 +710,9 @@ self.addEventListener("message", async (e) => { container: e.data.container, }); } + }).catch(async () => { + if (!aborted) startAbort(!inServiceWorker, "error"); + await abortDownload(e.data.id); }); if (inServiceWorker) { e.source.postMessage({ @@ -741,8 +736,8 @@ self.addEventListener("message", async (e) => { case "clear": clear(); break; - case "cancel": - cancelled = true; + case "abort": + if (!aborted) startAbort(!inServiceWorker, e.data.reason); break; } });