From a73525f4256460dcd84d5ec29aa5da116192fe43 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Mon, 7 Sep 2026 22:43:30 -0700 Subject: [PATCH] Build the shared file from its bytes, not from a Blob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI caught this on Node 22 while it passed here on 26. `new File([blob], …)` only puts the blob's contents in the file where that implementation recognises a Blob as a part; where it does not, it stringifies it, and the file contains the thirteen characters "[object Blob]". No error anywhere -- the name, the type and the attachment are all correct and the contents are gone. A browser would not have done this. It is worth not relying on that: an ArrayBuffer is a part on every implementation, and the whole file is in memory a moment later regardless, since it is about to be uploaded. --- web/src/lib/shareTarget.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/web/src/lib/shareTarget.ts b/web/src/lib/shareTarget.ts index 4b69b39..a43f77a 100644 --- a/web/src/lib/shareTarget.ts +++ b/web/src/lib/shareTarget.ts @@ -70,7 +70,19 @@ export async function collectShare(): Promise { const res = await cache.match(f.key); await cache.delete(f.key); if (!res) continue; - files.push(new File([await res.blob()], f.name, { type: f.type })); + /* + * The bytes, rather than the Blob holding them. + * + * `new File([blob], …)` is correct and works in a browser, but a Blob + * only counts as a part where the File constructor recognises it as one + * -- and where it does not, it is stringified instead, producing a file + * containing the thirteen characters "[object Blob]" and no error + * anywhere. That is exactly what CI caught on Node 22 while it passed + * here on 26. An ArrayBuffer is a part on any implementation, and this + * has the whole file in memory a moment later regardless: it is about to + * be uploaded as an attachment. + */ + files.push(new File([await res.arrayBuffer()], f.name, { type: f.type })); } if (typeof meta.at === "number" && Date.now() - meta.at > SHARE_MAX_AGE_MS) return null;