Skip to content

Latest commit

Β 

History

History
494 lines (404 loc) Β· 12 KB

File metadata and controls

494 lines (404 loc) Β· 12 KB

WebView Session Management

This document explains how session management works between the native React Native app and WebView, including authentication, cookies, and state synchronization.

🎯 Overview

Session management in the hybrid app ensures that:

  • User authentication state is synchronized between native and web
  • Cookies are properly shared and maintained
  • User data is consistently available across both environments
  • Logout events are handled properly in both contexts

πŸ” Authentication Flow

1. Login Process

// Native login flow
const handleLogin = async credentials => {
  // 1. Authenticate with backend
  const response = await authAPI.login(credentials);

  // 2. Store user data in Redux
  dispatch(setUserData(response.user));

  // 3. Store authentication tokens
  await AsyncStorage.setItem('authToken', response.token);

  // 4. Set cookies for WebView
  await setWebViewCookies(response.cookies);

  // 5. Navigate to WebView
  navigate(ROUTES.WEB_VIEW);
};

2. Cookie Management

// src/components/common/web-component/web-component.tsx
const injectCookies = async (domain: string) => {
  try {
    // Get cookies from native storage
    const cookies1 = await CookieManager.get('https://' + domain);
    const cookies2 = await CookieManager.get(Config.DOMAIN);
    const cookies = {...cookies1, ...cookies2};

    // Inject into Android WebView cookie store
    for (const [name, v] of Object.entries(cookies)) {
      let expires;
      if (v.expires) {
        expires = isNaN(new Date(v.expires).getTime())
          ? undefined
          : new Date(v.expires).toISOString();
      }

      await CookieManager.set('https://' + domain, {
        name,
        value: v.value,
        domain,
        path: '/',
        secure: true,
        httpOnly: false,
        ...(expires ? {expires} : {}),
      });
    }

    // Build JS string to inject into window.document
    const js = Object.entries(cookies)
      .map(([name, v]) => {
        return `document.cookie = "${name}=${v.value}; path=/; domain=${domain}; Secure; SameSite=None";`;
      })
      .join('\n');

    setCookieScript(js);
  } catch (e) {
    console.error('Error building injected cookies:', e);
  }
};

πŸ”„ Session Synchronization

1. User Data Injection

// src/helper/web-view-scripts.ts
export const addUserDataInWebStore = (userData: any) => {
  return `(function waitForStore() {
    const interval = setInterval(function () {
      if (window.fpi?.store?.dispatch) {
        console.log("Injecting mobile user data into Redux store");

        // Dispatch to Redux store
        window.fpi.store.dispatch({
          type: "auth/setUserData",
          payload: ${JSON.stringify({
            ...userData,
            isUserLogin: true,
            user_exists: true,
          })}
        });

        // Set global variable for easy access
        window.mobileUserData = ${JSON.stringify({
          ...userData,
          isUserLogin: true,
          user_exists: true,
        })};

        // Trigger custom event for other components
        window.dispatchEvent(new CustomEvent('mobileUserDataLoaded', {
          detail: window.mobileUserData
        }));

        clearInterval(interval);
      }
    }, 100);
  })();`;
};

2. Session State Management

// src/shared/hooks/use-web-view.tsx
export const useWebView = () => {
  const context = useContext(WebViewContext);
  const {fpi} = useFpi();
  const userData = useSelector(fpi.getters.USER_DATA);

  // Sync session state with WebView
  const syncSessionState = useCallback(() => {
    if (context.currentWebViewRef?.current && userData) {
      const script = addUserDataInWebStore(userData);
      context.currentWebViewRef.current.injectJavaScript(script);
    }
  }, [context.currentWebViewRef, userData]);

  return {
    ...context,
    syncSessionState,
  };
};

πŸšͺ Logout Handling

1. WebView to Native Logout

