🎯 Learning Path:

  1. Complete REL-ID Complete Activation & Login Flow Codelab
  2. You are here → Push Notification Integration

Welcome to the REL-ID Push Notification Integration codelab! This tutorial enhances your existing REL-ID application with secure push notification capabilities using REL-ID SDK's setDeviceToken API.

What You'll Build

In this codelab, you'll enhance your existing REL-ID application with:

What You'll Learn

By completing this codelab, you'll master:

  1. REL-ID Push Architecture: Understanding two-channel security model vs standard push notifications
  2. setDeviceToken API: Complete implementation of REL-ID device token registration
  3. Device Token Management: Implementing token retrieval, registration, and refresh cycles
  4. Service Architecture: Building scalable push notification services with singleton patterns
  5. REL-ID SDK Integration: Best practices for integrating with existing REL-ID service layer
  6. Production Deployment: Security best practices, error handling, and monitoring strategies

Prerequisites

Before starting this codelab, ensure you have:

Get the Code from GitHub

The code to get started can be found 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-push-notification-token folder in the repository you cloned earlier

Why REL-ID Push Notifications?

REL-ID push notifications provide a secure, two-channel architecture that goes beyond standard push messaging with secure wake-up signals, MITM-proof channels, transaction approvals, and device-bound credentials.

REL-ID Flow: FCM Wake-up → App Launch → Secure REL-ID Channel → Encrypted Data Retrieval → User Action

Codelab Architecture Overview

This codelab implements two core components:

  1. PushNotificationService: Singleton service managing device tokens and REL-ID registration
  2. PushNotificationProvider: React context provider for automatic initialization

Before implementing push notifications, let's understand how REL-ID's secure notification system works.

REL-ID Two-Channel Security Model

REL-ID uses a sophisticated two-channel approach for maximum security:

📱 REL-ID Server → FCM/APNS (Wake-up Signal) → Mobile App → REL-ID Secure Channel → Encrypted Data

Channel 1: FCM Wake-Up Signal

Channel 2: REL-ID Secure Channel

Device Token Registration Flow

Here's how setDeviceToken() enables secure REL-ID communications:

// Device Registration Flow
FCM Token Generation → setDeviceToken(token) → REL-ID Backend Registration →
Secure Channel Establishment → Transaction Approval Capability

Step

Description

Security Benefit

1. FCM Token

Generate platform-specific device identifier

Device uniqueness

2. REL-ID Registration

setDeviceToken() registers device with REL-ID backend

Device-server binding

3. Secure Channel

Establish encrypted communication channel

MITM protection

4. Transaction Support

Enable approve/reject actions with MFA

Multi-factor security

REL-ID Push Notification Use Cases

Once integrated, your app can handle these secure notification types:

Firebase Role: Provides the platform infrastructure (FCM token generation)

REL-ID Role: Provides the secure communication and transaction approval capabilities

Now let's implement the core push notification service that handles FCM token management and REL-ID integration.

Enhance rdnaService with setDeviceToken

First, add the device token registration method to your existing REL-ID service:

// src/uniken/services/rdnaService.ts (addition to existing class)

/**
 * Registers device push notification token with REL-ID SDK
 * enabling the server to send secure push notifications to this specific device.
 * The token is used to establish a secure communication channel for:
 * - Transaction approvals
 * - Authentication challenges
 * - Security alerts
 * - Multi-factor authentication requests
 * This method registers the device's FCM/APNS push notification token with the REL-ID SDK.
 * The token is used by the backend to send push notifications to this specific device.
 * Unlike other REL-ID APIs, this method is synchronous and doesn't use callbacks.
 *
 * @param token The FCM (Android) or APNS (iOS) device token string
 * @throws Error if token registration fails
 */
setDeviceToken(token: string): void {
  console.log('RdnaService - Registering device push token with REL-ID SDK');
  console.log('RdnaService - Token length:', token.length);

  try {
    // Register token with REL-ID native SDK
    RdnaClient.setDeviceToken(token);
    console.log('RdnaService - Device push token registration successful');
  } catch (error) {
    console.error('RdnaService - Device push token registration failed:', error);
    throw new Error(`Failed to register device push token: ${error}`);
  }
}

