From f211a13dc2e5dd82eaba49222171d6f29d858eb5 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 30 Sep 2026 13:22:17 +0200 Subject: fix(hub): a downloaded file opens in a tab only under a type that runs nothing Open is offered for PDFs, raster images, audio, video and plain text, typed from the name; HTML, SVG and the rest are not opened in the hub's origin. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_hub/static/file-utils.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/file-utils.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js index 255c162..aa45a25 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js @@ -432,7 +432,7 @@ async function downloadEntry(transfers, transport, gek, entry) { // Saved under a name every platform can write, and the row says so when that // is not the node's (portable-name.js, docs/MESHBAY_DESIGN.md §10). const saveName = portableName(entry.name); - const openRef = { url: null }; + const openRef = { blob: null }; let target = null; // The in-memory fallback's accumulator, held out here so a pause does not // discard what has already been decrypted. @@ -452,8 +452,10 @@ async function downloadEntry(transfers, transport, gek, entry) { if (target === false) return false; // `pausable` travels with the target, because only the target knows. The // in-memory fallback (a null target) is just an array and pauses fine. - return target ? { name: target.name, pausable: !!target.pausable } - : { pausable: true }; + // So does whether it can be opened: the app's own save opens through the + // OS, a page opens only a type that runs nothing (downloads.openInTab). + return target ? { name: target.name, pausable: !!target.pausable, openable: !!target.open } + : { pausable: true, openable: !!downloads.openableType(saveName) }; }, // After the target, never before: a granted slot has to be taken up within @@ -464,7 +466,7 @@ async function downloadEntry(transfers, transport, gek, entry) { kind: 'download', bytes: entry.size, chunks: totalChunks }), open: () => (target && target.open) ? target.open() - : (openRef.url ? window.open(openRef.url, '_blank') : undefined), + : (openRef.blob ? downloads.openInTab(openRef.blob, saveName) : undefined), // Kept across a pause: the chunks collected so far on the in-memory path. // A resumed run fills in from where it stopped rather than starting a @@ -495,7 +497,7 @@ async function downloadEntry(transfers, transport, gek, entry) { lease && lease.tr, from, memoryChunks); const blob = new Blob(chunks); _saveBlob(blob, saveName); - openRef.url = URL.createObjectURL(blob); + openRef.blob = blob; } }, }); @@ -551,7 +553,6 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE // totalBytes decides how this is delivered, but it is not the archive's // size — headers and the central directory come on top — so it is not // announced as a Content-Length that the download would then miss. - const zipOpenRef = { url: null }; let target = null; transfers.start({ @@ -571,7 +572,9 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE }))) { return false; } - return target ? { name: target.name } : true; + // An archive is never opened in a tab (downloads.openInTab); the app's + // own save opens it through the OS. + return target ? { name: target.name, openable: !!target.open } : { openable: false }; }, // **One** lease for the archive, not one per file. Dozens of leases for a @@ -580,8 +583,7 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE makeLease: () => transport.openTransfer({ kind: 'download', bytes: totalBytes, chunks: files.length }), - open: () => (target && target.open) ? target.open() - : (zipOpenRef.url ? window.open(zipOpenRef.url, '_blank') : undefined), + open: () => ((target && target.open) ? target.open() : undefined), run: async ({ signal, onProgress, lease }) => { const writable = target ? target.writable : null; const parts = writable ? null : []; @@ -611,7 +613,6 @@ async function downloadDirectory(transfers, transport, gek, entries, dir, { setE else { const blob = new Blob(parts, { type: 'application/zip' }); _saveBlob(blob, suggested); - zipOpenRef.url = URL.createObjectURL(blob); } } catch (err) { if (writable) await writable.abort().catch(() => {}); -- cgit v1.2.3