type SaveFilePicker = (options?: { suggestedName?: string; types?: Array<{ description?: string; accept: Record; }>; }) => Promise<{ createWritable: () => Promise<{ write: (data: Blob) => Promise; close: () => Promise; }>; }>; type NavigatorWithFileShare = Navigator & { canShare?: (data: { files?: File[] }) => boolean; share?: (data: { files?: File[]; title?: string }) => Promise; }; type WindowWithSavePicker = Window & { showSaveFilePicker?: SaveFilePicker; }; export async function downloadFile(url: string, filename: string) { const res = await fetch(url, { credentials: "include" }); if (!res.ok) throw new Error(await res.text()); const blob = await res.blob(); const safeName = filename || "download"; if (window.isSecureContext) { const picker = (window as WindowWithSavePicker).showSaveFilePicker; if (picker) { const handle = await picker({ suggestedName: safeName, types: blob.type ? [ { description: "File", accept: { [blob.type]: [extensionFromName(safeName)] }, }, ] : undefined, }); const writable = await handle.createWritable(); await writable.write(blob); await writable.close(); return; } } const file = new File([blob], safeName, { type: blob.type || "application/octet-stream", }); const nav = navigator as NavigatorWithFileShare; if (nav.canShare?.({ files: [file] }) && nav.share) { await nav.share({ files: [file], title: safeName }); return; } const objectUrl = URL.createObjectURL(blob); triggerDownload(objectUrl, safeName); window.setTimeout(() => URL.revokeObjectURL(objectUrl), 30_000); } function triggerDownload(url: string, filename: string) { const a = document.createElement("a"); a.href = url; a.download = filename; a.style.display = "none"; document.body.append(a); a.click(); a.remove(); } function extensionFromName(filename: string) { const match = /\.[^.]+$/.exec(filename); return match?.[0] || ".bin"; }