loopbackServer
uses node:http
A server on 127.0.0.1 (a port of its own) for the browser to come back to from signing in: its port, and the
query it comes back with (or a timeout, after waitMs). With state, only an answer with that state is taken:
any other request (another program on this machine's) is turned away, and the sign-in goes on waiting.
import { Refused } from "#codebase/tcmts/atproto"; import http from "node:http"; export async function loopbackServer( waitMs = 5 * 60_000, state?: string, ): Promise<{ port: number; answer: Promise<URLSearchParams>; close(): void }> { let done: (q: URLSearchParams) => void = () => undefined; let fail: (e: Error) => void = () => undefined; const answer = new Promise<URLSearchParams>((res, rej) => ((done = res), (fail = rej))); const server = http.createServer((req, res) => { const url = new URL(req.url ?? "/", "http://127.0.0.1"); if (url.pathname !== "/callback") return void res.writeHead(404).end(); if (state !== undefined && url.searchParams.get("state") !== state) return void res .writeHead(400, { "content-type": "text/plain" }) .end("not this sign-in's answer"); const failed = url.searchParams.get("error"); res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); res.end( `<!doctype html><meta charset="utf-8"><title>tcm</title><body style="font: 16px system-ui; margin: 4em auto; max-width: 30em">` + (failed ? `<p>Signing in didn't work: ${failed.replace(/[<&]/g, "")}.</p>` : `<p>Signed in. You can close this tab and go back to tcm.</p>`), ); done(url.searchParams); }); await new Promise<void>((r) => server.listen(0, "127.0.0.1", r)); const timer = setTimeout( () => fail(new Refused("signing in took too long (5 minutes): try again")), waitMs, ); return { port: (server.address() as { port: number }).port, answer, close: () => { clearTimeout(timer); server.close(); server.closeAllConnections(); }, }; }