Welcome to the REL-ID Additional Device Activation codelab! This tutorial builds upon the foundational MFA implementation to add sophisticated device onboarding capabilities using REL-ID Verify's push notification system.

What You'll Build

In this codelab, you'll enhance your existing MFA application with:

What You'll Learn

By completing this codelab, you'll master:

  1. Advanced SDK Event Handling: Managing addNewDeviceOptions events and device activation flows
  2. REL-ID Verify Workflows: Implementing automatic push notification-based device approval
  3. Fallback Strategies: Building robust alternative activation methods for various user scenarios
  4. Notification Systems: Creating comprehensive server notification management with user interactions
  5. Enhanced Navigation: Integrating drawer navigation with notification access points
  6. Production Patterns: Implementing error handling, status management, and user experience optimizations

Prerequisites

Before starting this codelab, ensure you have:

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-additional-device-activation folder in the repository you cloned earlier

Codelab Architecture Overview

This codelab extends your MFA application with three core device activation components:

  1. VerifyAuthScreen: Automatic REL-ID Verify activation with real-time status updates
  2. GetNotificationsScreen: Server notification management with interactive action modals
  3. Enhanced Event Handling: addNewDeviceOptions event processing and navigation coordination

Before implementing device activation screens, let's understand the key SDK events and APIs that power the additional device activation workflow.

Device Activation Event Flow

The device activation process follows this event-driven pattern:

User Completes MFA on Primary Device → SDK Detects New Device On Secondary Device → addNewDeviceOptions Event → VerifyAuthScreen → 
Push Notifications Sent → User Approves the Notification On  Primary Device → Continue MFA Flow -> Device Activated

Core Device Activation Types

Add these TypeScript definitions to understand device activation data structures:

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

/**
 * Device activation options data structure
 * Triggered when SDK detects unregistered device during authentication
 */
export interface RDNAAddNewDeviceOptionsData {
  userID: string;
  newDeviceOptions: string[];
  challengeInfo: RDNAChallengeInfo[];
}

/**
 * RDNA Notification Body
 * Localized content for notification
 */
export interface RDNANotificationBody {
  lng: string;
  subject: string;
  message: string;
  label: Record<string, string>;
}

/**
 * RDNA Notification Action
 * Available actions for notification
 */
export interface RDNANotificationAction {
  label: string;
  action: string;
  authlevel: string;
}

/**
 * RDNA Notification Item
 * Individual notification structure from API response
 */
export interface RDNANotificationItem {
  notification_uuid: string;
  create_ts: string;
  expiry_timestamp: string;
  create_ts_epoch: number;
  expiry_timestamp_epoch: number;
  body: RDNANotificationBody[];
  actions: RDNANotificationAction[];
  action_performed: string;
  ds_required: boolean;
}

/**
 * RDNA Notification Response Data
 * Response structure for notifications API
 */
export interface RDNANotificationResponseData {
  notifications: RDNANotificationItem[];
  start: string;
  count: string;
  total: string;
}

/**
 * RDNA Get Notifications Data
 * Unified notification response structure for onGetNotifications event
 */
export interface RDNAGetNotificationsData {
  errCode?: number;
  error?: RDNAError;
  eMethId?: number;
  userID?: string;
  challengeMode?: number;
  authenticationType?: number;
  challengeResponse?: RDNAChallengeResponse;
  pArgs?: {
    service_details: any;
    response: {
      ResponseData: RDNANotificationResponseData;
      ResponseDataLen: number;
      StatusMsg: string;
      StatusCode: number;
      CredOpMode: number;
    };
    pxyDetails: {
      isStarted: number;
      isLocalhostOnly: number;
      isAutoStarted: number;
      isPrivacyEnabled: number;
      portType: number;
      port: number;
    };
  };
}

/**
 * RDNA Update Notification Response Data
 * Response data structure for notification update
 */
export interface RDNAUpdateNotificationResponseData {
  status_code: number;
  message: string;
  notification_uuid: string;
  is_ds_verified: boolean;
}

/**
 * RDNA Update Notification Data
 * Complete response structure for onUpdateNotification event
 */
export interface RDNAUpdateNotificationData {
  errCode: number;
  error: RDNAError;
  eMethId: number;
  pArgs: {
    service_details: any;
    response: {
      ResponseData: RDNAUpdateNotificationResponseData;
      ResponseDataLen: number;
      StatusMsg: string;
      StatusCode: number;
      CredOpMode: number;
    };
    pxyDetails: {
      isStarted: number;
      isLocalhostOnly: number;
      isAutoStarted: number;
      isPrivacyEnabled: number;
      portType: number;
      port: number;
    };
  };
}

Device Activation Callback Types

Define callback types for device activation events:

// Callback type definitions for device activation events
export type RDNAAddNewDeviceOptionsCallback = (data: RDNAAddNewDeviceOptionsData) => void;
export type RDNAGetNotificationsCallback = (data: RDNAGetNotificationsData) => void;
export type RDNAUpdateNotificationCallback = (data: RDNAUpdateNotificationData) => void;

Understanding addNewDeviceOptions Event

The addNewDeviceOptions event is the cornerstone of device activation:

When It Triggers

REL-ID Verify Workflow

REL-ID Verify enables secure device-to-device approval:

  1. Push Notification Sent: SDK sends approval request to user's registered devices
  2. User Receives Notification: Registered device shows activation approval request
  3. User Approves/Rejects: User makes decision on registered device
  4. Response Processed: New device receives approval status
  5. Activation Completed: Device registration finalized, MFA flow continues

Enhance your existing RdnaService with device activation APIs. These methods handle REL-ID Verify workflows and notification management.

Adding Device Activation APIs

Extend your RdnaService class with these device activation methods:

// src/uniken/services/rdnaService.ts (device activation additions)

/**
 * Performs REL-ID Verify authentication for device activation
 * Sends push notifications to registered devices for approval
 * @param verifyAuthStatus User's decision (true = proceed with verification, false = cancel)
 * @returns Promise<RDNASyncResponse>
 */
async performVerifyAuth(verifyAuthStatus: boolean): Promise<RDNASyncResponse> {
  return new Promise((resolve, reject) => {
    console.log('RdnaService - Performing verify auth with status:', verifyAuthStatus);
    
    RdnaClient.performVerifyAuth(verifyAuthStatus, response => {
      console.log('RdnaService - PerformVerifyAuth sync callback received');
      
      const result: RDNASyncResponse = response;
      
      if (result.error && result.error.longErrorCode === 0) {
        console.log('RdnaService - PerformVerifyAuth sync response success, waiting for async events');
        resolve(result);
      } else {
        console.error('RdnaService - PerformVerifyAuth sync response error:', result);
        reject(result);
      }
    });
  });
}

/**
 * Initiates fallback device activation flow
 * Alternative method when REL-ID Verify is not available/accessible
 * @returns Promise<RDNASyncResponse>
 */
