The Notification module provides functionality for receiving and handling push notifications and silent notifications from the ESP RainMaker Cloud. Notifications enable real-time updates about device events, sharing invitations, and other system events.
Notifications are implemented across three layers:
- Native Layer: Handles platform-specific notification services (APNS for iOS, FCM for Android)
- SDK Layer: Provides notification adapters and event handling
- CDF Layer: Manages platform endpoints and node update subscriptions
Notifications work only after the device token is properly registered with the ESP RainMaker backend.
Notifications
This guide explains how the Notification module works, focusing on configuration, device token setup, push notifications, silent notifications, and how they integrate with the native layer, SDK, and CDF.
User Flow Overview
The Notification module has three main flows:
- Device Token Registration: App initialization → Native token generation → Platform endpoint creation → Backend registration
- Push Notifications: Cloud event → Native notification service → React Native bridge → User notification display
- Silent Notifications: Cloud event → Native notification service → React Native bridge → Application data update
Architecture Overview
Components Structure
Native Layer (iOS):
ios/
├── APP/AppDelegate.mm # iOS app delegate with notification handlers
└── Notification/
└── ESPNotificationModule.swift # React Native bridge for iOS notifications
Native Layer (Android):
android/app/src/main/java/com/app/notification/
├── ESPNotificationModule.kt # React Native bridge for Android notifications
├── FCMService.kt # Firebase Cloud Messaging service
├── ESPNotificationQueue.kt # Notification queue management
└── ESPNotificationHelper.kt # Notification display helper
SDK Layer:
adaptors/implementations/
└── ESPNotificationAdapter.ts # Notification adapter for SDK integration
CDF Layer:
utils/
└── notifications.ts # Platform endpoint creation and CDF integration
1. Device Token Setup Process
The device token is essential for notifications to work. The token identifies the device to the notification service (APNS for iOS, FCM for Android) and must be registered with the ESP RainMaker backend.
Step-by-Step Token Registration Flow
iOS Token Registration
AppDelegate.mm handles iOS notification registration:
// Request notification permissions
- (void)registerForRemoteNotifications {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:
(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] registerForRemoteNotifications];
});
}
}];
}
// Receive device token from APNS
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Convert token to hex string
const unsigned char *tokenBytes = (const unsigned char *)[deviceToken bytes];
NSMutableString *deviceTokenString = [NSMutableString string];
for (NSUInteger i = 0; i < deviceToken.length; i++) {
[deviceTokenString appendFormat:@"%02x", tokenBytes[i]];
}
// Store token in ESPNotificationModule
[[ESPNotificationModule shared] setDeviceToken:deviceTokenString];
}
ESPNotificationModule.swift stores and retrieves the token:
// Store device token
@objc(setDeviceToken:)
func setDeviceToken(_ token: String) {
UserDefaults.standard.setValue(token, forKey: deviceTokenKey)
}
// Retrieve device token
@objc(getDeviceToken:reject:)
func getDeviceToken(resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) {
if let token = UserDefaults.standard.value(forKey: deviceTokenKey) {
resolve(token)
} else {
reject("error", "Device token not set", nil)
}
}
Android Token Registration
ESPNotificationModule.kt retrieves FCM token:
@ReactMethod
fun getDeviceToken(promise: Promise) {
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) {
promise.reject("FCM_TOKEN_ERROR", task.exception?.message)
return@addOnCompleteListener
}
promise.resolve(task.result)
}
}
FCMService.kt handles token refresh:
override fun onNewToken(token: String) {
super.onNewToken(token)
// Token is automatically retrieved via ESPNotificationModule.getDeviceToken()
// when the app requests it
}
Platform Endpoint Creation
After obtaining the device token, the app creates a platform endpoint with the CDF SDK:
// utils/notifications.ts
export const createPlatformEndpoint = async (store: CDF): Promise<void> => {
const { user, platform, deviceToken } = await validate(store);
const endpointConfig: PlatformEndpointConfig = {
platform, // "APNS_NOVA" for iOS, "GCM_NOVA" for Android
deviceToken, // Device-specific token from native layer
};
// Register endpoint with ESP RainMaker Cloud
await user.createPlatformEndpoint(endpointConfig);
// Subscribe to node updates - this enables silent notifications
user.subscribe(ESPRMEventType.nodeUpdates, (event: any) => {
store.subscriptionStore.nodeUpdates.listen(event);
});
};
When Token Registration Happens
The platform endpoint is created automatically during the post-login pipeline:
// utils/postLoginPipeline.ts
const postLoginSteps: PipelineStep[] = [
{
name: "refreshESPRMUser",
run: refreshESPRMUser,
},
{
name: "createPlatformEndpoint",
dependsOn: ["refreshESPRMUser"],
optional: true,
background: true,
run: () => createPlatformEndpoint(store),
},
// ... other steps
];
The endpoint is also created when the app initializes and detects a logged-in user:
// app/index.tsx
useEffect(() => {
if (store && isInitialized && store?.userStore.user) {
initNotification();
}
}, [store, isInitialized, store?.userStore.user]);
const initNotification = async () => {
try {
await createPlatformEndpoint(store);
} catch (err) {
console.error("Failed to initialize notification");
}
};
2. Push Notifications
Push notifications are visible notifications that appear to users, typically showing device events like device added, removed, connected, or disconnected.
Push Notification Flow
iOS Push Notification Handling
AppDelegate.mm handles notifications in different app states:
// Handle notification when app is in foreground
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSDictionary *userInfo = notification.request.content.userInfo;
// Send notification data to React Native
[[ESPNotificationModule shared] handlePushNotification:userInfo];
// Show notification even when app is in foreground
completionHandler(UNNotificationPresentationOptionList |
UNNotificationPresentationOptionBanner |
UNNotificationPresentationOptionSound |
UNNotificationPresentationOptionBadge);
}
// Handle notification tap/interaction
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
NSDictionary *userInfo = response.notification.request.content.userInfo;
[[ESPNotificationModule shared] handlePushNotification:userInfo];
completionHandler();
}
ESPNotificationModule.swift forwards notifications to React Native:
@objc(handlePushNotification:)
func handlePushNotification(_ userInfo: [String: Any]) {
if sendEvent {
sendEvent(withName: self.eventKey, body: userInfo)
}
}
Android Push Notification Handling
FCMService.kt processes incoming FCM messages:
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val notificationData = remoteMessage.data.toMutableMap()
// Forward to React Native
ESPNotificationQueue.addNotification(notificationData)
// Process and display system notification
processSystemNotification(remoteMessage.data)
}
private fun processSystemNotification(data: Map<String, String>) {
val eventPayload = data[KEY_EVENT_DATA_PAYLOAD]
val title = data[KEY_TITLE] ?: DEFAULT_TITLE
val body = data[KEY_BODY] ?: DEFAULT_BODY
val eventJson = JSONObject(eventPayload)
val eventType = eventJson.optString(KEY_EVENT_TYPE)
// Map event types to notification channels
val mappedEventType = when (eventType) {
"rmaker.event.user_node_added" -> "node_added"
"rmaker.event.user_node_removed" -> "node_removed"
"rmaker.event.node_connected" -> "node_online"
"rmaker.event.node_disconnected" -> "node_offline"
else -> null
}
if (mappedEventType != null) {
ESPNotificationHelper.showEventNotification(this, mappedEventType, title, body)
}
}
React Native Notification Listener
ESPNotificationAdapter.ts sets up the notification listener:
addNotificationListener: async (
callback: (data: Record<string, any>) => void
): Promise<() => void> => {
// Listen for incoming notifications
const notificationListener = DeviceEventEmitter.addListener(
"ESPNotificationModule",
(data: Record<string, any>) => {
callback(data);
}
);
ESPNotificationAdapter.currentListener = notificationListener;
// Return cleanup function
return () => {
ESPNotificationAdapter.removeNotificationListener();
};
}
The adapter is configured in the SDK config:
// rainmaker.config.ts
export const SDKConfig = {
// ... other config
notificationAdapter: ESPNotificationAdapter,
};
Supported Push Notification Events
The following ESP RainMaker events trigger push notifications:
rmaker.event.user_node_added: Device added to user accountrmaker.event.user_node_removed: Device removed from user accountrmaker.event.node_connected: Device came onlinermaker.event.node_disconnected: Device went offline
3. Silent Notifications
Silent notifications update application data in the background without showing a visible notification to the user. They are used to keep the app state synchronized with the cloud.
Silent Notification Flow
iOS Silent Notification Handling
AppDelegate.mm handles silent notifications separately:
// Silent notification handling (background fetch)
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[ESPNotificationModule shared] handleSilentNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
ESPNotificationModule.swift processes silent notifications:
@objc(handleSilentNotification:)
func handleSilentNotification(_ userInfo: [String: Any]) {
// Forward to React Native without showing UI
if sendEvent {
sendEvent(withName: self.eventKey, body: userInfo)
}
}
Android Silent Notification Handling
FCMService.kt handles both push and silent notifications:
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val notificationData = remoteMessage.data.toMutableMap()
// All notifications are forwarded to React Native
ESPNotificationQueue.addNotification(notificationData)
// Only show system notification if it's a push notification
// Silent notifications don't have notification payload
if (remoteMessage.notification != null) {
processSystemNotification(remoteMessage.data)
}
}
How Silent Notifications Update Application Data
When a silent notification is received, it contains node update information. The CDF SDK processes these updates:
// utils/notifications.ts
export const createPlatformEndpoint = async (store: CDF): Promise<void> => {
// ... endpoint creation ...
// Subscribe to node updates - this is the key for silent notifications
user.subscribe(ESPRMEventType.nodeUpdates, (event: any) => {
store.subscriptionStore.nodeUpdates.listen(event);
});
};
The nodeUpdates subscription handler processes the event:
- Event Reception: Silent notification arrives with node update data
- CDF Processing:
store.subscriptionStore.nodeUpdates.listen(event)processes the event - Store Update: Node store is updated with new device states, parameters, or metadata
- UI Update: React components subscribed to the store automatically re-render with updated data
Example: Device State Update via Silent Notification
When a device's state changes (e.g., a light is turned on):
- ESP32 Device sends state update to ESP RainMaker Cloud via MQTT
- Cloud sends silent notification to all registered devices
- Native Layer receives notification and forwards to React Native
- CDF Store processes the
nodeUpdatesevent - Node Store updates the specific device's state
- React Components using
useCDF()hook automatically receive updated data - UI reflects the new device state without user interaction
Notification Queue Management
ESPNotificationQueue (Android) manages notifications when the React Native bridge isn't ready:
// ESPNotificationModule.kt
@ReactMethod
fun toggleNotificationListener(enable: Boolean) {
synchronized(this) {
isNotificationListenerActive = enable
}
if (enable) {
// Flush any queued notifications
ESPNotificationQueue.flushPendingNotifications()
}
}