| 1 | // @effect-diagnostics nodeBuiltinImport:off globalTimers:off - Native loopback helper uses a bounded Node listener with AbortController cleanup. |
| 2 | import * as Schema from "effect/Schema"; |
| 3 | import * as NodeHttp from "node:http"; |
| 4 | import { codexAuthorizationRequest, codexCallbackUrl } from "@t3tools/shared/codexAuthHandoff"; |
| 5 | |
| 6 | export class CodexAuthCallbackError extends Schema.TaggedError<CodexAuthCallbackError>()( |
| 7 | "CodexAuthCallbackError", |
| 8 | { detail: Schema.String }, |
| 9 | ) { |
| 10 | override get message() { |
| 11 | return this.detail; |
| 12 | } |
| 13 | } |
| 14 | |
| 15 | const listeners = new Map<string, AbortController>(); |
| 16 | |
| 17 | export function cancelCodexAuthCallback(authorizationUrl: string) { |
| 18 | const { state } = codexAuthorizationRequest(authorizationUrl); |
| 19 | listeners.get(state)?.abort(); |
| 20 | } |
| 21 | |
| 22 | /** Receive an authorization code locally. Credentials and PKCE stay on the target environment. */ |
| 23 | export async function receiveCodexAuthCallback( |
| 24 | authorizationUrl: string, |
| 25 | openBrowser: (url: string) => Promise<boolean>, |
| 26 | destination?: (callbackUrl: string) => string, |
| 27 | signal?: AbortSignal, |
| 28 | ) { |
| 29 | const request = codexAuthorizationRequest(authorizationUrl); |
| 30 | if (listeners.has(request.state)) |
| 31 | throw new Error("This sign-in is already open on this computer."); |
| 32 | const abort = new AbortController(); |
| 33 | const interrupted = () => abort.abort(); |
| 34 | signal?.addEventListener("abort", interrupted, { once: true }); |
| 35 | listeners.set(request.state, abort); |
| 36 | const callback = Promise.withResolvers<string>(); |
| 37 | // Keep early open/bind failures from leaving an unobserved rejection behind. |
| 38 | void callback.promise.catch(() => undefined); |
| 39 | const server = NodeHttp.createServer((incoming, response) => { |
| 40 | try { |
| 41 | if (incoming.method !== "GET") throw new Error("method"); |
| 42 | const url = codexCallbackUrl( |
| 43 | new URL(incoming.url ?? "/", request.redirectUri).toString(), |
| 44 | request.redirectUri, |
| 45 | request.state, |
| 46 | ).toString(); |
| 47 | const returnUrl = destination?.(url); |
| 48 | response.setHeader("cache-control", "no-store"); |
| 49 | response.setHeader("referrer-policy", "no-referrer"); |
| 50 | response.setHeader("x-content-type-options", "nosniff"); |
| 51 | if (returnUrl) { |
| 52 | response.writeHead(303, { location: returnUrl }).end(); |
| 53 | } else { |
| 54 | response.setHeader( |
| 55 | "content-security-policy", |
| 56 | "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'", |
| 57 | ); |
| 58 | response |
| 59 | .writeHead(200, { "content-type": "text/html; charset=utf-8" }) |
| 60 | .end( |
| 61 | '<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="light dark"><title>T3 Code</title><style>body{font-family:system-ui;display:grid;place-items:center;min-height:90vh;margin:0}main{max-width:360px;padding:32px}h1{font-size:24px}p{line-height:1.6;opacity:.7}</style></head><body><main><h1>Return to T3 Code</h1><p>Your sign-in response has been received. T3 Code is finishing the connection. You can close this tab.</p></main></body></html>', |
| 62 | ); |
| 63 | } |
| 64 | callback.resolve(url); |
| 65 | } catch { |
| 66 | response.writeHead(400).end("This response does not belong to the active sign-in."); |
| 67 | } |
| 68 | }); |
| 69 | const cancelled = () => callback.reject(new Error("Sign-in cancelled on this computer.")); |
| 70 | abort.signal.addEventListener("abort", cancelled, { once: true }); |
| 71 | const timer = setTimeout( |
| 72 | () => callback.reject(new Error("Sign-in expired. Try again.")), |
| 73 | 300_000, |
| 74 | ); |
| 75 | timer.unref(); |
| 76 | try { |
| 77 | if (signal?.aborted) throw new Error("Sign-in cancelled on this computer."); |
| 78 | await new Promise<void>((resolve, reject) => { |
| 79 | server.once("error", () => |
| 80 | reject( |
| 81 | new Error( |
| 82 | "The ChatGPT callback port is in use on this computer. Close the other sign-in and try again, or paste the redirect URL in T3 Code.", |
| 83 | ), |
| 84 | ), |
| 85 | ); |
| 86 | server.listen(Number(new URL(request.redirectUri).port), "127.0.0.1", resolve); |
| 87 | }); |
| 88 | if (abort.signal.aborted) throw new Error("Sign-in cancelled on this computer."); |
| 89 | if (!(await openBrowser(request.authorizationUrl))) |
| 90 | throw new Error("Could not open your sign-in browser."); |
| 91 | return await callback.promise; |
| 92 | } finally { |
| 93 | clearTimeout(timer); |
| 94 | signal?.removeEventListener("abort", interrupted); |
| 95 | abort.signal.removeEventListener("abort", cancelled); |
| 96 | listeners.delete(request.state); |
| 97 | server.closeAllConnections(); |
| 98 | await new Promise<void>((resolve) => server.close(() => resolve())); |
| 99 | } |
| 100 | } |