async fallbackNewDeviceActivationFlow(): Promise<RDNASyncResponse> {
  return new Promise((resolve, reject) => {
    console.log('RdnaService - Starting fallback new device activation flow');
    
    RdnaClient.fallbackNewDeviceActivationFlow(response => {
      console.log('RdnaService - FallbackNewDeviceActivationFlow sync callback received');
      
      const result: RDNASyncResponse = response;
      
      if (result.error && result.error.longErrorCode === 0) {
        console.log('RdnaService - FallbackNewDeviceActivationFlow sync response success, alternative activation started');
        resolve(result);
      } else {
        console.error('RdnaService - FallbackNewDeviceActivationFlow sync response error:', result);
        reject(result);
      }
    });
  });
}

/**
 * Retrieves server notifications for the current user
 * Loads all pending notifications with actions
 * @param recordCount Number of records to fetch (0 = all active notifications)
 * @param startIndex Index to begin fetching from (must be >= 1)
 * @param startDate Start date filter (optional)
 * @param endDate End date filter (optional)
 * @returns Promise<RDNASyncResponse>
 */
async getNotifications(recordCount: number = 0, startIndex: number = 1, startDate: string = '', endDate: string = ''): Promise<RDNASyncResponse> {
  return new Promise((resolve, reject) => {
    console.log('RdnaService - Fetching notifications with recordCount:', recordCount, 'startIndex:', startIndex);
    
    RdnaClient.getNotifications(
      recordCount,     // recordCount
      '',              // enterpriseID (optional)
      startIndex,      // startIndex 
      startDate,       // startDate (optional)
      endDate,         // endDate (optional)
      response => {    // syncCallback
        console.log('RdnaService - GetNotifications sync callback received');

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

/**
 * Updates a notification with user action
 * Processes user decision on notification actions
 * @param notificationId Notification identifier (UUID)
 * @param response Action response value selected by user
 * @returns Promise<RDNASyncResponse>
 */
async updateNotification(notificationId: string, response: string): Promise<RDNASyncResponse> {
  return new Promise((resolve, reject) => {
    console.log('RdnaService - Updating notification:', notificationId, 'with response:', response);
    
    RdnaClient.updateNotification(
      notificationId,  // notificationId
      response,        // response
      result => {      // syncCallback
        console.log('RdnaService - UpdateNotification sync callback received');

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

Understanding Device Activation APIs

performVerifyAuth API

fallbackNewDeviceActivationFlow API

getNotifications API

updateNotification API

API Response Pattern

All device activation APIs follow the established REL-ID SDK pattern:

  1. Immediate Sync Response: Indicates if API call was accepted by SDK
  2. Success Check: longErrorCode === 0 means API call succeeded
  3. Async Event Processing: Actual results delivered via SDK events
  4. Error Handling: Sync errors rejected as Promise failures

Enhance your existing event manager to handle device activation events. Add support for addNewDeviceOptions, notification retrieval, and notification updates.

Adding Device Activation Event Handlers

Extend your RdnaEventManager class with device activation event handling:

// src/uniken/services/rdnaEventManager.ts (device activation additions)

class RdnaEventManager {
  // Add device activation event handlers
  private addNewDeviceOptionsHandler?: RDNAAddNewDeviceOptionsCallback;
  private getNotificationsHandler?: RDNAGetNotificationsCallback;
  private updateNotificationHandler?: RDNAUpdateNotificationCallback;

  private registerEventListeners() {
    // ... existing MFA and MTD listeners ...

    // Register device activation event listeners
    this.listeners.push(
      this.rdnaEmitter.addListener('addNewDeviceOptions', this.onAddNewDeviceOptions.bind(this)),
      this.rdnaEmitter.addListener('getNotifications', this.onGetNotifications.bind(this)),
      this.rdnaEmitter.addListener('updateNotification', this.onUpdateNotification.bind(this))
    );
  }
}

Device Activation Event Implementations

Add these event handler methods to your event manager:

/**
 * Handles device activation options event
 * Triggered when SDK detects unregistered device during authentication
 */
private onAddNewDeviceOptions(response: RDNAJsonResponse) {
  console.log("RdnaEventManager - Add new device options event received");
  
  try {
    const addNewDeviceOptionsData: RDNAAddNewDeviceOptionsData = JSON.parse(response.response);
    console.log("RdnaEventManager - UserID:", addNewDeviceOptionsData.userID);
    console.log("RdnaEventManager - Available options:", addNewDeviceOptionsData.newDeviceOptions.length);
    console.log("RdnaEventManager - Challenge info count:", addNewDeviceOptionsData.challengeInfo.length);
    
    // Log each activation option for debugging
    addNewDeviceOptionsData.newDeviceOptions.forEach((option, index) => {
      console.log(`RdnaEventManager - Option ${index + 1}:`, option);
    });
    
    if (this.addNewDeviceOptionsHandler) {
      this.addNewDeviceOptionsHandler(addNewDeviceOptionsData);
    }
  } catch (error) {
    console.error("RdnaEventManager - Failed to parse add new device options:", error);
  }
}

/**
 * Handles get notifications response
 * Triggered after getNotifications API call completes
 */
private onGetNotifications(response: RDNAJsonResponse) {
  console.log("RdnaEventManager - Get notifications event received");
  
  try {
    const getNotificationsData: RDNAGetNotificationsData = JSON.parse(response.response);
    
    console.log("RdnaEventManager - Get notifications data:", {
      errCode: getNotificationsData.errCode,
      userID: getNotificationsData.userID,
      notificationCount: getNotificationsData.pArgs?.response?.ResponseData?.notifications?.length || 0
    });
    
    if (this.getNotificationsHandler) {
      this.getNotificationsHandler(getNotificationsData);
    }
  } catch (error) {
    console.error("RdnaEventManager - Failed to parse get notifications:", error);
  }
}

/**
 * Handles update notification response
 * Triggered after updateNotification API call completes
 */
private onUpdateNotification(response: RDNAJsonResponse) {
  console.log("RdnaEventManager - Update notification event received");
  
  try {
    const updateNotificationData: RDNAUpdateNotificationData = JSON.parse(response.response);
    
    console.log("RdnaEventManager - Update notification data:", {
      errCode: updateNotificationData.errCode,
      statusCode: updateNotificationData.pArgs?.response?.StatusCode,
      statusMsg: updateNotificationData.pArgs?.response?.StatusMsg
    });
    
    if (this.updateNotificationHandler) {
      this.updateNotificationHandler(updateNotificationData);
    }
  } catch (error) {
    console.error("RdnaEventManager - Failed to parse update notification:", error);
  }
}

Event Handler Registration Methods

Add public methods for setting device activation event handlers:

// Public setter methods for device activation event handlers
public setAddNewDeviceOptionsHandler(callback?: RDNAAddNewDeviceOptionsCallback): void {
  this.addNewDeviceOptionsHandler = callback;
}

public setGetNotificationsHandler(callback?: RDNAGetNotificationsCallback): void {
  this.getNotificationsHandler = callback;
}

public setUpdateNotificationHandler(callback?: RDNAUpdateNotificationCallback): void {
  this.updateNotificationHandler = callback;
}

// Enhanced cleanup method to clear device activation handlers
public clearDeviceActivationHandlers(): void {
  this.addNewDeviceOptionsHandler = undefined;
  this.getNotificationsHandler = undefined;
  this.updateNotificationHandler = undefined;
}

// Enhanced cleanup method to clear all handlers
public cleanup(): void {
  // Clear existing MFA handlers
  this.clearActivationHandlers();
  
  // Clear device activation handlers
  this.clearDeviceActivationHandlers();
  
  // Clear existing MTD handlers
  this.clearMTDHandlers();
  
  // Remove all event listeners
  this.listeners.forEach(listener => listener.remove());
  this.listeners = [];
}

Understanding Device Activation Events

addNewDeviceOptions Event

getNotifications Event

updateNotification Event

Event Manager Integration Pattern

The device activation events integrate with existing event management:

// Example of comprehensive event setup in SDKEventProvider
useEffect(() => {
  const eventManager = rdnaService.getEventManager();
  
  // Existing MFA event handlers
  eventManager.setGetUserHandler(handleGetUser);
  eventManager.setGetPasswordHandler(handleGetPassword);
  // ... other MFA handlers ...
  
  // Device activation event handlers
  eventManager.setAddNewDeviceOptionsHandler(handleAddNewDeviceOptions);
  eventManager.setGetNotificationsHandler(handleGetNotifications);
  eventManager.setUpdateNotificationHandler(handleUpdateNotification);
  
  // Cleanup on unmount
  return () => {
    eventManager.cleanup();
  };
}, []);

Create the VerifyAuthScreen that handles REL-ID Verify device activation with automatic push notification processing and fallback options.

VerifyAuthScreen Component Structure

// src/tutorial/screens/mfa/VerifyAuthScreen.tsx
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  StyleSheet,
  StatusBar,
  ScrollView,
  SafeAreaView,
} from 'react-native';
import { useRoute } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native';
import type { RDNAAddNewDeviceOptionsData, RDNASyncResponse } from '../../../uniken/types/rdnaEvents';
import { RDNASyncUtils } from '../../../uniken/types/rdnaEvents';
import rdnaService from '../../../uniken/services/rdnaService';
import { CloseButton, Button, StatusBanner } from '../components';
import type { RootStackParamList } from '../../navigation/AppNavigator';

type VerifyAuthScreenRouteProp = RouteProp<RootStackParamList, 'VerifyAuthScreen'>;

/**
 * Verify Auth Screen Component
 */
const VerifyAuthScreen: React.FC = () => {
  const route = useRoute<VerifyAuthScreenRouteProp>();
  
  const {
    eventData,
    title = 'Additional Device Activation',
    subtitle = 'Activate this device for secure access',
    responseData,
  } = route.params;

  const [isProcessing, setIsProcessing] = useState<boolean>(false);
  const [error, setError] = useState<string>('');
  const [activationData, setActivationData] = useState<{
    userID: string;
    options: string[];
  } | null>(null);

  /**
   * Handle close button - direct resetAuthState call
   */
  const handleClose = async () => {
    try {
      console.log('VerifyAuthScreen - Calling resetAuthState');
      await rdnaService.resetAuthState();
      console.log('VerifyAuthScreen - ResetAuthState successful');
    } catch (error) {
      console.error('VerifyAuthScreen - ResetAuthState error:', error);
    }
  };

  /**
   * Process activation data
   */
  const processActivationData = (data: RDNAAddNewDeviceOptionsData) => {
    return {
      userID: data.userID,
      options: data.newDeviceOptions,
    };
  };

  /**
   * Handle response data from route params
   */
  useEffect(() => {
    if (responseData) {
      console.log('VerifyAuthScreen - Processing response data from route params:', responseData);
      
      try {
        // Process activation data
        const processed = processActivationData(responseData);
        setActivationData(processed);
        
        console.log('VerifyAuthScreen - Processed activation data:', {
          userID: processed.userID,
          options: processed.options,
        });
        
        // Automatically call performVerifyAuth(true) when data is processed
        handleVerifyAuth(true);
      } catch (error) {
        console.error('VerifyAuthScreen - Failed to process activation data:', error);
        setError('Failed to process activation data');
      }
    }
  }, [responseData]);

  /**
   * Handle REL-ID Verify authentication
   */
  const handleVerifyAuth = async (proceed: boolean) => {
    if (isProcessing) return;

    setIsProcessing(true);
    setError('');

    try {
      console.log('VerifyAuthScreen - Performing verify auth:', proceed);
      
      const syncResponse: RDNASyncResponse = await rdnaService.performVerifyAuth(proceed);
      
      console.log('VerifyAuthScreen - PerformVerifyAuth sync response successful, waiting for async events');
      console.log('VerifyAuthScreen - Sync response received:', {
        longErrorCode: syncResponse.error?.longErrorCode,
        shortErrorCode: syncResponse.error?.shortErrorCode,
        errorString: syncResponse.error?.errorString
      });
      
      if (proceed) {
        // Log success message for approval
        console.log('VerifyAuthScreen - REL-ID Verify notification has been sent to registered devices');
      }
      
    } catch (error) {
      // This catch block handles sync response errors (rejected promises)
      console.error('VerifyAuthScreen - PerformVerifyAuth sync error:', error);
      
      // Cast the error back to RDNASyncResponse as per other screens pattern
      const result: RDNASyncResponse = error as RDNASyncResponse;
      const errorMessage = RDNASyncUtils.getErrorMessage(result);
      
      setError(errorMessage);
    } finally {
      setIsProcessing(false);
    }
  };

  /**
   * Handle fallback new device activation flow
   */
  const handleFallbackFlow = async () => {
    if (isProcessing) return;

    setIsProcessing(true);
    setError('');

    try {
      console.log('VerifyAuthScreen - Initiating fallback new device activation flow');
      
      const syncResponse: RDNASyncResponse = await rdnaService.fallbackNewDeviceActivationFlow();
      
      console.log('VerifyAuthScreen - FallbackNewDeviceActivationFlow sync response successful, waiting for async events');
      console.log('VerifyAuthScreen - Sync response received:', {
        longErrorCode: syncResponse.error?.longErrorCode,
        shortErrorCode: syncResponse.error?.shortErrorCode,
        errorString: syncResponse.error?.errorString
      });
      
      // Log success message for fallback initiation
      console.log('VerifyAuthScreen - Alternative device activation process has been initiated');
      
    } catch (error) {
      // This catch block handles sync response errors (rejected promises)
      console.error('VerifyAuthScreen - FallbackNewDeviceActivationFlow sync error:', error);
      
      // Cast the error back to RDNASyncResponse as per other screens pattern
      const result: RDNASyncResponse = error as RDNASyncResponse;
      const errorMessage = RDNASyncUtils.getErrorMessage(result);
      
      setError(errorMessage);
    } finally {
      setIsProcessing(false);
    }
  };

  return (
    <SafeAreaView style={styles.safeArea}>
      <StatusBar barStyle="dark-content" backgroundColor="#f8f9fa" />
      <ScrollView style={styles.container}>
        {/* Close Button */}
        <CloseButton 
          onPress={handleClose}
          disabled={isProcessing}
        />
        
        <View style={styles.content}>
          <Text style={styles.title}>{title}</Text>
          <Text style={styles.subtitle}>{subtitle}</Text>
          
          {/* Error Display */}
          {error && (
            <StatusBanner
              type="error"
              message={error}
            />
          )}

          {/* Processing Status */}
          {isProcessing && (
            <View style={styles.processingContainer}>
              <StatusBanner
                type="info"
                message="Processing device activation..."
              />
            </View>
          )}
          
          {/* Activation Information */}
          {activationData && (
            <>
              {/* Processing Message */}
              <View style={styles.messageContainer}>
                <Text style={styles.messageTitle}>REL-ID Verify Authentication</Text>
                <Text style={styles.messageText}>
                  REL-ID Verify notification has been sent to your registered devices. Please approve it to activate this device.
                </Text>
              </View>

              {/* Fallback Option */}
              <View style={styles.fallbackContainer}>
                <Text style={styles.fallbackTitle}>Device Not Handy?</Text>
                <Text style={styles.fallbackDescription}>
                  If you don't have access to your registered devices, you can use an alternative activation method.
                </Text>

                <Button
                  title="Activate using fallback method"
                  onPress={handleFallbackFlow}
                  loading={isProcessing}
                  variant="outline"
                  style={styles.fallbackButton}
                />
              </View>
            </>
          )}
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#f8f9fa',
  },
  container: {
    flex: 1,
  },
  content: {
    flex: 1,
    padding: 20,
    paddingTop: 80, // Add space for close button
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#2c3e50',
    textAlign: 'center',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#7f8c8d',
    textAlign: 'center',
    marginBottom: 30,
  },
  processingContainer: {
    marginBottom: 20,
  },
  messageContainer: {
    backgroundColor: '#e3f2fd',
    borderRadius: 12,
    padding: 20,
    marginBottom: 20,
    borderLeftWidth: 4,
    borderLeftColor: '#2196f3',
  },
  messageTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1976d2',
    marginBottom: 8,
  },
  messageText: {
    fontSize: 16,
    color: '#1565c0',
    lineHeight: 24,
  },
  fallbackContainer: {
    backgroundColor: '#f5f5f5',
    borderRadius: 12,
    padding: 20,
    marginBottom: 20,
    borderWidth: 1,
    borderColor: '#e0e0e0',
  },
  fallbackTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#2c3e50',
    marginBottom: 8,
    textAlign: 'center',
  },
  fallbackDescription: {
    fontSize: 14,
    color: '#7f8c8d',
    textAlign: 'center',
    marginBottom: 16,
    lineHeight: 20,
  },
  fallbackButton: {
    alignSelf: 'center',
    paddingHorizontal: 24,
  },
});

export default VerifyAuthScreen;

Key VerifyAuthScreen Features

Automatic Activation Flow

Fallback Integration

User Experience Enhancements

The following image showcases screen from the sample application:

Mobile Threat Detection Screen

Create the GetNotificationsScreen that automatically loads server notifications and provides interactive action modals for user responses.

GetNotificationsScreen Component Implementation

// src/tutorial/screens/notification/GetNotificationsScreen.tsx
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  StyleSheet,
  StatusBar,
  SafeAreaView,
  TouchableOpacity,
  ScrollView,
  FlatList,
  Alert,
  ActivityIndicator,
  Modal,
} from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native';
import rdnaService from '../../../uniken/services/rdnaService';
import type {
  RDNAGetNotificationsData,
  RDNANotificationItem,
  RDNANotificationAction,
  RDNAUpdateNotificationData,
} from '../../../uniken/types/rdnaEvents';

/**
 * Route Parameters for Get Notifications Screen
 */
interface GetNotificationsScreenParams {
  userID: string;
  sessionID: string;
  sessionType: number;
  jwtToken: string;
  loginTime?: string;
  userRole?: string;
  currentWorkFlow?: string;
}

type GetNotificationsScreenRouteProp = RouteProp<
  { GetNotifications: GetNotificationsScreenParams },
  'GetNotifications'
>;

interface Props {
  route: GetNotificationsScreenRouteProp;
  navigation: GetNotificationsScreenNavigationProp;
}

const GetNotificationsScreen: React.FC<Props> = ({ route, navigation }) => {
  // State management
  const [notifications, setNotifications] = useState<RDNANotificationItem[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
  const [error, setError] = useState<string>('');
  const [selectedNotification, setSelectedNotification] = useState<RDNANotificationItem | null>(null);
  const [selectedAction, setSelectedAction] = useState<string>('');
  const [isProcessingAction, setIsProcessingAction] = useState<boolean>(false);
  const [showActionModal, setShowActionModal] = useState<boolean>(false);

  // Extract user parameters
  const userParams = route.params;
  const userID = userParams?.userID || 'Unknown User';

  /**
   * Set up notification event handlers
   */
  useEffect(() => {
    const eventManager = rdnaService.getEventManager();
    
    // Set up notification event handlers
    eventManager.setGetNotificationsHandler(handleGetNotificationsResponse);
    eventManager.setUpdateNotificationHandler(handleUpdateNotificationResponse);

    // Auto-load notifications when screen loads
    loadNotifications();

    // Cleanup handlers on unmount
    return () => {
      eventManager.setGetNotificationsHandler(undefined);
      eventManager.setUpdateNotificationHandler(undefined);
    };
  }, []);

  /**
   * Handle notifications received from onGetNotifications event
   */
  const handleNotificationsReceived = (data: RDNAGetNotificationsData) => {
    console.log('GetNotificationsScreen - Received notifications event');
    
    // Check if this is the standard response format (has pArgs)
    if (data.pArgs) {
      const notificationList = data.pArgs.response.ResponseData.notifications;
      
      console.log('GetNotificationsScreen - Received notifications:', notificationList.length);
      setNotifications(notificationList);
    } else if (data.userID) {
      // This is the authentication context format - no notifications in this format
      console.log('GetNotificationsScreen - Authentication context format, userID:', data.userID);
      setNotifications([]);
    } else {
      // Unknown format or error
      console.log('GetNotificationsScreen - Unknown response format');
      setNotifications([]);
    }
    
    setIsLoading(false);
  };

  /**
   * Handle update notification response from onUpdateNotification event
   */
  const handleUpdateNotificationReceived = (data: RDNAUpdateNotificationData) => {
    console.log('GetNotificationsScreen - Received update notification event');
    setActionLoading(false);

    // Check for errors first
    if (data.error.longErrorCode !== 0) {
      const errorMessage = data.error.errorString || 'Failed to update notification';
      console.error('GetNotificationsScreen - Update notification error:', data.error);
      console.error('GetNotificationsScreen - Update notification statusCode:', data.pArgs?.response.StatusCode);
      
      Alert.alert(
        'Update Failed',
        errorMessage,
        [{ text: 'OK' }]
      );
      return;
    }

    // Check response status
    const responseData = data.pArgs?.response;
    if (responseData?.StatusCode === 100) {
      const notificationUuid = responseData.ResponseData.notification_uuid;
      const message = responseData.StatusMsg;
      
      console.log('GetNotificationsScreen - Update notification success:', message);
      setShowActionModal(false);
      loadNotifications();
    } else {
      const statusMessage = responseData?.StatusMsg || 'Unknown error occurred';
      console.error('GetNotificationsScreen - Update notification status error:', statusMessage);
      
      Alert.alert(
        'Update Failed',
        statusMessage,
        [
          {
            text: 'OK',
            onPress: () => {
              setShowActionModal(false);
              // Refresh notifications to get updated status
              loadNotifications();
            }
          }
        ]
      );
    }
  };

  /**
   * Load notifications from server
   */
  const loadNotifications = async () => {
    try {
      setError('');
      console.log('GetNotificationsScreen - Loading notifications for user:', userID);
      
      await rdnaService.getNotifications();
      console.log('GetNotificationsScreen - GetNotifications API called, waiting for response');
      
    } catch (error: any) {
      console.error('GetNotificationsScreen - Error loading notifications:', error);
      
      setIsLoading(false);
      setIsRefreshing(false);
      setError(error?.error?.errorString || 'Failed to load notifications. Please try again.');
    }
  };

  /**
   * Handle pull-to-refresh
   */
  const handleRefresh = () => {
    setIsRefreshing(true);
    loadNotifications();
  };

  /**
   * Open action modal for notification
   */
  const openActionModal = (notification: RDNANotificationItem) => {
    if (!notification.actions || notification.actions.length === 0) {
      Alert.alert('No Actions', 'This notification has no available actions.');
      return;
    }

    if (notification.status === 'PROCESSED') {
      Alert.alert('Already Processed', 'This notification has already been processed.');
      return;
    }

    setSelectedNotification(notification);
    setSelectedAction('');
    setShowActionModal(true);
  };

  /**
   * Process selected notification action
   */
  const processNotificationAction = async () => {
    if (!selectedNotification || !selectedAction) {
      Alert.alert('Error', 'Please select an action to proceed.');
      return;
    }

    const action = selectedNotification.actions?.find(a => a.actionId === selectedAction);
    if (!action) {
      Alert.alert('Error', 'Invalid action selected.');
      return;
    }

    // Show confirmation if required
    if (action.requiresConfirmation) {
      Alert.alert(
        'Confirm Action',
        `Are you sure you want to ${action.actionName.toLowerCase()}?`,
        [
          { text: 'Cancel', style: 'cancel' },
          { text: 'Confirm', onPress: executeNotificationAction }
        ]
      );
    } else {
      executeNotificationAction();
    }
  };

  /**
   * Execute the notification action
   */
  const executeNotificationAction = async () => {
    if (!selectedNotification || !selectedAction) return;

    setIsProcessingAction(true);

    try {
      console.log('GetNotificationsScreen - Processing notification action:', {
        notificationId: selectedNotification.notificationId,
        actionId: selectedAction
      });

      await rdnaService.updateNotification(selectedNotification.notification_uuid, selectedAction);
      
      console.log('GetNotificationsScreen - UpdateNotification API called, waiting for response');
      
    } catch (error: any) {
      console.error('GetNotificationsScreen - Error processing action:', error);
      
      setIsProcessingAction(false);
      Alert.alert(
        'Error',
        error?.error?.errorString || 'Failed to process notification action. Please try again.'
      );
    }
  };

  /**
   * Get status color for notification
   */
  const getStatusColor = (status: string) => {
    switch (status) {
      case 'PENDING': return '#FF9500';
      case 'PROCESSED': return '#34C759';
      case 'EXPIRED': return '#FF3B30';
      default: return '#8E8E93';
    }
  };

  /**
   * Get priority color for notification
   */
  const getPriorityColor = (priority: string) => {
    switch (priority) {
      case 'HIGH': return '#FF3B30';
      case 'MEDIUM': return '#FF9500';
      case 'LOW': return '#34C759';
      default: return '#8E8E93';
    }
  };

  /**
   * Format timestamp for display
   */
  const formatTimestamp = (timestamp: string) => {
    const date = new Date(timestamp);
    return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
  };

  /**
   * Render notification item
   */
  const renderNotificationItem = ({ item }: { item: RDNANotificationItem }) => {
    // Get the primary body content (usually first language entry)
    const primaryBody = item.body[0] || {};
    const { subject = 'No Subject', message = 'No Message' } = primaryBody;
    
    return (
      <TouchableOpacity
        style={styles.notificationItem}
        onPress={() => handleNotificationSelect(item)}
      >
        <View style={styles.notificationHeader}>
          <Text style={styles.notificationTitle}>{subject}</Text>
          <Text style={styles.notificationTime}>
            {new Date(item.create_ts.replace('UTC', 'Z')).toLocaleString()}
          </Text>
        </View>
        <Text style={styles.notificationMessage} numberOfLines={3}>
          {message}
        </Text>
        <View style={styles.notificationFooter}>
          <Text style={styles.notificationCategory}>
            {item.actions.length} action{item.actions.length !== 1 ? 's' : ''} available
          </Text>
          <Text style={styles.notificationType}>
            {item.action_performed || 'Pending'}
          </Text>
        </View>
        {item.expiry_timestamp && (
          <Text style={styles.notificationExpiry}>
            Expires: {new Date(item.expiry_timestamp.replace('UTC', 'Z')).toLocaleString()}
          </Text>
        )}
      </TouchableOpacity>
    );
  };

  /**
   * Render action modal
   */
  const renderActionModal = () => (
    <Modal
      visible={showActionModal}
      transparent={true}
      animationType="slide"
      onRequestClose={() => !isProcessingAction && setShowActionModal(false)}
    >
      <View style={styles.modalOverlay}>
        <View style={styles.modalContent}>
          <View style={styles.modalHeader}>
            <Text style={styles.modalTitle}>Notification Actions</Text>
            {!isProcessingAction && (
              <TouchableOpacity
                onPress={() => setShowActionModal(false)}
                style={styles.closeButton}
              >
                <Text style={styles.closeButtonText}>×</Text>
              </TouchableOpacity>
            )}
          </View>
          
          {selectedNotification && (
            <View style={styles.modalBody}>
              <Text style={styles.modalNotificationTitle}>
                {selectedNotification.title}
              </Text>
              <Text style={styles.modalNotificationMessage}>
                {selectedNotification.message}
              </Text>
              
              <Text style={styles.actionsLabel}>Select an action:</Text>
              
              {selectedNotification.actions?.map((action) => (
                <TouchableOpacity
                  key={action.actionId}
                  style={[
                    styles.actionOption,
                    selectedAction === action.actionId && styles.selectedActionOption
                  ]}
                  onPress={() => setSelectedAction(action.actionId)}
                  disabled={isProcessingAction}
                >
                  <View style={styles.radioButton}>
                    {selectedAction === action.actionId && <View style={styles.radioButtonSelected} />}
                  </View>
                  <View style={styles.actionContent}>
                    <Text style={styles.actionName}>{action.actionName}</Text>
                    <Text style={styles.actionType}>{action.actionType}</Text>
                  </View>
                </TouchableOpacity>
              ))}
              
              <View style={styles.modalActions}>
                <Button
                  title={isProcessingAction ? "Processing..." : "Submit Action"}
                  onPress={processNotificationAction}
                  variant="primary"
                  disabled={!selectedAction || isProcessingAction}
                />
                {!isProcessingAction && (
                  <Button
                    title="Cancel"
                    onPress={() => setShowActionModal(false)}
                    variant="outline"
                    style={styles.cancelButton}
                  />
                )}
              </View>
            </View>
          )}
        </View>
      </View>
    </Modal>
  );

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>Notifications</Text>
        <Text style={styles.subtitle}>
          Manage your REL-ID notifications
        </Text>
        <Text style={styles.userInfo}>User: {userID}</Text>
      </View>

      <View style={styles.content}>
        {error && (
          <StatusBanner type="error" message={error} />
        )}

        {isLoading && !isRefreshing ? (
          <View style={styles.loadingContainer}>
            <StatusBanner 
              type="processing" 
              message="Loading notifications..." 
            />
          </View>
        ) : (
          <FlatList
            data={notifications}
            keyExtractor={(item) => item.notificationId}
            renderItem={renderNotificationItem}
            refreshControl={
              <RefreshControl
                refreshing={isRefreshing}
                onRefresh={handleRefresh}
                tintColor="#007AFF"
              />
            }
            ListEmptyComponent={
              <View style={styles.emptyContainer}>
                <Text style={styles.emptyTitle}>No Notifications</Text>
                <Text style={styles.emptyMessage}>
                  You don't have any notifications at the moment.
                </Text>
                <Button
                  title="Refresh"
                  onPress={loadNotifications}
                  variant="outline"
                  style={styles.refreshButton}
                />
              </View>
            }
            showsVerticalScrollIndicator={false}
          />
        )}
      </View>

      {renderActionModal()}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    backgroundColor: '#fff',
    paddingHorizontal: 20,
    paddingTop: 60,
    paddingBottom: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginBottom: 8,
  },
  userInfo: {
    fontSize: 14,
    color: '#007AFF',
    fontWeight: '500',
  },
  content: {
    flex: 1,
    padding: 20,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
  },
  notificationItem: {
    backgroundColor: '#fff',
    padding: 16,
    borderRadius: 12,
    marginBottom: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.05,
    shadowRadius: 2,
    elevation: 2,
  },
  notificationHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  notificationTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
    flex: 1,
    marginRight: 12,
  },
  statusContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  statusDot: {
    width: 8,
    height: 8,
    borderRadius: 4,
    marginRight: 6,
  },
  statusText: {
    fontSize: 12,
    fontWeight: '500',
  },
  notificationMessage: {
    fontSize: 14,
    color: '#666',
    lineHeight: 20,
    marginBottom: 12,
  },
  notificationFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  metadataContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  categoryText: {
    fontSize: 12,
    color: '#8E8E93',
    marginRight: 8,
  },
  priorityIndicator: {
    paddingHorizontal: 8,
    paddingVertical: 2,
    borderRadius: 10,
  },
  priorityText: {
    fontSize: 10,
    color: '#fff',
    fontWeight: '600',
  },
  timestampText: {
    fontSize: 12,
    color: '#8E8E93',
  },
  actionsPreview: {
    borderTopWidth: 1,
    borderTopColor: '#f0f0f0',
    paddingTop: 8,
  },
  actionsText: {
    fontSize: 12,
    color: '#007AFF',
    fontWeight: '500',
  },
  emptyContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    paddingVertical: 60,
  },
  emptyTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
    marginBottom: 8,
  },
  emptyMessage: {
    fontSize: 14,
    color: '#666',
    textAlign: 'center',
    marginBottom: 20,
  },
  refreshButton: {
    marginTop: 10,
  },
  // Modal styles
  modalOverlay: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  modalContent: {
    backgroundColor: '#fff',
    borderRadius: 12,
    width: '100%',
    maxHeight: '80%',
  },
  modalHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  modalTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333',
  },
  closeButton: {
    width: 30,
    height: 30,
    borderRadius: 15,
    backgroundColor: '#f0f0f0',
    justifyContent: 'center',
    alignItems: 'center',
  },
  closeButtonText: {
    fontSize: 20,
    color: '#666',
    fontWeight: 'bold',
  },
  modalBody: {
    padding: 20,
  },
  modalNotificationTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
    marginBottom: 8,
  },
  modalNotificationMessage: {
    fontSize: 14,
    color: '#666',
    lineHeight: 20,
    marginBottom: 20,
  },
  actionsLabel: {
    fontSize: 14,
    fontWeight: '600',
    color: '#333',
    marginBottom: 12,
  },
  actionOption: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    marginBottom: 8,
  },
  selectedActionOption: {
    borderColor: '#007AFF',
    backgroundColor: '#f0f8ff',
  },
  radioButton: {
    width: 20,
    height: 20,
    borderRadius: 10,
    borderWidth: 2,
    borderColor: '#ccc',
    marginRight: 12,
    justifyContent: 'center',
    alignItems: 'center',
  },
  radioButtonSelected: {
    width: 10,
    height: 10,
    borderRadius: 5,
    backgroundColor: '#007AFF',
  },
  actionContent: {
    flex: 1,
  },
  actionName: {
    fontSize: 14,
    fontWeight: '500',
    color: '#333',
  },
  actionType: {
    fontSize: 12,
    color: '#666',
    marginTop: 2,
  },
  modalActions: {
    marginTop: 20,
  },
  cancelButton: {
    marginTop: 10,
  },
});

