File size: 3,826 Bytes
6111b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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, "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;
  });
}