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);
}