export default GetNotificationsScreen;

Key GetNotificationsScreen Features

Automatic Notification Loading

Interactive Action Modals

Enhanced User Experience

Production Features

The following images showcase screens from the sample application:

Get Notifications Dashboard Menu

Get Notifictions Screen

Get Notifiction Actions Screen

Extend your existing SDKEventProvider to handle device activation events and coordinate navigation for the additional device activation workflow.

Adding Device Activation Event Handlers

Enhance your SDKEventProvider with device activation event handling:

// src/uniken/providers/SDKEventProvider.tsx (device activation additions)
import type { 
  // ... existing imports ...
  RDNAAddNewDeviceOptionsData,
  RDNAGetNotificationsData,
  RDNAUpdateNotificationData
} from '../types/rdnaEvents';

export const SDKEventProvider: React.FC<SDKEventProviderProps> = ({ children }) => {
  const [currentScreen, setCurrentScreen] = useState<string | null>(null);

  // ... existing MFA event handlers ...

  /**
   * Event handler for device activation options
   * Triggered when SDK detects unregistered device during authentication
   */
  const handleAddNewDeviceOptions = useCallback((data: RDNAAddNewDeviceOptionsData) => {
    console.log('SDKEventProvider - Add new device options event received for user:', data.userID);
    console.log('SDKEventProvider - Available options:', data.newDeviceOptions);
    console.log('SDKEventProvider - Challenge info count:', data.challengeInfo.length);
    
    // Use navigateOrUpdate to prevent duplicate screens and update existing screen with new event data
    NavigationService.navigateOrUpdate('VerifyAuthScreen', {
      eventName: 'addNewDeviceOptions',
      eventData: data,
      title: 'Additional Device Activation',
      subtitle: `Activate this device for user: ${data.userID}`,
      // Pass response data directly
      responseData: data,
    });
  }, []);

  /**
   * Event handler for get notifications response
   * Triggered after getNotifications API call completes
   */
  const handleGetNotifications = useCallback((data: RDNAGetNotificationsData) => {
    console.log('SDKEventProvider - Get notifications event received');
    console.log('SDKEventProvider - Total notifications:', data.totalCount);
    console.log('SDKEventProvider - Notifications received:', data.notifications.length);
    
    // The GetNotificationsScreen handles this event directly through its own event subscription
    // No navigation needed here - this is handled by the screen itself
    console.log('SDKEventProvider - Get notifications event handled by GetNotificationsScreen');
  }, []);

  /**
   * Event handler for update notification response
   * Triggered after updateNotification API call completes
   */
  const handleUpdateNotification = useCallback((data: RDNAUpdateNotificationData) => {
    console.log('SDKEventProvider - Update notification event received');
    console.log('SDKEventProvider - Notification updated:', {
      notificationId: data.notificationId,
      actionId: data.actionId,
      success: data.success
    });
    
    // The GetNotificationsScreen handles this event directly through its own event subscription
    // No navigation needed here - this is handled by the screen itself
    console.log('SDKEventProvider - Update notification event handled by GetNotificationsScreen');
  }, []);

  /**
   * Enhanced handleUserLoggedIn for device activation support
   * Updated to handle drawer navigation with GetNotifications
   */
  const handleUserLoggedIn = useCallback((data: RDNAUserLoggedInData) => {
    console.log('SDKEventProvider - User logged in event received for user:', data.userID);
    console.log('SDKEventProvider - Session ID:', data.challengeResponse.session.sessionID);
    console.log('SDKEventProvider - Current workflow:', data.challengeResponse.additionalInfo.currentWorkFlow);
    
    // Extract session and JWT information
    const sessionID = data.challengeResponse.session.sessionID;
    const sessionType = data.challengeResponse.session.sessionType;
    const additionalInfo = data.challengeResponse.additionalInfo;
    const jwtToken = additionalInfo.jwtJsonTokenInfo;
    const userRole = additionalInfo.idvUserRole;
    const currentWorkFlow = additionalInfo.currentWorkFlow;
    
    // Navigate to DrawerNavigator with all session data
    // This now includes access to GetNotifications screen
    NavigationService.navigate('DrawerNavigator', {
      screen: 'Dashboard',
      params: {
        userID: data.userID,
        sessionID,
        sessionType,
        jwtToken,
        loginTime: new Date().toLocaleString(),
        userRole,
        currentWorkFlow,
      }
    });
  }, []);

  /**
   * Set up SDK Event Subscriptions on mount
   * Enhanced with device activation event handlers
   */
  useEffect(() => {
    const eventManager = rdnaService.getEventManager();
    
    // Existing MFA event handlers
    eventManager.setInitializedHandler(handleInitialized);
    eventManager.setGetUserHandler(handleGetUser);
    eventManager.setGetActivationCodeHandler(handleGetActivationCode);
    eventManager.setGetUserConsentForLDAHandler(handleGetUserConsentForLDA);
    eventManager.setGetPasswordHandler(handleGetPassword);
    eventManager.setOnUserLoggedInHandler(handleUserLoggedIn);
    eventManager.setCredentialsAvailableForUpdateHandler(handleCredentialsAvailableForUpdate);
    eventManager.setOnUserLoggedOffHandler(handleUserLoggedOff);

    // Device activation event handlers
    eventManager.setAddNewDeviceOptionsHandler(handleAddNewDeviceOptions);
    eventManager.setGetNotificationsHandler(handleGetNotifications);
    eventManager.setUpdateNotificationHandler(handleUpdateNotification);

    // Only cleanup on component unmount
    return () => {
      console.log('SDKEventProvider - Component unmounting, cleaning up event handlers');
      eventManager.cleanup();
    };
  }, []); // Empty dependency array - setup once on mount

  // ... rest of component remains the same ...
};

