270 lines
9.5 KiB
TypeScript
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,
|
|
},
|
|
});
|