import React, { useState, useEffect } from "react"; import { render, Box, Text, useInput } from "ink"; import Spinner from "ink-spinner"; import { StatusBadge } from "../tui-components/StatusBadge.jsx"; import { ConfirmDialog } from "../tui-components/ConfirmDialog.jsx"; const PHASE = { WAITING: "waiting", POLLING: "polling", DONE: "done", FAILED: "failed", CANCELLED: "cancelled", }; let _globalDone = null; let _globalFail = null; function OAuthFlowApp({ provider, url, deviceCode, onCancel, onDone, onFail }) { const [phase, setPhase] = useState(PHASE.WAITING); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [elapsed, setElapsed] = useState(0); const [confirmCancel, setConfirmCancel] = useState(false); useEffect(() => { const id = setInterval(() => setElapsed((e) => e + 1), 1000); return () => clearInterval(id); }, []); useEffect(() => { _globalDone = (res) => { setResult(res); setPhase(PHASE.DONE); onDone?.(res); }; _globalFail = (err) => { setError(typeof err === "string" ? err : (err?.message ?? String(err))); setPhase(PHASE.FAILED); onFail?.(err); }; setPhase(PHASE.POLLING); return () => { _globalDone = null; _globalFail = null; }; }, []); useInput((input, key) => { if (phase === PHASE.DONE || phase === PHASE.FAILED || phase === PHASE.CANCELLED) return; if (input === "q" || (key.ctrl && input === "c")) { setConfirmCancel(true); } }); function handleCancelConfirm(yes) { setConfirmCancel(false); if (yes) { setPhase(PHASE.CANCELLED); onCancel?.(); } } const elapsed_str = `${Math.floor(elapsed / 60)}:${String(elapsed % 60).padStart(2, "0")}`; if (confirmCancel) { return ( ); } return ( OmniRoute OAuth — {provider} {url && ( Open this URL in your browser to authorize: {url} )} {deviceCode && ( Device code:{" "} {deviceCode} )} {phase === PHASE.POLLING || phase === PHASE.WAITING ? ( Waiting for authorization... ({elapsed_str}) ) : phase === PHASE.DONE ? ( Authorized: {result?.email ?? result?.account ?? "connected"} ) : phase === PHASE.FAILED ? ( Failed: {error} ) : ( Cancelled. )} {(phase === PHASE.POLLING || phase === PHASE.WAITING) && ( [q] cancel )} ); } export async function startOAuthTui({ provider, url, deviceCode }) { return new Promise((resolve, reject) => { let resolved = false; function onDone(result) { if (resolved) return; resolved = true; unmount(); resolve({ status: "authorized", result }); } function onFail(err) { if (resolved) return; resolved = true; unmount(); resolve({ status: "failed", error: err }); } function onCancel() { if (resolved) return; resolved = true; unmount(); resolve({ status: "cancelled" }); } const { unmount, waitUntilExit } = render( ); waitUntilExit() .then(() => { if (!resolved) resolve({ status: "exited" }); }) .catch(reject); }); } export function markOAuthDone(result) { _globalDone?.(result); } export function markOAuthFailed(error) { _globalFail?.(error); }