T4B_Chat/utilities/webViewUtils.ts

234 lines
8.1 KiB
TypeScript

/**
* WebView Utilities
* Handles WebView management, navigation, and message handling
*/
import { WebViewNavigation } from 'react-native-webview';
import { Linking, Platform } from 'react-native';
import { ALLOWED_DOMAIN } from './constants';
/**
* Interface for WebView message data
*/
export interface WebViewMessageData {
type?: string;
cookies?: string;
[key: string]: any;
}
/**
* Interface for WebView navigation state
*/
export interface WebViewNavigationState {
currentChannel: string | null;
isNavigating: boolean;
}
/**
* WebView Handler Class
*/
export class WebViewHandler {
private webViewRef: React.RefObject<any>;
private updateWebViewUrl: (url: string) => void;
private navigationState: WebViewNavigationState;
constructor(
webViewRef: React.RefObject<any>,
updateWebViewUrl: (url: string) => void,
navigationState: WebViewNavigationState
) {
this.webViewRef = webViewRef;
this.updateWebViewUrl = updateWebViewUrl;
this.navigationState = navigationState;
}
/**
* Handle WebView navigation requests
* @param request - Navigation request from WebView
* @returns boolean indicating whether to allow navigation
*/
handleNavigation(request: WebViewNavigation): boolean {
const { url } = request;
console.log('🧭 Navigation to:', url);
// Validate URL before processing
try {
new URL(url);
} catch (error) {
console.error("❌ Invalid URL detected:", url, error);
return false; // Block invalid URLs
}
// 🧪 Test notification when navigating to specific URL (temporary for testing)
if (url.includes('test-notification')) {
console.log('🧪 Test notification triggered via URL');
// You can trigger test notification here if needed
return false;
}
// ✅ Stay inside Odoo instance
if (
url.startsWith(`${ALLOWED_DOMAIN}`) ||
url.startsWith(`${ALLOWED_DOMAIN}`)
) {
return true;
}
// 🌍 Open external links in default browser
Linking.openURL(url).catch((err) =>
console.error("Couldn't open external link", err)
);
return false; // block WebView from handling it
}
/**
* Handle messages from WebView
* @param event - Message event from WebView
* @param setWebViewCookies - Function to set cookies
*/
handleWebViewMessage(
event: any,
setWebViewCookies: (cookies: string | null) => void
): void {
try {
const messageData = event.nativeEvent.data;
// Try to parse as JSON first
try {
const data: WebViewMessageData = JSON.parse(messageData);
console.log('📨 WebView JSON message received:', data);
// Handle cookie extraction (if needed)
if (data.type === 'cookies') {
console.log('🍪 Cookies from WebView:', data.cookies);
setWebViewCookies(data.cookies || null);
}
} catch (jsonError) {
// If JSON parsing fails, treat as plain text message
console.log('📨 WebView text message received:', messageData);
// Handle navigation feedback messages
if (messageData.startsWith('NAVIGATION_RESULT:')) {
const result = messageData.replace('NAVIGATION_RESULT:', '');
console.log('🔗 Navigation Result:', result);
}
// Handle other plain text messages here if needed
}
} catch (error) {
console.error('❌ Error handling WebView message:', error);
}
}
/**
* Handle WebView load completion
* @param currentChannel - Current channel being navigated to
* @param isNavigating - Whether navigation is in progress
*/
handleWebViewLoadEnd(currentChannel: string | null, isNavigating: boolean): void {
console.log('🔗 WebView loaded, current channel:', currentChannel, 'isNavigating:', isNavigating);
// Check if we successfully navigated to the target channel
if (currentChannel && this.webViewRef.current) {
console.log('🔗 WebView loaded - checking if navigation was successful');
// Add a simple debug script to check current URL
const checkScript = `
(function() {
try {
console.log('🔗 WebView loaded - Current URL:', window.location.href);
console.log('🔗 WebView loaded - Current hash:', window.location.hash);
// Check if we're on the target channel page
if (window.location.hash.includes('mail.channel_${currentChannel}')) {
console.log('✅ Successfully navigated to channel ${currentChannel}');
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage('NAVIGATION_RESULT:Successfully navigated to channel ${currentChannel}');
}
} else {
console.log('⚠️ Not on target channel page yet');
console.log('🔗 Expected: mail.channel_${currentChannel}');
console.log('🔗 Actual hash:', window.location.hash);
}
} catch (error) {
console.error('❌ Error in check script:', error);
}
})();
true;
`;
this.webViewRef.current.injectJavaScript(checkScript);
}
// If we're navigating and have a current channel, we might need to retry
if (currentChannel && isNavigating) {
console.log('🔗 Navigation in progress - checking if retry needed');
// Don't retry immediately, let the two-step navigation complete
} else if (currentChannel && !isNavigating) {
console.log('🔗 Navigation complete - no retry needed');
} else {
console.log('🔗 No active navigation');
}
}
/**
* Inject debug script into WebView
*/
injectDebugScript(): void {
if (this.webViewRef.current) {
const debugScript = `
(function() {
console.log('🔍 WebView Debug Info:');
console.log(' - Current URL:', window.location.href);
console.log(' - Current hash:', window.location.hash);
console.log(' - Document ready state:', document.readyState);
console.log(' - Available chat elements:', document.querySelectorAll('[class*="mail"], [class*="chat"], [data-model*="mail"]').length);
console.log(' - Available channel elements:', document.querySelectorAll('[data-channel-id], [data-id*="mail.channel"], [data-oe-id*="mail.channel"]').length);
// List all elements with channel-related attributes
const channelElements = document.querySelectorAll('[data-channel-id], [data-id*="mail.channel"], [data-oe-id*="mail.channel"], a[href*="mail.channel"]');
console.log('🔍 Channel elements found:', channelElements.length);
channelElements.forEach((el, index) => {
console.log(\` \${index + 1}. \${el.tagName} - \${el.className} - \${el.getAttribute('data-channel-id') || el.getAttribute('data-id') || el.getAttribute('href')}\`);
});
// Check Odoo availability
console.log('🔍 Odoo Debug:');
console.log(' - window.odoo exists:', !!window.odoo);
console.log(' - odoo.loader exists:', !!(window.odoo && window.odoo.loader));
console.log(' - odoo.services exists:', !!(window.odoo && window.odoo.services));
console.log(' - odoo.__DEBUG__ exists:', !!(window.odoo && window.odoo.__DEBUG__));
if (window.odoo && window.odoo.__DEBUG__) {
const services = Object.keys(window.odoo.__DEBUG__.services || {});
console.log(' - Available services:', services);
}
})();
true;
`;
this.webViewRef.current.injectJavaScript(debugScript);
}
}
/**
* Get WebView console messages
* @param event - Console message event
*/
handleConsoleMessage(event: any): void {
console.log('🌐 WebView Console:', event.nativeEvent.message);
}
}
/**
* Create WebView handler instance
*/
export function createWebViewHandler(
webViewRef: React.RefObject<any>,
updateWebViewUrl: (url: string) => void,
navigationState: WebViewNavigationState
): WebViewHandler {
return new WebViewHandler(webViewRef, updateWebViewUrl, navigationState);
}