Navigation Service Enhancements

Update your navigation types to support the new device activation screens:

// src/tutorial/navigation/AppNavigator.tsx (type additions)
export type RootStackParamList = {
  // ... existing screens ...

  // Device Activation Screens
  VerifyAuthScreen: {
    eventData?: RDNAAddNewDeviceOptionsData;
    responseData?: RDNAAddNewDeviceOptionsData;
    userID: string;
    options: RDNADeviceActivationOption[];
  };

  // Drawer Navigator (enhanced with GetNotifications)
  DrawerNavigator: {
    screen: keyof DrawerParamList;
    params?: any;
  };
};

Enhanced DrawerNavigator Integration

Update your DrawerNavigator to include the GetNotifications screen:

// src/tutorial/navigation/DrawerNavigator.tsx (already implemented in your project)
import GetNotificationsScreen from '../screens/notification/GetNotificationsScreen';

// DrawerParamList already includes GetNotifications
export type DrawerParamList = {
  Dashboard: {
    userID: string;
    sessionID: string;
    sessionType: number;
    jwtToken: string;
    loginTime?: string;
    userRole?: string;
    currentWorkFlow?: string;
  };
  GetNotifications: {
    userID: string;
    sessionID: string;
    sessionType: number;
    jwtToken: string;
    loginTime?: string;
    userRole?: string;
    currentWorkFlow?: string;
  };
};

