🎯 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-cordova.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: Initialization manager for automatic push notification setup

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

Project Structure

This project includes a local Cordova plugin. Ensure the plugin directory exists in your project root:

# Verify plugin directory exists
ls -la ./RdnaClient

Add the SDK Plugin (Local)

The SDK plugin is included as a local plugin in the project. Install it from the local directory:

cordova plugin add ./RdnaClient

Add Firebase Plugin

For push notification support, install cordova-plugin-firebasex:

cordova plugin add cordova-plugin-firebasex

Add File Plugin

For loading local JSON files, install cordova-plugin-file:

cordova plugin add cordova-plugin-file

Platform Setup

# Add platforms
cordova platform add ios
cordova platform add android

# Prepare platforms
cordova prepare

Firebase Configuration

Ensure you have the Firebase configuration files in place:

Android: google-services.json in the root folder iOS: GoogleService-Info.plist in the root folder

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.js (addition to existing class)

/**
 * Registers device push notification token with REL-ID SDK
 * Used to enable push notifications for REL-ID server notifications
 *
 * @param {string} token - FCM token from Firebase (Android/iOS)
 * @throws {Error} if token registration fails
 */
setDeviceToken(token) {
  console.log('RdnaService - Registering device push token with REL-ID SDK');
  console.log('RdnaService - Token length:', token ? token.length : 0);

  if (!token || typeof token !== 'string') {
    const error = 'Invalid token: must be a non-empty string';
    console.error('RdnaService - ' + error);
    throw new Error(error);
  }

  try {
    // Call native plugin to register token with REL-ID backend
    // This is a fire-and-forget operation - no callback needed
    com.uniken.rdnaplugin.RdnaClient.setDeviceToken(
      () => {
        console.log('RdnaService - Device push token registration successful');
      },
      (error) => {
        console.error('RdnaService - Device push token registration failed:', JSON.stringify(error, null, 2));
      },
      [token]
    );

    console.log('RdnaService - Device push token registration initiated');
  } catch (error) {
    console.error('RdnaService - Device push token registration error:', JSON.stringify(error, null, 2));
    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.js

/**
 * Push Notification Service
 *
 * Cross-platform FCM integration for REL-ID SDK using cordova-plugin-firebasex.
 * Handles token registration with REL-ID backend via rdnaService.setDeviceToken().
 *
 * Features:
 * - Cross-platform FCM token retrieval (Android & iOS)
 * - Notification permission handling
 * - Automatic token refresh handling
 * - REL-ID SDK integration
 * - Singleton pattern
 *
 * Usage:
 * const pushService = PushNotificationService.getInstance();
 * await pushService.initialize();
 *
 * @requires cordova-plugin-firebasex
 * @requires google-services.json (Android) or GoogleService-Info.plist (iOS)
 */

/**
 * Push Notification Service
 * Singleton for FCM token management using cordova-plugin-firebasex
 */
const PushNotificationService = {
  /** @type {PushNotificationService|null} */
  _instance: null,

  /** @type {boolean} */
  _initialized: false,

  /** @type {RdnaService|null} */
  _rdnaService: null,

  /**
   * Gets singleton instance
   * @returns {PushNotificationService}
   */
  getInstance() {
    if (!this._instance) {
      this._instance = this;
      // Lazy-load rdnaService when first accessed
      if (typeof RdnaService !== 'undefined') {
        this._rdnaService = RdnaService.getInstance();
      }
    }
    return this._instance;
  },

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

    const platform = getPlatformId();
    console.log('PushNotificationService - Starting FCM initialization for', platform);

    try {
      // Ensure Firebase plugin is available
      if (typeof FirebasePlugin === 'undefined') {
        throw new Error('FirebasePlugin not available. Ensure cordova-plugin-firebasex is installed.');
      }

      // Request permissions
      const hasPermission = await this.requestPermissions();
      if (!hasPermission) {
        console.warn('PushNotificationService - Permission not granted on', platform);
        return;
      }

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

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

      this._initialized = true;
      console.log('PushNotificationService - Initialization complete for', platform);
    } catch (error) {
      console.error('PushNotificationService - Initialization failed:', JSON.stringify(error, null, 2));
      throw error;
    }
  },

  /**
   * Request FCM permissions
   * Android: Notification permissions (auto-granted on Android <13)
   * iOS: Requests notification authorization (Alert, Sound, Badge)
   * @returns {Promise<boolean>}
   */
  async requestPermissions() {
    return new Promise((resolve) => {
      try {
        const platform = getPlatformId();
        console.log('PushNotificationService - Checking permission for', platform);

        // Check current permission status
        FirebasePlugin.hasPermission((hasPermission) => {
          console.log('PushNotificationService - Current permission status:', hasPermission);

          if (hasPermission) {
            console.log('PushNotificationService - Permission already granted');
            resolve(true);
            return;
          }

          // Request permission (iOS will show dialog, Android handles automatically)
          console.log('PushNotificationService - Requesting permission');
          FirebasePlugin.grantPermission((granted) => {
            console.log('PushNotificationService - Permission result:', granted);
            resolve(granted);
          }, (error) => {
            console.error('PushNotificationService - Permission request failed:', JSON.stringify(error, null, 2));
            resolve(false);
          });
        }, (error) => {
          console.error('PushNotificationService - Permission check failed:', JSON.stringify(error, null, 2));
          resolve(false);
        });
      } catch (error) {
        console.error('PushNotificationService - Permission request error:', JSON.stringify(error, null, 2));
        resolve(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)
   * @returns {Promise<void>}
   */
  async getAndRegisterToken() {
    return new Promise((resolve, reject) => {
      try {
        const platform = getPlatformId();
        console.log('PushNotificationService - Getting FCM token for', platform);

        // On iOS, also log APNS token for debugging
        if (platform === 'ios') {
          FirebasePlugin.getAPNSToken((apnsToken) => {
            if (apnsToken) {
              console.log('PushNotificationService - iOS APNS token available, length:', apnsToken.length);
              console.log('PushNotificationService - APNS TOKEN:', apnsToken);
            } else {
              console.log('PushNotificationService - iOS APNS token not yet available');
            }
          }, (error) => {
            console.log('PushNotificationService - APNS token check failed (non-critical):', JSON.stringify(error, null, 2));
          });
        }

        FirebasePlugin.getToken((token) => {
          if (!token) {
            console.warn('PushNotificationService - No FCM token received for', platform);
            resolve();
            return;
          }

          console.log('PushNotificationService - FCM token received for', platform, ', length:', token.length);
          console.log('PushNotificationService - FCM TOKEN:', token);

          try {
            // Register with REL-ID SDK
            if (this._rdnaService && typeof this._rdnaService.setDeviceToken === 'function') {
              this._rdnaService.setDeviceToken(token);
              console.log('PushNotificationService - Token registered with REL-ID SDK');
            } else {
              console.warn('PushNotificationService - rdnaService not available, token not registered');
            }

            resolve();
          } catch (error) {
            console.error('PushNotificationService - REL-ID registration failed:', JSON.stringify(error, null, 2));
            reject(error);
          }
        }, (error) => {
          // On iOS, APNS token may not be ready yet (error code 505)
          // Firebase will trigger token refresh when APNS becomes available
          const platform = getPlatformId();
          const errorStr = error ? error.toString() : '';
          const hasCode505 = (error && error.code === 505) || (errorStr.includes('Code=505') || errorStr.includes('code 505'));

          if (hasCode505 && platform === 'ios') {
            console.log('PushNotificationService - APNS not ready yet, waiting for token refresh listener');
            resolve(); // Don't fail - token will come through refresh listener
            return;
          }

          console.error('PushNotificationService - Token retrieval failed:', JSON.stringify(error, null, 2));
          reject(error);
        });
      } catch (error) {
        console.error('PushNotificationService - getAndRegisterToken error:', JSON.stringify(error, null, 2));
        reject(error);
      }
    });
  },

  /**
   * Set up automatic token refresh
   * Handles token refresh for both Android and iOS
   */
  setupTokenRefreshListener() {
    const platform = getPlatformId();
    console.log('PushNotificationService - Setting up token refresh listener for', platform);

    try {
      FirebasePlugin.onTokenRefresh((token) => {
        console.log('PushNotificationService - Token refreshed for', platform, ', length:', token.length);
        console.log('PushNotificationService - REFRESHED FCM TOKEN:', token);

        try {
          // Register new token with REL-ID SDK
          if (this._rdnaService && typeof this._rdnaService.setDeviceToken === 'function') {
            this._rdnaService.setDeviceToken(token);
            console.log('PushNotificationService - Refreshed token registered with REL-ID SDK');
          } else {
            console.warn('PushNotificationService - rdnaService not available, refreshed token not registered');
          }
        } catch (error) {
          console.error('PushNotificationService - Token refresh registration failed:', JSON.stringify(error, null, 2));
        }
      }, (error) => {
        console.error('PushNotificationService - Token refresh listener error:', JSON.stringify(error, null, 2));
      });
    } catch (error) {
      console.error('PushNotificationService - setupTokenRefreshListener error:', JSON.stringify(error, null, 2));
    }
  },

  /**
   * Get current FCM token (for debugging)
   * @returns {Promise<string|null>}
   */
  async getCurrentToken() {
    return new Promise((resolve) => {
      try {
        FirebasePlugin.getToken((token) => {
          resolve(token);
        }, (error) => {
          console.error('PushNotificationService - Failed to get current token:', JSON.stringify(error, null, 2));
          resolve(null);
        });
      } catch (error) {
        console.error('PushNotificationService - getCurrentToken error:', JSON.stringify(error, null, 2));
        resolve(null);
      }
    });
  },

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

// Export singleton instance for global access
// Usage: window.pushNotificationService.initialize()
if (typeof window !== 'undefined') {
  window.pushNotificationService = PushNotificationService.getInstance();
}

Service Architecture Benefits

This implementation follows enterprise-grade patterns:

Pattern

Benefit

Implementation

Singleton

Single point of control

getInstance() method

Promise-based

Clean async handling

async/await throughout

Error Handling

Graceful failure management

Try-catch with logging

State Management

Prevents double initialization

_initialized flag

Platform Abstraction

Cross-platform compatibility

Platform checks via getPlatformId()

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

Create Push Notification Provider

Build a simple initializer that integrates with your existing app initialization:

// src/uniken/providers/PushNotificationProvider.js

/**
 * Push Notification Provider
 *
 * Ultra-simplified provider that initializes FCM push notifications
 * and registers tokens directly with REL-ID SDK. No complex state management needed
 * since the pushNotificationService singleton handles everything internally.
 *
 * In Cordova, this is a simple initializer (not a React Context).
 * Called once during app startup in AppInitializer.
 *
 * Usage:
 * PushNotificationProvider.initialize();
 */

/**
 * Push Notification Provider
 * Simple initialization manager for FCM push notifications
 */
const PushNotificationProvider = {
  /** @type {boolean} */
  _initialized: false,

  /**
   * Initialize FCM push notifications
   * Safe to call multiple times - will skip if already initialized
   * @returns {Promise<void>}
   */
  async initialize() {
    if (this._initialized) {
      console.log('PushNotificationProvider - Already initialized, skipping');
      return;
    }

    console.log('PushNotificationProvider - Initializing FCM push notifications');

    try {
      // Wait for pushNotificationService to be available
      if (typeof window.pushNotificationService === 'undefined') {
        console.warn('PushNotificationProvider - pushNotificationService not available yet, waiting...');
        // Wait a bit for service to load
        await new Promise(resolve => setTimeout(resolve, 100));
      }

      if (typeof window.pushNotificationService === 'undefined') {
        console.error('PushNotificationProvider - pushNotificationService still not available');
        return;
      }

      // Initialize the push notification service
      await window.pushNotificationService.initialize();

      this._initialized = true;
      console.log('PushNotificationProvider - FCM initialization successful');
    } catch (error) {
      console.error('PushNotificationProvider - FCM initialization failed:', JSON.stringify(error, null, 2));
      // Don't throw - push notifications are not critical to app function
      // App can continue without push notifications
    }
  },

  /**
   * Check if provider is initialized
   * @returns {boolean}
   */
  isInitialized() {
    return this._initialized;
  },

  /**
   * Reset initialization state (for testing)
   */
  reset() {
    console.log('PushNotificationProvider - Resetting initialization state');
    this._initialized = false;
  }
};

// Export for global access
if (typeof window !== 'undefined') {
  window.PushNotificationProvider = PushNotificationProvider;
}

Integrate with App Initialization

Update your app initializer to include push notification setup:

// src/uniken/AppInitializer.js (add to existing initialization)

const AppInitializer = {
  initialize() {
    console.log('AppInitializer - Setting up SDK handlers and providers');

    // ... existing SDK handler setup ...

    // Initialize push notifications
    console.log('AppInitializer - Initializing push notification provider');
    if (typeof PushNotificationProvider !== 'undefined') {
      PushNotificationProvider.initialize()
        .then(() => {
          console.log('AppInitializer - Push notification provider initialized');
        })
        .catch((error) => {
          console.error('AppInitializer - Push notification provider failed:', error);
        });
    }
  }
};

Update index.html Script Loading

Ensure push notification scripts are loaded in the correct order:

<!-- index.html (script loading section) -->

<!-- Services -->
<script src="src/uniken/services/rdnaService.js"></script>
<script src="src/uniken/services/pushNotificationService.js"></script>

<!-- Providers -->
<script src="src/uniken/providers/PushNotificationProvider.js"></script>

<!-- App Initializer -->
<script src="src/uniken/AppInitializer.js"></script>

<!-- App Bootstrap -->
<script src="js/app.js"></script>

Provider Pattern Benefits

This approach provides several architectural advantages:

Benefit

Description

Implementation Detail

Automatic Initialization

Push notifications start immediately when app launches

Called in AppInitializer

Simple Integration

No complex setup needed

Single initialize() call

Error Isolation

Push notification failures don't crash the app

Try-catch in provider 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
    # Android
    cordova run android
    
    # iOS
    cordova run ios
    
  2. Monitor Console Logs Look for this successful initialization sequence:
    ✅ PushNotificationProvider - Initializing FCM push notifications
    ✅ PushNotificationService - Starting FCM initialization
    ✅ PushNotificationService - Permission result: true
    ✅ 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:

📱 Permission request → User grants → FCM initialization
📱 Permission request → User denies → Graceful fallback

Test Scenario 3: Firebase Configuration Validation

Test Missing Configuration Files:

  1. Temporarily rename google-services.json or GoogleService-Info.plist
  2. Build project: cordova build android or cordova build ios
  3. Verify error handling - should see Firebase configuration errors
  4. Restore files and rebuild successfully

Verify Plugin Installation:

# Check installed plugins
cordova plugin ls

# Should see:
# cordova-plugin-firebasex
# cordova-plugin-rdna (or local path)

Cordova Debugging Tools

Android Debugging:

# Chrome DevTools
# 1. Connect Android device via USB
# 2. Open Chrome: chrome://inspect
# 3. Select your app under "Remote Target"

iOS Debugging:

# Safari Web Inspector
# 1. Connect iOS device or use Simulator
# 2. Safari → Develop → [Device Name] → [Your App]

Production Readiness Checklist

Before deploying to production, verify:

Common Cordova-Specific Issues

"FirebasePlugin is not defined"

"Plugin not found"

Firebase initialization errors

Token retrieval fails (Error 505 on iOS)

Changes not reflecting

Permission denied errors

Platform-Specific Issues

Android:

iOS:

Debugging Strategies

# View Android logs
adb logcat | grep -i firebase

# View iOS logs
# Use Xcode Console or Safari Web Inspector

# Check plugin installation
cordova plugin ls

# Reinstall all plugins
cordova plugin remove cordova-plugin-firebasex
cordova plugin remove cordova-plugin-rdna
cordova plugin add ./RdnaClient
cordova plugin add cordova-plugin-firebasex
cordova prepare

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 cordova-plugin-firebasex ✅ Production-Ready Service - Singleton architecture with error handling and token refresh ✅ Cross-Platform Support - Works on both Android and iOS ✅ Provider Integration - Clean initialization with existing app architecture

Key Files Created/Modified

www/
├── src/uniken/services/
│   ├── pushNotificationService.js     ✅ FCM token management singleton
│   └── rdnaService.js                 ✅ Enhanced with setDeviceToken()
│
├── src/uniken/providers/
│   └── PushNotificationProvider.js    ✅ Initialization manager
│
├── index.html                         ✅ Script loading order
└── js/app.js                          ✅ App bootstrap

Plugins:
├── ./RdnaClient                       ✅ Local REL-ID SDK plugin
└── cordova-plugin-firebasex           ✅ Firebase plugin

Configuration:
├── android/app/google-services.json   ✅ Android Firebase config
└── ios/GoogleService-Info.plist       ✅ iOS Firebase config

Architecture Achievement

Your implementation demonstrates enterprise-grade patterns:

Component

Pattern

Benefit

PushNotificationService

Singleton

Centralized token management

PushNotificationProvider

Initializer

Automatic setup

rdnaService Integration

Method extension

Clean service layer

Firebase Configuration

Native integration

Zero-configuration startup

Error Handling

Graceful Degradation

Production reliability

Security Benefits Unlocked

Your REL-ID push notification integration now enables:

Cordova Push Notification Patterns Learned

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!