Add session expired detection and auto-refresh in WebView

- Monitor page content for "session expired" patterns
- Send message to React Native when detected
- Auto-refresh token and reload WebView
- Add logging to refreshToken for debugging
This commit is contained in:
Sergei 2026-01-18 22:48:41 -08:00
parent bc33230739
commit 173c0a8262
2 changed files with 54 additions and 3 deletions

View File

@ -10,6 +10,8 @@ import { AppColors, FontSizes, Spacing } from '@/constants/theme';
const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard'; const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard';
// URLs that indicate session expired (login page) // URLs that indicate session expired (login page)
const LOGIN_URL_PATTERNS = ['/login', '/auth', '/signin']; 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() { export default function HomeScreen() {
const { user } = useAuth(); const { user } = useAuth();
@ -134,12 +136,13 @@ export default function HomeScreen() {
} }
}, [isRefreshingToken]); }, [isRefreshingToken]);
// JavaScript to inject auth token into localStorage // JavaScript to inject auth token into localStorage and monitor for session expiry
// Web app expects auth2 as JSON: {username, token, user_id} // Web app expects auth2 as JSON: {username, token, user_id}
const injectedJavaScript = authToken const injectedJavaScript = authToken
? ` ? `
(function() { (function() {
try { try {
// Inject auth data
var authData = { var authData = {
username: '${userName || ''}', username: '${userName || ''}',
token: '${authToken}', token: '${authToken}',
@ -147,6 +150,33 @@ export default function HomeScreen() {
}; };
localStorage.setItem('auth2', JSON.stringify(authData)); localStorage.setItem('auth2', JSON.stringify(authData));
console.log('Auth injected:', authData.username); 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) { } catch(e) {
console.error('Failed to inject token:', e); console.error('Failed to inject token:', e);
} }
@ -199,6 +229,19 @@ export default function HomeScreen() {
setIsLoading(false); 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 // Wait for token to load
if (!isTokenLoaded) { if (!isTokenLoaded) {
return ( return (
@ -285,6 +328,7 @@ export default function HomeScreen() {
onHttpError={handleError} onHttpError={handleError}
onNavigationStateChange={handleNavigationStateChange} onNavigationStateChange={handleNavigationStateChange}
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest} onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
onMessage={handleWebViewMessage}
javaScriptEnabled={true} javaScriptEnabled={true}
domStorageEnabled={true} domStorageEnabled={true}
startInLoadingState={true} startInLoadingState={true}

View File

@ -100,13 +100,20 @@ class ApiService {
const userName = await SecureStore.getItemAsync('userName'); const userName = await SecureStore.getItemAsync('userName');
const password = await SecureStore.getItemAsync('userPassword'); const password = await SecureStore.getItemAsync('userPassword');
console.log('[API] refreshToken - userName:', userName ? 'exists' : 'missing');
console.log('[API] refreshToken - password:', password ? 'exists' : 'missing');
if (!userName || !password) { if (!userName || !password) {
console.log('[API] refreshToken - NO_CREDENTIALS');
return { ok: false, error: { message: 'No stored credentials', code: 'NO_CREDENTIALS' } }; return { ok: false, error: { message: 'No stored credentials', code: 'NO_CREDENTIALS' } };
} }
console.log('Refreshing token for user:', userName); console.log('[API] Refreshing token for user:', userName);
return await this.login(userName, password); const result = await this.login(userName, password);
console.log('[API] refreshToken result:', result.ok ? 'SUCCESS' : result.error?.message);
return result;
} catch (error) { } catch (error) {
console.error('[API] refreshToken error:', error);
return { return {
ok: false, ok: false,
error: { message: 'Failed to refresh token', code: 'REFRESH_ERROR' } error: { message: 'Failed to refresh token', code: 'REFRESH_ERROR' }