// Drawer.Screen for GetNotifications already configured
<Drawer.Screen
  name="GetNotifications"
  component={GetNotificationsScreen}
  initialParams={userParams}
  options={{
    drawerLabel: 'Get Notifications',
  }}
/>

Key SDKEventProvider Enhancements

Device Activation Event Integration

Notification Event Handling

Enhanced MFA Integration

Event Flow Coordination

The enhanced SDKEventProvider coordinates these device activation flows:

  1. MFA Authentication Flow: User completes username/password → MFA validation
  2. Device Detection: SDK detects unregistered device → triggers addNewDeviceOptions
  3. Automatic Navigation: SDKEventProvider navigates to VerifyAuthScreen with options
  4. Device Activation: User completes REL-ID Verify or fallback activation
  5. MFA Continuation: Flow continues to LDA consent or final authentication
  6. Dashboard Access: User reaches dashboard with drawer navigation including GetNotifications

Event Handler Coordination

The provider uses a layered event handling approach:

Test your device activation implementation to ensure REL-ID Verify workflows, fallback methods, and notification management work correctly across different scenarios.

Device Activation Test Scenarios

Scenario 1: Automatic REL-ID Verify Activation

Test the complete automatic device activation flow:

  1. Prepare Test Environment:
    # Ensure you have multiple physical devices
    # Device A: Already registered with REL-ID
    # Device B: New device for activation testing
    
    # Build and deploy to both devices
    npx react-native run-ios --device "Device-A-Name"
    npx react-native run-ios --device "Device-B-Name"
    
  2. Execute Test Flow:
    • On Device B (New Device): Complete MFA username
    • Verify Event Trigger: Check console for addNewDeviceOptions event
    • Automatic Navigation: Confirm VerifyAuthScreen loads automatically
    • REL-ID Verify Start: Verify performVerifyAuth(true) called automatically
    • Push Notification: Check Device A receives activation approval request
    • User Approval: On Device A, approve the device activation request
    • Activation Success: Verify Device B completes activation and continues MFA
  3. Expected Console Output:
    SDKEventProvider - Add new device options event received for user: testuser@example.com
    SDKEventProvider - Available options: 2
    SDKEventProvider - Option 1: {optionId: "verify-auth", optionName: "REL-ID Verify", isDefault: true}
    VerifyAuthScreen - Auto-starting REL-ID Verify for user: testuser@example.com
    VerifyAuthScreen - PerformVerifyAuth sync response successful
    

