diff --git a/src/utils/api.ts b/src/utils/api.ts index 423058c..57c3fbc 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -21,6 +21,33 @@ import { createStore } from "solid-js/store"; import { setPDS } from "../components/navbar"; import { plcDirectory } from "../views/settings"; +const proxyFetch = (rewrite: (url: URL) => string): typeof fetch => { + return async (input, init) => { + try { + return await fetch(input, init); + } catch (err) { + if (init?.signal?.aborted) throw err; + const url = new URL( + typeof input === "string" ? input + : input instanceof URL ? input.href + : input.url, + ); + return fetch(rewrite(url)); + } + }; +}; + +const didWebProxyFetch = proxyFetch( + (url) => `/resolve-did-web?host=${encodeURIComponent(url.host)}`, +); +const dnsProxyFetch = proxyFetch( + (url) => + `/resolve-handle-dns?handle=${encodeURIComponent(url.searchParams.get("name")?.replace("_atproto.", "") ?? "")}`, +); +const handleHttpProxyFetch = proxyFetch( + (url) => `/resolve-handle-http?handle=${encodeURIComponent(url.host)}`, +); + export const didDocumentResolver = createMemo( () => new CompositeDidDocumentResolver({ @@ -28,16 +55,16 @@ export const didDocumentResolver = createMemo( plc: new PlcDidDocumentResolver({ apiUrl: plcDirectory(), }), - web: new AtprotoWebDidDocumentResolver(), + web: new AtprotoWebDidDocumentResolver({ fetch: didWebProxyFetch }), }, }), ); export const handleResolver = new CompositeHandleResolver({ - strategy: "race", + strategy: "dns-first", methods: { - dns: new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }), - http: new WellKnownHandleResolver(), + dns: new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?", fetch: dnsProxyFetch }), + http: new WellKnownHandleResolver({ fetch: handleHttpProxyFetch }), }, }); @@ -223,7 +250,7 @@ export interface HandleResolveResult { export const resolveHandleDetailed = async (handle: Handle) => { const dnsResolver = new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }); - const httpResolver = new WellKnownHandleResolver(); + const httpResolver = new WellKnownHandleResolver({ fetch: handleHttpProxyFetch }); const tryResolve = async ( resolver: DohJsonHandleResolver | WellKnownHandleResolver, diff --git a/src/worker.js b/src/worker.js index b065b4b..4f8ea66 100644 --- a/src/worker.js +++ b/src/worker.js @@ -488,6 +488,45 @@ class HeadEndRewriter { const MAX_FAVICON_SIZE = 100 * 1024; // 100KB +async function corsProxy(url, fetchOpts = {}) { + const res = await fetch(url, { + signal: AbortSignal.timeout(5000), + ...fetchOpts, + }); + + return new Response(res.body, { + status: res.status, + headers: { + "Content-Type": res.headers.get("content-type") ?? "application/json", + "Access-Control-Allow-Origin": "*", + }, + }); +} + +function handleResolveDidWeb(searchParams) { + const host = searchParams.get("host"); + if (!host) return new Response("Missing host param", { status: 400 }); + return corsProxy(`https://${host}/.well-known/did.json`, { + redirect: "manual", + headers: { accept: "application/did+ld+json,application/json" }, + }); +} + +function handleResolveHandleDns(searchParams) { + const handle = searchParams.get("handle"); + if (!handle) return new Response("Missing handle param", { status: 400 }); + const url = new URL("https://dns.google/resolve"); + url.searchParams.set("name", `_atproto.${handle}`); + url.searchParams.set("type", "TXT"); + return corsProxy(url, { headers: { accept: "application/dns-json" } }); +} + +function handleResolveHandleHttp(searchParams) { + const handle = searchParams.get("handle"); + if (!handle) return new Response("Missing handle param", { status: 400 }); + return corsProxy(`https://${handle}/.well-known/atproto-did`, { redirect: "manual" }); +} + async function handleFavicon(searchParams) { const domain = searchParams.get("domain"); if (!domain) { @@ -604,6 +643,18 @@ export default { ); } + const proxyRoutes = { + "/resolve-did-web": handleResolveDidWeb, + "/resolve-handle-dns": handleResolveHandleDns, + "/resolve-handle-http": handleResolveHandleHttp, + }; + + if (url.pathname in proxyRoutes) { + return proxyRoutes[url.pathname](url.searchParams).catch( + (err) => new Response(`Proxy error: ${err?.message ?? err}`, { status: 500 }), + ); + } + const ua = request.headers.get("user-agent") ?? ""; if (!isBot(ua)) {