Create Push Notification Service

Now create the singleton service that manages all push notification functionality for both Android and iOS:

// src/uniken/services/pushNotificationService.ts

/**
 * Push Notification Service
 *
 * Cross-platform FCM integration for REL-ID SDK (Android & iOS).
 * Handles token registration with REL-ID backend via rdnaService.setDeviceToken().
 *
 * Features:
 * - Android & iOS FCM token retrieval and registration
 * - Android 13+ POST_NOTIFICATIONS permission handling
 * - iOS authorization via @react-native-firebase/messaging (no AppDelegate changes needed)
 * - Automatic token refresh handling
 * - REL-ID SDK integration
 *
 * iOS Note: Requires GoogleService-Info.plist and APNS certificate uploaded to Firebase Console.
 * The @react-native-firebase/messaging library handles APNS delegate methods automatically via swizzling.
 *
 * Usage:
 * const pushService = PushNotificationService.getInstance();
 * await pushService.initialize();
 */

import { Platform, PermissionsAndroid } from 'react-native';
import { getApp, getApps, initializeApp } from '@react-native-firebase/app';
import messaging from '@react-native-firebase/messaging';
import { RdnaService } from './rdnaService';

/**
 * Push Notification Service
 * Cross-platform singleton for FCM token management (Android & iOS)
 */
export class PushNotificationService {
  private static instance: PushNotificationService;
  private rdnaService: RdnaService;
  private isInitialized: boolean = false;

  private constructor() {
    this.rdnaService = RdnaService.getInstance();
  }

  /**
   * Gets singleton instance
   */
  static getInstance(): PushNotificationService {
    if (!PushNotificationService.instance) {
      PushNotificationService.instance = new PushNotificationService();
    }
    return PushNotificationService.instance;
  }

  /**
   * Initialize FCM and register token with REL-ID SDK
   * Supports both Android and iOS platforms
   */
  async initialize(): Promise<void> {
    if (this.isInitialized) {
      console.log('PushNotificationService - Already initialized');
      return;
    }

    console.log(`PushNotificationService - Starting FCM initialization for ${Platform.OS}`);

    try {
      // Ensure Firebase is initialized (auto-init or manual fallback)
      await this.ensureFirebaseInitialized();

      // Request permissions (handles both Android and iOS)
      const hasPermission = await this.requestPermissions();
      if (!hasPermission) {
        console.warn(`PushNotificationService - Permission not granted on ${Platform.OS}`);
        return;
      }

      // Get and register initial token
      await this.getAndRegisterToken();

      // Set up token refresh listener
      this.setupTokenRefreshListener();

      this.isInitialized = true;
      console.log(`PushNotificationService - Initialization complete for ${Platform.OS}`);
    } catch (error) {
      console.error('PushNotificationService - Initialization failed:', error);
      throw error;
    }
  }

  /**
   * Ensure Firebase is initialized
   * React Native Firebase auto-initializes natively, this just verifies it's ready
   */
  private async ensureFirebaseInitialized(): Promise<void> {
    try {
      // Give native auto-initialization a moment to complete
      // React Native Firebase initializes from native side before JS loads
      await new Promise(resolve => setTimeout(resolve, 100));

      const apps = getApps();
      if (apps.length === 0) {
        console.warn('PushNotificationService - No Firebase app found after native init');
        console.warn('PushNotificationService - This may indicate GoogleService-Info.plist is not being loaded');
        throw new Error('Firebase failed to auto-initialize. Check GoogleService-Info.plist configuration.');
      } else {
        console.log('PushNotificationService - Firebase already initialized via native auto-init');
      }
    } catch (error) {
      console.error('PushNotificationService - Firebase initialization check failed:', error);
      throw error;
    }
  }

  /**
   * Request FCM permissions
   * Android: Handles POST_NOTIFICATIONS permission for Android 13+
   * iOS: Requests notification authorization (Alert, Sound, Badge)
   */
  private async requestPermissions(): Promise<boolean> {
    try {
      console.log('PushNotificationService - Platform OS:', Platform.OS);
      console.log('PushNotificationService - Platform Version:', Platform.Version);

      // Android 13+ (API 33+) requires POST_NOTIFICATIONS permission
      if (Platform.OS === 'android' && Platform.Version >= 33) {
        console.log('PushNotificationService - Requesting POST_NOTIFICATIONS permission (Android 13+)');
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
        );
        console.log('PushNotificationService - POST_NOTIFICATIONS result:', granted);
        if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
          console.log('PushNotificationService - POST_NOTIFICATIONS permission denied');
          return false;
        }
      }

      // Request FCM authorization (works for both Android and iOS)
      console.log(`PushNotificationService - Requesting FCM authorization for ${Platform.OS}`);

      // Use modular API - get app then messaging
      const app = getApp();
      const messagingInstance = messaging(app);
      console.log('PushNotificationService - Got messaging instance');

      const authStatus = await messagingInstance.requestPermission();
      console.log('PushNotificationService - FCM auth status:', authStatus);
      console.log('PushNotificationService - AUTHORIZED value:', messaging.AuthorizationStatus.AUTHORIZED);
      console.log('PushNotificationService - PROVISIONAL value:', messaging.AuthorizationStatus.PROVISIONAL);

      // iOS supports PROVISIONAL authorization (quiet notifications without prompt)
      const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
                      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

      console.log('PushNotificationService - FCM permission:', enabled ? 'granted' : 'denied');
      return enabled;
    } catch (error) {
      console.error('PushNotificationService - Permission request failed:', error);
      return false;
    }
  }

  /**
   * Get FCM token and register with REL-ID SDK
   * Android: Gets FCM registration token
   * iOS: Gets FCM token (mapped from APNS token by Firebase automatically)
   */
  private async getAndRegisterToken(): Promise<void> {
    try {
      console.log(`PushNotificationService - Getting FCM token for ${Platform.OS}`);

      const app = getApp();

      // On iOS, check if APNS token is available first
      if (Platform.OS === 'ios') {
        const apnsToken = await messaging(app).getAPNSToken();
        if (apnsToken) {
          console.log('PushNotificationService - iOS APNS token available, length:', apnsToken.length);
        } else {
          console.log('PushNotificationService - iOS APNS token not yet available, will retry via getToken()');
        }
      }

      const token = await messaging(app).getToken();
      if (!token) {
        console.warn(`PushNotificationService - No FCM token received for ${Platform.OS}`);
        return;
      }

      console.log(`PushNotificationService - FCM token received for ${Platform.OS}, length:`, token.length);
      console.log('PushNotificationService - FCM TOKEN:', token);

      // Register with REL-ID SDK (works for both Android FCM and iOS FCM tokens)
      this.rdnaService.setDeviceToken(token);

      console.log('PushNotificationService - Token registered with REL-ID SDK');
    } catch (error) {
      console.error('PushNotificationService - Token registration failed:', error);
      throw error;
    }
  }

  /**
   * Set up automatic token refresh
   * Handles token refresh for both Android and iOS
   */
  private setupTokenRefreshListener(): void {
    console.log(`PushNotificationService - Setting up token refresh listener for ${Platform.OS}`);

    const app = getApp();
    messaging(app).onTokenRefresh(async (token) => {
      console.log(`PushNotificationService - Token refreshed for ${Platform.OS}, length:`, token.length);
      console.log('PushNotificationService - REFRESHED FCM TOKEN:', token);
      try {
        // Register new token with REL-ID SDK
        this.rdnaService.setDeviceToken(token);
        console.log('PushNotificationService - Refreshed token registered with REL-ID SDK');
      } catch (error) {
        console.error('PushNotificationService - Token refresh registration failed:', error);
      }
    });
  }

  /**
   * Get current FCM token (for debugging)
   * Works for both Android and iOS
   */
  async getCurrentToken(): Promise<string | null> {
    try {
      const app = getApp();
      return await messaging(app).getToken();
    } catch (error) {
      console.error('PushNotificationService - Failed to get current token:', error);
      return null;
    }
  }

  /**
   * Cleanup (reset initialization state)
   */
  cleanup(): void {
    console.log('PushNotificationService - Cleanup');
    this.isInitialized = false;
  }
}

