This codelab demonstrates how to implement the RELID Initialization flow using the react-native-rdna-client npm plugin. The RELID SDK provides secure identity verification and session management for mobile applications.

What You'll Learn

What You'll Need

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-initialize folder in the repository you cloned earlier

Before implementing your own RELID initialization, let's examine the sample app structure to understand the recommended architecture:

Component

Purpose

Sample App Reference

Connection Profile

Configuration data

src/uniken/cp/agent_info.json

Profile Parser

Utility to parse connection data

src/uniken/utils/connectionProfileParser.ts

Event Manager

Handles SDK callbacks

src/uniken/services/rdnaEventManager.ts

RELID Service

Main SDK interface

src/uniken/services/rdnaService.ts

Event Provider

Global event handling

src/uniken/providers/SDKEventProvider.tsx

UI Components

User interface screens

src/tutorial/screens/tutorial/TutorialHomeScreen.tsx

Recommended Directory Structure

Create the following directory structure in your React Native project:

src/
├── uniken/
│   ├── cp/
│   │   └── agent_info.json
│   ├── services/
│   │   ├── rdnaEventManager.ts
│   │   └── rdnaService.ts
│   ├── utils/
│   │   └── connectionProfileParser.ts
│   ├── types/
│   │   └── rdnaEvents.ts
│   ├── providers/
│   │   └── SDKEventProvider.tsx
│   └── MTDContext/
│       └── MTDThreatContext.tsx
├── tutorial/
│   ├── navigation/
│   │   ├── AppNavigator.tsx
│   │   └── NavigationService.ts
│   └── screens/
│       ├── index.ts
│       └── tutorial/
│           ├── SecurityExitScreen.tsx
│           ├── TutorialHomeScreen.tsx
│           ├── TutorialErrorScreen.tsx
│           └── TutorialSuccessScreen.tsx
└── assets/

Add the RELID Plugin

Add the react-native-rdna-client plugin to your project:

npm install react-native-rdna-client

Platform Setup

Follow the platform-specific setup instructions provided with the plugin for both iOS and Android configurations.

agent_info.json

Create your connection profile JSON file in src/uniken/cp/agent_info.json:

{
  "RelIds": [
    {
      "Name": "YourRELIDAgentName",
      "RelId": "your-rel-id-string-here"
    }
  ],
  "Profiles": [
    {
      "Name": "YourRELIDAgentName",
      "Host": "your-gateway-host.com",
      "Port": "443"
    }
  ]
}

Security Considerations

Define the TypeScript interfaces for type safety:

// src/uniken/utils/connectionProfileParser.ts
interface RelId {
  Name: string;
  RelId: string;
}

interface Profile {
  Name: string;
  Host: string;
  Port: string | number;
}

interface AgentInfo {
  RelIds: RelId[];
  Profiles: Profile[];
}

interface ParsedAgentInfo {
  relId: string;
  host: string;
  port: number;
}

export const parseAgentInfo = (profileData: AgentInfo): ParsedAgentInfo => {
  if (!profileData.RelIds || profileData.RelIds.length === 0) {
    throw new Error('No RelIds found in agent info');
  }

  if (!profileData.Profiles || profileData.Profiles.length === 0) {
    throw new Error('No Profiles found in agent info');
  }

  // Always pick the first array objects
  const firstRelId = profileData.RelIds[0];

  if (!firstRelId.Name || !firstRelId.RelId) {
    throw new Error('Invalid RelId object - missing Name or RelId');
  }

  // Find matching profile by Name
  const matchingProfile = profileData.Profiles.find(
    profile => profile.Name === firstRelId.Name
  );

  if (!matchingProfile) {
    throw new Error(`No matching profile found for RelId name: ${firstRelId.Name}`);
  }

  if (!matchingProfile.Host || !matchingProfile.Port) {
    throw new Error('Invalid Profile object - missing Host or Port');
  }

  // Convert port to number if it's a string
  const port = typeof matchingProfile.Port === 'string'
    ? parseInt(matchingProfile.Port, 10)
    : matchingProfile.Port;

  if (isNaN(port)) {
    throw new Error(`Invalid port value: ${matchingProfile.Port}`);
  }

  return {
    relId: firstRelId.RelId,
    host: matchingProfile.Host,
    port: port
  };
};

