import { useState } from 'react'; import { useApiRequest } from './useApi'; import { getApiBaseUrl } from '../../config/config'; // Connection interfaces - exactly matching backend UserConnection model export interface Connection { id: string; userId: string; authority: 'local' | 'google' | 'msft'; externalId: string; externalUsername: string; externalEmail?: string; status: 'active' | 'expired' | 'revoked' | 'pending'; connectedAt: number; // Backend uses float for UTC timestamp in seconds lastChecked: number; // Backend uses float for UTC timestamp in seconds expiresAt?: number; // Backend uses Optional[float] for UTC timestamp in seconds } export interface CreateConnectionData { id?: string; userId?: string; authority?: 'msft' | 'google'; // Keep for compatibility with existing code type?: 'msft' | 'google'; // Backend expects this field externalId?: string; externalUsername?: string; externalEmail?: string; status?: 'active' | 'expired' | 'revoked' | 'pending'; connectedAt?: number; // Backend uses float for UTC timestamp in seconds lastChecked?: number; // Backend uses float for UTC timestamp in seconds expiresAt?: number; // Backend uses Optional[float] for UTC timestamp in seconds } export interface ConnectResponse { authUrl: string; } // Hook for managing connections export function useConnections() { const [connections, setConnections] = useState([]); const { request, isLoading, error } = useApiRequest(); // Fetch all connections const fetchConnections = async (): Promise => { try { const data = await request({ url: '/api/connections/', method: 'get' }); setConnections(data); return data; } catch (error) { console.error('Error fetching connections:', error); setConnections([]); throw error; } }; // Create a new connection const createConnection = async (connectionData: CreateConnectionData): Promise => { try { const data = await request({ url: '/api/connections/', method: 'post', data: connectionData }); // Update local state setConnections(prev => { const existing = prev.find(conn => conn.id === data.id); if (existing) { return prev.map(conn => conn.id === data.id ? data : conn); } else { return [...prev, data]; } }); return data; } catch (error) { console.error('Error creating connection:', error); throw error; } }; // Connect to a service (initiate OAuth) const connectService = async (connectionId: string): Promise => { try { const data = await request({ url: `/api/connections/${connectionId}/connect`, method: 'post' }); return data; } catch (error) { console.error('Error connecting service:', error); throw error; } }; // Disconnect from a service const disconnectService = async (connectionId: string): Promise<{ message: string }> => { try { const data = await request({ url: `/api/connections/${connectionId}/disconnect`, method: 'post' }); // Update local state setConnections(prev => prev.map(conn => conn.id === connectionId ? { ...conn, status: 'inactive' as any, lastChecked: Math.floor(Date.now() / 1000) } : conn ) ); return data; } catch (error) { console.error('Error disconnecting service:', error); throw error; } }; // Delete a connection const deleteConnection = async (connectionId: string): Promise<{ message: string }> => { try { const data = await request({ url: `/api/connections/${connectionId}`, method: 'delete' }); // Update local state setConnections(prev => prev.filter(conn => conn.id !== connectionId)); return data; } catch (error) { console.error('Error deleting connection:', error); throw error; } }; // Update a connection const updateConnection = async (connectionId: string, updateData: Partial): Promise => { try { // Use PUT endpoint for updating connections const data = await request({ url: `/api/connections/${connectionId}`, method: 'put', data: updateData }); // Update local state setConnections(prev => prev.map(conn => conn.id === connectionId ? { ...conn, ...data } : conn) ); return data; } catch (error) { console.error('Error updating connection:', error); throw error; } }; return { connections, fetchConnections, createConnection, updateConnection, connectService, disconnectService, deleteConnection, isLoading, error }; } // Hook for OAuth connection popup flow (similar to useMsalAuth) export function useOAuthConnect() { const { connectService, fetchConnections } = useConnections(); const [isConnecting, setIsConnecting] = useState(false); const [connectError, setConnectError] = useState(null); const connectWithPopup = async (connectionId: string): Promise => { setIsConnecting(true); setConnectError(null); try { // Get the OAuth URL from backend const response = await connectService(connectionId); if (!response.authUrl) { throw new Error('No OAuth URL received from backend'); } console.log('OAuth URL from backend:', response.authUrl); return new Promise((resolve, reject) => { // Convert relative URL to absolute URL if needed let authUrl = response.authUrl; if (authUrl.startsWith('/')) { authUrl = `${getApiBaseUrl()}${authUrl}`; } // Open popup using the same pattern as useAuthentication.ts const popup = window.open( authUrl, 'oauth-connect', 'width=500,height=600,scrollbars=yes,resizable=yes' ); if (!popup) { setConnectError('Popup was blocked. Please allow popups and try again.'); setIsConnecting(false); reject(new Error('Popup was blocked')); return; } // Handle popup closing without completing auth const checkClosed = setInterval(() => { if (popup.closed) { clearInterval(checkClosed); window.removeEventListener('message', messageListener); setIsConnecting(false); console.log('OAuth popup closed'); // Refresh connections anyway in case it succeeded fetchConnections(); setConnectError('Authentication was cancelled'); reject(new Error('Authentication was cancelled')); } }, 1000); // Listen for messages from the popup (similar to useMsalAuth) const messageListener = (event: MessageEvent) => { // Verify origin for security const apiUrl = new URL(getApiBaseUrl()); if (event.origin !== apiUrl.origin) { return; } if (event.data.type === 'msft_connection_success' || event.data.type === 'google_connection_success') { // Clean up - IMPORTANT: clear the checkClosed interval first clearInterval(checkClosed); window.removeEventListener('message', messageListener); popup.close(); setIsConnecting(false); console.log('OAuth connection successful'); // Refresh connections fetchConnections(); resolve(); } else if (event.data.type === 'msft_connection_error' || event.data.type === 'google_connection_error') { // Handle error - also clear the checkClosed interval clearInterval(checkClosed); window.removeEventListener('message', messageListener); popup.close(); setIsConnecting(false); setConnectError(event.data.error || 'OAuth connection failed'); reject(new Error(event.data.error || 'OAuth connection failed')); } }; // Add message listener window.addEventListener('message', messageListener); }); } catch (error: any) { setConnectError(error.message || 'OAuth connection failed'); setIsConnecting(false); throw error; } }; return { connectWithPopup, isConnecting, error: connectError }; } // Hook for disconnecting services export function useDisconnect() { const { disconnectService, fetchConnections } = useConnections(); const [isDisconnecting, setIsDisconnecting] = useState(false); const [disconnectError, setDisconnectError] = useState(null); const disconnect = async (connectionId: string): Promise => { setIsDisconnecting(true); setDisconnectError(null); try { await disconnectService(connectionId); console.log('Service disconnected successfully'); // Refresh connections to update the status await fetchConnections(); } catch (error: any) { setDisconnectError(error.message || 'Disconnect failed'); console.error('Error disconnecting service:', error); throw error; } finally { setIsDisconnecting(false); } }; return { disconnect, isDisconnecting, error: disconnectError }; } // Hook for individual connection operations export function useConnection(connectionId?: string) { const [connection, setConnection] = useState(null); const { request, isLoading, error } = useApiRequest(); const fetchConnection = async (id: string = connectionId!): Promise => { if (!id) return null; try { // Since there's no individual connection endpoint, fetch all and filter const data: Connection[] = await request({ url: '/api/connections/', method: 'get' }); const foundConnection = data.find((conn: Connection) => conn.id === id); setConnection(foundConnection || null); return foundConnection || null; } catch (error) { console.error('Error fetching connection:', error); setConnection(null); throw error; } }; return { connection, fetchConnection, isLoading, error }; }