(
html: string,
options: { timeoutMs?: number } = {},
)
| 101 | * @param timeoutMs - per-request timeout (default 8000ms) |
| 102 | */ |
| 103 | export async function lintMediaUrls( |
| 104 | html: string, |
| 105 | options: { timeoutMs?: number } = {}, |
| 106 | ): Promise<HyperframeLintFinding[]> { |
| 107 | const urls = extractMediaUrls(html); |
| 108 | if (urls.length === 0) return []; |
| 109 | |
| 110 | const timeout = options.timeoutMs ?? 8000; |
| 111 | const findings: HyperframeLintFinding[] = []; |
| 112 | |
| 113 | const seen = new Set<string>(); |
| 114 | const unique = urls.filter((u) => { |
| 115 | if (seen.has(u.url)) return false; |
| 116 | seen.add(u.url); |
| 117 | return true; |
| 118 | }); |
| 119 | |
| 120 | const checks = unique.map(async ({ url, tagName, elementId, snippet }) => { |
| 121 | try { |
| 122 | const controller = new AbortController(); |
| 123 | const timer = setTimeout(() => controller.abort(), timeout); |
| 124 | const resp = await fetch(url, { |
| 125 | method: "HEAD", |
| 126 | signal: controller.signal, |
| 127 | redirect: "follow", |
| 128 | }); |
| 129 | clearTimeout(timer); |
| 130 | if (!resp.ok) { |
| 131 | findings.push({ |
| 132 | code: "inaccessible_media_url", |
| 133 | severity: "error", |
| 134 | message: `<${tagName}${elementId ? ` id="${elementId}"` : ""}> references a URL that returned HTTP ${resp.status}: ${url.slice(0, 100)}`, |
| 135 | elementId, |
| 136 | fixHint: "This URL is not accessible. Replace with a valid, reachable media URL.", |
| 137 | snippet, |
| 138 | }); |
| 139 | } |
| 140 | } catch (err) { |
| 141 | const reason = err instanceof Error ? err.name : "unknown"; |
| 142 | findings.push({ |
| 143 | code: "inaccessible_media_url", |
| 144 | severity: "error", |
| 145 | message: `<${tagName}${elementId ? ` id="${elementId}"` : ""}> references an unreachable URL (${reason}): ${url.slice(0, 100)}`, |
| 146 | elementId, |
| 147 | fixHint: "This URL is not accessible. Replace with a valid, reachable media URL.", |
| 148 | snippet, |
| 149 | }); |
| 150 | } |
| 151 | }); |
| 152 | |
| 153 | await Promise.all(checks); |
| 154 | return findings; |
| 155 | } |
nothing calls this directly
no test coverage detected