interface ChromeDownloadsLike { download( options: { filename: string; saveAs?: boolean; url: string; }, callback?: () => void ): Promise | void; } interface ChromeRuntimeLike { onMessage?: { addListener( listener: ( message: unknown, sender: unknown, sendResponse: (response: unknown) => void ) => boolean | void ): void; }; } interface ChromeLike { downloads?: ChromeDownloadsLike; runtime?: ChromeRuntimeLike; } type DownloadMarketCsvMessage = { csv: string; filename: string; type: "download-market-csv"; }; export function registerBackgroundMessageHandler( chromeLike: ChromeLike = ( globalThis as typeof globalThis & { chrome?: ChromeLike; } ).chrome ?? {} ): void { chromeLike.runtime?.onMessage?.addListener((message, _sender, sendResponse) => { if (!isDownloadMarketCsvMessage(message)) { return; } void triggerCsvDownload(chromeLike, message) .then(() => { sendResponse({ ok: true }); }) .catch((error) => { sendResponse({ error: error instanceof Error ? error.message : String(error), ok: false }); }); return true; }); } async function triggerCsvDownload( chromeLike: ChromeLike, message: DownloadMarketCsvMessage ): Promise { if (!chromeLike.downloads?.download) { throw new Error("chrome.downloads.download is unavailable"); } const csvUrl = `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${message.csv}`)}`; await Promise.resolve( chromeLike.downloads.download({ filename: message.filename, saveAs: false, url: csvUrl }) ); } function isDownloadMarketCsvMessage( message: unknown ): message is DownloadMarketCsvMessage { if (!message || typeof message !== "object") { return false; } const candidate = message as Partial; return ( candidate.type === "download-market-csv" && typeof candidate.csv === "string" && typeof candidate.filename === "string" ); } registerBackgroundMessageHandler();