export const loadAgentInfo = async (): Promise<ParsedAgentInfo> => {
  try {
    const profileData = require('../cp/agent_info.json');
    return parseAgentInfo(profileData);
  } catch (error) {
    throw new Error(`Failed to load agent info: ${error instanceof Error ? error.message : String(error)}`);
  }
};

The parser performs several critical functions:

The event manager handles all RELID SDK callbacks using a singleton pattern:

// src/uniken/services/rdnaEventManager.ts
import { NativeEventEmitter, NativeModules } from 'react-native';
import type {
  RDNAJsonResponse,
  RDNAProgressData,
  RDNAInitializeErrorData,
  RDNAInitializedData,
  RDNAInitializeProgressCallback,
  RDNAInitializeErrorCallback,
  RDNAInitializeSuccessCallback
} from '../types/rdnaEvents';

class RdnaEventManager {
  private static instance: RdnaEventManager;
  private rdnaEmitter: NativeEventEmitter;
  private listeners: Array<any> = [];

    // Composite event handlers (can handle multiple concerns)
  private initializeProgressHandler?: RDNAInitializeProgressCallback;
  private initializeErrorHandler?: RDNAInitializeErrorCallback;
  private initializedHandler?: RDNAInitializeSuccessCallback;

  constructor() {
    this.rdnaEmitter = new NativeEventEmitter(NativeModules.RdnaClient);
    this.registerEventListeners();
  }

  static getInstance(): RdnaEventManager {
    if (!RdnaEventManager.instance) {
      RdnaEventManager.instance = new RdnaEventManager();
    }
    return RdnaEventManager.instance;
  }

  private registerEventListeners() {
    console.log('RdnaEventManager - Registering native event listeners');

    this.listeners.push(
      this.rdnaEmitter.addListener('onInitializeProgress', this.onInitializeProgress.bind(this)),
      this.rdnaEmitter.addListener('onInitializeError', this.onInitializeError.bind(this)),
      this.rdnaEmitter.addListener('onInitialized', this.onInitialized.bind(this)),
    );
    
    console.log('RdnaEventManager - Native event listeners registered');
  }
}

Event Processing

Each event handler performs JSON parsing and calls appropriate callbacks:

/**
 * Handles SDK initialization progress events
 */
private onInitializeProgress(response: RDNAJsonResponse) {
  console.log("RdnaEventManager - Initialize progress event received");

  try {
    const progressData: RDNAProgressData = JSON.parse(response.response);
    console.log("RdnaEventManager - Progress:", progressData.initializeStatus);

    if (this.initializeProgressHandler) {
      this.initializeProgressHandler(progressData);
    }
  } catch (error) {
    console.error("RdnaEventManager - Failed to parse initialize progress:", error);
  }
}

/**
 * Handles SDK initialization error events
 */
private onInitializeError(response: RDNAJsonResponse) {
  console.log("RdnaEventManager - Initialize error event received");

  try {
    const errorData: RDNAInitializeErrorData = JSON.parse(response.response);
    console.error("RdnaEventManager - Initialize error:", errorData.errorString);
    
    if (this.initializeErrorHandler) {
      this.initializeErrorHandler(errorData);
    }
  } catch (error) {
    console.error("RdnaEventManager - Failed to parse initialize error:", error);
  }
}

/**
 * Handles SDK initialization success events
 */
private onInitialized(response: RDNAJsonResponse) {
  console.log("RdnaEventManager - Initialize success event received");

  try {
    const initializedData: RDNAInitializedData = JSON.parse(response.response);
    console.log("RdnaEventManager - Successfully initialized, Session ID:", initializedData.session.sessionID);

    if (this.initializedHandler) {
      this.initializedHandler(initializedData);
    }
  } catch (error) {
    console.error("RdnaEventManager - Failed to parse initialize success:", error);
  }
}

/**
 * Sets event handlers for SDK events. Only one handler per event type.
 */