Scenario 2: Fallback Activation Method

Test the fallback activation when REL-ID Verify is not accessible:

  1. Test Setup:
    • Use Device B (new device) without accessible registered devices
    • Or simulate scenario where Device A is offline/unreachable
  2. Execute Fallback Test:
    • Start REL-ID Verify: Allow automatic verification to start
    • Use Fallback: Tap "Use Alternative Method" button
    • Fallback Processing: Verify fallbackNewDeviceActivationFlow() called
    • Alternative Method: Complete server-configured alternative activation
  3. Expected Behavior:
    VerifyAuthScreen - Starting fallback activation for user: testuser@example.com
    VerifyAuthScreen - FallbackNewDeviceActivationFlow sync response successful
    

Scenario 3: Notification Management Testing

Test the GetNotificationsScreen functionality:

  1. Access Notifications:
    • Complete device activation and reach dashboard
    • Open drawer navigation menu
    • Tap "🔔 Get Notifications" menu item
    • Verify automatic navigation to GetNotificationsScreen
  2. Test Notification Loading:
    • Auto-load: Verify notifications load automatically on screen entry
    • Loading State: Check loading indicator displays during API call
    • Data Display: Confirm notifications appear in chronological order
  3. Test Notification Actions:
    • Select Notification: Tap on notification with available actions
    • Action Modal: Verify modal opens with radio button options
    • Action Selection: Select an action and submit
    • Processing State: Check processing indicator during update
    • Success Feedback: Verify success message and notification status update
  4. Test Pull-to-Refresh:
    • Pull down on notification list
    • Verify refresh indicator appears
    • Confirm getNotifications() API called again

