import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { getErrorMessage } from "@/lib/store/apis/baseApi"; import { useCompleteOAuthFlowMutation, useLazyGetOAuthConfigStatusQuery } from "@/lib/store/apis/mcpApi"; import { Loader2 } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; interface OAuth2AuthorizerProps { open: boolean; onClose: () => void; onSuccess: () => void; onError: (error: string) => void; authorizeUrl: string; oauthConfigId: string; mcpClientId: string; isPerUserOauth?: boolean; } export const OAuth2Authorizer: React.FC = ({ open, onClose, onSuccess, onError, authorizeUrl, oauthConfigId, isPerUserOauth, }) => { const [status, setStatus] = useState<"confirm" | "pending" | "polling" | "success" | "failed">(isPerUserOauth ? "confirm" : "pending"); const [errorMessage, setErrorMessage] = useState(null); const popupRef = useRef(null); const pollIntervalRef = useRef(null); const isCompletingRef = useRef(false); // RTK Query hooks const [getOAuthStatus] = useLazyGetOAuthConfigStatusQuery(); const [completeOAuth] = useCompleteOAuthFlowMutation(); // Stop polling const stopPolling = useCallback(() => { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); pollIntervalRef.current = null; } }, []); // Handle successful OAuth completion const handleOAuthComplete = useCallback(async () => { // Guard against concurrent calls (race between postMessage and polling) if (isCompletingRef.current) return; isCompletingRef.current = true; // Close popup if still open if (popupRef.current && !popupRef.current.closed) { popupRef.current.close(); } // Call complete-oauth endpoint using RTK Query mutation // Use oauthConfigId instead of mcpClientId for multi-instance support try { await completeOAuth(oauthConfigId).unwrap(); setStatus("success"); onSuccess(); setTimeout(() => { onClose(); }, 1000); } catch (error) { const errMsg = getErrorMessage(error); setStatus("failed"); setErrorMessage(errMsg); onError(errMsg); } }, [oauthConfigId, completeOAuth, onSuccess, onClose, onError]); // Handle OAuth failure const handleOAuthFailed = useCallback( (reason: string) => { stopPolling(); if (popupRef.current && !popupRef.current.closed) { popupRef.current.close(); } setStatus("failed"); setErrorMessage(reason); onError(reason); }, [stopPolling, onError], ); // Check OAuth status (called by postMessage or polling) const checkOAuthStatus = useCallback(async () => { try { const result = await getOAuthStatus(oauthConfigId).unwrap(); if (result.status === "authorized") { stopPolling(); await handleOAuthComplete(); } else if (result.status === "failed" || result.status === "expired") { handleOAuthFailed(`Authorization ${result.status}`); } } catch (error) { console.error("Error checking OAuth status:", error); } }, [oauthConfigId, getOAuthStatus, stopPolling, handleOAuthComplete, handleOAuthFailed]); // Poll OAuth status const startPolling = useCallback(() => { // Clear any existing interval if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); } pollIntervalRef.current = setInterval(async () => { // Check if popup is still open if (popupRef.current && popupRef.current.closed) { // Popup closed - check status before assuming cancellation // (OAuth callback page closes the popup after success) try { const result = await getOAuthStatus(oauthConfigId).unwrap(); if (result.status === "authorized") { stopPolling(); await handleOAuthComplete(); } else if (result.status === "failed" || result.status === "expired") { stopPolling(); handleOAuthFailed("Authorization failed"); } // pending or other non-terminal: let polling continue } catch { // transient fetch error: let polling continue } return; } await checkOAuthStatus(); }, 2000); // Poll every 2 seconds }, [checkOAuthStatus, handleOAuthFailed]); // Open popup and start polling const openPopup = useCallback(() => { // Reset completion guard for each fresh OAuth attempt isCompletingRef.current = false; // Close any existing popup if (popupRef.current && !popupRef.current.closed) { popupRef.current.close(); } // Open OAuth popup const width = 600; const height = 700; const left = window.screen.width / 2 - width / 2; const top = window.screen.height / 2 - height / 2; popupRef.current = window.open( authorizeUrl, "oauth_popup", `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`, ); setStatus("polling"); // Start polling OAuth status startPolling(); }, [authorizeUrl, startPolling]); // Listen for postMessage from OAuth callback popup useEffect(() => { const handleMessage = (event: MessageEvent) => { // Verify message is from OAuth callback if (event.data?.type === "oauth_success") { // Trigger immediate status check; stopPolling is called inside // checkOAuthStatus only after a confirmed terminal state, so // transient fetch errors still allow polling to continue. checkOAuthStatus(); } }; window.addEventListener("message", handleMessage); return () => { window.removeEventListener("message", handleMessage); }; }, [checkOAuthStatus]); // Open popup when dialog opens (skip if waiting for user confirmation) useEffect(() => { if (open && status === "pending") { openPopup(); } }, [open, status, openPopup]); // Handle user confirming per-user OAuth test const handleConfirmPerUserOAuth = () => { setStatus("pending"); openPopup(); }; // Cleanup on unmount useEffect(() => { return () => { stopPolling(); if (popupRef.current && !popupRef.current.closed) { popupRef.current.close(); } }; }, [stopPolling]); const handleRetry = () => { setErrorMessage(null); isCompletingRef.current = false; if (isPerUserOauth) { setStatus("confirm"); } else { setStatus("pending"); openPopup(); } }; const handleCancel = () => { stopPolling(); isCompletingRef.current = false; if (popupRef.current && !popupRef.current.closed) { popupRef.current.close(); } onClose(); }; return ( e.preventDefault()} onEscapeKeyDown={(e) => e.preventDefault()}> {status === "confirm" ? "Test OAuth Configuration" : "OAuth Authorization"} {status === "confirm" && "A one-time login is needed to verify your OAuth setup."} {status === "pending" && "Opening authorization window..."} {status === "polling" && "Waiting for authorization..."} {status === "success" && "Authorization successful!"} {status === "failed" && "Authorization failed"}
{status === "confirm" && ( <>

To set up this MCP server, we need to verify that your OAuth configuration is correct and discover the available tools.

You will be asked to log in to the OAuth provider. This is a one-time test to confirm the setup works. Your credentials will not be stored or used for any other purpose.

Once verified, each user will authenticate individually when they use this MCP server.

)} {status === "polling" && ( <>

Please complete authorization in the popup window

)} {status === "success" && ( <>

MCP server connected successfully!

)} {status === "failed" && ( <>

{errorMessage || "An error occurred"}

)}
{status === "polling" && (
)}
); };