// src/helper/web-view-scripts.ts
export const listenToLogoutEvent = () => {
  return `(function waitForFPI() {
    const interval = setInterval(function () {
      if (window?.FPI?.event) {
        window.FPI.event.on("user.logout", (e) => {
          // Send logout event to native
          window?.ReactNativeWebView?.postMessage(
            JSON.stringify({eventName: "user.logout", eventData: e})
          );
        });
        clearInterval(interval);
      }
    }, 100);
  })();`;
};

2. Native Logout Handler

// src/shared/hooks/use-webview-event-handler.tsx
const handleLogout = async () => {
  try {
    // 1. Clear all cookies
    await CookieManager.clearAll();

    // 2. Clear native storage
    await AsyncStorage.multiRemove(['authToken', 'userData']);

    // 3. Clear Redux state
    dispatch(clearUserData());

    // 4. Reset WebView state
    updateCurrentPath?.('/');
    setCanGoBack?.(false);

    // 5. Navigate to login
    navigationReset(ROUTES.LOGIN as never);

    console.log('Logout completed successfully');
  } catch (error) {
    console.error('Error during logout:', error);
  }
};

3. Native to WebView Logout

// Trigger logout from native
const triggerWebViewLogout = () => {
  if (currentWebViewRef?.current) {
    const logoutScript = `
      if (window?.FPI?.event) {
        window.FPI.event.emit("user.logout", { source: "native" });
      }
      true;
    `;
    currentWebViewRef.current.injectJavaScript(logoutScript);
  }
};

πŸͺ Cookie Management Strategies

1. Cookie Injection Timing

// src/components/common/web-component/web-component.tsx
export default function WebComponent({
  initialUrl,
  injectJs,
  loadCookies = true,
  routeKey,
  permissions = [],
}: WebComponentProps) {
  const [cookieScript, setCookieScript] = useState('');

  // Inject cookies before content loads
  useEffect(() => {
    if (loadCookies) {
      injectCookies(webSiteDomain);
    }
  }, [loadCookies, webSiteDomain]);

  return (
    <WebView
      // ... other props
      injectedJavaScriptBeforeContentLoaded={
        cookieScript + nativeNavigation() + getWebViewLocation()
      }
      sharedCookiesEnabled={true}
      thirdPartyCookiesEnabled={true}
    />
  );
}

2. Cookie Synchronization

// Sync cookies from WebView to native
const syncCookiesFromWebView = async () => {
  try {
    const script = `
      const cookies = document.cookie.split(';').map(cookie => {
        const [name, value] = cookie.trim().split('=');
        return { name, value };
      });
      window.ReactNativeWebView.postMessage(JSON.stringify({
        eventName: 'cookiesUpdate',
        eventData: cookies
      }));
      true;
    `;

    currentWebViewRef?.current?.injectJavaScript(script);
  } catch (error) {
    console.error('Error syncing cookies:', error);
  }
};

// Handle cookie updates from WebView
const handleCookieUpdate = async (cookies: any[]) => {
  try {
    for (const cookie of cookies) {
      await CookieManager.set(Config.DOMAIN, {
        name: cookie.name,
        value: cookie.value,
        domain: Config.DOMAIN,
        path: '/',
        secure: true,
        httpOnly: false,
      });
    }
  } catch (error) {
    console.error('Error updating cookies:', error);
  }
};

πŸ”„ State Synchronization

1. User Data Sync

// Sync user data when WebView becomes active
useEffect(() => {
  const unsubscribe = navigation.addListener('focus', () => {
    setIsFocused(true);
    setCurrentWebViewRef(routeKey);

    // Sync user data if available
    if (userData && Object.keys(userData).length > 0) {
      const syncScript = addUserDataInWebStore(userData);
      webViewRef.current?.injectJavaScript(syncScript);
    }
  });

  return unsubscribe;
}, [navigation, userData]);

2. Authentication State Monitoring

