Save local changes (GSC/Bing integrations) before merging PR #354

This commit is contained in:
ajaysi
2026-02-13 13:11:27 +05:30
parent 43e66835ac
commit 08a1f4a1d8
144 changed files with 8310 additions and 2748 deletions

View File

@@ -3,7 +3,7 @@
* Manages Bing Webmaster Tools OAuth2 authentication state and operations
*/
import { useState, useEffect, useCallback } from 'react';
import { useState, useCallback } from 'react';
import { bingOAuthAPI, BingOAuthStatus, BingOAuthResponse } from '../api/bingOAuth';
interface UseBingOAuthReturn {
@@ -95,99 +95,11 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
setError(null);
console.log('Bing OAuth: Initiating connection...');
// Get authorization URL
console.log('Bing OAuth: Calling bingOAuthAPI.getAuthUrl()...');
// 1. Get the auth URL from backend
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.');
}
// Track if we've already handled success/error to avoid duplicate processing
let messageHandled = false;
// 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);
messageHandled = true;
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);
messageHandled = true;
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...');
if (!messageHandled) {
console.log('Bing OAuth: Popup closed without receiving success/error message');
} else {
console.log('Bing OAuth: Popup closed after successful message handling');
}
// Refresh status after OAuth completion
setTimeout(() => {
checkStatus();
}, 1000);
}
}, 1000);
// 2. Redirect the user
window.location.href = authData.auth_url;
} catch (error) {
console.error('Error connecting to Bing Webmaster:', error);
@@ -195,7 +107,7 @@ export const useBingOAuth = (): UseBingOAuthReturn => {
} finally {
setIsConnecting(false);
}
}, [checkStatus]);
}, []);
/**
* Disconnect a Bing Webmaster site

View File

@@ -173,6 +173,8 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
if (status.status === 'completed') {
setResult(status.result);
onComplete?.(status.result);
// Trigger global usage stats refresh
window.dispatchEvent(new Event('alwrity:refresh-usage'));
stopPolling();
} else if (status.status === 'failed') {
setError(status.error || 'Persona generation failed');

View File

@@ -1,30 +1,31 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { blogWriterApi, TaskStatusResponse } from '../services/blogWriterApi';
import { researchEngineApi } from '../services/researchEngineApi';
import { pollVideoTaskStatus } from '../api/videoStudioApi';
import { triggerSubscriptionError } from '../api/client';
export interface UsePollingOptions {
export interface UsePollingOptions<T = any> {
interval?: number; // Polling interval in milliseconds
maxAttempts?: number; // Maximum number of polling attempts
onProgress?: (message: string) => void; // Callback for progress updates
onComplete?: (result: any) => void; // Callback when task completes
onComplete?: (result: T) => void; // Callback when task completes
onError?: (error: string) => void; // Callback when task fails
}
export interface UsePollingReturn {
export interface UsePollingReturn<T = any> {
isPolling: boolean;
currentStatus: string;
progressMessages: Array<{ timestamp: string; message: string }>;
result: any;
result: T | null;
error: string | null;
startPolling: (taskId: string) => void;
stopPolling: () => void;
}
export function usePolling(
pollFunction: (taskId: string) => Promise<TaskStatusResponse>,
options: UsePollingOptions = {}
): UsePollingReturn {
export function usePolling<T = any>(
pollFunction: (taskId: string) => Promise<TaskStatusResponse<T>>,
options: UsePollingOptions<T> = {}
): UsePollingReturn<T> {
const {
interval = 5000, // 5 seconds default - increased to reduce load
onProgress,
@@ -35,7 +36,7 @@ export function usePolling(
const [isPolling, setIsPolling] = useState(false);
const [currentStatus, setCurrentStatus] = useState<string>('idle');
const [progressMessages, setProgressMessages] = useState<Array<{ timestamp: string; message: string }>>([]);
const [result, setResult] = useState<any>(null);
const [result, setResult] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
// Debug state changes only in development and when state actually changes meaningfully
@@ -116,8 +117,10 @@ export function usePolling(
if (status.status === 'completed') {
console.info('[usePolling] ✅ Task completed', { taskId: currentTaskIdRef.current });
setResult(status.result);
onComplete?.(status.result);
setResult(status.result ?? null);
if (status.result !== undefined) {
onComplete?.(status.result);
}
stopPolling();
return; // Exit early to prevent further processing
} else if (status.status === 'failed') {
@@ -215,18 +218,24 @@ export function usePolling(
// Start polling immediately, then at intervals
poll();
intervalRef.current = setInterval(poll, interval);
}, [isPolling, interval, onProgress, onComplete, onError, pollFunction, stopPolling, progressMessages.length]);
}, [isPolling, interval, onProgress, onComplete, onError, pollFunction, stopPolling]);
// Cleanup on unmount - only if actually polling
// Use a ref to access the latest stopPolling function without triggering re-runs
const stopPollingRef = useRef(stopPolling);
useEffect(() => {
stopPollingRef.current = stopPolling;
}, [stopPolling]);
useEffect(() => {
return () => {
// Only call stopPolling if we have an active interval (actually polling)
// This prevents unnecessary cleanup calls when component unmounts while idle
if (intervalRef.current) {
stopPolling();
stopPollingRef.current();
}
};
}, [stopPolling]);
}, []); // Empty dependency array ensures this only runs on unmount
return {
isPolling,
@@ -287,3 +296,7 @@ export function useRewritePolling(options: UsePollingOptions = {}) {
// eslint-disable-next-line react-hooks/rules-of-hooks
return usePolling(wrapped, options);
}
export function useVideoGenerationPolling(options: UsePollingOptions = {}) {
return usePolling(pollVideoTaskStatus, options);
}

View File

@@ -61,12 +61,18 @@ export const useRealTimeData = (options: RealTimeDataOptions) => {
wsUrl = `ws://${window.location.host}/ws/strategy/${strategyId}/live`;
} else {
// Production: derive from API URL
const wsProtocol = apiUrl.startsWith('https://') ? 'wss://' : 'ws://';
const wsHost = apiUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
wsUrl = `${wsProtocol}${wsHost}/ws/strategy/${strategyId}/live`;
const wsProtocol = apiUrl.startsWith('https://') ? 'wss://' : 'ws://';
let wsHost = apiUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
// Fix for ngrok URLs having port 3000 appended incorrectly
if (wsHost.includes('ngrok-free.dev')) {
wsHost = wsHost.replace(/:3000$/, '');
}
wsUrl = `${wsProtocol}${wsHost}/ws/strategy/${strategyId}/live`;
}
const ws = new WebSocket(wsUrl);
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
ws.onopen = () => {

View File

@@ -35,25 +35,34 @@ export const useWixConnection = () => {
const checkStatus = useCallback(async () => {
setIsLoading(true);
try {
// Check sessionStorage for Wix tokens (like WixTestPage does)
// Check sessionStorage for Wix tokens and site info
const connectedFlag = sessionStorage.getItem('wix_connected') === 'true';
const tokensRaw = sessionStorage.getItem('wix_tokens');
const siteInfoRaw = sessionStorage.getItem('wix_site_info');
if (connectedFlag && tokensRaw) {
// Set connected status with site information from tokens
let siteInfo: any = {};
try {
if (siteInfoRaw) {
siteInfo = JSON.parse(siteInfoRaw);
}
} catch (e) {
// Ignore parse errors
}
// Set connected status with site information
setStatus({
connected: true,
sites: [{
id: 'wix-site-1',
blog_url: 'Connected Wix Site',
id: siteInfo.siteId || siteInfo.site_id || 'wix-site-1',
blog_url: siteInfo.url || siteInfo.viewUrl || 'Connected Wix Site',
blog_id: 'wix-blog',
created_at: new Date().toISOString(),
created_at: siteInfo.createdAt || new Date().toISOString(),
scope: 'BLOG.CREATE-DRAFT,BLOG.PUBLISH,MEDIA.MANAGE'
}],
total_sites: 1
});
console.log('Wix status checked: connected via sessionStorage');
} else {
setStatus({
connected: false,
@@ -61,10 +70,8 @@ export const useWixConnection = () => {
total_sites: 0,
error: 'No Wix connection found'
});
console.log('Wix status checked: not connected');
}
} catch (error) {
console.error('Error checking Wix status:', error);
setStatus({
connected: false,
sites: [],

View File

@@ -87,8 +87,6 @@ export const useWordPressConnection = (): UseWordPressConnectionReturn => {
setConnected(status.connected);
setSites(status.sites || []);
setTotalSites(status.total_sites);
console.log('WordPress status checked:', status);
} catch (error) {
console.error('Error checking WordPress status:', error);
setConnected(false);
@@ -175,7 +173,6 @@ export const useWordPressConnection = (): UseWordPressConnectionReturn => {
site_url: formattedUrl
});
console.log('WordPress connection test result:', success);
return success;
} catch (error) {
console.error('Error testing WordPress connection:', error);

View File

@@ -53,26 +53,18 @@ export const useWordPressOAuth = (): UseWordPressOAuthReturn => {
const THROTTLE_MS = 10000; // 10 seconds - status doesn't change frequently
if (now - lastStatusCheck < THROTTLE_MS) {
console.log('WordPress OAuth: Status check throttled (10s)');
return;
}
try {
setIsLoading(true);
setLastStatusCheck(now);
console.log('WordPress OAuth: Checking status...');
const status: WordPressOAuthStatus = await wordpressOAuthAPI.getStatus();
console.log('WordPress OAuth: Status response:', status);
setConnected(status.connected);
setSites(status.sites || []);
setTotalSites(status.total_sites);
console.log('WordPress OAuth status checked:', {
connected: status.connected,
sitesCount: status.sites?.length || 0,
totalSites: status.total_sites
});
} catch (error) {
console.error('Error checking WordPress OAuth status:', error);
setConnected(false);
@@ -108,23 +100,21 @@ export const useWordPressOAuth = (): UseWordPressOAuthReturn => {
// Listen for popup completion and messages
const messageHandler = (event: MessageEvent) => {
console.log('WordPress OAuth: Message received from any source:', {
origin: event.origin,
data: event.data,
source: event.source === popup ? 'our-popup' : 'other'
});
// Accept messages only from the popup we opened and from trusted origins
const trustedOrigins = [window.location.origin, 'https://littery-sonny-unscrutinisingly.ngrok-free.dev'];
const ngrokOrigin = process.env.REACT_APP_NGROK_ORIGIN || 'https://littery-sonny-unscrutinisingly.ngrok-free.dev';
const productionOrigin = 'https://alwrity-ai.vercel.app';
const trustedOrigins = [window.location.origin, ngrokOrigin, productionOrigin];
if (event.source !== popup) return;
if (!trustedOrigins.includes(event.origin)) return;
if (!trustedOrigins.includes(event.origin)) {
return;
}
console.log('WordPress OAuth: Valid message from popup:', event.data);
if (event.data.type === 'WPCOM_OAUTH_SUCCESS') {
popup.close();
clearInterval(checkClosed);
console.log('WordPress OAuth: Success message received, refreshing status...');
// Refresh status after OAuth completion
setTimeout(() => {
checkStatus();
@@ -132,7 +122,6 @@ export const useWordPressOAuth = (): UseWordPressOAuthReturn => {
} else if (event.data.type === 'WPCOM_OAUTH_ERROR') {
popup.close();
clearInterval(checkClosed);
console.error('WordPress OAuth error:', event.data.error);
// Refresh status to show disconnected state
setTimeout(() => {
checkStatus();
@@ -142,14 +131,10 @@ export const useWordPressOAuth = (): UseWordPressOAuthReturn => {
window.addEventListener('message', messageHandler);
// Test if popup is working
console.log('WordPress OAuth: Popup opened, waiting for messages...');
const checkClosed = setInterval(() => {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageHandler);
console.log('WordPress OAuth: Popup closed, refreshing status...');
// Refresh status after OAuth completion
setTimeout(() => {
checkStatus();
@@ -157,7 +142,6 @@ export const useWordPressOAuth = (): UseWordPressOAuthReturn => {
}
}, 1000);
console.log('WordPress OAuth flow started');
} else {
throw new Error('Failed to get WordPress OAuth URL');
}