234 lines
8.1 KiB
TypeScript
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);
|
|
}
|