// Export singleton instance
const pushNotificationService = PushNotificationService.getInstance();
export default pushNotificationService;

Service Architecture Benefits

This implementation follows enterprise-grade patterns:

Pattern

Benefit

Implementation

Singleton

Single point of control

getInstance() method

Dependency Injection

Testable, maintainable

Constructor injection of rdnaService

Error Handling

Graceful failure management

Try-catch with logging

State Management

Prevents double initialization

isInitialized flag

Platform Abstraction

Cross-platform compatibility

Platform OS checks

Now let's create a React context provider that automatically initializes push notifications when your app starts.

Create Push Notification Provider

Build a clean provider component that integrates with your existing context hierarchy:

// src/uniken/providers/PushNotificationProvider.tsx

/**
 * Push Notification Provider
 *
 * Ultra-simplified provider that initializes Android FCM push notifications
 * and registers tokens directly with REL-ID SDK. No complex state management needed
 * since the pushNotificationService singleton handles everything internally.
 *
 * Usage:
 * <PushNotificationProvider>
 *   <App />
 * </PushNotificationProvider>
 */

import React, { useEffect, ReactNode } from 'react';
import pushNotificationService from '../services/pushNotificationService';

/**
 * Provider props
 */
interface PushNotificationProviderProps {
  children: ReactNode;
}

/**
 * Push Notification Provider Component
 * Simply initializes FCM on mount and lets the service handle everything
 */
export const PushNotificationProvider: React.FC<PushNotificationProviderProps> = ({ children }) => {
  useEffect(() => {
    console.log('PushNotificationProvider - Initializing FCM');

    pushNotificationService
      .initialize()
      .then(() => {
        console.log('PushNotificationProvider - FCM initialization successful');
      })
      .catch((error) => {
        console.error('PushNotificationProvider - FCM initialization failed:', error);
      });
  }, []);

  return <>{children}</>;
};

export default PushNotificationProvider;

Update Provider Index

Add the new provider to your providers index:

// src/uniken/providers/index.ts

export { default as SDKEventProvider } from './SDKEventProvider';
export { default as PushNotificationProvider } from './PushNotificationProvider';

Integrate with App Context Hierarchy

Update your main App component to include push notification initialization:

// App.tsx (integration example)

import React from 'react';
import { StatusBar } from 'react-native';
import AppNavigator from './src/tutorial/navigation/AppNavigator';
import {
  SessionProvider,
  MTDThreatProvider,
  SDKEventProvider,
  PushNotificationProvider  // Add this import
} from './src/uniken';

const App: React.FC = () => {
  return (
    <SessionProvider>
      <MTDThreatProvider>
        <SDKEventProvider>
          <PushNotificationProvider>  {/* Add this provider */}
            <StatusBar />
            <AppNavigator />
          </PushNotificationProvider>
        </SDKEventProvider>
      </MTDThreatProvider>
    </SessionProvider>
  );
};

export default App;

Provider Pattern Benefits

This approach provides several architectural advantages:

Benefit

Description

Implementation Detail

Automatic Initialization

Push notifications start immediately when app launches

useEffect with empty dependency

Context Integration

Fits naturally into existing provider hierarchy

Nested within existing providers

Error Isolation

Push notification failures don't crash the app

Try-catch in service layer

Development Friendly

No complex state management needed

Service handles all complexity

Production Ready

Graceful handling of permission denials and errors

Comprehensive error logging

Let's thoroughly test your push notification implementation with comprehensive scenarios to ensure production readiness.

