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)),
this.rdnaEmitter.addListener('onSdkLogPrintRequest', this.onSdkLogPrintRequest.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,
InitOptions,
} 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 with optional advanced configuration
* @param initOptions - Optional configuration for language, permissions, and telemetry
* @returns Promise<RDNASyncResponse> that resolves with sync response structure
*/
async initialize(initOptions?: InitOptions): Promise<RDNASyncResponse> {
const profile = await loadAgentInfo();
console.log('RdnaService - Loaded connection profile:', {
host: profile.host,
port: profile.port,
relId: profile.relId.substring(0, 10) + '...',
});
// Use provided options or default values
const defaultInitOptions: InitOptions = {
internationalizationOptions: {
localeCode: '', // Empty string - SDK will default to 'en'
localeName: '',
languageDirection: 0 // 0 = LTR (Left-to-Right)
},
permissionOptions: {
isLocationPermissionRequired: true,
isLocationPermissionMandatory: false
},
otelConfig: {
otelHTTPEndpointURL: '',
enableEncoding: '',
disableTrace: 1, // Tracing disabled by default
otelTraceFlushTimeout: 0
}
};
const finalInitOptions = initOptions || defaultInitOptions;
const initOptionsString = JSON.stringify(finalInitOptions);
console.log('RdnaService - Starting initialization');
console.log('RdnaService - InitOptions:', initOptionsString);
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
'', // dnsServerList: Empty for default
RDNALoggingLevel.RDNA_NO_LOGS, // logLevel: No logs for production
initOptionsString, // initOptions: Advanced SDK configuration (JSON string)
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 |
|
| SDK configuration (optional) | JSON string with language, permissions, and telemetry settings |
The RELID SDK supports advanced configuration through the initOptions parameter. This allows you to customize language settings, permission requirements, and telemetry options based on your application's needs.
The InitOptions interface is defined in src/uniken/types/rdnaEvents.ts:
export interface InitOptions {
internationalizationOptions: {
localeCode: string; // ISO 639-1 language code (e.g., 'en', 'ar', 'es')
localeName: string; // Display name for the locale
languageDirection: number; // 0 = LTR (Left-to-Right), 1 = RTL (Right-to-Left)
};
permissionOptions: {
isLocationPermissionRequired: boolean; // Whether location permission is needed
isLocationPermissionMandatory: boolean; // If true, SDK fails without permission
};
otelConfig: {
otelHTTPEndpointURL: string; // OpenTelemetry HTTP endpoint URL
enableEncoding: string; // Encoding format for OTel data
disableTrace: number; // 0 = enable tracing, 1 = disable tracing
otelTraceFlushTimeout: number; // Timeout in milliseconds for flushing traces
};
}
Language options control the SDK's display language and text direction (LTR/RTL):
// Example: Arabic (RTL) configuration
const language = 'ar'; // Get from i18n library or user settings
const languageDirection = ['ar', 'he', 'fa', 'ur'].includes(language) ? 1 : 0;
const initOptions: InitOptions = {
internationalizationOptions: {
localeCode: language, // ISO 639-1 code: 'ar' for Arabic
localeName: 'Arabic', // Display name
languageDirection: 1 // RTL direction
},
// ... other options
};
RTL Languages: Arabic (ar), Hebrew (he), Persian (fa), Urdu (ur) LTR Languages: All others (default)
Common Use Cases:
Permission options define whether the SDK requires location access and if it's mandatory:
const initOptions: InitOptions = {
// ... language options
permissionOptions: {
isLocationPermissionRequired: true, // Request location permission
isLocationPermissionMandatory: false // SDK works without it (limited functionality)
},
// ... otel config
};
Configuration Scenarios:
Scenario | Required | Mandatory | Behavior |
Location-based features |
|
| SDK requests permission but works without it |
Location required for compliance |
|
| SDK initialization fails without permission |
No location needed |
|
| SDK doesn't request location permission |
Use Cases:
OpenTelemetry configuration enables enterprise-level monitoring and distributed tracing:
const initOptions: InitOptions = {
// ... language options
// ... permission options
otelConfig: {
otelHTTPEndpointURL: 'https://your-otel-collector.example.com', // Your OTel endpoint
enableEncoding: '', // Encoding format (empty for default)
disableTrace: 0, // 0 = enabled, 1 = disabled
otelTraceFlushTimeout: 0 // Flush timeout in milliseconds (0 = default)
}
};
Default Configuration (Recommended for Most Apps):
otelConfig: {
otelHTTPEndpointURL: '', // Empty = telemetry disabled
enableEncoding: '',
disableTrace: 1, // Tracing disabled
otelTraceFlushTimeout: 0
}
Use Cases:
Here's a complete example showing all three configuration areas:
// Example: Arabic banking app with location and telemetry
const initOptions: InitOptions = {
internationalizationOptions: {
localeCode: 'ar',
localeName: 'Arabic',
languageDirection: 1 // RTL
},
permissionOptions: {
isLocationPermissionRequired: true,
isLocationPermissionMandatory: false
},
otelConfig: {
otelHTTPEndpointURL: 'https://telemetry.yourbank.com',
enableEncoding: '',
disableTrace: 0, // Enabled for enterprise monitoring
otelTraceFlushTimeout: 5000
}
};
// Call initialize with custom configuration
await rdnaService.initialize(initOptions);
The initOptions parameter is optional. Existing code without initOptions continues to work with default values:
// Works with default configuration
await rdnaService.initialize();
// Works with custom configuration
await rdnaService.initialize(initOptions);
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,
InitOptions,
} 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...');
// ========================================
// CONFIGURE SDK INITIALIZATION OPTIONS
// ========================================
// Customers: Customize this section based on your application requirements
// Determine language and direction
const language = 'en'; // Get from your i18n library (e.g., i18next.language)
const languageDirection = ['ar', 'he', 'fa', 'ur'].includes(language) ? 1 : 0;
// Configure permissions
const requireLocationPermission = true; // Does your app need location?
const locationIsMandatory = false; // Can SDK work without it?
// Configure telemetry
const enableTelemetry = false; // Set to true for enterprise monitoring
// Build the initOptions configuration object
const initOptions: InitOptions = {
internationalizationOptions: {
localeCode: language,
localeName: language,
languageDirection: languageDirection
},
permissionOptions: {
isLocationPermissionRequired: requireLocationPermission,
isLocationPermissionMandatory: locationIsMandatory
},
otelConfig: {
otelHTTPEndpointURL: enableTelemetry ? 'https://your-otel-collector.example.com' : '',
enableEncoding: '',
disableTrace: enableTelemetry ? 0 : 1,
otelTraceFlushTimeout: 0
}
};
console.log('TutorialHomeScreen - Initializing with custom options:', initOptions);
// 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() with custom configuration
rdnaService.initialize(initOptions)
.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 basic initialization (with default values)
rdnaService.initialize()
.then((syncResponse) => console.log('Initialization successful:', syncResponse))
.catch((error) => console.error('Initialization failed:', error));
// Test initialization with custom initOptions
const customOptions = {
internationalizationOptions: {
localeCode: 'ar',
localeName: 'Arabic',
languageDirection: 1
},
permissionOptions: {
isLocationPermissionRequired: true,
isLocationPermissionMandatory: false
},
otelConfig: {
otelHTTPEndpointURL: '',
enableEncoding: '',
disableTrace: 1,
otelTraceFlushTimeout: 0
}
};
rdnaService.initialize(customOptions)
.then((syncResponse) => console.log('Custom initialization successful:', syncResponse))
.catch((error) => console.error('Custom 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 288: 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: