T4B_Chat/App.tsx

270 lines
9.5 KiB
TypeScript

import React, { JSX, useEffect, useState } from 'react';
import { SafeAreaView, StyleSheet, Platform, StatusBar, AppState, View, Text, ActivityIndicator, Animated } from 'react-native';
import { WebView } from 'react-native-webview';
// Utility imports
import {
createNotificationChannel,
configureNotificationSettings,
testNotification
} from './utilities/notificationUtils';
import {
createDeepLinkHandler,
NotificationData
} from './utilities/deepLinkUtils';
import {
createWebViewHandler
} from './utilities/webViewUtils';
import {
createCookieHandler
} from './utilities/cookieUtils';
import {
createFCMHandler,
NotificationCallbacks
} from './utilities/fcmUtils';
import { ALLOWED_DOMAIN } from './utilities/constants';
import { CustomLoader, createOdooLoader, createNavigationLoader, createChatLoader } from './utilities/loaderUtils';
// Suppress Firebase deprecation warnings (temporary until Firebase v22+ is stable)
const originalWarn = console.warn;
console.warn = (...args) => {
if (args[0] && typeof args[0] === 'string' &&
args[0].includes('This method is deprecated') &&
args[0].includes('Firebase')) {
return; // Suppress only Firebase deprecation warnings
}
originalWarn.apply(console, args);
};
// @ts-ignore
import PushNotification from 'react-native-push-notification';
/**
* Main App Component
* Handles FCM notifications, WebView management, and deep linking
*/
export default function App(): JSX.Element {
// Track app state to avoid duplicate notifications
const [appState, setAppState] = React.useState(AppState.currentState);
// WebView reference for accessing cookies
const webViewRef = React.useRef<WebView>(null);
// Deep linking state
const [currentChannel, setCurrentChannel] = React.useState<string | null>(null);
const webViewUrlRef = React.useRef(`${ALLOWED_DOMAIN}/web`);
const [webViewKey, setWebViewKey] = React.useState(0); // Force re-render when URL changes
const [isNavigating, setIsNavigating] = React.useState(false);
const [navigationAttempts, setNavigationAttempts] = React.useState(0);
// State to store cookies
const [webViewCookies, setWebViewCookies] = React.useState<string | null>(null);
// Loading state
const [isLoading, setIsLoading] = useState(true);
const [loadingText, setLoadingText] = useState('Connecting to T4B...');
const [loaderType, setLoaderType] = useState<'odoo' | 'navigation' | 'chat'>('odoo');
// Helper function to update WebView URL immediately (without loader)
const updateWebViewUrl = (newUrl: string) => {
console.log('🔗 Updating WebView URL immediately:', newUrl);
// Don't show loader for navigation to avoid flickering
webViewUrlRef.current = newUrl;
setWebViewKey(prev => prev + 1); // Force WebView re-render
console.log('✅ WebView URL updated:', newUrl);
};
// Create utility handlers
const cookieHandler = createCookieHandler(setWebViewCookies);
const webViewHandler = createWebViewHandler(webViewRef, updateWebViewUrl, {
currentChannel,
isNavigating
});
// Navigation state for deep link handler
const navigationState = {
currentChannel,
isNavigating,
navigationAttempts
};
const deepLinkHandler = createDeepLinkHandler(
updateWebViewUrl,
setIsNavigating,
setCurrentChannel,
setNavigationAttempts,
navigationState
);
// FCM notification callbacks
const fcmCallbacks: NotificationCallbacks = {
onForegroundNotification: (data: NotificationData) => {
deepLinkHandler.handleForegroundBackgroundDeepLink(data);
},
onBackgroundNotification: (data: NotificationData) => {
deepLinkHandler.handleForegroundBackgroundDeepLink(data);
},
onKilledAppNotification: (data: NotificationData) => {
deepLinkHandler.handleKilledAppDeepLink(data);
}
};
const fcmHandler = createFCMHandler(
() => cookieHandler.getWebViewCookies(),
fcmCallbacks
);
// Handle app state changes
useEffect(() => {
const handleAppStateChange = (nextAppState: any) => {
console.log('App state changed:', appState, '->', nextAppState);
setAppState(nextAppState);
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => subscription?.remove();
}, [appState]);
// Initialize FCM and WebView
useEffect(() => {
// Configure push notifications
createNotificationChannel();
configureNotificationSettings();
// Initialize FCM notifications
fcmHandler.initializeNotifications();
// Start periodic cookie refresh
const cleanupCookieRefresh = cookieHandler.refreshWebViewCookies();
// Setup FCM message listeners
const cleanupFCMListeners = fcmHandler.setupMessageListeners(appState);
// Setup test functions for debugging
setupTestFunctions();
return () => {
cleanupCookieRefresh();
cleanupFCMListeners();
};
}, []);
// Setup test functions for debugging
const setupTestFunctions = () => {
if (typeof global !== 'undefined') {
(global as any).testNotification = testNotification;
(global as any).refreshCookies = () => {
console.log('🔄 Manual cookie refresh triggered');
cookieHandler.getWebViewCookies();
};
(global as any).checkCookies = () => {
console.log('🔍 Current cookie state:', webViewCookies);
console.log('🔍 WebView ref available:', !!webViewRef.current);
cookieHandler.getWebViewCookies();
};
(global as any).testDeepLink = (channelId: string) => {
console.log('🔗 Testing deep link for channel:', channelId);
deepLinkHandler.navigateToChannel(channelId);
};
(global as any).testDeepLinkUrl = (deepLink: string) => {
console.log('🔗 Testing deep link URL:', deepLink);
deepLinkHandler.handleDeepLink(deepLink);
};
(global as any).simulateNotification = (channelId: string) => {
console.log('🔗 Simulating notification tap for channel:', channelId);
setTimeout(() => {
deepLinkHandler.handleForegroundBackgroundDeepLink({ channel_id: channelId });
}, 100);
};
(global as any).testWebViewUrlUpdate = (channelId: string) => {
console.log('🔗 Testing WebView URL update for channel:', channelId);
deepLinkHandler.handleForegroundBackgroundDeepLink({ channel_id: channelId });
};
(global as any).debugWebView = () => {
webViewHandler.injectDebugScript();
};
(global as any).navigateToChannel = (channelId: string) => {
console.log('🔗 Direct navigation to channel:', channelId);
deepLinkHandler.navigateToChannel(channelId);
};
(global as any).updateWebViewUrl = (url: string) => {
console.log('🔗 Direct WebView URL update:', url);
updateWebViewUrl(url);
};
console.log('🧪 Test functions available:');
console.log(' - global.testNotification()');
console.log(' - global.refreshCookies()');
console.log(' - global.checkCookies()');
console.log(' - global.testDeepLink(channelId) - For killed app deep linking');
console.log(' - global.testWebViewUrlUpdate(channelId) - For foreground/background URL update');
console.log(' - global.testDeepLinkUrl("myapp://chat/123")');
console.log(' - global.simulateNotification(channelId)');
console.log(' - global.debugWebView() - Debug WebView state and elements');
console.log(' - global.navigateToChannel(channelId) - Direct navigation using WebView URL update');
console.log(' - global.updateWebViewUrl(url) - Immediate WebView URL update');
}
};
// Handle WebView navigation
const handleNavigation = (request: any): boolean => {
return webViewHandler.handleNavigation(request);
};
// Handle WebView messages
const handleWebViewMessage = (event: any) => {
webViewHandler.handleWebViewMessage(event, setWebViewCookies);
};
return (
<SafeAreaView style={styles.container}>
<WebView
ref={webViewRef}
source={{ uri: webViewUrlRef.current }}
style={styles.webview}
key={webViewKey}
javaScriptEnabled
domStorageEnabled
startInLoadingState={false} // Use custom loader instead
originWhitelist={['*']}
onShouldStartLoadWithRequest={handleNavigation}
onMessage={handleWebViewMessage}
onLoadStart={() => {
console.log('🔄 WebView started loading');
// Don't show loader during WebView navigation to avoid flickering
}}
onLoadEnd={() => {
console.log('✅ WebView finished loading');
setIsLoading(false); // Only hide initial loader
webViewHandler.handleWebViewLoadEnd(currentChannel, isNavigating);
}}
onError={(error) => {
console.error('❌ WebView error:', error);
setIsLoading(false);
setLoadingText('Connection error');
}}
//@ts-ignore
onConsoleMessage={(event: any) => {
console.log('🌐 WebView Console:', event.nativeEvent.message);
}}
/>
{/* Dynamic Beautiful Custom Loader */}
{loaderType === 'odoo' && createOdooLoader(isLoading, loadingText)}
{loaderType === 'navigation' && createNavigationLoader(isLoading, loadingText)}
{loaderType === 'chat' && createChatLoader(isLoading, loadingText)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
// paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight ?? 0 : 0,
},
webview: {
flex: 1,
},
});