fix: improve web media fetch errors

This commit is contained in:
Peter Steinberger
2026-01-09 07:09:15 +00:00
parent c46bab35df
commit 60b282cf1d
2 changed files with 55 additions and 2 deletions

View File

@@ -76,6 +76,26 @@ describe("web media loading", () => {
fetchMock.mockRestore();
});
it("includes URL + status in fetch errors", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({
ok: false,
body: true,
text: async () => "Not Found",
headers: { get: () => null },
status: 404,
statusText: "Not Found",
url: "https://example.com/missing.jpg",
} as Response);
await expect(
loadWebMedia("https://example.com/missing.jpg", 1024 * 1024),
).rejects.toThrow(
/Failed to fetch media from https:\/\/example\.com\/missing\.jpg.*HTTP 404/i,
);
fetchMock.mockRestore();
});
it("uses content-disposition filename when available", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({
ok: true,

View File

@@ -45,6 +45,22 @@ function parseContentDispositionFileName(
return undefined;
}
async function readErrorBodySnippet(
res: Response,
maxChars = 200,
): Promise<string | undefined> {
try {
const text = await res.text();
if (!text) return undefined;
const collapsed = text.replace(/\s+/g, " ").trim();
if (!collapsed) return undefined;
if (collapsed.length <= maxChars) return collapsed;
return `${collapsed.slice(0, maxChars)}`;
} catch {
return undefined;
}
}
async function loadWebMediaInternal(
mediaUrl: string,
options: WebMediaOptions = {},
@@ -85,9 +101,26 @@ async function loadWebMediaInternal(
} catch {
// ignore parse errors; leave undefined
}
const res = await fetch(mediaUrl);
let res: Response;
try {
res = await fetch(mediaUrl);
} catch (err) {
throw new Error(`Failed to fetch media from ${mediaUrl}: ${String(err)}`);
}
if (!res.ok || !res.body) {
throw new Error(`Failed to fetch media: HTTP ${res.status}`);
const statusText = res.statusText ? ` ${res.statusText}` : "";
const redirected =
res.url && res.url !== mediaUrl ? ` (redirected to ${res.url})` : "";
let detail = `HTTP ${res.status}${statusText}`;
if (!res.body) {
detail = `HTTP ${res.status}${statusText}; empty response body`;
} else if (!res.ok) {
const snippet = await readErrorBodySnippet(res);
if (snippet) detail += `; body: ${snippet}`;
}
throw new Error(
`Failed to fetch media from ${mediaUrl}${redirected}: ${detail}`,
);
}
const array = Buffer.from(await res.arrayBuffer());
const headerFileName = parseContentDispositionFileName(