public setInitializeProgressHandler(callback?: RDNAInitializeProgressCallback): void {
  this.initializeProgressHandler = callback;
}

public setInitializeErrorHandler(callback?: RDNAInitializeErrorCallback): void {
  this.initializeErrorHandler = callback;
}

public setInitializedHandler(callback?: RDNAInitializeSuccessCallback): void {
  this.initializedHandler = callback;
}

/**
 * Cleans up all event listeners and handlers
 */
public cleanup() {
  console.log('RdnaEventManager - Cleaning up event listeners and handlers');
  
  // Remove native event listeners
  this.listeners.forEach(listener => {
    if (listener && listener.remove) {
      listener.remove();
    }
  });
  this.listeners = [];
  
  // Clear all event handlers
  this.initializeProgressHandler = undefined;
  this.initializeErrorHandler = undefined;
  this.initializedHandler = undefined;
  console.log('RdnaEventManager - Cleanup completed');
}

export default RdnaEventManager;

Key features of the event manager:

The RELID service provides the main interface for SDK operations:

// src/uniken/services/rdnaService.ts
import RdnaClient, {
  RDNALoggingLevel,
} from 'react-native-rdna-client/src/index';
import { loadAgentInfo } from '../utils/connectionProfileParser';
import RdnaEventManager from './rdnaEventManager';
import type {
  RDNASyncResponse,
} from '../types/rdnaEvents';

export class RdnaService {
  private static instance: RdnaService;
  private eventManager: RdnaEventManager;

  constructor() {
    this.eventManager = RdnaEventManager.getInstance();
  }

  static getInstance(): RdnaService {
    if (!RdnaService.instance) {
      RdnaService.instance = new RdnaService();
    }
    return RdnaService.instance;
  }

    /**
   * Cleans up the service and event manager
   */
  cleanup(): void {
    console.log('RdnaService - Cleaning up service');
    this.eventManager.cleanup();
  }

  /**
   * Gets the event manager instance for external callback setup
   */
  getEventManager(): RdnaEventManager {
    return this.eventManager;
  }

