This codelab demonstrates how to implement Session Management flow using the react-native-rdna-client npm plugin. Session management provides critical security features including automatic session timeout handling, idle session warnings with extension capabilities, and seamless session lifecycle management to prevent unexpected user logouts.

What You'll Learn

Get the Code from GitHub

The code to get started is stored in a GitHub repository.

You can clone the repository using the following command:

git clone https://github.com/uniken-public/codelab-react-native.git

Navigate to the relid-MFA-session-management folder in the repository you cloned earlier

What You'll Need

The sample app provides a complete session management implementation. Let's examine the key components:

Component

Purpose

Sample App Reference

Session Context

Global session state management

src/uniken/SessionContext/SessionContext.tsx

Session Modal

UI with countdown and extension

src/uniken/components/modals/SessionModal.tsx

Event Handling

Extended event manager

src/uniken/services/rdnaEventManager.ts

Session Types

TypeScript interfaces

src/uniken/types/rdnaEvents.ts

Session Management Event Types

The RELID SDK triggers three main session management events:

Event Type

Description

User Action Required

onSessionTimeout

Hard session timeout - session already expired

User must acknowledge and app navigates to home

onSessionTimeOutNotification

Idle session warning - session will expire soon

User can extend session or let it expire

onSessionExtensionResponse

Response from session extension API call

Handle success/failure of extension attempt

Session Management Flow Architecture

The session management flow follows this pattern:

  1. SDK monitors session activity based on gateway configuration
  2. Idle Warning: onSessionTimeOutNotification triggers with countdown and extension option
  3. Extension Request: User can call extendSessionIdleTimeout() API
  4. Extension Response: onSessionExtensionResponse provides success/failure result
  5. Hard Timeout: onSessionTimeout forces app navigation when session expires

Define TypeScript interfaces for comprehensive session timeout handling:

// src/uniken/types/rdnaEvents.ts (additions)

/**
 * RDNA Session Timeout Data
 * Event triggered when session times out (hard timeout)
 */
export interface RDNASessionTimeoutData {
  message: string;
}

/**
 * RDNA Session Timeout Notification Data
 * Event triggered before session timeout with extension option
 */
export interface RDNASessionTimeoutNotificationData {
  userID: string;
  message: string;
  timeLeftInSeconds: number;
  sessionCanBeExtended: number; // 0 = cannot extend, 1 = can extend
  info: {
    sessionType: number;
    currentWorkFlow: string;
  };
}

/**
 * RDNA Session Extension Response Data
 * Response received after attempting to extend session timeout
 */
export interface RDNASessionExtensionResponseData {
  status: RDNAStatus;
  error: RDNAError;
}

// Session Management Callback Types
export type RDNASessionTimeoutCallback = (data: RDNASessionTimeoutData) => void;
export type RDNASessionTimeoutNotificationCallback = (data: RDNASessionTimeoutNotificationData) => void;
export type RDNASessionExtensionResponseCallback = (data: RDNASessionExtensionResponseData) => void;

Understanding Session Timeout Types

Session management handles two distinct scenarios:

Session Type

Trigger

User Options

Implementation

Hard Timeout

Session already expired

Close button only

Navigate to home screen

Idle Warning

Session expiring soon

Extend or Close

API call or natural expiry

Extend your existing event manager to handle session management events:

// src/uniken/services/rdnaEventManager.ts (additions)
class RdnaEventManager {
  // Add session management callback properties
  private sessionTimeoutHandler?: RDNASessionTimeoutCallback;
  private sessionTimeoutNotificationHandler?: RDNASessionTimeoutNotificationCallback;
  private sessionExtensionResponseHandler?: RDNASessionExtensionResponseCallback;

  private registerEventListeners() {
    // ... existing listeners ...

    // Add session management event listeners
    this.listeners.push(
      this.rdnaEmitter.addListener('onSessionTimeout', this.onSessionTimeout.bind(this)),
      this.rdnaEmitter.addListener('onSessionTimeOutNotification', this.onSessionTimeOutNotification.bind(this)),
      this.rdnaEmitter.addListener('onSessionExtensionResponse', this.onSessionExtensionResponse.bind(this))
    );
  }

