Build the shared file from its bytes, not from a Blob

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.
This commit is contained in:
2026-09-07 22:43:30 -07:00
parent 82470e8db0
commit a73525f425
+13 -1
View File
@@ -70,7 +70,19 @@ export async function collectShare(): Promise<SharedContent | null> {
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;