Initial commit
This commit is contained in:
116
src/lib/oauth/utils/server.js
Normal file
116
src/lib/oauth/utils/server.js
Normal file
@@ -0,0 +1,116 @@
|
||||
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, fixedPort = null) {
|
||||
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, "127.0.0.1", () => {
|
||||
const { port } = server.address();
|
||||
resolve({
|
||||
server,
|
||||
port,
|
||||
close: () => server.close(),
|
||||
});
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
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) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
resolve(params);
|
||||
}
|
||||
};
|
||||
|
||||
// Return the callback function
|
||||
resolve.__onCallback = onCallback;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user