import type { ApiErrorCode, DnsJsonResponse, TxtRecord } from './types'; export class DnsLookupError extends Error { constructor(message: string) { super(message); this.name = 'DnsLookupError'; } } function decodeTxtData(value: string): string { const parts = value.match(/"((?:\\"|[^"])*)"/g); if (parts === null) { return value; } return parts.map((part) => part.slice(1, -1).replaceAll('\\"', '"')).join(''); } export async function lookupTxtRecords(endpoint: string, name: string): Promise { const url = new URL(endpoint); url.searchParams.set('name', name); url.searchParams.set('type', 'TXT'); const response = await fetch(url, { headers: { accept: 'application/dns-json' } }); if (!response.ok) { throw new DnsLookupError(`DNS lookup failed with ${response.status} ${response.statusText}`); } const body = (await response.json()) as DnsJsonResponse; if (body.Status !== 0) { return []; } return (body.Answer ?? []) .filter((answer) => answer.type === 16) .map((answer) => { const record: TxtRecord = { name: answer.name, data: decodeTxtData(answer.data) }; if (answer.TTL !== undefined) { record.ttl = answer.TTL; } return record; }); } export function findLexiconDid(records: TxtRecord[]): string | null { for (const record of records) { const value = record.data.trim(); if (!value.startsWith('did=')) { continue; } const did = value.slice('did='.length); if (/^did:[a-z0-9]+:[A-Za-z0-9._:%-]+$/.test(did)) { return did; } } return null; } export function jsonResponse(body: unknown, init: ResponseInit = {}): Response { const headers = new Headers(init.headers); headers.set('content-type', 'application/json; charset=utf-8'); return Response.json(body, { ...init, headers }); } export function htmlResponse(body: string, init: ResponseInit = {}): Response { const headers = new Headers(init.headers); headers.set('content-type', 'text/html; charset=utf-8'); return new Response(body, { ...init, headers }); } export function apiError(status: number, code: ApiErrorCode, message: string): Response { return jsonResponse({ error: { code, message } }, { status }); }