  /**
   * Handles session timeout events for mandatory sessions
   */
  private onSessionTimeout(response: RDNAJsonResponse) {
    console.log("RdnaEventManager - Session timeout event received");

    try {
      let sessionTimeoutData: RDNASessionTimeoutData;
      
      if (typeof response.response === 'string') {
        // Treat the string as a plain message
        sessionTimeoutData = {
          message: response.response
        };
      } else {
        // If it's already an object, use it directly
        sessionTimeoutData = response.response as RDNASessionTimeoutData;
      }
      
      console.log("RdnaEventManager - Session timeout message:", sessionTimeoutData.message);

      if (this.sessionTimeoutHandler) {
        this.sessionTimeoutHandler(sessionTimeoutData);
      }
    } catch (error) {
      console.error("RdnaEventManager - Failed to handle session timeout:", error);
    }
  }

  /**
   * Handles session timeout notification events for idle sessions
   */
  private onSessionTimeOutNotification(response: RDNAJsonResponse) {
    console.log("RdnaEventManager - Session timeout notification event received");

    try {
      const sessionNotificationData: RDNASessionTimeoutNotificationData = JSON.parse(response.response);
      console.log("RdnaEventManager - Session timeout notification:", {
        userID: sessionNotificationData.userID,
        timeLeft: sessionNotificationData.timeLeftInSeconds,
        canExtend: sessionNotificationData.sessionCanBeExtended === 1
      });

      if (this.sessionTimeoutNotificationHandler) {
        this.sessionTimeoutNotificationHandler(sessionNotificationData);
      }
    } catch (error) {
      console.error("RdnaEventManager - Failed to parse session timeout notification:", error);
    }
  }

  /**
   * Handles session extension response events
   */
  private onSessionExtensionResponse(response: RDNAJsonResponse) {
    console.log("RdnaEventManager - Session extension response event received");

    try {
      const sessionExtensionData: RDNASessionExtensionResponseData = JSON.parse(response.response);
      console.log("RdnaEventManager - Session extension response:", {
        statusCode: sessionExtensionData.status.statusCode,
        statusMessage: sessionExtensionData.status.statusMessage,
        errorCode: sessionExtensionData.error.longErrorCode
      });

      if (this.sessionExtensionResponseHandler) {
        this.sessionExtensionResponseHandler(sessionExtensionData);
      }
    } catch (error) {
      console.error("RdnaEventManager - Failed to parse session extension response:", error);
    }
  }

  // Handler setter methods
  public setSessionTimeoutHandler(callback?: RDNASessionTimeoutCallback): void {
    this.sessionTimeoutHandler = callback;
  }

  public setSessionTimeoutNotificationHandler(callback?: RDNASessionTimeoutNotificationCallback): void {
    this.sessionTimeoutNotificationHandler = callback;
  }

  public setSessionExtensionResponseHandler(callback?: RDNASessionExtensionResponseCallback): void {
    this.sessionExtensionResponseHandler = callback;
  }
}

Key features of session event handling:

Add session extension capability to your RELID service:

// src/uniken/services/rdnaService.ts (addition)
export class RdnaService {
  /**
   * Extends the idle session timeout
   *
   * This method extends the current idle session timeout when the session is eligible for extension.
   * Should be called in response to onSessionTimeOutNotification events when sessionCanBeExtended = 1.
   * After calling this method, the SDK will trigger an onSessionExtensionResponse event with the result.
   *
   * @see https://developer.uniken.com/docs/extend-session-timeout
   *
   * Response Validation Logic:
   * 1. Check error.longErrorCode: 0 = success, > 0 = error
   * 2. An onSessionExtensionResponse event will be triggered with detailed response
   * 3. The extension success/failure will be determined by the async event response
   *
   * @returns Promise<RDNASyncResponse> that resolves with sync response structure
   */
  async extendSessionIdleTimeout(): Promise<RDNASyncResponse> {
    return new Promise((resolve, reject) => {
      console.log('RdnaService - Extending session idle timeout');
      
      RdnaClient.extendSessionIdleTimeout(response => {
        console.log('RdnaService - ExtendSessionIdleTimeout sync callback received');

        const result: RDNASyncResponse = response;
        
        if (result.error && result.error.longErrorCode === 0) {
          console.log('RdnaService - ExtendSessionIdleTimeout sync response success, waiting for onSessionExtensionResponse event');
          resolve(result);
        } else {
          console.error('RdnaService - ExtendSessionIdleTimeout sync response error:', result);
          reject(result);
        }
      });
    });
  }
}

Important Session Extension Logic

When handling session extension, two response layers must be considered:

Response Layer

Purpose

Success Criteria

Failure Handling

Sync Response

API call validation

error.longErrorCode === 0

Immediate rejection

Async Event

Extension result

status.statusCode === 100 & error.longErrorCode === 0

Display error message

Note: The sync response only indicates the API call was accepted. The actual extension success/failure is communicated through the onSessionExtensionResponse event.

Create a React Context to manage session state across your application:

// src/uniken/SessionContext/SessionContext.tsx
interface SessionState {
  // Modal visibility and data
  isSessionModalVisible: boolean;
  sessionTimeoutData?: RDNASessionTimeoutData;
  sessionTimeoutNotificationData?: RDNASessionTimeoutNotificationData;
  isProcessing: boolean;

  // Session management methods
  showSessionTimeout: (data: RDNASessionTimeoutData) => void;
  showSessionTimeoutNotification: (data: RDNASessionTimeoutNotificationData) => void;
  hideSessionModal: () => void;
  handleExtendSession: () => void;
  handleDismiss: () => void;
}

export const SessionProvider: React.FC<SessionProviderProps> = ({ children }) => {
  const [isSessionModalVisible, setIsSessionModalVisible] = useState(false);
  const [sessionTimeoutData, setSessionTimeoutData] = useState<RDNASessionTimeoutData | undefined>();
  const [sessionTimeoutNotificationData, setSessionTimeoutNotificationData] = useState<RDNASessionTimeoutNotificationData | undefined>();
  const [isProcessing, setIsProcessing] = useState(false);

  // Ref to track current session operation to avoid conflicts
  const currentOperationRef = useRef<'none' | 'extend'>('none');

  useEffect(() => {
    const eventManager = rdnaService.getEventManager();

    // Set up session event handlers
    eventManager.setSessionTimeoutHandler((data: RDNASessionTimeoutData) => {
      showSessionTimeout(data);
    });

    eventManager.setSessionTimeoutNotificationHandler((data: RDNASessionTimeoutNotificationData) => {
      showSessionTimeoutNotification(data);
    });

    eventManager.setSessionExtensionResponseHandler((data: RDNASessionExtensionResponseData) => {
      handleSessionExtensionResponse(data);
    });

    return () => {
      // Cleanup handlers
      eventManager.setSessionTimeoutHandler(undefined);
      eventManager.setSessionTimeoutNotificationHandler(undefined);
      eventManager.setSessionExtensionResponseHandler(undefined);
    };
  }, []);
};

Session Extension Logic

The context handles session extension with comprehensive state management:

const handleExtendSession = async () => {
  console.log('SessionProvider - User chose to extend session');
  
  if (currentOperationRef.current !== 'none') {
    console.log('SessionProvider - Operation already in progress, ignoring extend request');
    return;
  }

  setIsProcessing(true);
  currentOperationRef.current = 'extend';

  try {
    // Call extend session API
    await rdnaService.extendSessionIdleTimeout();
    console.log('SessionProvider - Session extension API called successfully');
    
    // Note: We don't hide the modal immediately as we're waiting for onSessionExtensionResponse
    // The response handler will determine success/failure and take appropriate action
  } catch (error) {
    console.error('SessionProvider - Session extension failed:', error);
    setIsProcessing(false);
    currentOperationRef.current = 'none';
    
    const result: RDNASyncResponse = error as RDNASyncResponse;
    Alert.alert(
      'Extension Failed',
      `Failed to extend session:\n${result.error.errorString}\n\nError Code: ${result.error.longErrorCode}`,
      [{ text: 'OK', style: 'default' }]
    );
  }
};

const handleSessionExtensionResponse = (data: RDNASessionExtensionResponseData) => {
  console.log('SessionProvider - Processing session extension response');
  
  // Only process if we're currently extending
  if (currentOperationRef.current !== 'extend') {
    console.log('SessionProvider - Extension response received but no extend operation in progress, ignoring');
    return;
  }

  const isSuccess = data.error.longErrorCode === 0 && data.status.statusCode === 100;
  
  if (isSuccess) {
    console.log('SessionProvider - Session extension successful');
    hideSessionModal();
  } else {
    console.log('SessionProvider - Session extension failed');
    
    setIsProcessing(false);
    currentOperationRef.current = 'none';
    
    const errorMessage = data.error.longErrorCode !== 0 
      ? data.error.errorString 
      : data.status.statusMessage;
    
    Alert.alert(
      'Extension Failed',
      `Failed to extend session:\n${errorMessage}`,
      [{ text: 'OK', style: 'default' }]
    );
  }
};

Key features of the session context:

Create a modal component to display session information and handle user interactions:

// src/uniken/components/modals/SessionModal.tsx
interface SessionModalProps {
  visible: boolean;
  sessionTimeoutData?: RDNASessionTimeoutData;
  sessionTimeoutNotificationData?: RDNASessionTimeoutNotificationData;
  isProcessing?: boolean;
  onExtendSession?: () => void;
  onDismiss?: () => void;
}

const SessionModal: React.FC<SessionModalProps> = ({
  visible,
  sessionTimeoutData,
  sessionTimeoutNotificationData,
  isProcessing = false,
  onExtendSession,
  onDismiss,
}) => {
  const [countdown, setCountdown] = useState<number>(0);
  const backgroundTimeRef = useRef<number | null>(null);
  
  // Determine session type
  const isMandatoryTimeout = !!sessionTimeoutData;
  const isIdleTimeout = !!sessionTimeoutNotificationData;
  const canExtendSession = sessionTimeoutNotificationData?.sessionCanBeExtended === 1;

  // Initialize countdown from notification data
  useEffect(() => {
    if (sessionTimeoutNotificationData?.timeLeftInSeconds) {
      const timeLeft = sessionTimeoutNotificationData.timeLeftInSeconds;
      setCountdown(timeLeft);
    }
  }, [sessionTimeoutNotificationData]);

  // Countdown timer effect
  useEffect(() => {
    if (countdown > 0 && (isIdleTimeout || isMandatoryTimeout) && visible) {
      const timer = setTimeout(() => {
        setCountdown(prev => prev - 1);
      }, 1000);
      return () => clearTimeout(timer);
    }
  }, [countdown, isIdleTimeout, isMandatoryTimeout, visible]);
};

Background/Foreground Timer Handling

Critical feature for accurate countdown when app goes to background:

// Handle app state changes for accurate countdown when app goes to background/foreground
useEffect(() => {
  const handleAppStateChange = (nextAppState: string) => {
    if (visible && (isIdleTimeout || isMandatoryTimeout)) {
      if (nextAppState === 'background' || nextAppState === 'inactive') {
        // App going to background - record the time
        backgroundTimeRef.current = Date.now();
        console.log('SessionModal - App going to background, recording time');
      } else if (nextAppState === 'active' && backgroundTimeRef.current) {
        // App returning to foreground - calculate elapsed time
        const elapsedSeconds = Math.floor((Date.now() - backgroundTimeRef.current) / 1000);
        console.log(`SessionModal - App returning to foreground, elapsed: ${elapsedSeconds}s`);
        
        // Update countdown based on actual elapsed time
        setCountdown(prevCountdown => {
          const newCountdown = Math.max(0, prevCountdown - elapsedSeconds);
          console.log(`SessionModal - Countdown updated: ${prevCountdown} -> ${newCountdown}`);
          return newCountdown;
        });
        
        backgroundTimeRef.current = null;
      }
    }
  };

  const subscription = AppState.addEventListener('change', handleAppStateChange);
  
  return () => {
    subscription?.remove();
  };
}, [visible, isIdleTimeout, isMandatoryTimeout]);

Platform-Specific Back Button Handling

Prevent accidental modal dismissal on Android:

// Disable back button when modal is visible
useEffect(() => {
  const handleBackPress = () => {
    if (visible) {
      return true; // Prevent default back action
    }
    return false;
  };

  const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackPress);
  return () => backHandler.remove();
}, [visible]);

Modal UI Implementation

return (
  <Modal visible={visible} transparent={true} animationType="fade">
    <View style={styles.modalOverlay}>
      <View style={styles.modalContainer}>
        {/* Header with session type indicator */}
        <View style={[styles.modalHeader, { backgroundColor: config.headerColor }]}>
          <Text style={styles.modalTitle}>
            {isMandatoryTimeout ? '🔐 Session Expired' : '⚠️ Session Timeout Warning'}
          </Text>
          <Text style={styles.modalSubtitle}>
            {isMandatoryTimeout 
              ? 'Your session has expired. You will be redirected to the home screen.'
              : canExtendSession 
                ? 'Your session will expire soon. You can extend it or let it timeout.'
                : 'Your session will expire soon.'
            }
          </Text>
        </View>

        {/* Content with countdown for idle timeout */}
        <View style={styles.contentContainer}>
          <Text style={styles.sessionMessage}>
            {getDisplayMessage()}
          </Text>

          {/* Countdown display for idle timeout */}
          {isIdleTimeout && countdown > 0 && (
            <View style={styles.countdownContainer}>
              <Text style={styles.countdownLabel}>Time Remaining:</Text>
              <Text style={styles.countdownText}>
                {Math.floor(countdown / 60)}:{(countdown % 60).toString().padStart(2, '0')}
              </Text>
            </View>
          )}
        </View>

        {/* Action Buttons */}
        <View style={styles.buttonContainer}>
          {/* Hard timeout - only Close option */}
          {isMandatoryTimeout && (
            <TouchableOpacity style={styles.secondaryButton} onPress={onDismiss}>
              <Text style={styles.secondaryButtonText}>Close</Text>
            </TouchableOpacity>
          )}

          {/* Idle timeout - extend or dismiss options */}
          {isIdleTimeout && (
            <>
              {canExtendSession && onExtendSession && (
                <TouchableOpacity 
                  style={[styles.primaryButton, isProcessing && styles.buttonDisabled]}
                  onPress={onExtendSession}
                  disabled={isProcessing}
                >
                  {isProcessing ? (
                    <View style={styles.buttonLoadingContent}>
                      <ActivityIndicator size="small" color="#ffffff" />
                      <Text style={styles.primaryButtonText}>Extending...</Text>
                    </View>
                  ) : (
                    <Text style={styles.primaryButtonText}>Extend Session</Text>
                  )}
                </TouchableOpacity>
              )}
              
              <TouchableOpacity 
                style={[styles.secondaryButton, isProcessing && styles.buttonDisabled]}
                onPress={onDismiss}
                disabled={isProcessing}
              >
                <Text style={styles.secondaryButtonText}>Close</Text>
              </TouchableOpacity>
            </>
          )}
        </View>
      </View>
    </View>
  </Modal>
);

Key features of the session modal:

The following image showcase screens from the sample application:

Session Extend Screen

Session Timeout Screen

Wrap your application with the session context provider:

// App.tsx
import SessionProvider from './src/uniken/SessionContext';
import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './src/tutorial/navigation/NavigationService';

const AppContent = () => {
  const {
    isSessionModalVisible,
    sessionTimeoutData,
    sessionTimeoutNotificationData,
    isProcessing,
    handleExtendSession,
    handleDismiss,
  } = useSession();

  return (
    <>
      <NavigationContainer ref={navigationRef}>
        {/* Your existing navigation */}
      </NavigationContainer>

      {/* Global Session Management Modal */}
      <SessionModal
        visible={isSessionModalVisible}
        sessionTimeoutData={sessionTimeoutData}
        sessionTimeoutNotificationData={sessionTimeoutNotificationData}
        isProcessing={isProcessing}
        onExtendSession={handleExtendSession}
        onDismiss={handleDismiss}
      />
    </>
  );
};

function App() {
  return (
    <SessionProvider>
      <AppContent />
    </SessionProvider>
  );
}

Integration Benefits

The context provider approach offers several advantages:

Test Scenarios

  1. Hard Session Timeout: Test mandatory session expiration
  2. Idle Session Timeout with Extension: Test warning with successful extension
  3. Extension Failure: Test extension API failure handling
  4. Background/Foreground Timer: Test countdown accuracy across app states
  5. Multiple Session Events: Test handling of rapid session events

Common Session Management Test Scenarios

Session Type

Test Case

Expected Behavior

Validation Points

Hard Timeout

Session expires

Modal with Close button only

Navigate to home screen

Idle Warning

Session expiring soon

Modal with countdown and Extend button

Extension API call works

Extension Success

Extend session API succeeds

Modal dismisses, session continues

No navigation occurs

Extension Failure

Extend session API fails

Error alert, modal remains

User can retry or close

Background Timer

App goes to background during countdown

Timer accurately reflects elapsed time

Countdown resumes correctly

Testing Background/Foreground Accuracy

Critical test for production reliability:

