Spaces:
Runtime error
Runtime error
| import http from "http"; | |
| import { URL } from "url"; | |
| /** | |
| * Start a local HTTP server to receive OAuth callback | |
| * @param {Function} onCallback - Called with query params when callback received | |
| * @param {number} fixedPort - Optional fixed port number (default: random) | |
| * @returns {Promise<{server: http.Server, port: number, close: Function}>} | |
| */ | |
| export function startLocalServer( | |
| onCallback: (params: Record<string, string>) => void, | |
| fixedPort: number | null = null | |
| ): Promise<{ server: any; port: number; close: () => void }> { | |
| return new Promise((resolve, reject) => { | |
| const server = http.createServer((req, res) => { | |
| const url = new URL(req.url || "/", `http://localhost`); | |
| if (url.pathname === "/callback" || url.pathname === "/auth/callback") { | |
| const params = Object.fromEntries(url.searchParams); | |
| // Send success response to browser with auto-close attempt | |
| res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); | |
| res.end(`<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Authentication Successful</title> | |
| <style> | |
| body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f5f5f5; } | |
| .container { text-align: center; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } | |
| .success { color: #22c55e; font-size: 3rem; } | |
| h1 { margin: 1rem 0; } | |
| p { color: #666; } | |
| #countdown { font-weight: bold; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="success">✓</div> | |
| <h1>Authentication Successful</h1> | |
| <p id="message">Closing in <span id="countdown">3</span> seconds...</p> | |
| </div> | |
| <script> | |
| let count = 3; | |
| const countdown = document.getElementById("countdown"); | |
| const message = document.getElementById("message"); | |
| const timer = setInterval(() => { | |
| count--; | |
| countdown.textContent = count; | |
| if (count <= 0) { | |
| clearInterval(timer); | |
| window.close(); | |
| setTimeout(() => { | |
| message.textContent = "Please close this tab manually."; | |
| }, 500); | |
| } | |
| }, 1000); | |
| </script> | |
| </body> | |
| </html>`); | |
| // Call callback with params | |
| onCallback(params); | |
| } else { | |
| res.writeHead(404); | |
| res.end("Not found"); | |
| } | |
| }); | |
| // Listen on fixed port or find available port | |
| const portToUse = fixedPort || 0; | |
| server.listen(portToUse, "0.0.0.0", () => { | |
| const addr = server.address() as { port: number }; | |
| resolve({ | |
| server, | |
| port: addr.port, | |
| close: () => server.close(), | |
| }); | |
| }); | |
| server.on("error", (err: any) => { | |
| if (err.code === "EADDRINUSE" && fixedPort) { | |
| reject( | |
| new Error( | |
| `Port ${fixedPort} is already in use. Please close other applications using this port.` | |
| ) | |
| ); | |
| } else { | |
| reject(err); | |
| } | |
| }); | |
| }); | |
| } | |
| /** | |
| * Wait for callback with timeout | |
| * @param {number} timeoutMs - Timeout in milliseconds | |
| * @returns {Promise<Object>} - Callback params | |
| */ | |
| export function waitForCallback(timeoutMs = 300000) { | |
| return new Promise((resolve, reject) => { | |
| let resolved = false; | |
| const timeout = setTimeout(() => { | |
| if (!resolved) { | |
| resolved = true; | |
| reject(new Error("Authentication timeout")); | |
| } | |
| }, timeoutMs); | |
| const onCallback = (params: Record<string, string>) => { | |
| if (!resolved) { | |
| resolved = true; | |
| clearTimeout(timeout); | |
| resolve(params); | |
| } | |
| }; | |
| // Return the callback function | |
| (resolve as any).__onCallback = onCallback; | |
| }); | |
| } | |