// Monitor authentication state changes
useEffect(() => {
  if (userData && isFocused) {
    // User is logged in and WebView is focused
    const authScript = `
      if (window.fpi?.store) {
        window.fpi.store.dispatch({
          type: "auth/setUserData",
          payload: ${JSON.stringify(userData)}
        });
      }
      true;
    `;
    currentWebViewRef?.current?.injectJavaScript(authScript);
  } else if (!userData && isFocused) {
    // User is logged out and WebView is focused
    const logoutScript = `
      if (window.fpi?.store) {
        window.fpi.store.dispatch({
          type: "auth/clearUserData"
        });
      }
      true;
    `;
    currentWebViewRef?.current?.injectJavaScript(logoutScript);
  }
}, [userData, isFocused]);

πŸ›‘οΈ Security Considerations

1. Secure Cookie Settings

const setSecureCookies = async (cookies: any[]) => {
  for (const cookie of cookies) {
    await CookieManager.set(Config.DOMAIN, {
      name: cookie.name,
      value: cookie.value,
      domain: Config.DOMAIN,
      path: '/',
      secure: true, // HTTPS only
      httpOnly: false, // Allow JavaScript access
      sameSite: 'None', // Cross-site requests
      expires: cookie.expires,
    });
  }
};

2. Token Management

// Store and retrieve authentication tokens
const storeAuthToken = async (token: string) => {
  try {
    await AsyncStorage.setItem('authToken', token);

    // Also store in secure storage if available
    if (Platform.OS === 'ios') {
      await Keychain.setInternetCredentials('authToken', 'user', token);
    }
  } catch (error) {
    console.error('Error storing auth token:', error);
  }
};

const getAuthToken = async () => {
  try {
    let token = await AsyncStorage.getItem('authToken');

    if (!token && Platform.OS === 'ios') {
      const credentials = await Keychain.getInternetCredentials('authToken');
      token = credentials.password;
    }

    return token;
  } catch (error) {
    console.error('Error retrieving auth token:', error);
    return null;
  }
};

πŸ” Session Debugging

1. Session State Logging

const logSessionState = () => {
  console.log('=== Session State Debug ===');
  console.log('User Data:', userData);
  console.log('Auth Token:', getAuthToken());
  console.log('WebView Focused:', isFocused);
  console.log('Current WebView Ref:', !!currentWebViewRef?.current);
  console.log('Cookies:', CookieManager.get(Config.DOMAIN));
};

// Add to WebView component
useEffect(() => {
  if (__DEV__) {
    logSessionState();
  }
}, [userData, isFocused]);

2. WebView Session Debug

const debugWebViewSession = () => {
  const debugScript = `
    console.log('=== WebView Session Debug ===');
    console.log('Mobile User Data:', window.mobileUserData);
    console.log('FPI Store State:', window.fpi?.store?.getState());
    console.log('Cookies:', document.cookie);
    console.log('Local Storage:', localStorage);
    true;
  `;

  currentWebViewRef?.current?.injectJavaScript(debugScript);
};

πŸš€ Best Practices

1. Session Initialization

  • Always inject cookies before content loads
  • Sync user data when WebView becomes focused
  • Handle authentication state changes properly

2. Error Handling

  • Implement fallback mechanisms for failed authentication
  • Handle network errors gracefully
  • Provide user feedback for session issues

3. Performance

  • Minimize cookie injection overhead
  • Use efficient state synchronization
  • Implement proper cleanup on logout

4. Security

  • Use secure cookie settings
  • Implement proper token storage
  • Validate all session data

πŸ”§ Troubleshooting

Common Session Issues

  1. Cookies not persisting: Check domain and path settings
  2. User data not syncing: Verify WebView focus state
  3. Logout not working: Check event listeners and handlers
  4. Authentication state mismatch: Verify data injection timing

Debug Commands

// Check session state
logSessionState();

// Debug WebView session
debugWebViewSession();

// Force session sync
syncSessionState();

// Clear all session data
await handleLogout();

This comprehensive session management guide ensures proper authentication and state synchronization between native and WebView components.