/** * 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; private updateWebViewUrl: (url: string) => void; private navigationState: WebViewNavigationState; constructor( webViewRef: React.RefObject, 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, updateWebViewUrl: (url: string) => void, navigationState: WebViewNavigationState ): WebViewHandler { return new WebViewHandler(webViewRef, updateWebViewUrl, navigationState); }