Save local changes (GSC/Bing integrations) before merging PR #354
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user