-
Notifications
You must be signed in to change notification settings - Fork 1.4k
chore: Migrate to expo-notifications #6846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
…andling and migrate notification logic - Added expo-notifications for push notification management, replacing react-native-notifications. - Implemented device token registration and notification response handling. - Enhanced badge count management and notification dismissal methods. - Set up notification categories for iOS to support actions like reply and video conference responses. - Updated MainApplication to reflect new notification architecture.
…ent custom FCM handling - Removed react-native-notifications dependency and related mock configurations. - Introduced RCFirebaseMessagingService for handling FCM messages and routing to CustomPushNotification. - Updated CustomPushNotification to manage notifications without react-native-notifications, enhancing E2E decryption and MessagingStyle support. - Adjusted MainApplication and notification classes to reflect the new architecture and improve notification processing. - Cleaned up unused imports and code related to the previous notification system.
…e reply handling - Removed react-native-notifications dependency and related code from the project. - Implemented a custom reply notification handler in ReplyNotification to manage direct replies from iOS notifications. - Updated AppDelegate to configure the new reply notification handler. - Adjusted Podfile and Podfile.lock to reflect the removal of react-native-notifications and added necessary Expo modules. - Cleaned up imports and ensured compatibility with the new notification architecture.
- Introduced a new video conference notification system, replacing the previous handling with a custom implementation. - Added VideoConfNotification class to manage incoming call notifications with accept/decline actions. - Implemented VideoConfBroadcast receiver to handle notification actions and store them for the JS layer. - Updated MainActivity to process video conference intents and integrate with the new notification system. - Enhanced getInitialNotification to check for pending video conference actions. - Updated AndroidManifest.xml to register the new broadcast receiver. - Cleaned up related code and ensured compatibility with the new notification architecture.
…or iOS - Added support for video conference notifications in the iOS NotificationService. - Implemented logic to process incoming video call notifications, including handling call status and displaying appropriate alerts. - Updated Payload and NotificationType models to accommodate video conference data. - Enhanced getInitialNotification to check for video conference actions on iOS using expo-notifications. - Improved error handling for notification responses.
- Enhanced the onNotification function to streamline processing of video conference notifications, including accept and decline actions. - Updated getInitialNotification to handle video conference actions more effectively, ensuring proper event dispatching based on user interaction. - Improved error handling and code readability by reducing nested conditions and clarifying logic flow.
…encies - Removed @notifee/react-native and @react-native-firebase/messaging from the project, along with related code and configurations. - Updated notification handling to utilize expo-notifications instead, ensuring a more streamlined approach to push notifications. - Cleaned up package.json, yarn.lock, and Podfile.lock to reflect the removal of obsolete dependencies. - Deleted background notification handler and adjusted notification settings management accordingly.
WalkthroughMigrates notifications from react-native-notifications/Notifee to Expo (expo-notifications/expo-device), removes react-native-firebase messaging dependency, and adds native video-conference notification handling on Android and iOS with TurboModule bridging and persistence of pending actions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant FCM as Firebase
participant Sys as Android OS
participant RCFMSvc as RCFirebaseMessagingService
participant Custom as CustomPushNotification
participant VideoNotif as VideoConfNotification
participant User as User (tap accept/decline)
participant Broadcast as VideoConfBroadcast
participant VideoModule as VideoConfModule (SharedPrefs)
participant MainAct as MainActivity
participant JS as React Native JS
FCM->>Sys: push data message (data payload)
Sys->>RCFMSvc: deliver message
RCFMSvc->>Custom: onReceived(bundle)
Custom->>VideoNotif: show incoming call notification (build)
Note right of VideoNotif: full-screen + actions
User->>VideoNotif: taps Accept/Decline action
VideoNotif->>Broadcast: send broadcast intent (ACTION_ACCEPT/DECLINE)
Broadcast->>VideoModule: storePendingAction(json)
Broadcast->>MainAct: startActivity with extras
MainAct->>VideoModule: getPendingAction()
VideoModule-->>MainAct: returns action JSON
MainAct->>JS: dispatch deepLinkingClickCallPush (via Gson -> JS)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus during review:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/lib/notifications/push.ts (1)
161-215: Use the correct async Expo APIgetLastNotificationResponseAsyncand fix the require-await violationTwo issues in
pushNotificationConfigure:
handleNotificationis declaredasyncbut has noawait, triggering therequire-awaitESLint rule.Notifications.getLastNotificationResponse()is treated as a synchronous API, but Expo's actual API isgetLastNotificationResponseAsync(), which is async and returns a Promise. This pattern is already correctly implemented elsewhere in the codebase (e.g.,app/lib/notifications/videoConf/getInitialNotification.ts).Make
pushNotificationConfigureitselfasync, change the handler to return aPromisewithout theasynckeyword, and useawaitwithgetLastNotificationResponseAsync:-export const pushNotificationConfigure = (onNotification: (notification: INotification) => void): Promise<any> => { +export const pushNotificationConfigure = async (onNotification: (notification: INotification) => void): Promise<any> => { if (configured) { - return Promise.resolve({ configured: true }); + return { configured: true }; } configured = true; // Set up how notifications should be handled when the app is in foreground Notifications.setNotificationHandler({ - handleNotification: async () => ({ + handleNotification: () => + Promise.resolve({ shouldShowAlert: false, shouldPlaySound: false, shouldSetBadge: false, shouldShowBanner: false, shouldShowList: false - }) + }) }); // ... (rest of code) - // Get initial notification (app was opened by tapping a notification) - const lastResponse = Notifications.getLastNotificationResponse(); + // Get initial notification (app was opened by tapping a notification) + const lastResponse = await Notifications.getLastNotificationResponseAsync(); if (lastResponse) { - return Promise.resolve(transformNotificationResponse(lastResponse)); + return transformNotificationResponse(lastResponse); } - return Promise.resolve(null); + return null; };Call sites already treat
pushNotificationConfigureas returning aPromise, so this change is compatible.
🧹 Nitpick comments (11)
app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx (1)
17-22: ConfirmLinking.openSettingssupport and consider a small safety netThe switch to
Linking.openSettings()on non‑iOS looks good and matches the intent of opening app settings, but this API depends on the React Native version and can reject on some devices/ROMs. It would be safer to both (a) confirm the app’s minimum RN version guaranteesLinking.openSettingsavailability, and (b) optionally add a.catchto avoid unhandled rejections and, if you want, fall back toLinking.openURL('app-settings:').- } else { - Linking.openSettings(); - } + } else { + Linking.openSettings().catch(() => { + // Optional: fallback if openSettings isn't supported + Linking.openURL('app-settings:').catch(() => { + // Swallow or log if even this fails + }); + }); + }app/views/JitsiMeetView/index.tsx (1)
49-57: Relying on WebView instead of BackHandler in the failure path is reasonableDropping the BackHandler listener in the
Linking.openURLfailure path avoids globally blocking the hardware back button and lets the WebView / navigator handle navigation, which is safer and more predictable. Consider optionally logging the error or showing a lightweight message ifopenURLfails for reasons other than “app not installed”, but the current behavior is acceptable as-is.ios/Podfile (1)
36-40: Consider adding a trailing comma for consistency.The static analysis tool suggests adding a trailing comma after the last item in the multiline array. This is a Ruby style convention that makes future diffs cleaner when adding new entries.
$static_framework = [ 'WatermelonDB', 'simdjson', - 'react-native-mmkv-storage' + 'react-native-mmkv-storage', ]ios/ReplyNotification.swift (1)
52-63: Consider the fallback presentation behavior.When
originalDelegateis nil,completionHandler([])suppresses foreground notification presentation. While expo-notifications should always be present, if for any reason the delegate becomes nil, users won't see foreground notifications.This is likely acceptable since expo-notifications is a core dependency, but worth being aware of during testing.
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (3)
113-147: Thread lifecycle: consider cleanup mechanism.The background thread used to wait for React initialization has no cancellation mechanism. If the notification is no longer relevant (e.g., user dismisses it before React initializes), the thread continues running and may post a stale notification.
Consider using a
ScheduledExecutorServiceor storing a reference to enable cancellation:+ private volatile boolean shouldCancelWait = false; + // Wait for React to initialize with timeout new Thread(() -> { int attempts = 0; int maxAttempts = 50; - while (!isReactInitialized() && attempts < maxAttempts) { + while (!isReactInitialized() && attempts < maxAttempts && !shouldCancelWait) {
238-244: Inconsistent synchronization onmBundle.At line 190,
mBundleassignment is synchronized, but here and at line 262, it's not. SincemBundlecan be accessed from multiple threads (background polling thread, E2E callback), consider making all assignments consistent:if (decrypted != null) { bundle.putString("message", decrypted); - mBundle = bundle; + synchronized(this) { + mBundle = bundle; + }Alternatively, declare
mBundleasvolatileif only single-assignment visibility is needed without compound operations.
342-350: Consider using named constants for video conf status values.Magic numbers
0and4for call status are unclear. Defining constants would improve readability and maintainability:+ private static final int VIDEO_CONF_STATUS_INCOMING = 0; + private static final int VIDEO_CONF_STATUS_ENDED = 4; + ... - if (status == null || status == 0) { + if (status == null || status == VIDEO_CONF_STATUS_INCOMING) { // Incoming call - show notification videoConf.showIncomingCall(bundle, ejson); - } else if (status == 4) { + } else if (status == VIDEO_CONF_STATUS_ENDED) { // Call cancelled/ended - dismiss notification videoConf.cancelCall(rid, callerId);android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt (1)
10-14: Remove unusedConfigurationimport.The
Configurationimport on line 11 is not used in this file.import android.content.Intent -import android.content.res.Configuration import chat.rocket.reactnative.notification.VideoConfModuleandroid/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.java (1)
54-68: Consider null safety for caller fields.The
extras.getString()calls forcallerIdandcallerNamedefault to empty strings, but if these fields contain sensitive information, logging them at line 72 might be a compliance concern depending on whetherridcontains user-identifiable data.Consider removing
ridfrom the debug log if it could contain user-identifiable information:- Log.d(TAG, "Stored video conf action: " + event + " for rid: " + extras.getString("rid")); + Log.d(TAG, "Stored video conf action: " + event);android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.java (1)
38-54: Consider usingcommit()instead ofapply()for the clear operation.The
apply()method is asynchronous. If the app terminates before the write completes, the action could be processed again on next launch. Usingcommit()ensures the clear happens synchronously before resolving the promise.// Clear the action after reading if (action != null) { - prefs.edit().remove(KEY_VIDEO_CONF_ACTION).apply(); + prefs.edit().remove(KEY_VIDEO_CONF_ACTION).commit(); }app/lib/notifications/push.ts (1)
12-26: Async badge/dismiss helpers look fine; consider centralizing loggingThe async wrappers with try/catch around
setBadgeCountAsyncanddismissAllNotificationsAsynclook good. If you have a central logging/analytics facility, you might want to use it instead ofconsole.logso failures in production are easier to track.
📜 Review details
Configuration used: CodeRabbit 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 ignored due to path filters (2)
ios/Podfile.lockis excluded by!**/*.lockyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (37)
android/app/build.gradle(0 hunks)android/app/src/main/AndroidManifest.xml(2 hunks)android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt(2 hunks)android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java(16 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java(3 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java(2 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.java(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.java(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.java(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfPackage.java(1 hunks)app/index.tsx(1 hunks)app/lib/notifications/index.ts(2 hunks)app/lib/notifications/push.ts(3 hunks)app/lib/notifications/videoConf/backgroundNotificationHandler.ts(0 hunks)app/lib/notifications/videoConf/getInitialNotification.ts(1 hunks)app/sagas/deepLinking.js(3 hunks)app/sagas/troubleshootingNotification.ts(2 hunks)app/views/JitsiMeetView/index.tsx(2 hunks)app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx(1 hunks)index.js(1 hunks)ios/AppDelegate.swift(1 hunks)ios/NotificationService/NotificationService.swift(2 hunks)ios/Podfile(1 hunks)ios/ReplyNotification.swift(1 hunks)ios/RocketChatRN-Bridging-Header.h(0 hunks)ios/RocketChatRN.xcodeproj/project.pbxproj(6 hunks)ios/Shared/Models/NotificationType.swift(1 hunks)ios/Shared/Models/Payload.swift(1 hunks)jest.setup.js(1 hunks)package.json(1 hunks)patches/@notifee+react-native+7.8.2.patch(0 hunks)patches/@react-native-firebase+messaging+21.12.2.patch(0 hunks)patches/react-native-notifications+5.1.0.patch(0 hunks)react-native.config.js(1 hunks)
💤 Files with no reviewable changes (6)
- android/app/build.gradle
- ios/RocketChatRN-Bridging-Header.h
- app/lib/notifications/videoConf/backgroundNotificationHandler.ts
- patches/@react-native-firebase+messaging+21.12.2.patch
- patches/react-native-notifications+5.1.0.patch
- patches/@notifee+react-native+7.8.2.patch
🧰 Additional context used
🧬 Code graph analysis (11)
app/lib/notifications/push.ts (3)
app/definitions/INotification.ts (1)
INotification(1-16)app/lib/notifications/index.ts (1)
onNotification(18-66)app/sagas/deepLinking.js (1)
token(36-36)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java (1)
VideoConfNotification(25-236)
app/lib/notifications/videoConf/getInitialNotification.ts (2)
app/lib/store/auxStore.ts (1)
store(6-6)app/actions/deepLinking.ts (1)
deepLinkingClickCallPush(26-31)
android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
CustomPushNotification(45-659)
ios/NotificationService/NotificationService.swift (2)
ios/Shared/Extensions/String+Extensions.swift (1)
removeTrailingSlash(25-31)ios/Shared/RocketChat/RocketChat.swift (1)
getPushWithId(25-37)
android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
CustomPushNotification(45-659)
android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (2)
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboPackage.java (1)
SSLPinningTurboPackage(15-45)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfPackage.java (1)
VideoConfPackage(17-32)
app/index.tsx (1)
app/lib/notifications/videoConf/getInitialNotification.ts (1)
getInitialNotification(14-63)
ios/ReplyNotification.swift (4)
ios/NotificationService/NotificationService.swift (1)
didReceive(9-56)ios/Shared/Extensions/String+Extensions.swift (1)
removeTrailingSlash(25-31)ios/Shared/RocketChat/RocketChat.swift (1)
sendMessage(39-75)ios/Shared/RocketChat/API/Requests/SendMessage.swift (1)
body(64-66)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java (1)
Notification(25-58)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java (1)
Notification(25-58)
🪛 GitHub Actions: Format Code with Prettier
app/lib/notifications/push.ts
[error] 170-170: Async method 'handleNotification' has no 'await' expression. ESLint: 'require-await' rule triggered.
🪛 GitHub Check: format
app/lib/notifications/push.ts
[failure] 170-170:
Async method 'handleNotification' has no 'await' expression
🪛 RuboCop (1.81.7)
ios/Podfile
[convention] 39-39: Put a comma after the last item of a multiline array.
(Style/TrailingCommaInArrayLiteral)
⏰ 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 (50)
ios/RocketChatRN.xcodeproj/project.pbxproj (1)
1543-1547: LGTM - Standard privacy bundle integration for Expo modulesThe changes correctly add required privacy manifest bundles (
ExpoApplication_privacy.bundle,ExpoNotifications_privacy.bundle,ExpoDevice_privacy.bundle) to all three targets (RocketChatRN, Rocket.Chat, NotificationService). This is a standard CocoaPods-generated update that ensures compliance with Apple's App Privacy Details requirements when using these Expo modules.The pattern is consistent with other privacy bundles already in the project, and having these bundles in all targets ensures proper privacy declarations across the main app and extensions.
Also applies to: 1576-1580, 1636-1640, 1669-1673, 1973-1977, 2006-2010
app/views/JitsiMeetView/index.tsx (1)
5-5: Import cleanup after BackHandler removal looks good
BackHandleris no longer used and everything still imported fromreact-nativehere is referenced in the file, so this simplification is correct and keeps the module tidy.index.js (1)
25-27: LGTM!The comment clearly documents the architectural change, explaining that Android video conference notifications are now handled natively in the
RCFirebaseMessagingService -> CustomPushNotification -> VideoConfNotificationchain. This helps future maintainers understand why the background handler was removed.ios/Shared/Models/Payload.swift (3)
11-14: LGTM!The new
Callerstruct properly mirrors the AndroidEjson.Callerclass, maintaining consistency across platforms. The_idnaming follows the Rocket.Chat/MongoDB convention and will decode correctly from JSON.
29-33: LGTM!The video conference fields are appropriately defined as optionals, ensuring backward compatibility with existing notifications that don't include these fields.
21-21: Callers properly handle the optional messageId.The only usage in NotificationService.swift (line 45) correctly guards access with
if let messageId = data.messageId, ensuring safe handling of the optional type. No unsafe unwrapping patterns detected.app/sagas/deepLinking.js (3)
49-56: LGTM!Minor formatting change with no functional impact.
226-226: Good use of optional chaining.Adding
params.caller?._idprevents potential TypeError whencalleris undefined or null, improving robustness for edge cases in video conference notifications.
249-251: LGTM!The early return when
hostis falsy prevents errors during host normalization and is consistent with the similar guard inhandleOpen()at line 142.android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (4)
25-36: Excellent documentation of the notification architecture.The comment clearly explains the hybrid approach where JS uses expo-notifications for token registration while native code handles FCM message processing, notification display, E2E decryption, and direct reply. This is valuable for future maintainers understanding the system.
37-37: LGTM!Removing
INotificationsApplicationfrom the class declaration completes the migration away fromreact-native-notifications.
45-45: LGTM!
VideoConfPackageis properly added to the packages list, enabling theVideoConfModulenative module for JS-native communication.
68-72: [rewritten review comment]
[classification tag]android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java (2)
320-327: LGTM - Context fallback pattern is sound.The fallback to
CustomPushNotification.reactApplicationContextis properly guarded with a null check before use. The static volatile field ensures visibility across threads.
369-375: LGTM - Direct context assignment is appropriate here.Unlike
decryptMessagewhich receives aContextparameter,encryptMessageContenthas no incoming context to check, so directly using the static field with a null guard is the correct approach.react-native.config.js (1)
1-3: LGTM - Config cleanup aligns with dependency removal.The platform-specific exclusion for
@react-native-firebase/messagingis no longer needed since the package is being removed from the project. The empty dependencies object is a clean approach.ios/AppDelegate.swift (1)
30-31: LGTM - Notification handler initialization is properly placed.
ReplyNotification.configure()is called after Firebase/MMKV initialization but before React Native starts, ensuring the notification delegate chain is established early. TheExpoAppDelegatebase class handles the standard remote notification lifecycle methods that were removed.ios/ReplyNotification.swift (2)
16-28: LGTM - Delegate chain pattern is well-implemented.The weak reference to
originalDelegateprevents retain cycles, and the configuration properly preserves the expo-notifications delegate while intercepting reply actions. This is the correct pattern for delegate interception.
79-117: LGTM - Reply handling with proper background task management.The
deferblock ensuresendBackgroundTaskandcompletionHandlerare always called, even on success. The pattern correctly handles:
- Payload decoding failures (early return with completionHandler)
- Background task lifecycle (safe to call
endBackgroundTaskeven if invalid)- Failure notification display on error
jest.setup.js (1)
139-156: LGTM! Comprehensive expo-notifications mock setup.The mock provides a complete API surface for testing, including proper subscription cleanup with
{ remove: jest.fn() }returns for all listener methods. This aligns well with the expo-notifications migration.android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (2)
360-371: LGTM! Notification channel creation is correct.Proper Android O+ check with
IMPORTANCE_HIGHfor push notifications. The null check onnotificationManageradds resilience.
393-397: LGTM! Correct PendingIntent flags for Android S+.Using
FLAG_IMMUTABLEfor content intents on Android S+ is correct. The reply intent at line 611 correctly usesFLAG_MUTABLEsinceRemoteInputrequires mutability.ios/Shared/Models/NotificationType.swift (1)
11-15: LGTM! Clean addition of videoconf notification type.The new case aligns with the cross-platform video conference notification support and integrates properly with the existing
Codableenum.app/index.tsx (1)
141-144: LGTM! Correct integration of video conference initial notification handling.The placement is appropriate—after push notification handling but before deep linking—ensuring video conference notifications from cold start are properly dispatched via
deepLinkingClickCallPush. The early return prevents duplicate processing.app/sagas/troubleshootingNotification.ts (1)
22-23: LGTM! Clean migration to expo-notifications permissions API.The permission check correctly uses
status === 'granted'. This is slightly stricter than the previous notifee implementation (which may have consideredundeterminedas enabled), which is the correct behavior for troubleshooting diagnostics.android/app/src/main/AndroidManifest.xml (2)
78-85: LGTM! Firebase messaging service properly configured.The service is correctly set with
exported=falseand a high priority intent-filter to ensure it receives FCM messages before other potential handlers.
95-103: LGTM! VideoConfBroadcast receiver properly secured.Good use of
exported=falsewith explicit intent-filter actions using the app's package namespace. This ensures only the app can trigger these video conference actions.android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.java (2)
22-45: LGTM! Solid FCM message handling implementation.Good defensive checks (empty data validation) and proper error handling. The try-catch ensures a malformed notification doesn't crash the service.
47-53: LGTM! Token refresh handling is appropriate.Delegating token management to the expo-notifications JS layer via
getDevicePushTokenAsync()is the correct approach for this migration.android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt (1)
45-85: LGTM! Video conference intent handling is well-implemented.Good defensive patterns: early return when
eventis missing, clearing the flag to prevent re-processing, and proper notification cancellation. The JSON serialization via Gson for JS interop is appropriate.android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (3)
38-45: LGTM! Video conference fields are well-documented.The inline comments explaining the purpose (
// For video conf notifications) and status values (// For video conf: 0=incoming, 4=cancelled) improve code readability. UsingIntegerinstead ofintforstatuscorrectly allows null-checking for the incoming call case.
232-235: LGTM! Caller class structure is consistent with existing Sender class.The new
Callerinner class mirrors theSenderclass structure, maintaining consistency in how user identity data is modeled.
77-80: No thread safety issue:reactApplicationContextis declaredvolatile. The field in CustomPushNotification is properly declared with thevolatilekeyword (line 50), which provides visibility guarantees for reads and writes across threads. This is a standard and safe pattern for shared mutable state in multi-threaded Java code. No race condition exists here.Likely an incorrect or invalid review comment.
ios/NotificationService/NotificationService.swift (1)
58-86: LGTM! Video conference notification handling is well-structured.Good implementation with proper status-based branching, iOS 15+ availability check for
interruptionLevel, and appropriate use of localized strings. Theringtone.mp3file is present in the project bundle, confirming the sound resource dependency is satisfied.android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.java (3)
1-22: LGTM! Well-structured BroadcastReceiver setup.Good use of constants for the TAG and clear class documentation explaining the purpose.
24-46: Proper validation and action mapping.The null checks and action-to-event mapping logic are correct. Early returns on invalid input prevent unnecessary processing.
74-80: Appropriate activity launch flags for BroadcastReceiver context.The flags
FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOPare correct when starting an activity from a non-Activity context. The extras forwarding ensures MainActivity receives all necessary data.android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.java (3)
1-32: LGTM! Standard React Native module setup.Proper use of
@ReactModuleannotation and clear constants for SharedPreferences.
59-68: Silent error handling is acceptable for cleanup operations.The
clearPendingActionmethod appropriately ignores errors since failing to clear a pending action is not critical.
74-77: LGTM! Static method allows native-to-native calls.Using a static method for
storePendingActionis correct since it's called fromVideoConfBroadcastbefore the React context may be available.app/lib/notifications/videoConf/getInitialNotification.ts (4)
1-8: LGTM! Proper imports for cross-platform handling.Clean separation of platform-specific dependencies.
14-29: LGTM! Android path with proper error handling.The null checks and try-catch ensure graceful degradation if the native module is unavailable or returns invalid data.
40-42: Type narrowing could be simplified.The trigger type checking is verbose but necessary for TypeScript safety. This is acceptable given expo-notifications' type definitions.
31-60: The VIDEOCONF notification category and action identifiers are properly configured.The code correctly checks for
'DECLINE_ACTION'on line 49. The iOS notification category setup inapp/lib/notifications/push.ts(lines 107–117) properly registers bothACCEPT_ACTIONandDECLINE_ACTIONfor theVIDEOCONFcategory, matching what the TypeScript code expects and what the iOS native code sets as the category identifier inNotificationService.swift.app/lib/notifications/index.ts (2)
18-28: LGTM! Clear separation of button action handling.Correctly distinguishes between explicit Accept/Decline button presses and other notification interactions.
30-64: Good fallback toappInit()when notification cannot be parsed.The try-catch ensures the app initializes even if notification parsing fails. The
typesmapping correctly handles all subscription types.android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfPackage.java (1)
1-32: LGTM! Standard ReactPackage implementation.Correctly registers the VideoConfModule for React Native access. VideoConfPackage is properly imported and registered in MainApplication.kt (line 45).
app/lib/notifications/push.ts (1)
131-159: The current implementation correctly pairs device token retrieval with device token updatesThe code properly uses
getDevicePushTokenAsync()to obtain the native device token (APNs/FCM) andaddPushTokenListener()to listen for device token updates.addPushTokenListeneris specifically designed for native device token changes, not Expo tokens, so there is no mismatch here. The token type remains consistent as a native device token throughout.Likely an incorrect or invalid review comment.
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java (2)
47-72: Notification channel configuration follows current Android best practicesThe channel is correctly configured with
IMPORTANCE_HIGHfor urgent head-up behavior, ringtone audio set viachannel.setSound()withAudioAttributes.USAGE_NOTIFICATION_RINGTONEfor proper audio routing, and public lockscreen visibility. This aligns with Android 8.0+ patterns for incoming call notifications. The null-check onnotificationManagerbefore creating the channel is good defensive coding.
214-235: Cancel helpers use hashCode() — note 32-bit collision risk
cancelCallreuses(rid + callerId).hashCode()fromshowIncomingCall, andcancelByIdcorrectly wrapsNotificationManager.cancel. The ID generation is consistent between both methods.However,
String.hashCode()carries practical caveats: it produces stable values across process restarts on Android but is not guaranteed by the hashCode specification, and 32-bit collisions are possible with large uid spaces. For production use, consider either accepting the collision risk or usingNotificationManager.notify(tag, id, ...)with a string tag to avoid the integer hash altogether.
| private String extractMessage(String message, Ejson ejson) { | ||
| if (message == null) { | ||
| return ""; | ||
| } | ||
| if (ejson != null && ejson.type != null && !ejson.type.equals("d")) { | ||
| int pos = message.indexOf(":"); | ||
| int start = pos == -1 ? 0 : pos + 2; | ||
| return message.substring(start, message.length()); | ||
| return message.substring(start); | ||
| } | ||
| return message; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential StringIndexOutOfBoundsException in extractMessage.
If the message contains a colon at position pos where pos + 2 > message.length(), substring(start) will throw. For example, message "a:" has pos=1, start=3, but length=2.
private String extractMessage(String message, Ejson ejson) {
if (message == null) {
return "";
}
if (ejson != null && ejson.type != null && !ejson.type.equals("d")) {
int pos = message.indexOf(":");
- int start = pos == -1 ? 0 : pos + 2;
- return message.substring(start);
+ if (pos == -1) {
+ return message;
+ }
+ int start = Math.min(pos + 2, message.length());
+ return message.substring(start);
}
return message;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private String extractMessage(String message, Ejson ejson) { | |
| if (message == null) { | |
| return ""; | |
| } | |
| if (ejson != null && ejson.type != null && !ejson.type.equals("d")) { | |
| int pos = message.indexOf(":"); | |
| int start = pos == -1 ? 0 : pos + 2; | |
| return message.substring(start, message.length()); | |
| return message.substring(start); | |
| } | |
| return message; | |
| } | |
| private String extractMessage(String message, Ejson ejson) { | |
| if (message == null) { | |
| return ""; | |
| } | |
| if (ejson != null && ejson.type != null && !ejson.type.equals("d")) { | |
| int pos = message.indexOf(":"); | |
| if (pos == -1) { | |
| return message; | |
| } | |
| int start = Math.min(pos + 2, message.length()); | |
| return message.substring(start); | |
| } | |
| return message; | |
| } |
🤖 Prompt for AI Agents
In
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
around lines 510 to 520, the code computes start = pos == -1 ? 0 : pos + 2 and
then calls message.substring(start), which can throw
StringIndexOutOfBoundsException when pos+2 > message.length(); change the start
calculation to clamp it within the message bounds (for example start = pos == -1
? 0 : Math.min(pos + 2, message.length())) so substring(start) is safe and
returns an empty string when the colon is at the end.
| // Extract bundle directly from intent extras | ||
| bundle = intent.getBundleExtra("pushNotification"); | ||
| if (bundle == null) { | ||
| bundle = intent.getExtras(); | ||
| } | ||
| notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); | ||
|
|
||
| String notId = bundle.getString("notId"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add null check for bundle to prevent potential NPE.
If both intent.getBundleExtra("pushNotification") and intent.getExtras() return null, the subsequent bundle.getString("notId") at line 51 will throw a NullPointerException.
// Extract bundle directly from intent extras
bundle = intent.getBundleExtra("pushNotification");
if (bundle == null) {
bundle = intent.getExtras();
}
+ if (bundle == null) {
+ return;
+ }
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);🤖 Prompt for AI Agents
In
android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java
around lines 44 to 51, the code assumes bundle is non-null but both
intent.getBundleExtra("pushNotification") and intent.getExtras() can be null,
causing a NullPointerException when calling bundle.getString("notId"); add a
null check immediately after attempting to populate bundle and handle the null
case by logging a warning and returning early (or providing a safe default)
before accessing bundle, ensuring any subsequent use of notificationManager or
bundle fields is only done when bundle is confirmed non-null.
| public void showIncomingCall(Bundle bundle, Ejson ejson) { | ||
| String rid = ejson.rid; | ||
| // Video conf uses 'caller' field, regular messages use 'sender' | ||
| String callerId = ""; | ||
| String callerName = "Unknown"; | ||
|
|
||
| if (ejson.caller != null) { | ||
| callerId = ejson.caller._id != null ? ejson.caller._id : ""; | ||
| callerName = ejson.caller.name != null ? ejson.caller.name : "Unknown"; | ||
| } else if (ejson.sender != null) { | ||
| // Fallback to sender if caller is not present | ||
| callerId = ejson.sender._id != null ? ejson.sender._id : ""; | ||
| callerName = ejson.sender.name != null ? ejson.sender.name : (ejson.senderName != null ? ejson.senderName : "Unknown"); | ||
| } | ||
|
|
||
| // Generate unique notification ID from rid + callerId | ||
| String notificationIdStr = (rid + callerId).replaceAll("[^A-Za-z0-9]", ""); | ||
| int notificationId = notificationIdStr.hashCode(); | ||
|
|
||
| Log.d(TAG, "Showing incoming call notification from: " + callerName); | ||
|
|
||
| // Create intent data for actions - include all required fields for JS | ||
| Bundle intentData = new Bundle(); | ||
| intentData.putString("rid", rid != null ? rid : ""); | ||
| intentData.putString("notificationType", "videoconf"); | ||
| intentData.putString("callerId", callerId); | ||
| intentData.putString("callerName", callerName); | ||
| intentData.putString("host", ejson.host != null ? ejson.host : ""); | ||
| intentData.putString("callId", ejson.callId != null ? ejson.callId : ""); | ||
| intentData.putString("ejson", bundle.getString("ejson", "{}")); | ||
| intentData.putInt("notificationId", notificationId); | ||
|
|
||
| Log.d(TAG, "Intent data - rid: " + rid + ", callerId: " + callerId + ", host: " + ejson.host + ", callId: " + ejson.callId); | ||
|
|
||
| // Full screen intent - opens app when notification is tapped | ||
| Intent fullScreenIntent = new Intent(context, MainActivity.class); | ||
| fullScreenIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); | ||
| fullScreenIntent.putExtras(intentData); | ||
| fullScreenIntent.putExtra("event", "default"); | ||
|
|
||
| PendingIntent fullScreenPendingIntent; | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| fullScreenPendingIntent = PendingIntent.getActivity( | ||
| context, notificationId, fullScreenIntent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE | ||
| ); | ||
| } else { | ||
| fullScreenPendingIntent = PendingIntent.getActivity( | ||
| context, notificationId, fullScreenIntent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT | ||
| ); | ||
| } | ||
|
|
||
| // Accept action - directly opens MainActivity (Android 12+ blocks trampoline pattern) | ||
| Intent acceptIntent = new Intent(context, MainActivity.class); | ||
| acceptIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); | ||
| acceptIntent.putExtras(intentData); | ||
| acceptIntent.putExtra("event", "accept"); | ||
| acceptIntent.putExtra("videoConfAction", true); | ||
| acceptIntent.setAction(ACTION_ACCEPT + "_" + notificationId); // Unique action to differentiate intents | ||
|
|
||
| PendingIntent acceptPendingIntent; | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| acceptPendingIntent = PendingIntent.getActivity( | ||
| context, notificationId + 1, acceptIntent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE | ||
| ); | ||
| } else { | ||
| acceptPendingIntent = PendingIntent.getActivity( | ||
| context, notificationId + 1, acceptIntent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT | ||
| ); | ||
| } | ||
|
|
||
| // Decline action - directly opens MainActivity | ||
| Intent declineIntent = new Intent(context, MainActivity.class); | ||
| declineIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); | ||
| declineIntent.putExtras(intentData); | ||
| declineIntent.putExtra("event", "decline"); | ||
| declineIntent.putExtra("videoConfAction", true); | ||
| declineIntent.setAction(ACTION_DECLINE + "_" + notificationId); // Unique action to differentiate intents | ||
|
|
||
| PendingIntent declinePendingIntent; | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| declinePendingIntent = PendingIntent.getActivity( | ||
| context, notificationId + 2, declineIntent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE | ||
| ); | ||
| } else { | ||
| declinePendingIntent = PendingIntent.getActivity( | ||
| context, notificationId + 2, declineIntent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT | ||
| ); | ||
| } | ||
|
|
||
| // Get icons | ||
| Resources res = context.getResources(); | ||
| String packageName = context.getPackageName(); | ||
| int smallIconResId = res.getIdentifier("ic_notification", "drawable", packageName); | ||
|
|
||
| // Build notification | ||
| NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) | ||
| .setSmallIcon(smallIconResId) | ||
| .setContentTitle("Incoming call") | ||
| .setContentText("Video call from " + callerName) | ||
| .setPriority(NotificationCompat.PRIORITY_HIGH) | ||
| .setCategory(NotificationCompat.CATEGORY_CALL) | ||
| .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) | ||
| .setAutoCancel(false) | ||
| .setOngoing(true) | ||
| .setFullScreenIntent(fullScreenPendingIntent, true) | ||
| .setContentIntent(fullScreenPendingIntent) | ||
| .addAction(0, "Decline", declinePendingIntent) | ||
| .addAction(0, "Accept", acceptPendingIntent); | ||
|
|
||
| // Set sound for pre-O devices | ||
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { | ||
| Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); | ||
| builder.setSound(ringtoneUri); | ||
| } | ||
|
|
||
| // Show notification | ||
| if (notificationManager != null) { | ||
| notificationManager.notify(notificationId, builder.build()); | ||
| Log.d(TAG, "Video call notification displayed with ID: " + notificationId); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Android NotificationCompat.Builder setSmallIcon(0) missing resource crash behavior
💡 Result:
Short answer: Passing 0 to setSmallIcon (i.e. no valid drawable resource) will cause the notification to fail at post time and crash the app — typically with errors like "Invalid notification (no valid small icon)" / "Couldn't create icon: StatusBarIcon" or a RemoteServiceException (Bad notification posted). The platform docs state small icon is required and NotificationCompat expects a valid resource id. [1][2][3][4]
Fix: Always pass a valid drawable resource id (not 0). Provide a default notification icon (manifest meta-data or app drawable) and/or guard your code before calling setSmallIcon(…) so you never call it with 0. [1][4]
Sources:
- Android docs: small icon is required / NotificationCompat.Builder setSmallIcon. [1][4]
- Reports and stack traces showing "Invalid notification (no valid small icon)" / RemoteServiceException when icon/resource invalid. [2][3]
🏁 Script executed:
find . -name "VideoConfNotification.java" -type fRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 169
🏁 Script executed:
wc -l ./android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 173
🏁 Script executed:
cat -n ./android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 12205
Fix critical small icon crash and add null-safety guards to showIncomingCall
The method has several robustness issues that can crash the app:
- Critical: Small icon resource lookup can crash notifications
Line 178–182: Ific_notificationdrawable is missing or renamed,getIdentifier()returns0, andsetSmallIcon(0)will crash the notification post withRemoteServiceException: Bad notification postedor"Invalid notification (no valid small icon)". Always validate the resource ID before passing it tosetSmallIcon():
int smallIconResId = res.getIdentifier("ic_notification", "drawable", packageName);
+ if (smallIconResId == 0) {
+ smallIconResId = android.R.drawable.sym_call_incoming; // or your app's default icon
+ }- Null-safety for
ejsonandbundle
Line 81 dereferencesejson.ridwithout null checks; line 109 callsbundle.getString()without null validation. Either can crash if upstream parsing fails:
+ if (ejson == null) {
+ Log.w(TAG, "showIncomingCall called with null Ejson payload");
+ return;
+ }
+ if (bundle == null) {
+ bundle = new Bundle();
+ }
String rid = ejson.rid;-
Notification ID collision when both
ridandcallerIdare empty
Line 96–97: If both are empty,notificationIdStrbecomes"", andhashCode()returns0, causing multiple such calls to reuse notification ID 0. Consider a fallback:notificationIdStr = (rid + callerId).isEmpty() ? String.valueOf(System.currentTimeMillis()) : (rid + callerId). -
Hard-coded user-facing strings
Lines 183–193:"Incoming call","Video call from ","Accept","Decline"should be moved to string resources for localization and consistency.
🤖 Prompt for AI Agents
In
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java
around lines 80–206, make showIncomingCall null-safe and robust: check bundle
and ejson for null before dereferencing (guard ejson.rid, ejson.caller/sender,
ejson.host/callId and use empty defaults), validate bundle before calling
bundle.getString("ejson", "{}"), and return/log if required data is missing;
validate the drawable resource id from getIdentifier() and if it's 0 use a safe
fallback (app icon or a bundled default) before calling setSmallIcon(); avoid
notification ID collisions by using a fallback when (rid + callerId) is empty
(e.g., use System.currentTimeMillis() or a secure random value to build
notificationIdStr); and replace hard-coded UI strings ("Incoming call", "Video
call from ", "Accept", "Decline") with string resources referenced via
context.getString(...).
| export const removeNotificationsAndBadge = async (): Promise<void> => { | ||
| await removeAllNotifications(); | ||
| await setNotificationsBadgeCount(); | ||
| }; | ||
| export const initializePushNotifications = (): Promise<INotification | { configured: boolean }> | undefined => { | ||
| setBadgeCount(); | ||
| export const initializePushNotifications = async (): Promise<INotification | { configured: boolean } | null> => { | ||
| await setNotificationsBadgeCount(); | ||
| return pushNotificationConfigure(onNotification); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "removeNotificationsAndBadge|initializePushNotifications" --type ts --type tsx -A 2 -B 2Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 105
🏁 Script executed:
rg -n "removeNotificationsAndBadge|initializePushNotifications" --type typescript -A 2 -B 2Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 112
🏁 Script executed:
rg -n "removeNotificationsAndBadge|initializePushNotifications" -A 2 -B 2Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2124
🏁 Script executed:
cat -n app/lib/store/appStateMiddleware.ts | head -30Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1105
Fix unhandled promise in app/lib/store/appStateMiddleware.ts.
removeNotificationsAndBadge() is called at line 20 without awaiting the returned promise. Since the parent function handleAppStateChange is not async, add .catch() to handle any rejections or refactor the callback to be async. The call to initializePushNotifications() in app/index.tsx is correctly handled with await.
🤖 Prompt for AI Agents
In app/lib/notifications/index.ts around lines 72-78 the exported async helpers
can reject and cause unhandled promise rejections when callers don’t await them
(removeNotificationsAndBadge is called without await). Wrap the internal awaits
in try/catch (or .catch) and log/handle errors so the function always resolves
(e.g., try { await removeAllNotifications(); await setNotificationsBadgeCount();
} catch (err) { logger.warn(...) } and similarly guard pushNotificationConfigure
in initializePushNotifications), ensuring these functions do not propagate
unhandled rejections to callers that are not async.
| if let messageId = data.messageId { | ||
| self.rocketchat?.getPushWithId(messageId) { notification in | ||
| if let notification = notification { | ||
| self.bestAttemptContent?.title = notification.title | ||
| self.bestAttemptContent?.body = notification.text | ||
| self.processPayload(payload: notification.payload) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing contentHandler call when messageId is nil.
If messageId is nil, the if let block is skipped and contentHandler is never invoked, causing the notification service extension to time out (approximately 30 seconds) before iOS terminates it.
// Request the content from server
if let messageId = data.messageId {
self.rocketchat?.getPushWithId(messageId) { notification in
if let notification = notification {
self.bestAttemptContent?.title = notification.title
self.bestAttemptContent?.body = notification.text
self.processPayload(payload: notification.payload)
}
}
+ } else {
+ // No messageId available, deliver the notification as-is
+ if let bestAttemptContent = self.bestAttemptContent {
+ self.contentHandler?(bestAttemptContent)
+ }
}🤖 Prompt for AI Agents
In ios/NotificationService/NotificationService.swift around lines 45 to 54, the
code skips calling contentHandler when messageId is nil (or when getPushWithId
returns nil), which causes the extension to time out; ensure contentHandler is
invoked in every control path: when messageId is nil immediately call
contentHandler(self.bestAttemptContent ?? self.request.content), and also call
contentHandler with the current bestAttemptContent (or original request.content)
inside the completion handler if notification is nil or after processing the
payload, guaranteeing the extension always finishes.
| "expo-device": "^8.0.10", | ||
| "expo-document-picker": "13.1.4", | ||
| "expo-file-system": "18.1.7", | ||
| "expo-haptics": "14.1.3", | ||
| "expo-image": "^2.3.2", | ||
| "expo-keep-awake": "14.1.3", | ||
| "expo-local-authentication": "16.0.3", | ||
| "expo-navigation-bar": "^4.2.4", | ||
| "expo-notifications": "^0.32.14", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
expo-notifications 0.32 Expo SDK 53 compatibility requirements
💡 Result:
Short answer
- expo-notifications 0.32 (0.32.x) is published as the current 0.32 release series and is compatible with Expo SDK 53. The package's package.json lists generic peerDependencies (expo/react/react-native: "*"), and the SDK 53 docs and changelog reference expo-notifications updates for SDK 53. [1][2][3]
Key compatibility/requirements for using expo-notifications 0.32 with Expo SDK 53
- You must install expo-notifications (e.g. npx expo install expo-notifications or npm/yarn). Its package.json shows it as the 0.32.x release and expects to be used inside an Expo project. [2][3]
- Push notifications on Android: from SDK 53 remote push notifications are not available in Expo Go — a development build (or standalone/EAS build) is required to test/send push notifications on Android. Local notifications still work in Expo Go. [1]
- App config / build-time plugin: expo-notifications requires using its config plugin (or the built-in plugin) and proper native build configuration (EAS / npx expo run:android|ios) to enable push token registration and entitlements (especially for iOS). Users have reported that omitting the plugin in app.json/app.config.* prevents iOS push from working. Add the plugin and follow the push-notifications setup guide when building. [1][4]
- Platform / OS build requirements: follow SDK 53 platform requirements (compile/target SDK and Xcode versions) when building native apps. See the Expo SDK 53 platform table for supported OS/Xcode/compileSdk targets. [5]
References
- Expo SDK 53 notifications docs (push-notifications behavior in Expo Go / configuration). [1]
- expo-notifications package (0.32.10) package.json on UNPKG (shows 0.32.x release metadata). [2]
- expo-notifications CHANGELOG (0.32.x release notes). [3]
- GitHub issue noting need to add expo-notifications config plugin for iOS push on SDK 53. [4]
- Expo SDK platform/compile requirements table (SDK 53). [5]
If you want, I can paste the exact install/config steps for SDK 53 (app.json plugin entry, EAS build steps, and the minimal code to request permissions and get the device token).
🌐 Web query:
expo-device 8.0 Expo SDK 53 compatibility
💡 Result:
Short answer: No — [email protected] is not the version bundled with Expo SDK 53. SDK 53 was released in Apr/May 2025 and expects the v7.x family of Expo modules; expo-device 8.0.x was released later (Aug–Oct 2025). Use npx expo install expo-device (or pin to the v7.x matching your SDK) to get the correct module for SDK 53. [1][2]
Sources
- Expo SDK 53 release notes. [1]
- expo-device changelog showing 8.0.x release dates. [2]
Fix expo-device version — 8.0.x is incompatible with Expo SDK 53.
The expo-device version should be from the v7.x family (e.g., ^7.x.x), not 8.0.x. Expo SDK 53 was released in April/May 2025 and expects expo-device v7.x; version 8.0.x was released later and is not compatible with this SDK version. Update the dependency to use the correct version range.
The expo-notifications version (^0.32.14) is compatible with SDK 53 and requires no changes.
🤖 Prompt for AI Agents
In package.json around lines 64 to 72, the expo-device dependency is pinned to
^8.0.10 which is incompatible with Expo SDK 53; change the expo-device entry to
a v7.x range (for example ^7.0.0 or ^7.x.x) so it matches SDK 53 expectations,
then run yarn/npm install and verify metro/build to ensure no version conflicts;
leave expo-notifications (^0.32.14) as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/lib/notifications/push.ts (1)
190-206: Missing listener cleanup could cause memory leaks.The listeners added via
addPushTokenListenerandaddNotificationResponseReceivedListenerare never removed. While theconfiguredflag prevents re-initialization, this could still cause issues during testing or hot-reloading.Consider storing the subscriptions and exposing a cleanup function:
let tokenSubscription: Notifications.Subscription | null = null; let responseSubscription: Notifications.Subscription | null = null; // In pushNotificationConfigure: tokenSubscription = Notifications.addPushTokenListener(tokenData => { deviceToken = tokenData.data; }); responseSubscription = Notifications.addNotificationResponseReceivedListener(response => { // ... }); // Export cleanup function: export const cleanupPushNotifications = () => { tokenSubscription?.remove(); responseSubscription?.remove(); configured = false; };
🧹 Nitpick comments (4)
app/lib/notifications/videoConf/getInitialNotification.ts (3)
8-8: Add TypeScript types for VideoConfModule.The
VideoConfModulelacks TypeScript type definitions, which reduces type safety and IDE support.Consider defining an interface for the module:
interface IVideoConfModule { getPendingAction: () => Promise<string | null>; } const { VideoConfModule } = NativeModules as { VideoConfModule?: IVideoConfModule };
17-24: Consider validating parsed data structure.The code parses JSON but only checks
notificationType. If the parsed data has an unexpected structure, it could cause issues downstream indeepLinkingClickCallPush.Consider adding basic validation:
const data = JSON.parse(pendingAction); -if (data?.notificationType === 'videoconf') { +if (data && typeof data === 'object' && data.notificationType === 'videoconf') { store.dispatch(deepLinkingClickCallPush(data)); return true; }
40-42: Type guard relies on runtime property checking.The condition
'type' in trigger && trigger.type === 'push'uses runtime checks that TypeScript can't fully validate. This is acceptable but worth noting.If expo-notifications exports type guards or discriminated unions for triggers, prefer those for better type safety.
app/lib/notifications/push.ts (1)
33-79: Consider improving type safety for payload transformation.The extensive use of
Record<string, any>and fallback to empty strings could hide missing data issues. While functional, stronger typing would catch transformation errors earlier.Consider defining intermediate types:
interface RemoteMessagePayload { message?: string; style?: string; ejson?: string; collapse_key?: string; notId?: string; msgcnt?: string; title?: string; from?: string; image?: string; soundname?: string; }This would make the transformation more self-documenting and catch missing fields during development.
📜 Review details
Configuration used: CodeRabbit 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 (2)
app/lib/notifications/push.ts(3 hunks)app/lib/notifications/videoConf/getInitialNotification.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/lib/notifications/push.ts (2)
app/definitions/INotification.ts (1)
INotification(1-16)app/lib/notifications/index.ts (1)
onNotification(18-66)
app/lib/notifications/videoConf/getInitialNotification.ts (2)
app/lib/store/auxStore.ts (1)
store(6-6)app/actions/deepLinking.ts (1)
deepLinkingClickCallPush(26-31)
🔇 Additional comments (6)
app/lib/notifications/videoConf/getInitialNotification.ts (1)
10-14: LGTM!The signature change to return
Promise<boolean>is well-documented and provides clear semantics for whether a video conference action was dispatched.app/lib/notifications/push.ts (5)
12-18: LGTM!The migration to
setBadgeCountAsynccorrectly updates the signature to returnPromise<void>and includes appropriate error handling.
20-26: LGTM!Properly migrated to
dismissAllNotificationsAsyncwith consistent error handling.
84-125: LGTM!The notification categories are properly configured with appropriate actions and localized titles. The structure correctly distinguishes between MESSAGE (reply in background) and VIDEOCONF (foreground) use cases.
130-158: LGTM!The permission request flow is correctly implemented with proper device checks and graceful error handling.
209-212: No issues identified. The code correctly callsNotifications.getLastNotificationResponse()withoutawait, which is the appropriate synchronous method in expo-notifications v0.32.14. The library also providesNotifications.getLastNotificationResponseAsync()for async use cases, but the synchronous variant is intentionally used here and is the correct choice.
| }); | ||
|
|
||
| // Set up notification categories for iOS | ||
| setupNotificationCategories(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing await for setupNotificationCategories.
The async function setupNotificationCategories() is called without await, which means configuration might continue before categories are set up. While this may work in practice due to iOS's asynchronous nature, it's inconsistent with async patterns.
Apply this diff to await the call:
- setupNotificationCategories();
+ await setupNotificationCategories();Note: This requires making pushNotificationConfigure async or restructuring the promise chain.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/lib/notifications/push.ts around line 180, the async function
setupNotificationCategories() is invoked without awaiting it, risking
continuation before categories are configured; update pushNotificationConfigure
to be async (or return a Promise) and add await setupNotificationCategories();
so the call is awaited, or alternatively chain the promise (return
setupNotificationCategories().then(...)) to ensure configuration waits for
completion; ensure callers of pushNotificationConfigure are updated to handle
the async return.
|
iOS Build Available Rocket.Chat Experimental 4.68.0.107809 |
|
Android Build Available Rocket.Chat Experimental 4.68.0.107808 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNR5VeS_aT_14h-wCQVjHcPgTUTPbsNvxCauJr7aPm-67ifOGf8bQEkD5sD41uX9kFBaHueVG6_KzWKS4c1p |
…ng to TurboModules - Replaced the existing VideoConfModule and related classes with a TurboModule implementation for improved performance and integration. - Updated MainApplication to use VideoConfTurboPackage instead of the legacy VideoConfPackage. - Enhanced notification handling by introducing a new User-Agent header in relevant requests. - Removed obsolete Java classes and streamlined the notification architecture to utilize Kotlin for better maintainability. - Improved the handling of video conference actions and ensured compatibility with the new TurboModule system.
c6f20e7 to
6620a1b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
ios/fastlane/Fastfile (1)
115-125: ForcingARCHS=arm64limits simulator builds to Apple Silicon MacsAdding
ARCHS=arm64toxcargswill prevent this lane from building on Intel Macs (which needx86_64for iOS Simulator). If your CI or contributors still use Intel hardware, this lane will start failing there.If the intent is to fix Apple Silicon / arm64-simulator issues, consider one of:
- Gate this via an env var or separate lane (e.g.,
build_experimental_simulator_apple_silicon).- Let Xcode choose architectures (no explicit
ARCHS) and instead handle problematic pods via their own settings.- Document in the README / iOS build doc that this lane now assumes Apple Silicon-only.
Please confirm this trade-off is acceptable for your build matrix.
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt (1)
56-65: Consider logging errors in clearPendingAction for debugging.While ignoring errors in a cleanup method may be intentional, logging the exception would aid debugging without affecting functionality.
Apply this diff to add logging:
@ReactMethod override fun clearPendingAction() { try { reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .edit() .remove(KEY_VIDEO_CONF_ACTION) .apply() } catch (e: Exception) { - // Ignore errors + Log.w("VideoConfModule", "Failed to clear pending action", e) } }android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt (1)
188-195: Consider addingFLAG_IMMUTABLEfor pre-S devices.For security best practices,
FLAG_IMMUTABLEis recommended on Android M+ when the PendingIntent doesn't need to be modified. Currently, pre-S devices don't include this flag.private fun createPendingIntent(requestCode: Int, intent: Intent): PendingIntent { val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } return PendingIntent.getActivity(context, requestCode, intent, flags) }
📜 Review details
Configuration used: CodeRabbit 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 (14)
android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/NativeVideoConfSpec.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java(2 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java(2 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt(1 hunks)android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfTurboPackage.kt(1 hunks)app/lib/native/NativeVideoConfAndroid.ts(1 hunks)app/lib/notifications/videoConf/getInitialNotification.ts(1 hunks)ios/Shared/RocketChat/API/Request.swift(2 hunks)ios/fastlane/Fastfile(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java
🧰 Additional context used
🧬 Code graph analysis (5)
android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java (1)
NotificationHelper(10-40)
app/lib/notifications/videoConf/getInitialNotification.ts (2)
app/lib/store/auxStore.ts (1)
store(6-6)app/actions/deepLinking.ts (1)
deepLinkingClickCallPush(26-31)
android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
CustomPushNotification(45-659)
android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (1)
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboPackage.java (1)
SSLPinningTurboPackage(15-45)
ios/Shared/RocketChat/API/Request.swift (1)
app/lib/constants/userAgent.ts (1)
userAgent(3-5)
🪛 detekt (1.23.8)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt
[warning] 62-62: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (19)
ios/Shared/RocketChat/API/Request.swift (2)
58-58: LGTM!The User-Agent header is now set using the new computed property, which improves maintainability and provides proper identification of the client.
74-82: Format inconsistency between platforms and Bundle.main usage in notification service extension warrant verification.The implementation constructs a custom User-Agent string. Two concerns should be addressed:
Format inconsistency: The React Native app uses a browser-style User-Agent (
Mozilla/5.0 (iPhone...)), while this iOS notification service extension uses a custom format (RC Mobile; ios ...). Confirm this difference is intentional and appropriate for the API requirements.Bundle.main in notification extension: Since this code is in the NotificationService extension,
Bundle.mainrefers to the extension's bundle, not the main app's bundle. If version info must match the main app, consider usingBundle(identifier: "com.yourapp.mainapp")or sharing version data via app groups.android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java (1)
130-130: LGTM! User-Agent header added for better request tracking.The addition of the User-Agent header using NotificationHelper.getUserAgent() will help with server-side analytics and debugging by providing Android version, app version, and build number information.
android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java (1)
29-39: LGTM! Well-documented User-Agent helper method.The getUserAgent() method is implemented correctly with clear formatting and appropriate usage of Android Build and BuildConfig constants.
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfModule.kt (1)
12-50: LGTM! SharedPreferences pattern correctly implemented.The module correctly uses SharedPreferences to pass video conference actions from native to JS. The getPendingAction method properly clears the action after reading to ensure one-time consumption.
app/lib/native/NativeVideoConfAndroid.ts (1)
1-9: LGTM! Clean TurboModule interface definition.The interface correctly defines the video conference module bridge with appropriate return types that match the native Kotlin implementation.
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt (2)
20-38: LGTM! Action validation and mapping correctly implemented.The broadcast receiver properly validates the action and extras before processing, and correctly maps native action constants to event strings for the JS layer.
47-55: No issues found with caller field key consistency.The Android implementation correctly uses the same field names as iOS. Both platforms receive
caller._idandcaller.namefrom the server payload. Android's intermediate representation in intent extras ("callerId"and"callerName"strings) is necessary sinceIntent.putExtra()only accepts primitive types, but these are correctly reconstructed back to thecaller._idandcaller.namestructure in VideoConfBroadcast and MainActivity, matching the iOS approach. The field naming is consistent throughout the notification flow.android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfTurboPackage.kt (1)
12-37: LGTM! Standard TurboReactPackage implementation.The package correctly implements TurboReactPackage with appropriate module info flags and follows the same pattern as other TurboModule packages in the codebase (e.g., SSLPinningTurboPackage).
app/lib/notifications/videoConf/getInitialNotification.ts (2)
13-28: LGTM! Android path correctly retrieves and dispatches pending video conference actions.The Android implementation properly checks for the native module's existence, parses the pending action JSON, validates the notification type, and dispatches the action to the store.
30-59: No action required. The DECLINE_ACTION constant is consistently defined and used across the codebase. It is properly registered withexpo-notifications.setNotificationCategoryAsync()inapp/lib/notifications/push.tsand all references in the JS/TS layer (including the code under review) use the same string identifier. The native iOS implementation is abstracted by the expo-notifications library, so no separate native code verification is necessary.android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (1)
23-46: LGTM! VideoConfTurboPackage correctly registered and architecture documented.The changes properly integrate the VideoConfTurboPackage into the React Native package list and provide helpful documentation about the notification architecture. The removal of INotificationsApplication interface aligns with the migration to expo-notifications.
android/app/src/main/java/chat/rocket/reactnative/notification/NativeVideoConfSpec.kt (1)
9-23: LGTM!Clean TurboModule spec definition. The abstract class correctly defines the API surface for video conference action handling, with proper module naming and React method annotations that will be inherited by the concrete implementation.
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt (2)
85-183: Well-structured incoming call notification implementation.The notification handling correctly:
- Extracts caller info with appropriate fallbacks
- Generates unique notification IDs from rid + callerId
- Uses proper intent flags for Android 12+ trampoline restrictions
- Configures full-screen intent for call-style UX
- Handles pre-O sound configuration separately
54-77: LGTM!Notification channel creation properly handles Android O+ requirements with appropriate importance level, visibility, and audio attributes for incoming call notifications.
android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt (4)
1-6: LGTM!Package declaration and imports are appropriate for the Firebase messaging service implementation.
8-13: LGTM!Documentation clearly explains the service's purpose and responsibilities.
14-18: LGTM!Idiomatic Kotlin implementation with proper FirebaseMessagingService inheritance.
23-27: Notification-only FCM payloads are not processed.The code only handles FCM messages with a data payload (
remoteMessage.data). Messages containing only a notification payload (without data) are ignored at line 24-26. Confirm this is intentional for the Rocket.Chat integration, or add a comment explaining why notification payloads are not processed.
| try { | ||
| val notification = CustomPushNotification(this, bundle) | ||
| notification.onReceived() | ||
| } catch (e: Exception) { | ||
| Log.e(TAG, "Error processing FCM message", e) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find CustomPushNotification class definition
find . -name "*.kt" -o -name "*.java" | xargs rg -l "class CustomPushNotification" 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 170
🏁 Script executed:
# Also search for the file directly
fd -e kt -e java "CustomPushNotification" .Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 170
🏁 Script executed:
# Check the RCFirebaseMessagingService file to understand context
cat -n android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt | head -60Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2013
🏁 Script executed:
# Search for WorkManager, JobScheduler usage in the notification package
rg -n "WorkManager|JobScheduler|HandlerThread|CoroutineScope|launch|async" android/app/src/main/java/chat/rocket/reactnative/notification/ --type kotlin --type javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1039
🏁 Script executed:
# Read the full CustomPushNotification.java file
wc -l android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 172
🏁 Script executed:
# Read CustomPushNotification.java in sections
head -100 android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 3472
🏁 Script executed:
# Find and read the onReceived method
ast-grep --pattern $'onReceived() {
$$$
}'Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 60
🏁 Script executed:
# Search for the onReceived method with context
rg -A 30 "fun onReceived|void onReceived" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1398
🏁 Script executed:
# Check E2ENotificationProcessor to see if it uses proper async patterns
cat -n android/app/src/main/java/chat/rocket/reactnative/notification/E2ENotificationProcessor.java | head -80Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 3639
🏁 Script executed:
# Read more of the onReceived method
rg -A 80 "public void onReceived" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 3566
🏁 Script executed:
# Check if E2ENotificationProcessor.processAsync is actually used
rg -n "E2ENotificationProcessor\|processAsync" android/app/src/main/java/chat/rocket/reactnative/notification/Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 60
🏁 Script executed:
# Check for Thread spawning in the onReceived flow
rg -B 5 -A 10 "new Thread" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 944
🏁 Script executed:
# Look for any blocking/synchronous calls after notification.onReceived() in RCFirebaseMessagingService
rg -A 5 "notification.onReceived" android/app/src/main/java/chat/rocket/reactnative/notification/Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 781
🏁 Script executed:
# Find the handleNotification and processNotification methods
rg -A 50 "private void handleNotification" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2980
🏁 Script executed:
# Check what processNotification does
rg -A 30 "private void processNotification" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1647
🏁 Script executed:
# Look for blocking operations: network calls, MMKV access, Glide image loading with .get()
rg -n "\.get\(\)|\.execute\(\)|\.load\(|Glide|MMKV|notificationManager\.notify" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | head -30Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 636
🏁 Script executed:
# Check for E2E encryption handling and if it's synchronous
rg -B 3 -A 15 "notificationType.*encrypted|E2E|decryption|decrypt" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | head -60Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2531
🏁 Script executed:
# Find and read showNotification method
rg -B 5 -A 80 "private void showNotification\(" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | head -120Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 3795
🏁 Script executed:
# Check specifically around the Glide.get() call
rg -B 10 -A 5 "Glide.with.*\.get\(\)" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 60
🏁 Script executed:
# Check if there's any error handling or timeout for the Glide operation
rg -B 5 -A 10 "catch.*Exception.*Glide|try.*Glide" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 60
🏁 Script executed:
# Find buildNotification method
rg -B 5 -A 100 "private.*Notification\.Builder buildNotification" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | head -150Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 4987
🏁 Script executed:
# Search for the exact context of Glide.get() to understand when it's called
rg -B 20 "\.get\(\)" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | grep -A 20 "Glide"Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 338
🏁 Script executed:
# Find all calls to getAvatar
rg -n "getAvatar\(" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 259
🏁 Script executed:
# Get context around getAvatar() calls
rg -B 5 -A 5 "getAvatar\(" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1360
🏁 Script executed:
# Check the full getAvatar method including error handling
rg -A 40 "private Bitmap getAvatar\(" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1659
🏁 Script executed:
# Check if there's any timeout configuration in Glide usage
rg -B 10 "submit\(100, 100\)" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.javaRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 502
🏁 Script executed:
# Check for any other blocking operations like notificationLoad
rg -B 5 -A 20 "private void notificationLoad" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | head -40Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1017
🏁 Script executed:
# Verify the complete execution path for message-id-only notifications
rg -B 3 -A 10 "loadNotificationAndProcess" android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java | head -30Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1408
Consider the blocking Glide image loading operations within the FCM service lifecycle.
The implementation defers work to a background thread when React is not initialized (good), but when React is already available, buildNotification() calls Glide.get() synchronously to fetch avatar images—this blocks until the image is downloaded and decoded. Combined with the 5-second wait for React initialization, this can approach or exceed the 10-second FCM service timeout in edge cases.
Either add an explicit timeout to the Glide operation, move avatar loading to a separate WorkManager task, or ensure image loading fails fast with a fallback icon rather than blocking the FCM callback.
🤖 Prompt for AI Agents
In
android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt
around lines 37-42, the current code can block the FCM service because
buildNotification() calls Glide synchronously to fetch avatars; update the
implementation so image decoding cannot block the FCM callback: either (1)
perform the Glide load asynchronously with an explicit short timeout and fall
back to a default icon on timeout/error, (2) or offload full notification
building (including avatar fetching) to a background job via WorkManager and
immediately acknowledge the message in the FCM service, or (3) ensure Glide
requests use a synchronous-with-timeout helper that cancels after a few hundred
milliseconds and returns the fallback; finally make sure any path that fails to
obtain an avatar still posts a notification quickly using the fallback icon.
| override fun onNewToken(token: String) { | ||
| Log.d(TAG, "FCM token refreshed") | ||
| // Token handling is done by expo-notifications JS layer | ||
| // which uses getDevicePushTokenAsync() | ||
| super.onNewToken(token) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for getDevicePushTokenAsync usage in JS/TS files
echo "=== Searching for getDevicePushTokenAsync usage ==="
rg -l 'getDevicePushTokenAsync' --type=ts --type=tsx --type=js 2>/dev/null || echo "No files found"
# Search for token registration patterns
echo -e "\n=== Searching for token-related updates and registration ==="
rg -n 'registerForPushNotificationsAsync|registerDeviceToken|updateToken' --type=ts --type=tsx --type=js --type=java --type=kotlin -A 3 2>/dev/null | head -50
# Check for native event emission patterns
echo -e "\n=== Searching for event emission to JS ==="
rg -n 'sendEvent|DeviceEventEmitter|NativeModules.*emit' --type=java --type=kotlin android/ 2>/dev/null | head -30Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 250
🏁 Script executed:
#!/bin/bash
# Find JS layer integration files related to push notifications
echo "=== Searching JS push notification integration files ==="
fd -e ts -e tsx -e js | xargs grep -l 'expo-notifications\|push.*notification' 2>/dev/null | head -20
# Look for initialization and token handling in JS
echo -e "\n=== Searching for push initialization and token handling ==="
rg -n 'expo.*notification|getPushToken|device.*token' --type=ts --type=tsx --type=js -B 2 -A 2 2>/dev/null | head -60Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 749
🏁 Script executed:
#!/bin/bash
# Check the RCFirebaseMessagingService for complete implementation
echo "=== Viewing RCFirebaseMessagingService.kt complete context ==="
if [ -f "android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt" ]; then
wc -l "android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt"
echo "---"
cat -n "android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt"
else
find . -name "RCFirebaseMessagingService.kt" 2>/dev/null
fiRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2176
🏁 Script executed:
#!/bin/bash
# Search for how the token reaches Rocket.Chat server
echo "=== Searching for Rocket.Chat server token registration ==="
rg -n 'registerPushToken|updatePushToken|push.*register' --type=ts --type=tsx --type=js -B 2 -A 5 2>/dev/null | head -80Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 137
🏁 Script executed:
#!/bin/bash
# Read the main push notification handler
echo "=== Content of app/lib/notifications/push.ts ==="
wc -l app/lib/notifications/push.ts
echo "---"
cat -n app/lib/notifications/push.ts
echo -e "\n=== Check if there's initialization in app/index.tsx ==="
grep -n 'push\|notification\|token' app/index.tsx | head -30Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 8181
🏁 Script executed:
#!/bin/bash
# Search more specifically for where tokens are obtained
echo "=== Search for getPushToken or similar ==="
rg -n 'getPushToken|pushToken|getToken|notify.*token' --type=ts --type=tsx --type=js -B 2 -A 5 2>/dev/null | head -100
# Look for any native module calls from JS
echo -e "\n=== Search for NativeModules or native bridge calls ==="
rg -n 'NativeModules|native.*push|native.*notification|bridge' --type=ts --type=tsx --type=js -B 1 -A 2 2>/dev/null | head -60Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 177
🏁 Script executed:
#!/bin/bash
# Look specifically at troubleshooting notification saga for insights
echo "=== Content of troubleshootingNotification.ts ==="
wc -l app/sagas/troubleshootingNotification.ts
if [ $? -eq 0 ]; then
cat -n app/sagas/troubleshootingNotification.ts
fiRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2266
🏁 Script executed:
#!/bin/bash
# Search for where server registration happens
echo "=== Search for server push registration ==="
rg -n 'registerPush|pushToken|deviceToken' --type=ts --type=tsx --type=js -i -B 3 -A 3 2>/dev/null | head -120
# Check package.json for expo-notifications version and setup
echo -e "\n=== Check expo-notifications setup ==="
if [ -f "package.json" ]; then
grep -A 5 -B 5 'expo-notifications' package.json
fiRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 525
🏁 Script executed:
#!/bin/bash
# Find where deviceToken is used
echo "=== Searching for deviceToken usage ==="
rg -n 'deviceToken' --type=ts --type=tsx --type=js -B 2 -A 5 2>/dev/null
echo -e "\n=== Search for user settings/profile update endpoints ==="
rg -n 'user.*push|push.*device|registerDevice|addPushToken' --type=ts --type=tsx --type=js -i -B 2 -A 3 2>/dev/null | head -80Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 176
🏁 Script executed:
#!/bin/bash
# Look for API calls that might send the token
echo "=== Search for notification/push related API calls ==="
rg -n 'notifications/push|notification.*device|api.*push' --type=ts --type=tsx --type=js -B 1 -A 2 2>/dev/null
# Check if there's a method to set push token in user settings
echo -e "\n=== Search in methods or services for push token registration ==="
find app/lib/methods app/lib/services -name "*.ts" -o -name "*.tsx" -o -name "*.js" 2>/dev/null | xargs grep -l 'push\|notification\|token' 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1316
🏁 Script executed:
#!/bin/bash
# Check the notifications initialization flow
echo "=== Look for the full initializePushNotifications implementation ==="
rg -n 'export.*initializePushNotifications|function initializePushNotifications' --type=ts --type=tsx --type=js -A 20 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 146
🏁 Script executed:
#!/bin/bash
# Find the notifications module entry point
echo "=== Search for notifications folder structure ==="
find app/lib/notifications -type f -name "*.ts" -o -name "*.tsx" -o -name "*.js" 2>/dev/null | sort
echo -e "\n=== Check notifications/index.ts or similar ==="
if [ -f "app/lib/notifications/index.ts" ]; then
cat -n app/lib/notifications/index.ts
else
echo "index.ts not found in notifications folder"
fi
echo -e "\n=== List all files in notifications ==="
ls -la app/lib/notifications/ 2>/dev/null || echo "Directory not found"Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 3684
🏁 Script executed:
#!/bin/bash
# Search more carefully for where deviceToken might be exported/imported
echo "=== Search for 'from.*notifications' or 'import.*deviceToken' ==="
rg -n "from.*['\"].*notifications" --type=ts --type=tsx --type=js 2>/dev/null | head -30
echo -e "\n=== Search for any file that might handle server registration ==="
rg -l 'users\.info|users\.update|user\.setPreferences|notification' app/lib/services/ 2>/dev/null
echo -e "\n=== Check restApi.ts for push-related endpoints ==="
if [ -f "app/lib/services/restApi.ts" ]; then
grep -n 'push\|notification\|token' app/lib/services/restApi.ts 2>/dev/null | head -30
fiRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1075
🏁 Script executed:
#!/bin/bash
# Look at package.json to understand how push is initialized
echo "=== Check if there's any initialization in package.json scripts ==="
cat package.json | grep -A 5 -B 5 'start\|native\|expo'
echo -e "\n=== Search for app initialization and push setup ==="
rg -n 'pushNotificationConfigure|push.*notify|expo.*notify' --type=ts --type=tsx --type=js 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 7578
🏁 Script executed:
#!/bin/bash
# Search for the function that calls the push token API
echo "=== Find function names in restApi.ts that use push.token ==="
grep -n "export.*=" app/lib/services/restApi.ts | grep -A 2 -B 2 "push\|notification" | head -40
echo -e "\n=== Search for calls to push token registration (look for patterns like sendPushToken, registerPushToken) ==="
rg -n 'push\.token|sendPushToken|registerPushToken' --type=ts --type=tsx --type=js -B 3 -A 3 2>/dev/null | head -80
echo -e "\n=== Get exact function names around line 991-1001 in restApi.ts ==="
sed -n '985,1015p' app/lib/services/restApi.tsRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1675
🏁 Script executed:
#!/bin/bash
# Search for where these push functions are called
echo "=== Search for where push token functions are invoked ==="
rg -n 'savePushToken|deletePushToken|getPushToken' --type=ts --type=tsx --type=js 2>/dev/null
echo -e "\n=== Look for import of restApi functions and their usage ==="
rg -n 'from.*restApi|import.*savePush|import.*deletePush' --type=ts --type=tsx --type=js 2>/dev/null | head -20Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 197
🏁 Script executed:
#!/bin/bash
# Check sagas for push token registration
echo "=== Search app/sagas for push token handling ==="
find app/sagas -name "*.ts" -o -name "*.js" 2>/dev/null | xargs grep -l 'push\|token' 2>/dev/null
echo -e "\n=== Check app/actions for push token actions ==="
find app/actions -name "*.ts" -o -name "*.js" 2>/dev/null | xargs grep -l 'push\|token' 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 403
🏁 Script executed:
#!/bin/bash
# Search all saga files for registerPushToken usage
echo "=== Search for registerPushToken in saga files ==="
grep -rn 'registerPushToken' app/sagas/ 2>/dev/null
echo -e "\n=== Search for registerPushToken in entire codebase ==="
rg -n 'registerPushToken' --type=ts --type=tsx --type=js 2>/dev/null
echo -e "\n=== Check init.js for push token registration ==="
cat -n app/sagas/init.js | head -100Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 4119
🏁 Script executed:
#!/bin/bash
# Check login.js for push token handling
echo "=== Check app/sagas/login.js ==="
wc -l app/sagas/login.js
echo "---"
grep -n 'push\|token\|notification\|registerPush' app/sagas/login.js 2>/dev/null
echo -e "\n=== Check selectServer.ts ==="
if [ -f "app/sagas/selectServer.ts" ]; then
grep -n 'push\|token\|registerPush' app/sagas/selectServer.ts 2>/dev/null
fiRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1021
🏁 Script executed:
#!/bin/bash
# Look for all imports of registerPushToken
echo "=== Search for imports and usage of registerPushToken ==="
rg -n "registerPushToken|removePushToken" --type=ts --type=tsx --type=js 2>/dev/null
# Also check if there's middleware or interceptors
echo -e "\n=== Check middleware or effects that might auto-register ==="
rg -n 'addPushTokenListener|tokenData' --type=ts --type=tsx --type=js -B 2 -A 5 2>/dev/null | head -60Repository: RocketChat/Rocket.Chat.ReactNative
Length of output: 197
🏁 Script executed:
#!/bin/bash
# Look at the complete login.js to understand the token refresh flow
echo "=== View registerPushTokenFork function in context ==="
sed -n '185,250p' app/sagas/login.js
echo -e "\n=== Check if there's any listener or effect that calls registerPushToken on token change ==="
rg -n 'addPushTokenListener|tokenListener|token.*listener' --type=ts --type=tsx --type=js -B 3 -A 10 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1688
🏁 Script executed:
#!/bin/bash
# Verify the complete push.ts token listener implementation
echo "=== Verify push.ts addPushTokenListener and what happens after ==="
sed -n '185,215p' app/lib/notifications/push.ts
echo -e "\n=== Search for any saga or action triggered on token update ==="
rg -n 'pushToken|tokenRefresh|token.*update' --type=ts --type=tsx --type=js app/sagas/ 2>/dev/nullRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 1017
Token refresh from FCM is not synced to the Rocket.Chat server.
While expo-notifications correctly detects FCM token refreshes via addPushTokenListener() (line 190 in app/lib/notifications/push.ts), the updated token is never re-registered with the server. The registerPushToken() function in app/lib/services/restApi.ts is only called during login (app/sagas/login.js:240), not when the token is refreshed.
When FCM refreshes the token:
- expo-notifications updates
deviceTokenin the JS layer - But
registerPushToken()is never invoked to send the new token to the server - The server retains the stale token, causing push notifications to fail after token refresh
Either the Kotlin onNewToken() method needs to explicitly notify the JS layer to re-register, or the JS layer needs to call registerPushToken() when the token listener fires.
🤖 Prompt for AI Agents
In
android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt
around lines 45-50, onNewToken currently only logs and calls super but does not
inform the JS layer so the server never receives refreshed tokens; modify
onNewToken to send the new token to the React Native JS layer (for example emit
an event via the ReactContext / RCTDeviceEventEmitter or write the token to a
shared place the JS bridge reads) so the JS side can call registerPushToken(),
and ensure you still call super.onNewToken(token) after emitting; additionally
add/verify a corresponding JS listener that calls registerPushToken() when the
token event is received.
| // Get icons | ||
| val packageName = context.packageName | ||
| val smallIconResId = context.resources.getIdentifier("ic_notification", "drawable", packageName) | ||
|
|
||
| // Build notification | ||
| val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply { | ||
| setSmallIcon(smallIconResId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle missing icon resource gracefully.
getIdentifier returns 0 if the resource is not found. Using 0 as the small icon resource ID can cause the notification to fail silently or display incorrectly on some Android versions.
// Get icons
val packageName = context.packageName
val smallIconResId = context.resources.getIdentifier("ic_notification", "drawable", packageName)
+ val validIconResId = if (smallIconResId != 0) smallIconResId else android.R.drawable.ic_dialog_info
// Build notification
val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply {
- setSmallIcon(smallIconResId)
+ setSmallIcon(validIconResId)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Get icons | |
| val packageName = context.packageName | |
| val smallIconResId = context.resources.getIdentifier("ic_notification", "drawable", packageName) | |
| // Build notification | |
| val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply { | |
| setSmallIcon(smallIconResId) | |
| // Get icons | |
| val packageName = context.packageName | |
| val smallIconResId = context.resources.getIdentifier("ic_notification", "drawable", packageName) | |
| val validIconResId = if (smallIconResId != 0) smallIconResId else android.R.drawable.ic_dialog_info | |
| // Build notification | |
| val builder = NotificationCompat.Builder(context, CHANNEL_ID).apply { | |
| setSmallIcon(validIconResId) |
🤖 Prompt for AI Agents
In
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt
around lines 154-160, getIdentifier may return 0 for "ic_notification" which
will break setSmallIcon; check if smallIconResId == 0 and handle it by falling
back to a valid icon resource (for example the app icon from
context.applicationInfo.icon or a safe android.R.drawable.* fallback), then pass
that non-zero id to setSmallIcon; also consider logging a warning when the
custom drawable is missing so it can be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
app/sagas/deepLinking.js (2)
226-239: Guard against missingcaller._idbefore notifying video-conference eventsUsing optional chaining avoids a hard crash when
params.calleris absent, but if that happens you’ll now send requests tonotifyUserwith a path like'undefined/video-conference'anduidin the payload asundefined. That’s likely not a meaningful or valid request.Consider bailing out (and logging) when
uidis missing so you don’t emit malformed events:- const uid = params.caller?._id; - const { rid, callId, event } = params; + const uid = params.caller?._id; + if (!uid) { + log('Missing caller uid in OPEN_VIDEO_CONF params', params); + return; + } + const { rid, callId, event } = params;
249-252: Early return on missinghostprevents crashes but may leave UX in a no-op stateThe new
!hostguard avoidshost.slicethrowing, which is good, but it now makeshandleClickCallPusha silent no-op whenhostis missing. InhandleOpen, the analogous case callsfallbackNavigation()so the app at least initializes instead of “doing nothing”.You may want to mirror that behavior here:
- if (!host) { - return; - } + if (!host) { + yield fallbackNavigation(); + return; + }This keeps the app from feeling unresponsive when a malformed/partial push payload arrives.
📜 Review details
Configuration used: CodeRabbit 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 (1)
app/sagas/deepLinking.js(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/sagas/deepLinking.js (2)
app/sagas/login.js (1)
params(423-423)app/sagas/rooms.js (1)
params(145-145)
|
Android Build Available Rocket.Chat Experimental 4.68.0.107811 Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNSU5-XHlfn0jLRCr9n9Twy6EZIMaa0dyPAZ-72bYqXKvcqnxC2RQtwSdN_Td4cFqoC6mPP1DqqpWhGj8VRH |
Proposed changes
Issue(s)
How to test or reproduce
Screenshots
Types of changes
Checklist
Further comments
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.