  /**
   * Initializes the REL-ID SDK
   * @returns Promise<RDNASyncResponse> that resolves with sync response structure
   */
  async initialize(): Promise<RDNASyncResponse> {
   
    const profile = await loadAgentInfo();
    console.log('RdnaService - Loaded connection profile:', {
      host: profile.host,
      port: profile.port,
      relId: profile.relId.substring(0, 10) + '...',
    });

    console.log('RdnaService - Starting initialization');

    return new Promise((resolve, reject) => {
      RdnaClient.initialize(
        profile.relId,                   // agentInfo: RelId from connection profile
        profile.host,                    // gatewayHost: Server hostname from connection profile
        profile.port,                    // gatewayPort: Server port from connection profile
        '',                              // cipherSpec: Empty for default
        '',                              // cipherSalt: Empty for default
        '',                              // proxySettings: Empty for no proxy
        '',                              // sslCertificate: Empty for default
        RDNALoggingLevel.RDNA_NO_LOGS,   // logLevel: No logs for production
        response => {
          console.log('RdnaService - Initialize sync callback received');
          console.log('RdnaService - initialize Sync raw response', response);

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

  /**
   * Gets the version of the REL-ID SDK
   */
  async getSDKVersion(): Promise<string> {
    return new Promise((resolve, reject) => {
      RdnaClient.getSDKVersion(response => {
        console.log('RdnaService - SDK version response received');

        try {
          const version = response?.response || 'Unknown';
          console.log('RdnaService - SDK Version:', version);
          resolve(version);
        } catch (error) {
          console.error('RdnaService - Failed to parse SDK version:', error);
          reject(new Error('Failed to parse SDK version response'));
        }
      });
    });
  }
}

export default RdnaService.getInstance();

Important API Parameters

The RdnaClient.initialize() call requires specific parameter ordering:

Parameter

Purpose

Example

agentInfo

RelId

From connection profile Ex. {"Name": "YourRELIDAgentName","RelId": "your-rel-id-string-here"}

gatewayHost

Server hostname

From connection profile Ex. {"Host": "your-gateway-host.com","Port": "443"}

gatewayPort

Server port

From connection profile Ex. {"Host": "your-gateway-host.com","Port": "443"}

logLevel

Logging setting

RDNA_NO_LOGS for production

Create a screen component that handles user interaction and displays progress:

// src/tutorial/screens/tutorial/TutorialHomeScreen.tsx
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
  StatusBar,
  ScrollView,
  ActivityIndicator,
  Alert,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../../navigation/AppNavigator';
import rdnaService from '../../../uniken/services/rdnaService';
import { getProgressMessage } from '../../../uniken/utils/progressHelper';
import type { 
  RDNAProgressData, 
  RDNASyncResponse,
  RDNAError,
  RDNAInitializeErrorData
} from '../../../uniken/types/rdnaEvents';

type TutorialHomeNavigationProp = NativeStackNavigationProp<RootStackParamList, 'TutorialHome'>;

const TutorialHomeScreen: React.FC = () => {
  const navigation = useNavigation<TutorialHomeNavigationProp>();
  const [sdkVersion, setSdkVersion] = useState<string>('Loading...');
  const [isInitializing, setIsInitializing] = useState<boolean>(false);
  const [progressMessage, setProgressMessage] = useState<string>('');
  const [initializeError, setInitializeError] = useState<RDNAInitializeErrorData | null>(null);

  useEffect(() => {
    loadSDKVersion();

    // Register error handler directly in TutorialHomeScreen
    const eventManager = rdnaService.getEventManager();
    eventManager.setInitializeErrorHandler((errorData: RDNAInitializeErrorData) => {
      console.log('TutorialHomeScreen - Received initialize error:', errorData);
      
      // Update local state
      setIsInitializing(false);
      setProgressMessage('');
      setInitializeError(errorData);
      
      // Navigate to error screen with the error details
      navigation.navigate('TutorialError', {
        shortErrorCode: errorData.shortErrorCode,
        longErrorCode: errorData.longErrorCode,
        errorString: errorData.errorString,
      });
    });

    return () => {
      // Cleanup - reset handlers
      const eventManager = rdnaService.getEventManager();
      eventManager.setInitializeProgressHandler(undefined);
      eventManager.setInitializeErrorHandler(undefined);
      //rdnaService.cleanup();
      setIsInitializing(false);
      setProgressMessage('');
      setInitializeError(null);
    };
  }, [navigation]);

  const loadSDKVersion = async () => {
    try {
      const version = await rdnaService.getSDKVersion();
      setSdkVersion(version);
    } catch (error) {
      console.error('Failed to load SDK version:', error);
      setSdkVersion('Unknown');
    }
  };

  const handleInitializePress = () => {
    if (isInitializing) return;
    
    setIsInitializing(true);
    setProgressMessage('Starting RDNA initialization...');
    
    console.log('TutorialHomeScreen - User clicked Initialize - Starting RDNA...');
    
    // Register progress handler directly with the event manager
    const eventManager = rdnaService.getEventManager();
    eventManager.setInitializeProgressHandler((data: RDNAProgressData) => {
      console.log('TutorialHomeScreen - Progress update:', data);
      const message = getProgressMessage(data);
      setProgressMessage(message);
    });
    
    // Call rdnaService.initialize() using .then()/.catch() pattern
    rdnaService.initialize()
      .then((syncResponse: RDNASyncResponse) => {
        console.log('TutorialHomeScreen - RDNA initialization promise resolved successfully');
        console.log('TutorialHomeScreen - Sync response received:', {
          longErrorCode: syncResponse.error?.longErrorCode,
          shortErrorCode: syncResponse.error?.shortErrorCode,
          errorString: syncResponse.error?.errorString
        });
        
        // Success will be handled by the SDKEventProvider's onInitialized handler
        // which automatically navigates to the success screen
      })
      .catch((error) => {
        console.error('TutorialHomeScreen - RDNA initialization promise rejected:', error);
        setIsInitializing(false);
        setProgressMessage('');
        
        const result: RDNASyncResponse = error;
       /*
        Error Code: 88 (RDNA_ERR_RDNA_ALREADY_INITIALIZED)
        Terminate the SDK to avoid re-initialization. This helps during development and prevents errors during React Native code refresh.
        Error Code: 179 (RDNA_ERR_INITIALIZE_ALREADY_IN_PROGRESS)
        Avoid invoking the Initialize API again while initialization is already in progress. Wait for the current initialization to complete.
        Error Code: 218 (RDNA_ERR_DEVICE_SECURITY_CHECKS_FAILED_FRIDA_MODULES_DETECTED)
        The SDK detected a Frida attack.
        */
        
        Alert.alert(
          'Initialization Failed',
          `${result.error.errorString}\n\nError Codes:\nLong: ${result.error.longErrorCode}\nShort: ${result.error.shortErrorCode}`,
          [
            { text: 'OK', style: 'default' }
          ]
        );
      });
  };

  // Component JSX would include SDK version display, initialize button, and progress display
};

Success Event Handling

The sample app uses a centralized approach for handling the onInitialized success event through SDKEventProvider.tsx:

// src/uniken/providers/SDKEventProvider.tsx
export const SDKEventProvider: React.FC<SDKEventProviderProps> = ({ children }) => {
  useEffect(() => {
    const eventManager = rdnaService.getEventManager();
    // Set up event handlers once on mount
    eventManager.setInitializedHandler(handleInitialized);
    return () => {
      console.log('SDKEventProvider - Component unmounting, cleaning up event handlers');
      eventManager.cleanup();
    };
  }, []);

  /**
   * Event handler for successful initialization
   */
  const handleInitialized = useCallback((data: RDNAInitializedData) => {
    console.log('SDKEventProvider - Successfully initialized, Session ID:', data.session.sessionID);
    
    NavigationService.navigate('TutorialSuccess', {
      statusCode: data.status.statusCode,
      statusMessage: data.status.statusMessage,
      sessionId: data.session.sessionID,
      sessionType: data.session.sessionType,
    });
  }, []);

  return (
    <SDKEventContext.Provider value={contextValue}>
      {children}
    </SDKEventContext.Provider>
  );
};

This provider should wrap your app's root navigation to ensure the onInitialized event is handled globally.

Key Implementation Features

The sample app demonstrates several important patterns:

Promise-based API Usage:

Error Handling:

Progress Tracking:

Event-Driven Architecture:

The following images showcase screens from the sample application:

Initialize Progress Screen

Initialize Error Screen

Initialize Screen

Key Test Scenarios

  1. Successful Initialization: Verify the complete flow works end-to-end
  2. Network Errors: Test with invalid host/port configurations
  3. Invalid Credentials: Test with incorrect RelId values
  4. Progress Tracking: Verify progress events are properly displayed

Verification Steps

Test your implementation with below APIs:

// Test SDK version retrieval
rdnaService.getSDKVersion()
  .then((version) => console.log('SDK Version:', version))
  .catch((error) => console.error('Version failed:', error));

// Test initialization
rdnaService.initialize()
  .then((syncResponse) => console.log('Initialization successful:', syncResponse))
  .catch((error) => console.error('Initialization failed:', error));

The sample app includes comprehensive testing patterns in the tutorial screens:

Connection Profile Issues

Error: "No RelIds found in agent info" Solution: Verify your JSON structure matches the sample in src/uniken/cp/agent_info.json

Error: "No matching profile found for RelId name" Solution: Ensure the Name field in RelIds matches the Name field in Profiles

Network Connectivity

Error: Network connection failures Solution: Check host/port values and network accessibility

Error: REL-ID connection issues Solution: Verify the REL-ID server setup and running

SDK Initialization

Error Code 88: SDK already initialized Solution: Terminate the sdk

Error Code 88: SDK detected dynamic attack performed on the app Solution: Terminate the app

Error Code 179: Initialization in progress Solution: Wait for current initialization to complete before retrying

Security Considerations

Memory Management

User Experience

Congratulations! You've successfully learned how to implement RELID SDK initialization with:

Next Steps