shinroute / src /lib /oauth /utils /server.ts
shinmentakezo07
Add OmniRoute codebase without binary assets
9e4583c
Raw
History Blame Contribute Delete
3.71 kB
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">&#10003;</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, "127.0.0.1", () => {
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;
});
}