Test Scenario 1: Complete Token Registration Flow

Prerequisites:

Test Steps:

  1. Launch Application
    npx react-native run-android
    
  2. Monitor Console Logs Look for this successful initialization sequence:
    ✅ PushNotificationProvider - Initializing FCM
    ✅ PushNotificationService - Starting FCM initialization
    ✅ PushNotificationService - POST_NOTIFICATIONS result: granted
    ✅ PushNotificationService - FCM token received, length: 142
    ✅ RdnaService - Device push token registration successful
    ✅ PushNotificationService - Initialization complete
    
  3. Verify Token Generation Confirm you see a valid FCM token logged (142+ character string)
  4. Test Token Refresh The refresh listener should also fire automatically:
    ✅ PushNotificationService - Token refreshed, length: 142
    ✅ RdnaService - Refreshed token registered with REL-ID SDK
    

Expected Results:

Test Scenario 2: Permission Handling

Android 13+ Permission Test:

  1. Install on Android 13+ device/emulator
  2. First launch - verify permission request appears
  3. Grant permission - verify FCM initialization continues
  4. Deny permission test - verify graceful handling

iOS Permission Test:

  1. Install on iOS device/simulator
  2. First launch - verify notification permission dialog appears
  3. Allow - verify FCM initialization continues
  4. Test provisional authorization - verify quiet notifications work

Expected Permission Flow:

📱 POST_NOTIFICATIONS permission request → User grants → FCM initialization
📱 POST_NOTIFICATIONS permission request → User denies → Graceful fallback

Test Scenario 3: Firebase Configuration Validation

Test Missing google-services.json:

  1. Temporarily rename google-services.json to google-services.json.backup
  2. Build project: npx react-native run-android
  3. Verify error handling - should see Google Services related build errors
  4. Restore file and rebuild successfully

Test Google Services Plugin:

# Verify Google Services plugin processing
cd android && ./gradlew app:dependencies | grep google-services

Production Readiness Checklist

Before deploying to production, verify:

Congratulations! You've successfully implemented secure push notification integration with the REL-ID SDK. Here's your complete implementation overview.

🚀 What You've Built

Secure Device Registration - FCM tokens registered with REL-ID backend for two-channel security ✅ Firebase Integration - Complete FCM setup with Google Services auto-initialization ✅ Production-Ready Service - Singleton architecture with error handling and token refresh ✅ Android Configuration - Google Services plugin, permissions, and network security ✅ Provider Integration - Clean React context integration with existing app architecture

Key Files Created/Modified

src/uniken/services/
├── pushNotificationService.ts     ✅ FCM token management singleton
└── rdnaService.ts                 ✅ Enhanced with setDeviceToken()

src/uniken/providers/
├── PushNotificationProvider.tsx   ✅ React context provider
└── index.ts                       ✅ Updated exports

android/
├── build.gradle                   ✅ Google Services plugin
├── app/build.gradle              ✅ Plugin application
├── app/google-services.json      ✅ Firebase configuration
└── app/src/main/res/xml/
    └── network_security_config.xml ✅ Development network config

App.tsx                           ✅ Provider integration
index.js                          ✅ Clean Firebase initialization

Architecture Achievement

Your implementation demonstrates enterprise-grade patterns:

Component

Pattern

Benefit

PushNotificationService

Singleton

Centralized token management

PushNotificationProvider

Context Provider

Automatic initialization

rdnaService Integration

Dependency Injection

Clean service layer

Firebase Configuration

Auto-initialization

Zero-configuration startup

Error Handling

Graceful Degradation

Production reliability

Security Benefits Unlocked

Your REL-ID push notification integration now enables:

Additional Resources

🎉 Congratulations!

You've successfully implemented secure push notification capabilities that integrate seamlessly with the REL-ID security ecosystem. Your app can now participate in secure, two-channel communications for transaction approvals, authentication challenges, and security notifications.

Your users now have a more secure, responsive authentication experience with the power of REL-ID's push notification infrastructure!