Debug and Troubleshooting

Common Device Activation Issues

  1. addNewDeviceOptions Event Not Triggered:
    // Check if device is already registered
    // Verify MFA flow completion before device detection
    // Ensure proper connection profile configuration
    
  2. REL-ID Verify Push Notifications Not Received:
    • Verify registered device has push notifications enabled
    • Check network connectivity on both devices
    • Confirm REL-ID Verify service configuration
  3. Fallback Activation Fails:
    • Check server configuration for fallback methods
    • Verify network connectivity and SDK configuration
    • Review error logs for specific failure reasons
  4. Notification Loading Issues:
    // Check GetNotificationsScreen event handler setup
    eventManager.setGetNotificationsHandler(handleGetNotificationsResponse);
    
    // Verify API call execution
    await rdnaService.getNotifications();
    

Testing Best Practices

  1. Use Physical Devices: REL-ID Verify requires real device-to-device communication
  2. Test Network Conditions: Test with different network conditions and connectivity
  3. Error Scenarios: Test error conditions like network failures and server timeouts
  4. User Experience: Test complete user flows from start to finish
  5. Performance: Monitor performance impact on existing MFA flows

Validation Checklist

Production Deployment Considerations

Security Validation

User Experience

Congratulations! You've successfully implemented a comprehensive Additional Device Activation system with REL-ID Verify push notifications, fallback methods, and notification management.

What You've Accomplished

Core Device Activation Features

REL-ID Verify Integration: Automatic push notification-based device activation
VerifyAuthScreen Implementation: Auto-starting activation with real-time status updates
Fallback Activation Methods: Alternative activation when registered devices aren't accessible
GetNotificationsScreen: Server notification management with interactive action processing
Enhanced Drawer Navigation: Seamless access to notifications via enhanced navigation

Key Architectural Patterns Mastered

  1. Event-Driven Device Activation: Seamless integration with existing MFA workflows
  2. Push Notification Workflows: Real device-to-device communication and approval systems
  3. Fallback Strategy Implementation: Robust alternative activation methods for various scenarios
  4. Interactive Notification Management: Server notification retrieval with action processing
  5. Enhanced Navigation Patterns: Drawer navigation with notification access integration

Advanced Device Activation Scenarios

Your implementation now handles these production scenarios:

Seamless MFA Integration

Multi-Device Management

Network Resilience

Next Steps and Advanced Features

Potential Enhancements

  1. Advanced Notification Features: Push notification customization and rich media support
  2. Biometric Integration: Enhanced biometric authentication during device activation
  3. Admin Dashboard: Administrative interface for managing device activations
  4. Advanced Analytics: Machine learning-based fraud detection during activation
  5. Multi-Tenant Support: Enterprise-grade multi-organization support

Resources for Continued Learning

REL-ID Documentation

Congratulations! 🎉

You've mastered Advanced Device Activation with REL-ID Verify and built a production-ready system that provides:

Your application now provides enterprise-grade device activation capabilities that enhance security while maintaining user convenience. You're ready to deploy this solution in production environments and scale to support thousands of users across multiple devices.

🚀 You're now equipped to build sophisticated device activation workflows that combine security, usability, and reliability!