UNPKG

gensx

Version:
131 lines 6.41 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Buffer } from "node:buffer"; import { createHash, getRandomValues } from "node:crypto"; import { hostname } from "node:os"; import { Box, Text, useApp, useInput } from "ink"; import Spinner from "ink-spinner"; import { useCallback, useState } from "react"; import { ErrorMessage } from "../components/ErrorMessage.js"; import { LoadingSpinner } from "../components/LoadingSpinner.js"; import { resolveApiBaseUrl, resolveAppBaseUrl, saveAuth, saveState, } from "../utils/config.js"; import { USER_AGENT } from "../utils/user-agent.js"; function generateVerificationCode() { return Buffer.from(getRandomValues(new Uint8Array(32))).toString("base64url"); } function createCodeHash(code) { return createHash("sha256").update(code).digest("base64url"); } async function createLoginRequest(verificationCode) { const apiBaseUrl = await resolveApiBaseUrl(); const url = new URL("/auth/device/request", apiBaseUrl); const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT, }, body: JSON.stringify({ clientId: hostname(), codeChallenge: createCodeHash(verificationCode), codeChallengeMethod: "S256", }), }); if (!response.ok) { throw new Error(`Failed to create login request: ${response.statusText}`); } const body = (await response.json()); if (!body.requestId || !body.expiresAt) { throw new Error("Invalid response from server"); } return body; } async function pollLoginStatus(requestId, verificationCode) { const apiBaseUrl = await resolveApiBaseUrl(); const url = new URL(`/auth/device/request/${requestId}`, apiBaseUrl); url.searchParams.set("code_verifier", verificationCode); const response = await fetch(url, { headers: { "User-Agent": USER_AGENT, }, }); if (!response.ok) { throw new Error(`Failed to check login status: ${response.statusText}`); } const body = (await response.json()); if (body.status === "pending") { return { status: "pending" }; } if (body.status === "expired") { throw new Error("Login expired"); } return body; } export function LoginUI() { const { exit } = useApp(); const [phase, setPhase] = useState("initial"); const [error, setError] = useState(null); const [authUrl, setAuthUrl] = useState(null); const startLogin = useCallback(async () => { try { setPhase("creating"); const verificationCode = generateVerificationCode(); const request = await createLoginRequest(verificationCode); setPhase("opening"); const appBaseUrl = await resolveAppBaseUrl(); const url = new URL(`/auth/device/${request.requestId}`, appBaseUrl); url.searchParams.set("code_verifier", verificationCode); setAuthUrl(url.toString()); // Open the browser await import("open").then((open) => open.default(url.toString())); setPhase("waiting"); // Poll until we get a completed status let status; do { status = await pollLoginStatus(request.requestId, verificationCode); if (status.status === "completed") { await saveAuth({ token: status.token, org: status.orgSlug, }); await saveState({ hasCompletedFirstTimeSetup: true, lastLoginAt: new Date().toISOString(), }); setPhase("done"); setTimeout(() => { exit(); }, 100); return; } // Wait 1 second before polling again await new Promise((resolve) => setTimeout(resolve, 1000)); } while (status.status === "pending"); } catch (err) { setError(err.message); setPhase("error"); setTimeout(() => { exit(); }, 100); } }, [exit]); // Handle input useInput((input, key) => { if (key.escape) { exit(); return; } if (phase === "initial" && (input === "\r" || input === "\n" || key.return)) { void startLogin(); } }); if (error) { return _jsx(ErrorMessage, { message: error }); } return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [phase === "initial" && (_jsx(Text, { children: _jsx(Text, { color: "yellow", children: "Press Enter to open your browser and log in to GenSX Cloud (or press Escape to skip)" }) })), phase === "creating" && (_jsx(LoadingSpinner, { message: "Creating login request..." })), phase === "opening" && authUrl && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Login request created"] }), _jsxs(Text, { children: [_jsx(Spinner, { type: "dots" }), " Opening ", _jsx(Text, { color: "cyan", children: authUrl })] })] })), phase === "waiting" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Login request created"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Browser opened ", _jsx(Text, { color: "cyan", children: authUrl })] }), _jsx(LoadingSpinner, { message: "Waiting for authentication..." })] })), phase === "done" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Login request created"] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Browser opened ", _jsx(Text, { color: "cyan", children: authUrl })] }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "\u2714" }), " ", "Successfully logged into GenSX"] })] }))] })); } export function login() { return { skipped: false }; } //# sourceMappingURL=login.js.map