// Test background/foreground timer accuracy
const testBackgroundTimer = async () => {
  // 1. Trigger idle session timeout notification
  // 2. Note the countdown time (e.g., 60 seconds)
  // 3. Background the app for 30 seconds
  // 4. Foreground the app
  // 5. Verify countdown shows ~30 seconds remaining
  
  console.log('Testing background timer accuracy');
  console.log('Initial countdown:', initialCountdown);
  console.log('Time spent in background:', backgroundTime);
  console.log('Expected remaining time:', initialCountdown - backgroundTime);
  console.log('Actual remaining time:', currentCountdown);
};

Debugging Session Events

Use these debugging techniques to verify session functionality:

// Verify callback registration
console.log('Session callbacks:', {
  timeout: !!eventManager.sessionTimeoutHandler,
  notification: !!eventManager.sessionTimeoutNotificationHandler,
  extension: !!eventManager.sessionExtensionResponseHandler
});

// Log session event data
console.log('Session timeout notification:', {
  userID: data.userID,
  timeLeft: data.timeLeftInSeconds,
  canExtend: data.sessionCanBeExtended === 1,
  message: data.message
});

Modal Not Appearing

Cause: Session callbacks not properly registered Solution: Verify SessionProvider wraps your app and callbacks are set

Cause: Event listeners not attached Solution: Check that rdnaEmitter.addListener calls are successful

Timer Accuracy Issues

Cause: Countdown doesn't account for background time Solution: Implement AppState change handling

// Correct background/foreground handling
useEffect(() => {
  const handleAppStateChange = (nextAppState: string) => {
    if (nextAppState === 'background') {
      backgroundTimeRef.current = Date.now();
    } else if (nextAppState === 'active' && backgroundTimeRef.current) {
      const elapsedSeconds = Math.floor((Date.now() - backgroundTimeRef.current) / 1000);
      setCountdown(prev => Math.max(0, prev - elapsedSeconds));
      backgroundTimeRef.current = null;
    }
  };
  
  const subscription = AppState.addEventListener('change', handleAppStateChange);
  return () => subscription?.remove();
}, []);

Extension API Failures

Cause: Calling extension API when sessionCanBeExtended is false Solution: Check extension eligibility before API call

const handleExtendSession = async () => {
  if (sessionTimeoutNotificationData?.sessionCanBeExtended !== 1) {
    Alert.alert('Extension Not Available', 'This session cannot be extended.');
    return;
  }
  
  // Proceed with extension API call
  await rdnaService.extendSessionIdleTimeout();
};

Cause: Multiple concurrent extension requests Solution: Use operation tracking to prevent duplicates

const currentOperationRef = useRef<'none' | 'extend'>('none');

const handleExtendSession = async () => {
  if (currentOperationRef.current !== 'none') {
    console.log('Extension already in progress');
    return;
  }
  
  currentOperationRef.current = 'extend';
  // ... perform extension
};

Modal Dismissal Issues

Cause: Back button dismissing modal on Android Solution: Implement BackHandler prevention

useEffect(() => {
  const handleBackPress = () => {
    if (visible) {
      return true; // Prevent default back action
    }
    return false;
  };

  const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackPress);
  return () => backHandler.remove();
}, [visible]);

Best Practice: Test session management behavior on both iOS and Android devices with different timeout scenarios.

Important Security Guidelines

  1. Never bypass session timeouts - Always respect hard timeout requirements
  2. Limit extension attempts - Implement reasonable limits on extension requests
  3. Log session events securely - Track session management for security analysis
  4. Keep SDK updated - Regular updates include latest session security features
  5. Test thoroughly - Verify session behavior across different usage patterns

Session Extension Guidelines

Extension Scenario

Recommended Action

Implementation

Frequent Extensions

Set reasonable limits

Track extension count per session

Critical Operations

Allow extensions during important tasks

Context-aware extension logic

Inactive Sessions

Enforce timeouts

Don't extend completely idle sessions

Memory and Performance

// Proper cleanup in SessionProvider
useEffect(() => {
  const eventManager = rdnaService.getEventManager();
  
  // Set handlers
  eventManager.setSessionTimeoutHandler(handleSessionTimeout);
  
  // Cleanup function
  return () => {
    eventManager.setSessionTimeoutHandler(undefined);
    eventManager.setSessionTimeoutNotificationHandler(undefined);  
    eventManager.setSessionExtensionResponseHandler(undefined);
  };
}, []);

Session Data Protection

Congratulations! You've successfully learned how to implement comprehensive session management functionality with:

Key Security Benefits

Your session management implementation now provides:

Next Steps