mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
178 lines
5.1 KiB
TypeScript
178 lines
5.1 KiB
TypeScript
import { useState, useCallback, useRef, useEffect } from "react";
|
|
import { getElectronAPI } from "@/lib/electron";
|
|
|
|
type AuthState = "idle" | "running" | "success" | "error" | "manual";
|
|
|
|
interface UseOAuthAuthenticationOptions {
|
|
cliType: "claude" | "codex";
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export function useOAuthAuthentication({
|
|
cliType,
|
|
enabled = true,
|
|
}: UseOAuthAuthenticationOptions) {
|
|
const [authState, setAuthState] = useState<AuthState>("idle");
|
|
const [output, setOutput] = useState<string[]>([]);
|
|
const [token, setToken] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
|
|
// Reset state when disabled
|
|
useEffect(() => {
|
|
if (!enabled) {
|
|
setAuthState("idle");
|
|
setOutput([]);
|
|
setToken("");
|
|
setError(null);
|
|
|
|
// Cleanup subscription
|
|
if (unsubscribeRef.current) {
|
|
unsubscribeRef.current();
|
|
unsubscribeRef.current = null;
|
|
}
|
|
}
|
|
}, [enabled]);
|
|
|
|
const startAuth = useCallback(async () => {
|
|
const api = getElectronAPI();
|
|
if (!api.setup) {
|
|
setError("Setup API not available");
|
|
setAuthState("error");
|
|
return;
|
|
}
|
|
|
|
setAuthState("running");
|
|
setOutput([
|
|
"Starting authentication...",
|
|
`Running ${cliType} CLI in an embedded terminal so you don't need to copy/paste.`,
|
|
"When your browser opens, complete sign-in and return here.",
|
|
"",
|
|
]);
|
|
setError(null);
|
|
setToken("");
|
|
|
|
// Subscribe to progress events
|
|
if (api.setup.onAuthProgress) {
|
|
unsubscribeRef.current = api.setup.onAuthProgress((progress) => {
|
|
if (progress.cli === cliType && progress.data) {
|
|
// Split by newlines and add each line
|
|
const normalized = progress.data.replace(/\r/g, "\n");
|
|
const lines = normalized
|
|
.split("\n")
|
|
.map((line: string) => line.trimEnd())
|
|
.filter((line: string) => line.length > 0);
|
|
if (lines.length > 0) {
|
|
setOutput((prev) => [...prev, ...lines]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
try {
|
|
// Call the appropriate auth API based on cliType
|
|
const result =
|
|
cliType === "claude"
|
|
? await api.setup.authClaude()
|
|
: await api.setup.authCodex?.();
|
|
|
|
// Cleanup subscription
|
|
if (unsubscribeRef.current) {
|
|
unsubscribeRef.current();
|
|
unsubscribeRef.current = null;
|
|
}
|
|
|
|
if (!result) {
|
|
setError("Authentication API not available");
|
|
setAuthState("error");
|
|
return;
|
|
}
|
|
|
|
// Check for token (only available for Claude)
|
|
const resultToken =
|
|
cliType === "claude" && "token" in result ? result.token : undefined;
|
|
const resultTerminalOpened =
|
|
cliType === "claude" && "terminalOpened" in result
|
|
? result.terminalOpened
|
|
: false;
|
|
|
|
if (result.success && resultToken && typeof resultToken === "string") {
|
|
setToken(resultToken);
|
|
setAuthState("success");
|
|
setOutput((prev) => [
|
|
...prev,
|
|
"",
|
|
"✓ Authentication successful!",
|
|
"✓ Token captured automatically.",
|
|
]);
|
|
} else if (result.requiresManualAuth) {
|
|
// Terminal was opened - user needs to copy token manually
|
|
setAuthState("manual");
|
|
// Don't add extra messages if terminalOpened - the progress messages already explain
|
|
if (!resultTerminalOpened) {
|
|
const extraMessages = [
|
|
"",
|
|
"⚠ Could not capture token automatically.",
|
|
];
|
|
if (result.error) {
|
|
extraMessages.push(result.error);
|
|
}
|
|
setOutput((prev) => [
|
|
...prev,
|
|
...extraMessages,
|
|
"Please copy the token from above and paste it below.",
|
|
]);
|
|
}
|
|
} else {
|
|
setError(result.error || "Authentication failed");
|
|
setAuthState("error");
|
|
}
|
|
} catch (err: unknown) {
|
|
// Cleanup subscription
|
|
if (unsubscribeRef.current) {
|
|
unsubscribeRef.current();
|
|
unsubscribeRef.current = null;
|
|
}
|
|
|
|
const errorMessage =
|
|
err instanceof Error
|
|
? err.message
|
|
: typeof err === "object" && err !== null && "error" in err
|
|
? String((err as { error: unknown }).error)
|
|
: "Authentication failed";
|
|
|
|
// Check if we should fall back to manual mode
|
|
if (
|
|
typeof err === "object" &&
|
|
err !== null &&
|
|
"requiresManualAuth" in err &&
|
|
(err as { requiresManualAuth: boolean }).requiresManualAuth
|
|
) {
|
|
setAuthState("manual");
|
|
setOutput((prev) => [
|
|
...prev,
|
|
"",
|
|
"⚠ " + errorMessage,
|
|
"Please copy the token manually and paste it below.",
|
|
]);
|
|
} else {
|
|
setError(errorMessage);
|
|
setAuthState("error");
|
|
}
|
|
}
|
|
}, [cliType]);
|
|
|
|
const reset = useCallback(() => {
|
|
setAuthState("idle");
|
|
setOutput([]);
|
|
setToken("");
|
|
setError(null);
|
|
if (unsubscribeRef.current) {
|
|
unsubscribeRef.current();
|
|
unsubscribeRef.current = null;
|
|
}
|
|
}, []);
|
|
|
|
return { authState, output, token, error, startAuth, reset };
|
|
}
|