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.
react-native-rdna-client
plugin installedThe 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 |
|
Profile Parser | Utility to parse connection data |
|
Event Manager | Handles SDK callbacks |
|
RELID Service | Main SDK interface |
|
Event Provider | Global event handling |
|
UI Components | User interface screens |
|
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 react-native-rdna-client plugin to your project:
npm install react-native-rdna-client
Follow the platform-specific setup instructions provided with the plugin for both iOS and Android configurations.
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"
}
]
}
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');
}
}
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:
setInitializeProgressHandler()
, setInitializeErrorHandler()
, etc. to register callbacksThe 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();
The RdnaClient.initialize()
call requires specific parameter ordering:
Parameter | Purpose | Example |
| RelId | From connection profile Ex. {"Name": "YourRELIDAgentName","RelId": "your-rel-id-string-here"} |
| Server hostname | From connection profile Ex. {"Host": "your-gateway-host.com","Port": "443"} |
| Server port | From connection profile Ex. {"Host": "your-gateway-host.com","Port": "443"} |
| Logging setting |
|
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
};
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.
The sample app demonstrates several important patterns:
Promise-based API Usage:
.then()/.catch()
pattern for clean asynchronous handlingError Handling:
RDNASyncResponse
and extracts error details directlyAlert.alert()
Progress Tracking:
Event-Driven Architecture:
.then()
block only logs the response and lets SDKEventProvider handle navigationThe following images showcase screens from the sample application:
|
|
|
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:
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
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
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
RDNALoggingLevel.RDNA_NO_LOGS
in productionrelId.substring(0, 10) + '...'
)useEffect
cleanup if neededCongratulations! You've successfully learned how to implement RELID SDK initialization with: