import { CID } from 'multiformats/cid' import { Logger } from 'besonders-logger' const { WARN, LOG, DEBUG } = Logger.setup(Logger.INFO) // eslint-disable-line unused-imports/no-unused-vars /** * Resolve an IPNS name to a CID using a public IPFS gateway's HTTP HEAD response. * * Mechanism: per the IPFS HTTP Gateway spec, `HEAD /ipns//` returns the * IPNS-resolved root CID in the `X-Ipfs-Roots` response header (space-separated, ordered * from root to leaf). The first entry is the IPNS-resolved CID. * * This works against any gateway that follows the spec and exposes CORS for HEAD * (most public gateways do, e.g. ipfs.zt.ax, ipfs.io, dweb.link). * * The legacy w3name HTTP endpoint (`GET /name/` returning `{value: "/ipfs/"}`) * is also supported here for back-compat with self-hosted w3name-like services — but the * X-Ipfs-Roots path is preferred since it's the standardised gateway mechanism. * * @param ipns - The IPNS name (k51... string) * @param gateways - List of gateway base URLs (e.g. `["https://ipfs.zt.ax"]`) * @returns The resolved CID, or null if no gateway could resolve the name */ export async function resolveIPNSViaGateway(ipns: string, gateways: string[]): Promise { if (!ipns.startsWith('k51')) return null // only IPNS libp2p-key names go through gateway if (!gateways?.length) return null for (const rawGateway of gateways) { const gateway = rawGateway.replace(/\/+$/, '') const url = `${gateway}/ipns/${ipns}/` try { DEBUG(`[resolveIPNSViaGateway] HEAD ${url}`) const response = await fetch(url, { method: 'HEAD' }) if (!response.ok) { DEBUG(`[resolveIPNSViaGateway] ${gateway} returned ${response.status}`) continue } const roots = response.headers.get('x-ipfs-roots') ?? response.headers.get('X-Ipfs-Roots') if (roots) { const first = roots.split(/[\s,]+/)[0]?.trim() if (first) { const cid = CID.parse(first) DEBUG(`[resolveIPNSViaGateway] resolved via ${gateway} x-ipfs-roots:`, cid.toString()) return cid } } // Some gateways omit X-Ipfs-Roots but put the CID in etag (e.g. `.dag-json`) const etag = response.headers.get('etag') if (etag) { const m = etag.match(/^"?([a-z0-9]+)/i) if (m) { const cid = CID.parse(m[1]) DEBUG(`[resolveIPNSViaGateway] resolved via ${gateway} etag:`, cid.toString()) return cid } } WARN(`[resolveIPNSViaGateway] ${gateway} returned 200 but no usable CID header`) } catch (err) { WARN(`[resolveIPNSViaGateway] ${gateway} failed:`, err) } } return null }