fix(Android): Push notification on killed apps and deep link#6888
fix(Android): Push notification on killed apps and deep link#6888diegolmello merged 9 commits intodevelopfrom
Conversation
|
Warning Rate limit exceeded@diegolmello has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 20 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (13)
WalkthroughThis PR removes React-context–dependent deferred E2E push processing and the E2ENotificationProcessor; adds a TurboModule push bridge, persistent pending-notification storage, and Android-side immediate decryption/notification handling, plus JS hooks to read and clear stored pending notifications. Changes
Sequence Diagram(s)sequenceDiagram
participant System as System / Notification Intent
participant Activity as MainActivity
participant Handler as NotificationIntentHandler
participant Native as PushNotificationModule (Kotlin)
participant Store as SharedPreferences
participant JS as React Native JS
rect rgb(230,245,255)
Note over System,Store: User taps notification → store pending payload
System->>Activity: deliver Intent (onCreate/onNewIntent)
Activity->>Handler: handleIntent(context, intent)
Handler->>Native: PushNotificationModule.storePendingNotification(json)
Native->>Store: save "pendingNotification"
end
rect rgb(230,255,230)
Note over JS,Store: App foreground/startup reads pending payload
JS->>Native: getPendingNotification()
Native->>Store: read & clear "pendingNotification"
Store-->>Native: pending JSON or null
Native-->>JS: resolve(pending JSON or null)
JS->>JS: parse JSON -> onNotification(dispatch)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI Agents
In
@android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java:
- Around line 168-181: handleE2ENotification updates shared state unsafely and
reparses Ejson unnecessarily; modify it to synchronize updates to mBundle the
same way loadNotificationAndProcess does (use the same lock or synchronized
block), stop re-parsing Ejson (use the existing ejson parameter instead of
calling safeFromJson again), and on decryption failure call showNotification
with the original bundle or a fallback notification (or at minimum record a
visible failure) so users see something when Encryption.shared.decryptMessage
returns null; update references: handleE2ENotification, mBundle,
loadNotificationAndProcess, Ejson, showNotification,
Encryption.shared.decryptMessage, and the ConcurrentHashMap usage accordingly.
🧹 Nitpick comments (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
92-92: Consider adding more context to the error message.The generic "Failed to process notification" message provides less debugging context than a more specific message would. Consider including details about the notification type or processing stage that failed.
💡 Example with more context
- Log.e(TAG, "Failed to process notification", e); + Log.e(TAG, "Failed to process notification (notId: " + notId + ")", e);
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
android/app/src/main/java/chat/rocket/reactnative/MainApplication.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaandroid/app/src/main/java/chat/rocket/reactnative/notification/E2ENotificationProcessor.javaandroid/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
💤 Files with no reviewable changes (3)
- android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
- android/app/src/main/java/chat/rocket/reactnative/notification/E2ENotificationProcessor.java
- android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt
🧰 Additional context used
🧬 Code graph analysis (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java (1)
Encryption(105-511)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ESLint and Test / run-eslint-and-test
🔇 Additional comments (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
87-89: LGTM! Clear documentation of the architectural change.The updated comments accurately reflect the shift from async React-context-dependent processing to immediate processing using MMKV and Android Context.
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
Show resolved
Hide resolved
|
Android Build Available Rocket.Chat Experimental 4.69.0.107992 |
|
Android Build Available Rocket.Chat Experimental 4.69.0.107997 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @app/lib/notifications/push.ts:
- Around line 246-253: The console.log in push.ts prints a snippet of
notification.payload.message which may contain user PII; remove the message
substring from the log or replace it with a non-PII indicator (e.g., a boolean
like hasMessage or just messageLength) so you never log user content. Locate the
log that references notification.payload.message (the transformed notification
dump) and change the message field to either undefined/omitted, hasMessage:
!!notification.payload?.message, or messageLength:
notification.payload?.message?.length to preserve debug signal without exposing
PII.
🧹 Nitpick comments (6)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
321-332: Inconsistent debug log gating.Lines 321-332 add extensive debug logging in
buildNotification(), but these logs are not gated byENABLE_VERBOSE_LOGSlike similar debug statements elsewhere in the method (lines 308-314). This creates inconsistent logging behavior and potential log spam in production builds.🔧 Proposed fix to gate debug logs consistently
- // Log intent extras for debugging - Log.d(TAG, "[buildNotification] Creating intent with extras:"); - Log.d(TAG, "[buildNotification] - ejson: " + (mBundle.getString("ejson") != null ? "[present, length=" + mBundle.getString("ejson").length() + "]" : "[null]")); - Log.d(TAG, "[buildNotification] - rid: " + (ejson != null && ejson.rid != null ? ejson.rid : "[null]")); - Log.d(TAG, "[buildNotification] - type: " + (ejson != null && ejson.type != null ? ejson.type : "[null]")); - Log.d(TAG, "[buildNotification] - host: " + (ejson != null && ejson.host != null ? "[present]" : "[null]")); - Log.d(TAG, "[buildNotification] - messageId: " + (ejson != null && ejson.messageId != null ? ejson.messageId : "[null]")); - Log.d(TAG, "[buildNotification] - notificationType: " + (ejson != null && ejson.notificationType != null ? ejson.notificationType : "[null]")); - Log.d(TAG, "[buildNotification] - notId: " + mBundle.getString("notId")); - Log.d(TAG, "[buildNotification] - title: " + (title != null ? "[present]" : "[null]")); - Log.d(TAG, "[buildNotification] - message: " + (message != null ? "[present, length=" + message.length() + "]" : "[null]")); + if (ENABLE_VERBOSE_LOGS) { + // Log intent extras for debugging + Log.d(TAG, "[buildNotification] Creating intent with extras:"); + Log.d(TAG, "[buildNotification] - ejson: " + (mBundle.getString("ejson") != null ? "[present, length=" + mBundle.getString("ejson").length() + "]" : "[null]")); + Log.d(TAG, "[buildNotification] - rid: " + (ejson != null && ejson.rid != null ? ejson.rid : "[null]")); + Log.d(TAG, "[buildNotification] - type: " + (ejson != null && ejson.type != null ? ejson.type : "[null]")); + Log.d(TAG, "[buildNotification] - host: " + (ejson != null && ejson.host != null ? "[present]" : "[null]")); + Log.d(TAG, "[buildNotification] - messageId: " + (ejson != null && ejson.messageId != null ? ejson.messageId : "[null]")); + Log.d(TAG, "[buildNotification] - notificationType: " + (ejson != null && ejson.notificationType != null ? ejson.notificationType : "[null]")); + Log.d(TAG, "[buildNotification] - notId: " + mBundle.getString("notId")); + Log.d(TAG, "[buildNotification] - title: " + (title != null ? "[present]" : "[null]")); + Log.d(TAG, "[buildNotification] - message: " + (message != null ? "[present, length=" + message.length() + "]" : "[null]")); + }app/sagas/state.js (1)
32-35: Consider yielding the pending notification check for better error handling.The current implementation fires
checkPendingNotification()asynchronously without yielding, which means:
- The saga continues immediately without waiting for completion
- Errors are only logged, not handled by the saga's error boundaries
- The operation races with
setUserPresenceOnline()While the fire-and-forget approach prevents blocking, it reduces visibility into failures and makes testing harder.
💡 Alternative: Yield the call for saga-managed execution
- // Check for pending notification when app comes to foreground (Android - notification tap while in background) - checkPendingNotification().catch((e) => { - log('[state.js] Error checking pending notification:', e); - }); + // Check for pending notification when app comes to foreground (Android - notification tap while in background) + try { + yield checkPendingNotification(); + } catch (e) { + log('[state.js] Error checking pending notification:', e); + }This approach:
- Maintains saga control flow and error boundaries
- Still doesn't block
setUserPresenceOnline()(saga effects are sequential but the check should be fast)- Improves testability
If the fire-and-forget behavior is intentional (to avoid any delay), consider documenting why.
app/lib/notifications/push.ts (2)
280-295: Extract shared notification transformation logic to reduce duplication.This payload transformation to
INotificationformat is duplicated inapp/lib/notifications/index.ts(lines 109-124). Consider extracting a shared helper function to avoid maintaining the same mapping logic in two places.♻️ Suggested helper function
Create a shared helper in a common location:
// e.g., in push.ts or a separate helper file export const transformNativePayloadToNotification = (data: Record<string, any>): INotification => ({ payload: { message: data.message || '', style: data.style || '', ejson: data.ejson || '', collapse_key: data.collapse_key || '', notId: data.notId || '', msgcnt: data.msgcnt || '', title: data.title || '', from: data.from || '', image: data.image || '', soundname: data.soundname || '', action: data.action }, identifier: data.notId || '' });Then use it in both files:
- const transformed: INotification = { - payload: { - message: notificationData.message || '', - // ... all the fields - }, - identifier: notificationData.notId || '' - }; + const transformed = transformNativePayloadToNotification(notificationData);
309-325: Consider simplifying the promise chain for clarity.The double
.then().catch()chain works correctly, but could be clearer. The second.catch()at line 322 only handles errors from the fallback logic (lines 314-320), while the first.catch()(line 305) handles native module errors. This structure is correct but slightly confusing to read.♻️ Optional: Use async/await for cleaner flow
// Alternative using async/await (would need to wrap in async IIFE) try { const pendingNotification = await NativePushNotificationModule.getPendingNotification(); if (pendingNotification) { const notificationData = JSON.parse(pendingNotification); return transformNativePayloadToNotification(notificationData); } } catch (e) { console.error('[push.ts] Error getting pending notification:', e); } // Fallback to expo-notifications const lastResponse = Notifications.getLastNotificationResponse(); return lastResponse ? transformNotificationResponse(lastResponse) : null;app/lib/notifications/index.ts (1)
103-104: Consider using static import for consistency withpush.ts.The dynamic
require()here contrasts with the static import inpush.ts(line 10). Since the function is already guarded byPlatform.OS === 'android', a static import at the top of the file with the same guard pattern would be more consistent and bundler-friendly.♻️ Use static import pattern
+ import NativePushNotificationModule from '../native/NativePushNotificationAndroid'; export const checkPendingNotification = async (): Promise<void> => { if (Platform.OS === 'android') { try { - const NativePushNotificationModule = require('../native/NativePushNotificationAndroid').default; if (NativePushNotificationModule) {android/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationModule.kt (1)
55-65: Consider adding debug logging for error visibility.The empty catch block silently swallows exceptions. While acceptable for cleanup operations, adding a log statement would aid debugging if issues arise.
♻️ Add error logging
@ReactMethod override fun clearPendingNotification() { try { reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .edit() .remove(KEY_PENDING_NOTIFICATION) .apply() } catch (e: Exception) { - // Ignore errors + android.util.Log.w("PushNotificationModule", "Failed to clear pending notification", e) } }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
android/app/src/main/java/chat/rocket/reactnative/MainActivity.ktandroid/app/src/main/java/chat/rocket/reactnative/MainApplication.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaandroid/app/src/main/java/chat/rocket/reactnative/notification/NativePushNotificationSpec.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationModule.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationTurboPackage.ktapp/lib/native/NativePushNotificationAndroid.tsapp/lib/notifications/index.tsapp/lib/notifications/push.tsapp/sagas/state.js
🚧 Files skipped from review as they are similar to previous changes (1)
- android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt
🧰 Additional context used
🧬 Code graph analysis (3)
app/sagas/state.js (1)
app/lib/notifications/index.ts (1)
checkPendingNotification(100-135)
app/lib/notifications/push.ts (2)
app/lib/notifications/index.ts (1)
onNotification(19-81)app/definitions/INotification.ts (1)
INotification(1-16)
app/lib/notifications/index.ts (1)
app/definitions/INotification.ts (1)
INotification(1-16)
🔇 Additional comments (13)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (3)
87-93: LGTM! Immediate notification processing without React Native dependency.The updated comments and immediate processing approach correctly reflects the architectural change to process notifications without waiting for React Native initialization. MMKV initialization at app startup enables this direct path.
168-189: Good: Thread safety and fallback notification added.The previous review concerns have been addressed:
- ✅ Synchronization added when updating
mBundle(lines 175-177, 184-186)- ✅ Fallback notification now shown when decryption fails (lines 180-188)
- ✅ No redundant
ejsonre-parsingThe fallback with "Encrypted message" placeholder ensures users know a message arrived even when decryption fails.
212-217: LGTM! Consistent synchronization pattern.The synchronized block before
buildNotification()ensures all bundle modifications (time, username, senderId, avatarUri, and any prior changes) are visible to the notification builder, maintaining consistency with the synchronization patterns used elsewhere in the file.app/sagas/state.js (1)
10-10: LGTM! Import added for pending notification handling.The import is correctly sourced from the notifications module.
android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt (3)
14-14: LGTM! Import added for notification handling.The import is correctly added to support the new notification intent handling flow.
38-41: LGTM! Notification intent handling added on activity creation.The call to
handleNotificationIntentcorrectly processes notification data when the app is launched from a killed state via a notification tap.
43-52: LGTM! Intent handling for running activity.The addition of
setIntent(intent)at line 45 is essential to update the activity's intent reference, and the subsequent calls to both video conf and regular notification handlers ensure all notification types are processed when the activity receives a new intent while already running.android/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationTurboPackage.kt (1)
1-38: LGTM! Standard TurboModule package implementation.The
PushNotificationTurboPackagecorrectly implements the TurboReactPackage pattern:
- Module instantiation is properly gated by name matching (line 15)
- Module metadata is correctly configured with
isTurboModule=trueand appropriate defaults for other flags- The package properly bridges the native module to React Native's TurboModule infrastructure
app/lib/native/NativePushNotificationAndroid.ts (1)
1-9: LGTM! Clean TurboModule TypeScript bridge.The TypeScript bridge correctly defines the native module interface:
- Extends
TurboModulefor proper type integration- Method signatures match the native Kotlin implementation (getPendingNotification returns Promise<string | null>, clearPendingNotification returns void)
- Module name 'PushNotificationModule' is consistent with
NativePushNotificationSpec.NAMEin the Kotlin code- Properly uses
TurboModuleRegistry.get<Spec>for type-safe module accessandroid/app/src/main/java/chat/rocket/reactnative/notification/NativePushNotificationSpec.kt (1)
9-23: LGTM! Clean TurboModule spec definition.The abstract class correctly defines the contract for the push notification module with proper use of companion object for the shared
NAMEconstant and appropriate@ReactMethodannotations on abstract methods.android/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationModule.kt (2)
14-28: LGTM! Clean SharedPreferences-based storage implementation.The companion object pattern with
@JvmStaticis correct for calling from Java/Kotlin code (MainActivity). Usingapply()for async writes is appropriate for non-critical persistence. ThePREFS_NAMEand key naming are clear.
36-50: Race condition concern is not applicable to the actual call patterns in this codebase.
getPendingNotification()is called exactly once during app initialization viapushNotificationConfigure()(line 272 in push.ts), not in rapid succession. The scenario described in the review—multiple rapid calls causing a race condition before the asyncapply()completes—does not occur. Additionally,apply()is the correct Android practice for background preference writes in this context.The
PREFS_NAMEsharing betweenPushNotificationModuleandVideoConfModuleis intentional and safe; both modules use distinct keys (KEY_PENDING_NOTIFICATIONvsKEY_VIDEO_CONF_ACTION) within the same preferences file.app/lib/notifications/index.ts (1)
100-135: The native module'sgetPendingNotification()already clears the stored notification after reading it (see PushNotificationModule.kt lines 43-46), so no explicitclearPendingNotification()call is needed. The notification is automatically removed from SharedPreferences during thegetPendingNotification()read operation itself, preventing duplicate processing regardless of which code path reads it first.Likely an incorrect or invalid review comment.
android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
@android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt:
- Around line 43-44: The handler currently returns true silently when event is
null; update the intent handling in NotificationIntentHandler (the block where
notificationId and event are read, and where videoConfAction is checked) to log
a warning/error when event == null (include notificationId and any relevant
intent extras) before returning, so malformed video conference notifications are
recorded for debugging; ensure you reference the same variables
(`notificationId`, `event`, `videoConfAction`) and keep the original control
flow (returning true) after logging.
🧹 Nitpick comments (3)
app/lib/notifications/push.ts (1)
218-274: Consider simplifying the promise chain structure.The promise chain has a nested
.then().catch().then().catch()pattern that's harder to follow. The second.catch()on line 270 is likely unreachable sincegetLastNotificationResponse()is synchronous and line 257's.then()doesn't throw.The transformation logic correctly maps all
INotificationfields per the interface definition.♻️ Suggested simplification using async/await
- // First check native module for stored notification data (Android - when notification was created natively) - if (Platform.OS === 'android' && NativePushNotificationModule) { - return NativePushNotificationModule.getPendingNotification() - .then(pendingNotification => { - if (pendingNotification) { - try { - // Parse the stored notification data - const notificationData = JSON.parse(pendingNotification); - - // Transform to INotification format - const transformed: INotification = { - payload: { - message: notificationData.message || '', - style: notificationData.style || '', - ejson: notificationData.ejson || '', - collapse_key: notificationData.collapse_key || '', - notId: notificationData.notId || '', - msgcnt: notificationData.msgcnt || '', - title: notificationData.title || '', - from: notificationData.from || '', - image: notificationData.image || '', - soundname: notificationData.soundname || '', - action: notificationData.action - }, - identifier: notificationData.notId || '' - }; - - return transformed; - } catch (parseError) { - console.error('[push.ts] Error parsing notification data:', parseError); - return null; - } - } - return null; - }) - .catch(e => { - console.error('[push.ts] Error getting pending notification from native module:', e); - return null; - }) - .then(nativeNotification => { - if (nativeNotification) { - return nativeNotification; - } - - // Fallback to expo-notifications (for iOS or if native module doesn't have data) - const lastResponse = Notifications.getLastNotificationResponse(); - if (lastResponse) { - return transformNotificationResponse(lastResponse); - } - - return null; - }) - .catch(e => { - console.error('[push.ts] Error in promise chain:', e); - return null; - }); - } + // First check native module for stored notification data (Android - when notification was created natively) + if (Platform.OS === 'android' && NativePushNotificationModule) { + return (async () => { + try { + const pendingNotification = await NativePushNotificationModule.getPendingNotification(); + if (pendingNotification) { + try { + const notificationData = JSON.parse(pendingNotification); + return { + payload: { + message: notificationData.message || '', + style: notificationData.style || '', + ejson: notificationData.ejson || '', + collapse_key: notificationData.collapse_key || '', + notId: notificationData.notId || '', + msgcnt: notificationData.msgcnt || '', + title: notificationData.title || '', + from: notificationData.from || '', + image: notificationData.image || '', + soundname: notificationData.soundname || '', + action: notificationData.action + }, + identifier: notificationData.notId || '' + } as INotification; + } catch (parseError) { + console.error('[push.ts] Error parsing notification data:', parseError); + } + } + } catch (e) { + console.error('[push.ts] Error getting pending notification from native module:', e); + } + // Fallback to expo-notifications + const lastResponse = Notifications.getLastNotificationResponse(); + return lastResponse ? transformNotificationResponse(lastResponse) : null; + })(); + }android/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationModule.kt (1)
46-61: Potential race condition: clearing before resolving the promise.The notification is cleared (line 54) before the promise is resolved (line 57). If the JS side encounters an error after receiving the notification but before processing it, the data is lost. Consider clearing only after successful JS-side processing, or document this as intentional "at-most-once" delivery.
Additionally, using
apply()for the remove (async) while usingcommit()for store (sync) creates an inconsistency. If the clear fails silently, duplicate processing could occur on next app launch.♻️ Consider returning the value and letting JS trigger clear explicitly
If at-most-once semantics are acceptable, the current approach is fine. Otherwise, keep
clearPendingNotification()as the explicit cleanup call from JS after successful processing:@ReactMethod override fun getPendingNotification(promise: Promise) { try { val prefs = reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) val notification = prefs.getString(KEY_PENDING_NOTIFICATION, null) - // Clear the notification after reading - notification?.let { - prefs.edit().remove(KEY_PENDING_NOTIFICATION).apply() - } + // Note: Caller should invoke clearPendingNotification() after successful processing promise.resolve(notification) } catch (e: Exception) { promise.reject("ERROR", e.message) } }android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt (1)
70-71: Consider reusing Gson instance.Two
GsonBuilder().create()calls create separate Gson instances. While this is fine for infrequent notification handling, a single shared instance in the companion object would be slightly more efficient.♻️ Optional: Share Gson instance
companion object { private const val TAG = "RocketChat.NotificationIntentHandler" + private val gson = GsonBuilder().create() // ... in handleVideoConfIntent: - val gson = GsonBuilder().create() - val jsonData = gson.toJson(data) + val jsonData = gson.toJson(data) // ... in handleNotificationIntent: - val gson = GsonBuilder().create() - val jsonData = gson.toJson(notificationData) + val jsonData = gson.toJson(notificationData)Also applies to: 139-140
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
android/app/src/main/java/chat/rocket/reactnative/MainActivity.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaandroid/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.ktandroid/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationModule.ktapp/lib/notifications/push.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
🧰 Additional context used
🧬 Code graph analysis (1)
app/lib/notifications/push.ts (1)
app/definitions/INotification.ts (1)
INotification(1-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: ESLint and Test / run-eslint-and-test
- GitHub Check: format
🔇 Additional comments (6)
android/app/src/main/java/chat/rocket/reactnative/notification/PushNotificationModule.kt (1)
23-39: Good use of synchronous commit() with error handling.Using
commit()instead ofapply()here ensures storage errors are caught and propagated. The explicit logging and re-throwing pattern is appropriate for this critical path.android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt (2)
121-131: The toString() heuristic is fragile but acceptable.The check
!stringValue.contains("@")could theoretically filter out legitimate data if a non-String type'stoString()happened to contain "@". However, this is unlikely in practice since standard types (String, Int, etc.) are handled explicitly above. The warning log provides visibility into what's being skipped.
95-136: Good defensive handling of Intent extras.The per-key try-catch (lines 102-135) ensures a problematic extra doesn't break the entire notification processing. The explicit type whitelist for primitives is safer than attempting to serialize unknown types.
app/lib/notifications/push.ts (1)
10-10: Import path and module exports are valid.The
NativePushNotificationAndroid.tsmodule exists at the expected path and correctly exports the TurboModule interface with the required methods:getPendingNotification()andclearPendingNotification().android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (2)
87-94: LGTM!The comments clearly document the architectural change, and the simplified error message appropriately reflects that React Native initialization is no longer a factor in notification processing.
212-216: LGTM!Good addition to ensure visibility of all bundle modifications before
buildNotification()readsmBundle. This synchronized block is consistent with the other synchronization points in the class.
android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt
Show resolved
Hide resolved
|
Android Build Available Rocket.Chat Experimental 4.69.0.107998 |
…nitialization checks. E2E notifications now decrypt immediately using Android Context, improving responsiveness.
…2ENotificationProcessor. Simplifies notification management by eliminating unnecessary React context dependencies.
- Added PushNotificationModule to manage notification data from taps. - Integrated notification handling in MainActivity for both new intents and existing activity. - Created NativePushNotificationSpec and PushNotificationTurboPackage for TurboModule support. - Updated notification processing in the app to check for pending notifications when coming to the foreground. - Enhanced logging for better debugging of notification data.
…management in PushNotificationModule - Updated MainActivity to filter non-serializable types from notification extras, improving JSON serialization reliability. - Added error handling for processing notification extras and storing pending notifications. - Modified PushNotificationModule to use synchronous commit for storing notifications, ensuring error detection during storage.
…tionIntentHandler - Consolidated notification intent processing into a new NotificationIntentHandler class for better organization and maintainability. - Updated MainActivity to utilize the new handler for both initial and new intents, streamlining the notification handling logic. - Removed redundant code related to video conference and regular notification handling from MainActivity.
7455557 to
820f18d
Compare
Proposed changes
This PR fixes a critical issue where the Android app was being killed when handling End-to-End Encrypted (E2E) push notifications. The root cause was that the notification processing logic was waiting for React Native initialization and using React context dependencies, which could cause the app to crash or be terminated by the system.
The solution simplifies the notification handling architecture by:
Removing React Native dependencies from E2E notification decryption: E2E notifications now decrypt immediately using the native Android Context, eliminating the need to wait for React Native initialization. This makes notifications more responsive and prevents crashes.
Eliminating E2ENotificationProcessor: Removed the separate
E2ENotificationProcessorclass that was creating unnecessary complexity and React context dependencies. All notification processing is now handled directly inCustomPushNotification.Implementing TurboModule for notification data: Added a new
PushNotificationModuleTurboModule to properly handle notification Intent data when the app opens from a notification tap, ensuring data is correctly passed to the JavaScript layer.Improving notification handling flow: Enhanced the notification processing pipeline to handle both regular and E2E notifications more reliably, with better error handling and logging.
Issue(s)
https://rocketchat.atlassian.net/browse/CORE-1645
How to test or reproduce
Test E2E notification handling:
Test notification tap handling:
Test app state scenarios:
Test video conf:
Screenshots
Types of changes
Checklist
Further comments
Technical Details
Key Changes:
E2ENotificationProcessor.java- Removed unnecessary React context dependencyCustomPushNotification.java- Simplified to decrypt E2E notifications immediately using Android Context instead of waiting for React NativePushNotificationModule.kt- New TurboModule to handle notification Intent dataNativePushNotificationSpec.ktandPushNotificationTurboPackage.kt- TurboModule infrastructureMainActivity.ktandMainApplication.kt- Integrated new notification handlingapp/lib/notifications/push.tsand related files - Updated to check for pending notificationsWhy this approach:
The previous implementation tried to use React Native context and initialization checks, which could fail when the app was in certain states (killed, background, etc.). By moving E2E decryption to the native layer using Android Context (which is always available), we eliminate these dependencies and make the notification handling more reliable. MMKV is initialized at app startup, so all notification types can work without React Native being ready.
Performance Impact:
E2ENotificationProcessorclassSummary by CodeRabbit
Bug Fixes
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.