/** * Bing Webmaster OAuth React Hook * Manages Bing Webmaster Tools OAuth2 authentication state and operations */ import { useState, useEffect, useCallback } from 'react'; import { bingOAuthAPI, BingOAuthStatus, BingOAuthResponse } from '../api/bingOAuth'; interface UseBingOAuthReturn { // Connection state connected: boolean; sites: Array<{ id: number; access_token: string; scope: string; created_at: string; sites: Array<{ id: string; name: string; url: string; status: string; }>; }>; totalSites: number; // Loading states isLoading: boolean; isConnecting: boolean; // Actions connect: () => Promise; disconnect: (tokenId: number) => Promise; refreshStatus: () => Promise; // Error handling error: string | null; clearError: () => void; } export const useBingOAuth = (): UseBingOAuthReturn => { const [connected, setConnected] = useState(false); const [sites, setSites] = useState>([]); const [totalSites, setTotalSites] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isConnecting, setIsConnecting] = useState(false); const [error, setError] = useState(null); const [lastStatusCheck, setLastStatusCheck] = useState(0); /** * Check Bing Webmaster connection status */ const checkStatus = useCallback(async () => { // Throttle status checks to prevent excessive API calls const now = Date.now(); const THROTTLE_MS = 10000; // 10 seconds - status doesn't change frequently if (now - lastStatusCheck < THROTTLE_MS) { console.log('Bing OAuth: Status check throttled (10s)'); return; } try { setIsLoading(true); setLastStatusCheck(now); console.log('Bing OAuth: Checking status...'); const status: BingOAuthStatus = await bingOAuthAPI.getStatus(); console.log('Bing OAuth: Status response:', status); setConnected(status.connected); setSites(status.sites || []); setTotalSites(status.total_sites); console.log('Bing OAuth status checked:', { connected: status.connected, sitesCount: status.sites?.length || 0, totalSites: status.total_sites }); } catch (error) { console.error('Error checking Bing OAuth status:', error); setConnected(false); setSites([]); setTotalSites(0); setError('Failed to check Bing Webmaster connection status'); } finally { setIsLoading(false); } }, [lastStatusCheck]); /** * Connect to Bing Webmaster Tools */ const connect = useCallback(async () => { try { setIsConnecting(true); setError(null); console.log('Bing OAuth: Initiating connection...'); // Get authorization URL console.log('Bing OAuth: Calling bingOAuthAPI.getAuthUrl()...'); const authData: BingOAuthResponse = await bingOAuthAPI.getAuthUrl(); console.log('Bing OAuth: Got auth URL:', authData.auth_url); // Open OAuth popup window const popup = window.open( authData.auth_url, 'bing-oauth', 'width=600,height=700,scrollbars=yes,resizable=yes' ); if (!popup) { throw new Error('Failed to open Bing OAuth popup. Please allow popups for this site.'); } // Listen for popup completion and messages const messageHandler = (event: MessageEvent) => { console.log('Bing OAuth: Message received from any source:', { origin: event.origin, data: event.data, dataType: event.data?.type, source: event.source === popup ? 'our-popup' : 'other', expectedOrigin: 'https://littery-sonny-unscrutinisingly.ngrok-free.dev', timestamp: new Date().toISOString() }); // Log the full message data for debugging console.log('Bing OAuth: Full message data:', JSON.stringify(event.data, null, 2)); // Check if message is from our expected origin (more reliable than checking source) console.log('Bing OAuth: Checking origin match...', { receivedOrigin: event.origin, expectedOrigin: 'https://littery-sonny-unscrutinisingly.ngrok-free.dev', originMatch: event.origin === 'https://littery-sonny-unscrutinisingly.ngrok-free.dev' }); if (event.origin === 'https://littery-sonny-unscrutinisingly.ngrok-free.dev') { console.log('Bing OAuth: Message from expected origin, processing...'); console.log('Bing OAuth: Message data:', event.data); console.log('Bing OAuth: Message data type:', event.data?.type); if (event.data?.type === 'BING_OAUTH_SUCCESS') { console.log('Bing OAuth: Success message received:', event.data); popup.close(); window.removeEventListener('message', messageHandler); // Refresh status after successful connection setTimeout(() => { checkStatus(); }, 1000); } else if (event.data?.type === 'BING_OAUTH_ERROR') { console.error('Bing OAuth: Error message received:', event.data); popup.close(); window.removeEventListener('message', messageHandler); setError(event.data.error || 'Bing OAuth connection failed'); } else { console.log('Bing OAuth: Unknown message type:', event.data?.type); console.log('Bing OAuth: Full message data:', event.data); } } else { console.log('Bing OAuth: Message from unexpected origin, ignoring:', event.origin); } }; window.addEventListener('message', messageHandler); // Test if popup is working console.log('Bing OAuth: Popup opened, waiting for messages...'); const checkClosed = setInterval(() => { if (popup.closed) { clearInterval(checkClosed); window.removeEventListener('message', messageHandler); console.log('Bing OAuth: Popup closed, refreshing status...'); console.log('Bing OAuth: Popup closed without receiving success/error message'); // Refresh status after OAuth completion setTimeout(() => { checkStatus(); }, 1000); } }, 1000); } catch (error) { console.error('Error connecting to Bing Webmaster:', error); setError(error instanceof Error ? error.message : 'Failed to connect to Bing Webmaster'); } finally { setIsConnecting(false); } }, [checkStatus]); /** * Disconnect a Bing Webmaster site */ const disconnect = useCallback(async (tokenId: number) => { try { console.log('Bing OAuth: Disconnecting site with token ID:', tokenId); await bingOAuthAPI.disconnectSite(tokenId); console.log('Bing OAuth: Site disconnected successfully'); // Refresh status after disconnection await checkStatus(); } catch (error) { console.error('Error disconnecting Bing site:', error); setError(error instanceof Error ? error.message : 'Failed to disconnect Bing Webmaster site'); } }, [checkStatus]); /** * Refresh connection status */ const refreshStatus = useCallback(async () => { await checkStatus(); }, [checkStatus]); /** * Clear error state */ const clearError = useCallback(() => { setError(null); }, []); // Check status on mount useEffect(() => { checkStatus(); }, [checkStatus]); return { connected, sites, totalSites, isLoading, isConnecting, connect, disconnect, refreshStatus, error, clearError }; };