🎯 Learning Path:
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.
In this codelab, you'll enhance your existing REL-ID application with:
setDeviceToken APIBy completing this codelab, you'll master:
Before starting this codelab, ensure you have:
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
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
This codelab implements two core components:
Before implementing push notifications, let's understand how REL-ID's secure notification system works.
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
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 |
| Device-server binding |
3. Secure Channel | Establish encrypted communication channel | MITM protection |
4. Transaction Support | Enable approve/reject actions with MFA | Multi-factor security |
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
This project includes a local Cordova plugin. Ensure the plugin directory exists in your project root:
# Verify plugin directory exists
ls -la ./RdnaClient
The SDK plugin is included as a local plugin in the project. Install it from the local directory:
cordova plugin add ./RdnaClient
For push notification support, install cordova-plugin-firebasex:
cordova plugin add cordova-plugin-firebasex
For loading local JSON files, install cordova-plugin-file:
cordova plugin add cordova-plugin-file
# Add platforms
cordova platform add ios
cordova platform add android
# Prepare platforms
cordova prepare
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.
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);
}
}
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();
}
This implementation follows enterprise-grade patterns:
Pattern | Benefit | Implementation |
Singleton | Single point of control |
|
Promise-based | Clean async handling |
|
Error Handling | Graceful failure management | Try-catch with logging |
State Management | Prevents double initialization |
|
Platform Abstraction | Cross-platform compatibility | Platform checks via |
Now let's create a provider that automatically initializes push notifications when your app starts.
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;
}
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);
});
}
}
};
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>
This approach provides several architectural advantages:
Benefit | Description | Implementation Detail |
Automatic Initialization | Push notifications start immediately when app launches | Called in |
Simple Integration | No complex setup needed | Single |
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.
Prerequisites:
google-services.json and GoogleService-Info.plistTest Steps:
# Android
cordova run android
# iOS
cordova run ios
✅ 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
✅ PushNotificationService - Token refreshed, length: 142
✅ RdnaService - Refreshed token registered with REL-ID SDK
Expected Results:
Android 13+ Permission Test:
iOS Permission Test:
Expected Permission Flow:
📱 Permission request → User grants → FCM initialization
📱 Permission request → User denies → Graceful fallback
Test Missing Configuration Files:
google-services.json or GoogleService-Info.plistcordova build android or cordova build iosVerify Plugin Installation:
# Check installed plugins
cordova plugin ls
# Should see:
# cordova-plugin-firebasex
# cordova-plugin-rdna (or local path)
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]
Before deploying to production, verify:
google-services.json and GoogleService-Info.plist from production projectcordova plugin ls shows all required plugins"FirebasePlugin is not defined"
cordova prepare and rebuildcordova plugin ls"Plugin not found"
cordova plugin add ./RdnaClientcordova plugin add cordova-plugin-firebasexcordova plugin lsFirebase initialization errors
google-services.json in android/app/GoogleService-Info.plist in ios/cordova build android/iosToken retrieval fails (Error 505 on iOS)
Changes not reflecting
cordova prepare after changescordova prepare && cordova run android/iosPermission denied errors
Android:
google-services.json matches app package nameiOS:
GoogleService-Info.plist bundle ID matches app# 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.
✅ 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
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
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 |
Your REL-ID push notification integration now enables:
FirebasePlugin and com.uniken.rdnaplugin.RdnaClient directlygetPlatformId()🎉 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!