import "server-only"; type UrlAccessOptions = { timeoutMs?: number; }; const DEFAULT_TIMEOUT_MS = 3500; function withTimeout(timeoutMs: number) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); return { signal: controller.signal, clear: () => clearTimeout(timer), }; } export async function isRemoteUrlReachable( url: string, options: UrlAccessOptions = {} ): Promise { const target = String(url || "").trim(); if (!/^https?:\/\//i.test(target)) return false; const timeoutMs = Math.max(800, Number(options.timeoutMs || DEFAULT_TIMEOUT_MS)); const headCtx = withTimeout(timeoutMs); try { const headResponse = await fetch(target, { method: "HEAD", redirect: "follow", cache: "no-store", signal: headCtx.signal, }); if (headResponse.ok) return true; if (![403, 405, 429, 500, 501, 502, 503, 504].includes(headResponse.status)) { return false; } } catch {} finally { headCtx.clear(); } const getCtx = withTimeout(timeoutMs); try { const getResponse = await fetch(target, { method: "GET", headers: { Range: "bytes=0-0" }, redirect: "follow", cache: "no-store", signal: getCtx.signal, }); return getResponse.ok || getResponse.status === 206; } catch { return false; } finally { getCtx.clear(); } } export async function filterReachableRemoteUrls( urls: string[], options: UrlAccessOptions = {} ): Promise<{ kept: string[]; dropped: string[] }> { const unique = Array.from( new Set( (Array.isArray(urls) ? urls : []) .map((item) => String(item || "").trim()) .filter((item) => /^https?:\/\//i.test(item)) ) ); if (unique.length === 0) { return { kept: [], dropped: [] }; } const checks = await Promise.all( unique.map(async (url) => ({ url, ok: await isRemoteUrlReachable(url, options), })) ); return { kept: checks.filter((item) => item.ok).map((item) => item.url), dropped: checks.filter((item) => !item.ok).map((item) => item.url), }; }