/* * SPDX-License-Identifier: GPL-3.0 * Vesktop, a desktop app aiming to give you a snappier Discord Experience * Copyright (c) 2023 Vendicated and Vencord contributors */ import { createWriteStream } from "fs"; import { Readable } from "stream"; import { pipeline } from "stream/promises"; import { setTimeout } from "timers/promises"; interface FetchieOptions { retryOnNetworkError?: boolean; } export async function downloadFile(url: string, file: string, options: RequestInit = {}, fetchieOpts?: FetchieOptions) { const res = await fetchie(url, options, fetchieOpts); await pipeline( // @ts-expect-error odd type error Readable.fromWeb(res.body!), createWriteStream(file, { autoClose: true }) ); } const ONE_MINUTE_MS = 1000 * 60; export async function fetchie(url: string, options?: RequestInit, { retryOnNetworkError }: FetchieOptions = {}) { let res: Response | undefined; try { res = await fetch(url, options); } catch (err) { if (retryOnNetworkError) { console.error("Failed to fetch", url + ".", "Gonna retry with backoff."); for (let tries = 0, delayMs = 500; tries < 20; tries++, delayMs = Math.min(2 * delayMs, ONE_MINUTE_MS)) { await setTimeout(delayMs); try { res = await fetch(url, options); break; } catch {} } } if (!res) throw new Error(`Failed to fetch ${url}\n${err}`); } if (res.ok) return res; let msg = `Got non-OK response for ${url}: ${res.status} ${res.statusText}`; const reason = await res.text().catch(() => ""); if (reason) msg += `\n${reason}`; throw new Error(msg); }