import React, { useState, useRef, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Image } from 'react-native'; import { WebView, WebViewNavigation } from 'react-native-webview'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useAuth } from '@/contexts/AuthContext'; import { api } from '@/services/api'; import { AppColors, FontSizes, Spacing } from '@/constants/theme'; const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard'; // URLs that indicate session expired (login page) const LOGIN_URL_PATTERNS = ['/login', '/auth', '/signin']; // Text patterns that indicate session expired (shown in page content) const SESSION_EXPIRED_PATTERNS = ['session expired', 'session has expired', 'token expired', 'please log in']; export default function HomeScreen() { const { user } = useAuth(); const webViewRef = useRef(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [canGoBack, setCanGoBack] = useState(false); const [authToken, setAuthToken] = useState(null); const [userName, setUserName] = useState(null); const [userId, setUserId] = useState(null); const [isTokenLoaded, setIsTokenLoaded] = useState(false); const [isRefreshingToken, setIsRefreshingToken] = useState(false); // Load credentials and check/refresh token if needed const loadCredentials = useCallback(async () => { try { // Check if token is expiring soon and refresh if needed const isExpiring = await api.isTokenExpiringSoon(); if (isExpiring) { console.log('Token expiring soon, refreshing...'); const refreshResult = await api.refreshToken(); if (!refreshResult.ok) { console.log('Token refresh failed, using existing credentials'); } } // Get credentials (possibly refreshed) const credentials = await api.getWebViewCredentials(); if (credentials) { setAuthToken(credentials.token); setUserName(credentials.userName); setUserId(credentials.userId); console.log('Home: Loaded credentials for WebView:', { hasToken: !!credentials.token, user: credentials.userName, uid: credentials.userId }); } } catch (err) { console.error('Failed to load credentials:', err); } finally { setIsTokenLoaded(true); } }, []); useEffect(() => { loadCredentials(); // Periodically check and refresh token (every 30 minutes) const tokenCheckInterval = setInterval(async () => { const isExpiring = await api.isTokenExpiringSoon(); if (isExpiring) { console.log('Periodic check: Token expiring, refreshing...'); const result = await api.refreshToken(); if (result.ok) { const credentials = await api.getWebViewCredentials(); if (credentials) { setAuthToken(credentials.token); // Re-inject token into WebView const injectScript = ` (function() { var authData = { username: '${credentials.userName}', token: '${credentials.token}', user_id: ${credentials.userId} }; localStorage.setItem('auth2', JSON.stringify(authData)); console.log('Token auto-refreshed'); })(); true; `; webViewRef.current?.injectJavaScript(injectScript); } } } }, 30 * 60 * 1000); // 30 minutes return () => clearInterval(tokenCheckInterval); }, [loadCredentials]); // Handle token refresh when WebView detects session expired const handleTokenRefresh = useCallback(async () => { if (isRefreshingToken) return; setIsRefreshingToken(true); console.log('WebView session expired, refreshing token...'); try { const refreshResult = await api.refreshToken(); if (refreshResult.ok) { // Get new credentials const credentials = await api.getWebViewCredentials(); if (credentials) { setAuthToken(credentials.token); setUserName(credentials.userName); setUserId(credentials.userId); // Inject new token and reload const injectScript = ` (function() { var authData = { username: '${credentials.userName}', token: '${credentials.token}', user_id: ${credentials.userId} }; localStorage.setItem('auth2', JSON.stringify(authData)); console.log('Auth refreshed:', authData.username); window.location.href = '${DASHBOARD_URL}'; })(); true; `; webViewRef.current?.injectJavaScript(injectScript); } } else { console.error('Token refresh failed'); setError('Session expired. Please restart the app.'); } } catch (err) { console.error('Error refreshing token:', err); } finally { setIsRefreshingToken(false); } }, [isRefreshingToken]); // JavaScript to inject auth token into localStorage and monitor for session expiry // Web app expects auth2 as JSON: {username, token, user_id} const injectedJavaScript = authToken ? ` (function() { try { // Inject auth data var authData = { username: '${userName || ''}', token: '${authToken}', user_id: ${userId || 'null'} }; localStorage.setItem('auth2', JSON.stringify(authData)); console.log('Auth injected:', authData.username); // Monitor page content for session expired messages var sessionExpiredPatterns = ${JSON.stringify(SESSION_EXPIRED_PATTERNS)}; function checkForSessionExpired() { var bodyText = (document.body?.innerText || '').toLowerCase(); for (var i = 0; i < sessionExpiredPatterns.length; i++) { if (bodyText.includes(sessionExpiredPatterns[i])) { console.log('Session expired detected in page content'); window.ReactNativeWebView?.postMessage(JSON.stringify({ type: 'SESSION_EXPIRED' })); return true; } } return false; } // Check after page loads and periodically setTimeout(checkForSessionExpired, 1000); setTimeout(checkForSessionExpired, 3000); // Also observe DOM changes for dynamic content var observer = new MutationObserver(function() { checkForSessionExpired(); }); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } } catch(e) { console.error('Failed to inject token:', e); } })(); true; ` : ''; const handleRefresh = () => { setError(null); setIsLoading(true); webViewRef.current?.reload(); }; const handleWebViewBack = () => { if (canGoBack) { webViewRef.current?.goBack(); } }; const handleNavigationStateChange = (navState: WebViewNavigation) => { setCanGoBack(navState.canGoBack); // Check if WebView is trying to navigate to login page (session expired) const url = navState.url?.toLowerCase() || ''; const isLoginPage = LOGIN_URL_PATTERNS.some(pattern => url.includes(pattern)); if (isLoginPage && !isRefreshingToken) { console.log('Detected navigation to login page, refreshing token...'); handleTokenRefresh(); } }; // Intercept navigation requests to prevent loading login page const handleShouldStartLoadWithRequest = useCallback((request: { url: string }) => { const url = request.url?.toLowerCase() || ''; const isLoginPage = LOGIN_URL_PATTERNS.some(pattern => url.includes(pattern)); if (isLoginPage) { console.log('Blocking navigation to login page, refreshing token instead'); handleTokenRefresh(); return false; // Block the navigation } return true; // Allow all other navigations }, [handleTokenRefresh]); const handleError = () => { setError('Failed to load dashboard. Please check your internet connection.'); setIsLoading(false); }; // Handle messages from WebView (session expired detection) const handleWebViewMessage = useCallback((event: { nativeEvent: { data: string } }) => { try { const message = JSON.parse(event.nativeEvent.data); if (message.type === 'SESSION_EXPIRED') { console.log('WebView reported session expired, refreshing token...'); handleTokenRefresh(); } } catch { // Ignore non-JSON messages } }, [handleTokenRefresh]); // Wait for token to load if (!isTokenLoaded) { return ( Hello, {user?.user_name || 'User'} Preparing dashboard... ); } if (error) { return ( Hello, {user?.user_name || 'User'} Connection Error {error} Try Again ); } return ( {/* Header */} Hello, {user?.user_name || 'User'} {canGoBack && ( )} {/* WebView Dashboard */} setIsLoading(true)} onLoadEnd={() => setIsLoading(false)} onError={handleError} onHttpError={handleError} onNavigationStateChange={handleNavigationStateChange} onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest} onMessage={handleWebViewMessage} javaScriptEnabled={true} domStorageEnabled={true} startInLoadingState={true} scalesPageToFit={true} allowsBackForwardNavigationGestures={true} injectedJavaScriptBeforeContentLoaded={injectedJavaScript} injectedJavaScript={injectedJavaScript} renderLoading={() => ( Loading dashboard... )} /> {isLoading && ( )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: AppColors.background, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: Spacing.lg, paddingVertical: Spacing.md, backgroundColor: AppColors.background, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, headerLeft: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, }, logo: { width: 36, height: 36, }, greeting: { fontSize: FontSizes.sm, color: AppColors.textSecondary, }, headerTitle: { fontSize: FontSizes.xl, fontWeight: '700', color: AppColors.textPrimary, }, headerActions: { flexDirection: 'row', alignItems: 'center', }, actionButton: { padding: Spacing.xs, marginLeft: Spacing.xs, }, refreshButton: { padding: Spacing.xs, }, webViewContainer: { flex: 1, }, webView: { flex: 1, }, loadingContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: AppColors.background, }, loadingOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(255,255,255,0.8)', }, loadingText: { marginTop: Spacing.md, fontSize: FontSizes.base, color: AppColors.textSecondary, }, errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: Spacing.xl, }, errorTitle: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textPrimary, marginTop: Spacing.md, }, errorText: { fontSize: FontSizes.base, color: AppColors.textSecondary, textAlign: 'center', marginTop: Spacing.xs, }, retryButton: { marginTop: Spacing.lg, paddingHorizontal: Spacing.xl, paddingVertical: Spacing.md, backgroundColor: AppColors.primary, borderRadius: 8, }, retryButtonText: { color: AppColors.white, fontSize: FontSizes.base, fontWeight: '600', }, });