Skip to content

Conversation

@diegolmello
Copy link
Member

@diegolmello diegolmello commented Dec 10, 2025

Proposed changes

Migrate push notification handling from react-native-notifications, @notifee/react-native, and @react-native-firebase/messaging to expo-notifications. This consolidates notification handling into a single, well-maintained library while implementing custom native handling for advanced features like video conference calls.

Key Changes

Dependencies:

  • Removed: react-native-notifications, @notifee/react-native, @react-native-firebase/messaging
  • Added: expo-notifications, expo-device
  • Removed 3 patches that were required for the old libraries

Android:

  • Implemented custom RCFirebaseMessagingService.kt for FCM handling
  • Created VideoConfNotification.kt for video conference call notifications with full-screen intent, accept/decline actions, and ringtone
  • Migrated VideoConfModule to TurboModules architecture (NativeVideoConfSpec.kt, VideoConfModule.kt, VideoConfTurboPackage.kt)
  • Added VideoConfBroadcast.kt for handling notification action intents
  • Added User-Agent header to notification API requests
  • Added timeout to Glide image loading to prevent FCM service timeouts

iOS:

  • Enhanced NotificationService.swift to handle message-ID-only notifications by fetching full content from server
  • Updated ejson payload with server response for correct navigation on E2EE and message-ID-only notifications
  • Improved video conference notification handling with proper call cancellation
  • Added User-Agent header to API requests
  • Ensured contentHandler is always called to prevent notification service extension timeouts

JavaScript:

  • Migrated to expo-notifications APIs for token registration, listeners, and badge management
  • Updated push.ts with new notification configuration and FCM token refresh handling
  • Simplified notification tap handling in notifications/index.ts
  • Removed backgroundNotificationHandler.ts (now handled natively)

Issue(s)

https://rocketchat.atlassian.net/browse/SUP-859

How to test or reproduce

Push Notifications

  1. Log in on iOS/Android device
  2. Send a message from another client (web/desktop)
  3. Verify notification appears with correct title, body, and sender info
  4. Tap notification and verify it navigates to the correct room

Video Conference Calls

  1. Initiate a video call from web to mobile user
  2. Verify incoming call notification appears with:
    • Caller name
    • Accept/Decline buttons
    • Ringtone playing
    • Full-screen intent (Android)
  3. Test Accept → should join call
  4. Test Decline → should dismiss notification
  5. Test call cancelled by caller → notification should be removed

E2EE Rooms

  1. Send message in E2EE room
  2. Verify notification shows decrypted content
  3. Tap notification and verify navigation works

Direct Reply (Android)

  1. Receive notification
  2. Use inline reply action
  3. Verify message is sent correctly

Token Refresh

  1. Clear app data or reinstall
  2. Log in
  3. Verify push notifications work (token was registered)

Screenshots

Screen_Recording_20251222_154458_RocketChat.Experimental.mp4
ScreenRecording_12-22-2025.15-39-52_1.MP4

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Why this migration?

  1. Reduced complexity: Consolidating 3 notification libraries into 1 simplifies maintenance
  2. Better Expo integration: expo-notifications works seamlessly with the Expo ecosystem we already use
  3. Fewer patches: Removed 3 patches required for the old libraries
  4. TurboModules: Video conference module now uses the new React Native architecture
  5. Native control: Custom FCM service gives us full control over notification handling

Files Changed Summary

Category Files
Android Native RCFirebaseMessagingService.kt, VideoConfNotification.kt, VideoConfModule.kt, VideoConfBroadcast.kt, CustomPushNotification.java, MainActivity.kt, MainApplication.kt
iOS Native NotificationService.swift, ReplyNotification.swift, AppDelegate.swift, Payload.swift
JavaScript push.ts, notifications/index.ts, getInitialNotification.ts
Config package.json, Podfile, AndroidManifest.xml, build.gradle

Breaking Changes

None. The migration maintains the same user-facing behavior while improving the underlying implementation.

Summary by CodeRabbit

  • New Features

    • Incoming video call notifications now show accept/decline actions and richer caller details; actions open the app to the call.
  • Improvements

    • Video call notifications handled natively for more reliable delivery and behavior across Android/iOS.
    • Badge and notification clearing are more reliable and async-safe.
    • Reply-from-notification flow and permissions handling updated for improved stability.
  • Removed

    • Legacy JS background handler for video conferences replaced by native handling.

✏️ Tip: You can customize this high-level summary in your review settings.

…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.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Replaces legacy Notifee/Firebase/react-native-notifications JS stack with Expo notifications/device; adds native Android video-conference handling (Firebase service, TurboModule, BroadcastReceiver, notification renderer), updates iOS notification service/delegate for videoconf, and migrates notification plumbing across JS, Android, and iOS.

Changes

Cohort / File(s) Summary
Dependency & Test config
package.json, react-native.config.js, jest.setup.js, ios/Podfile, patches/@notifee+react-native+7.8.2.patch, patches/@react-native-firebase+messaging+21.12.2.patch, patches/react-native-notifications+5.1.0.patch
Removed Notifee, Firebase Messaging and react-native-notifications; added expo-device and expo-notifications; updated react-native.config and Jest mocks; removed Notifee/Firebase patches; updated Pod static framework list.
Android build & manifest
android/app/build.gradle, android/app/src/main/AndroidManifest.xml
Removed RNNotifications Gradle entries; added RCFirebaseMessagingService service and VideoConfBroadcast receiver manifest entries.
Android core notifications
android/app/src/main/java/.../MainActivity.kt, .../MainApplication.kt, .../notification/CustomPushNotification.java, .../notification/Ejson.java
MainActivity: handle videoConf intents (onNewIntent/getInitial). MainApplication: remove INotificationsApplication, add VideoConfTurboPackage. CustomPushNotification: major refactor (constructor, React init wait, e2e handling, channeling, videoconf flows). Ejson: add caller, callId, status.
Android video-conf native subsystem
android/app/src/main/java/.../notification/NativeVideoConfSpec.kt, .../VideoConfModule.kt, .../VideoConfNotification.kt, .../VideoConfBroadcast.kt, .../VideoConfTurboPackage.kt, app/lib/native/NativeVideoConfAndroid.ts
New TurboModule spec and package, VideoConfModule (SharedPreferences storage + JS event emit), VideoConfNotification (incoming-call UI, cancel), VideoConfBroadcast (accept/decline receiver persists pending action), and TS TurboModule bridge.
Android notification utilities
android/app/src/main/java/.../notification/ReplyBroadcast.java, .../LoadNotification.java, .../NotificationHelper.java
ReplyBroadcast: extract push bundle directly and add User-Agent header; LoadNotification and NotificationHelper: add/get User-Agent header helper; small bundle/null-safety fixes.
Removed Android background handler
app/lib/notifications/videoConf/backgroundNotificationHandler.ts
Deleted Notifee-based background videoconf handler, channels, and background event/message handlers.
JS/TS notification plumbing (Expo)
app/lib/notifications/index.ts, app/lib/notifications/push.ts, app/lib/notifications/videoConf/getInitialNotification.ts
Rewrote push stack to use Expo APIs: registration, handlers, transformNotificationResponse, async badge/clear, iOS categories; getInitialNotification now platform-aware and returns boolean; added setupVideoConfActionListener.
App entry, deep linking & sagas
app/index.tsx, index.js, app/sagas/deepLinking.js, app/sagas/troubleshootingNotification.ts
App initializes videoConf listener and honors initial videoconf handling; removed dynamic Android background handler require; sagas: null-safety and Expo permission checks.
iOS notification & delegate changes
ios/AppDelegate.swift, ios/ReplyNotification.swift, ios/NotificationService/NotificationService.swift, ios/RocketChatRN-Bridging-Header.h
Removed RNNotifications startup and AppDelegate remote-notification delegates; ReplyNotification reworked as UNUserNotificationCenterDelegate with reply handling and forwarding; NotificationService added videoconf processing; removed RNNotifications bridging imports.
iOS models & network
ios/Shared/Models/NotificationType.swift, ios/Shared/Models/Payload.swift, ios/Shared/RocketChat/API/Request.swift
Added videoconf NotificationType; Payload adds caller/callId/status; Request adds User-Agent header builder.
iOS project & build updates
ios/RocketChatRN.xcodeproj/project.pbxproj, ios/fastlane/Fastfile
Updated PBX project to include Expo/privacy bundles and remapped Pod resource wiring; Fastlane simulator lane appends ARCHS=arm64.
JS UI & settings tweaks
app/views/JitsiMeetView/index.tsx, app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx
Removed BackHandler blocking fallback for Jitsi; use Linking.openSettings() on non-iOS for device notification settings.
App middleware
app/lib/store/appStateMiddleware.ts
removeNotificationsAndBadge() call now awaited and errors logged with .catch.
Tests setup
jest.setup.js
Replaced react-native-notifications mocks with expo-notifications and expo-device mocks.

Sequence Diagram(s)

sequenceDiagram
  participant FCM as Firebase (FCM)
  participant AndroidSvc as RCFirebaseMessagingService
  participant Custom as CustomPushNotification
  participant VideoNotif as VideoConfNotification
  participant Broadcast as VideoConfBroadcast
  participant VideoModule as VideoConfModule (TurboModule)
  participant Main as MainActivity
  participant JS as JavaScript app

  FCM ->> AndroidSvc: deliver data message
  AndroidSvc ->> Custom: onReceived(data -> Bundle)
  alt videoconf payload
    Custom ->> VideoNotif: showIncomingCall(bundle, ejson)
    VideoNotif ->> User: display incoming call (Accept / Decline)
    User ->> Broadcast: click action (ACCEPT/DECLINE)
    Broadcast ->> VideoModule: storePendingAction(json) (SharedPreferences + emit if JS active)
    Broadcast ->> Main: start MainActivity with extras
    Main ->> VideoModule: getPendingAction() -> resolves to action JSON
    Main ->> JS: dispatch deepLinkingClickCallPush with parsed action
  else regular push
    Custom ->> JS: forward push to RN runtime (push handling)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • Android TurboModule, BroadcastReceiver persistence and JSON shape (VideoConfModule, VideoConfBroadcast).
    • CustomPushNotification async React initialization, E2E decryption paths and notification fallback/cancellation logic.
    • iOS NotificationService videoconf rendering and ReplyNotification delegate forwarding.
    • JS Expo migration: transformNotificationResponse, getInitialNotification behavior, and category/action mappings.

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Poem

🐇
I hopped from burrow into code so spry,
Expo lanterns glimmered in the sky.
Kotlin drums and Swift bells chime,
Pending hops wait for app-time.
A carrot nod — the calls reply.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: Migrate to expo-notifications' accurately summarizes the primary change: a comprehensive migration from three legacy notification libraries (react-native-notifications, @notifee/react-native, @react-native-firebase/messaging) to expo-notifications.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore.expo-notifications

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 API getLastNotificationResponseAsync and fix the require-await violation

Two issues in pushNotificationConfigure:

  1. handleNotification is declared async but has no await, triggering the require-await ESLint rule.
  2. Notifications.getLastNotificationResponse() is treated as a synchronous API, but Expo's actual API is getLastNotificationResponseAsync(), 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 pushNotificationConfigure itself async, change the handler to return a Promise without the async keyword, and use await with getLastNotificationResponseAsync:

-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 pushNotificationConfigure as returning a Promise, so this change is compatible.

🧹 Nitpick comments (11)
app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx (1)

17-22: Confirm Linking.openSettings support and consider a small safety net

The 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 guarantees Linking.openSettings availability, and (b) optionally add a .catch to avoid unhandled rejections and, if you want, fall back to Linking.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 reasonable

Dropping the BackHandler listener in the Linking.openURL failure 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 if openURL fails 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 originalDelegate is 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 ScheduledExecutorService or 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 on mBundle.

At line 190, mBundle assignment is synchronized, but here and at line 262, it's not. Since mBundle can 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 mBundle as volatile if only single-assignment visibility is needed without compound operations.


342-350: Consider using named constants for video conf status values.

Magic numbers 0 and 4 for 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 unused Configuration import.

The Configuration import on line 11 is not used in this file.

 import android.content.Intent
-import android.content.res.Configuration
 import chat.rocket.reactnative.notification.VideoConfModule
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.java (1)

54-68: Consider null safety for caller fields.

The extras.getString() calls for callerId and callerName default to empty strings, but if these fields contain sensitive information, logging them at line 72 might be a compliance concern depending on whether rid contains user-identifiable data.

Consider removing rid from 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 using commit() instead of apply() 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. Using commit() 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 logging

The async wrappers with try/catch around setBadgeCountAsync and dismissAllNotificationsAsync look good. If you have a central logging/analytics facility, you might want to use it instead of console.log so 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1bd62a2 and 952068d.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is 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 modules

The 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

BackHandler is no longer used and everything still imported from react-native here 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 -> VideoConfNotification chain. This helps future maintainers understand why the background handler was removed.

ios/Shared/Models/Payload.swift (3)

11-14: LGTM!

The new Caller struct properly mirrors the Android Ejson.Caller class, maintaining consistency across platforms. The _id naming 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?._id prevents potential TypeError when caller is undefined or null, improving robustness for edge cases in video conference notifications.


249-251: LGTM!

The early return when host is falsy prevents errors during host normalization and is consistent with the similar guard in handleOpen() 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 INotificationsApplication from the class declaration completes the migration away from react-native-notifications.


45-45: LGTM!

VideoConfPackage is properly added to the packages list, enabling the VideoConfModule native 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.reactApplicationContext is 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 decryptMessage which receives a Context parameter, encryptMessageContent has 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/messaging is 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. The ExpoAppDelegate base 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 originalDelegate prevents 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 defer block ensures endBackgroundTask and completionHandler are always called, even on success. The pattern correctly handles:

  • Payload decoding failures (early return with completionHandler)
  • Background task lifecycle (safe to call endBackgroundTask even 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_HIGH for push notifications. The null check on notificationManager adds resilience.


393-397: LGTM! Correct PendingIntent flags for Android S+.

Using FLAG_IMMUTABLE for content intents on Android S+ is correct. The reply intent at line 611 correctly uses FLAG_MUTABLE since RemoteInput requires 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 Codable enum.

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 considered undetermined as 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=false and 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=false with 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 event is 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. Using Integer instead of int for status correctly allows null-checking for the incoming call case.


232-235: LGTM! Caller class structure is consistent with existing Sender class.

The new Caller inner class mirrors the Sender class structure, maintaining consistency in how user identity data is modeled.


77-80: No thread safety issue: reactApplicationContext is declared volatile. The field in CustomPushNotification is properly declared with the volatile keyword (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. The ringtone.mp3 file 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_TOP are 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 @ReactModule annotation and clear constants for SharedPreferences.


59-68: Silent error handling is acceptable for cleanup operations.

The clearPendingAction method 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 storePendingAction is correct since it's called from VideoConfBroadcast before 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 in app/lib/notifications/push.ts (lines 107–117) properly registers both ACCEPT_ACTION and DECLINE_ACTION for the VIDEOCONF category, matching what the TypeScript code expects and what the iOS native code sets as the category identifier in NotificationService.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 to appInit() when notification cannot be parsed.

The try-catch ensures the app initializes even if notification parsing fails. The types mapping 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 updates

The code properly uses getDevicePushTokenAsync() to obtain the native device token (APNs/FCM) and addPushTokenListener() to listen for device token updates. addPushTokenListener is 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 practices

The channel is correctly configured with IMPORTANCE_HIGH for urgent head-up behavior, ringtone audio set via channel.setSound() with AudioAttributes.USAGE_NOTIFICATION_RINGTONE for proper audio routing, and public lockscreen visibility. This aligns with Android 8.0+ patterns for incoming call notifications. The null-check on notificationManager before creating the channel is good defensive coding.


214-235: Cancel helpers use hashCode() — note 32-bit collision risk

cancelCall reuses (rid + callerId).hashCode() from showIncomingCall, and cancelById correctly wraps NotificationManager.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 using NotificationManager.notify(tag, id, ...) with a string tag to avoid the integer hash altogether.

Comment on lines 510 to 520
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines 80 to 206
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);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 f

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 169


🏁 Script executed:

wc -l ./android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 173


🏁 Script executed:

cat -n ./android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.java

Repository: 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:

  1. Critical: Small icon resource lookup can crash notifications
    Line 178–182: If ic_notification drawable is missing or renamed, getIdentifier() returns 0, and setSmallIcon(0) will crash the notification post with RemoteServiceException: Bad notification posted or "Invalid notification (no valid small icon)". Always validate the resource ID before passing it to setSmallIcon():
int smallIconResId = res.getIdentifier("ic_notification", "drawable", packageName);
+ if (smallIconResId == 0) {
+     smallIconResId = android.R.drawable.sym_call_incoming; // or your app's default icon
+ }
  1. Null-safety for ejson and bundle
    Line 81 dereferences ejson.rid without null checks; line 109 calls bundle.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;
  1. Notification ID collision when both rid and callerId are empty
    Line 96–97: If both are empty, notificationIdStr becomes "", and hashCode() returns 0, causing multiple such calls to reuse notification ID 0. Consider a fallback: notificationIdStr = (rid + callerId).isEmpty() ? String.valueOf(System.currentTimeMillis()) : (rid + callerId).

  2. 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(...).

package.json Outdated
Comment on lines 64 to 72
"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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.

@diegolmello diegolmello temporarily deployed to approve_e2e_testing December 10, 2025 21:25 — with GitHub Actions Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 addPushTokenListener and addNotificationResponseReceivedListener are never removed. While the configured flag 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 VideoConfModule lacks 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 in deepLinkingClickCallPush.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 952068d and d44a321.

📒 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 setBadgeCountAsync correctly updates the signature to return Promise<void> and includes appropriate error handling.


20-26: LGTM!

Properly migrated to dismissAllNotificationsAsync with 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 calls Notifications.getLastNotificationResponse() without await, which is the appropriate synchronous method in expo-notifications v0.32.14. The library also provides Notifications.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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.68.0.107809

@github-actions
Copy link

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.
@diegolmello diegolmello force-pushed the chore.expo-notifications branch from c6f20e7 to 6620a1b Compare December 11, 2025 18:32
@diegolmello diegolmello temporarily deployed to approve_e2e_testing December 11, 2025 18:32 — with GitHub Actions Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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: Forcing ARCHS=arm64 limits simulator builds to Apple Silicon Macs

Adding ARCHS=arm64 to xcargs will prevent this lane from building on Intel Macs (which need x86_64 for 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 adding FLAG_IMMUTABLE for pre-S devices.

For security best practices, FLAG_IMMUTABLE is 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.

📥 Commits

Reviewing files that changed from the base of the PR and between c6f20e7 and 6620a1b.

📒 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:

  1. 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.

  2. Bundle.main in notification extension: Since this code is in the NotificationService extension, Bundle.main refers to the extension's bundle, not the main app's bundle. If version info must match the main app, consider using Bundle(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._id and caller.name from the server payload. Android's intermediate representation in intent extras ("callerId" and "callerName" strings) is necessary since Intent.putExtra() only accepts primitive types, but these are correctly reconstructed back to the caller._id and caller.name structure 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 with expo-notifications.setNotificationCategoryAsync() in app/lib/notifications/push.ts and 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.

Comment on lines +154 to +160
// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
// 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 missing caller._id before notifying video-conference events

Using optional chaining avoids a hard crash when params.caller is absent, but if that happens you’ll now send requests to notifyUser with a path like 'undefined/video-conference' and uid in the payload as undefined. That’s likely not a meaningful or valid request.

Consider bailing out (and logging) when uid is 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 missing host prevents crashes but may leave UX in a no-op state

The new !host guard avoids host.slice throwing, which is good, but it now makes handleClickCallPush a silent no-op when host is missing. In handleOpen, the analogous case calls fallbackNavigation() 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 6620a1b and 7f98e86.

📒 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)

@github-actions
Copy link

Android Build Available

Rocket.Chat Experimental 4.68.0.107811

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNSU5-XHlfn0jLRCr9n9Twy6EZIMaa0dyPAZ-72bYqXKvcqnxC2RQtwSdN_Td4cFqoC6mPP1DqqpWhGj8VRH

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

93-161: Race condition on mBundle field.

mBundle is accessed from multiple threads without consistent synchronization. While line 190 uses synchronized(CustomPushNotification.this), other accesses throughout the class (lines 94, 164, 203, 216, 242, 262, 375-378, etc.) do not. This can lead to visibility issues, stale reads, and potential crashes.

Apply this diff to add proper synchronization:

 public class CustomPushNotification {
     // Instance fields
     private final Context mContext;
-    private Bundle mBundle;
+    private volatile Bundle mBundle;
     private final NotificationManager notificationManager;

Additionally, wrap all reads and writes to mBundle with synchronized(this) blocks, or consider using AtomicReference<Bundle> for thread-safe access. For example:

private synchronized Bundle getBundle() {
    return mBundle;
}

private synchronized void setBundle(Bundle bundle) {
    this.mBundle = bundle;
}

Then replace direct mBundle accesses with these getters/setters throughout the class.

♻️ Duplicate comments (3)
android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java (1)

44-51: Add null check for bundle to prevent NPE.

This issue has already been flagged in a previous review. If both getBundleExtra("pushNotification") and getExtras() return null, line 51 will throw a NullPointerException when accessing bundle.getString("notId").

package.json (1)

64-64: ⚠️ Duplicate concern: expo-device version remains incompatible with Expo SDK 53.

This issue was already flagged in a previous review. Expo SDK 53 includes React Native 0.79 and was released in April 2025. The version ^8.0.10 is incompatible with SDK 53 and should be downgraded to the v7.x family.

Apply this fix:

-		"expo-device": "^8.0.10",
+		"expo-device": "^7.x.x",

After updating, run yarn install and verify the lock file to ensure no version conflicts.

android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

510-520: 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.

Apply this diff to fix:

 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;
 }
🧹 Nitpick comments (3)
package.json (1)

211-211: Consider updating Node.js minimum version requirement.

Node 18 reached End-Of-Life on April 30, 2025, and Node 20 is recommended for Expo SDK 53 projects. Consider updating the engines field to "node": ">=20" to align with current best practices for this SDK version.

ios/AppDelegate.swift (1)

28-32: Consider using the existing helper method and review log level.

The code duplicates logic from FileManager+Extensions.swift which provides a groupDir() method (lines 3-11). Additionally, using .debug log level in production may produce excessive logging.

Apply this diff to use the existing helper:

-    // Initialize MMKV with app group
-    if let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String,
-       let groupDir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)?.path {
-      MMKV.initialize(rootDir: nil, groupDir: groupDir, logLevel: .debug)
-    }
+    // Initialize MMKV with app group
+    let groupDir = FileManager.default.groupDir()
+    if !groupDir.isEmpty {
+      MMKV.initialize(rootDir: nil, groupDir: groupDir, logLevel: .info)
+    }

This reduces duplication and changes the log level to .info (adjust as appropriate for your needs).

Please verify whether silent failure is intentional if the app group is not configured. If MMKV requires the app group directory for proper operation with notification extensions, consider logging a warning or error when initialization is skipped.

ios/Podfile (1)

37-40: Add trailing comma to satisfy RuboCop style hint

The multiline $static_framework array is missing a trailing comma on the last element, which RuboCop is flagging.

You can align with the style hint by updating as:

 $static_framework = [ 
   'WatermelonDB',
-  'simdjson'
+  'simdjson',
 ]

As per static_analysis_hints.

📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f98e86 and b42d2ec.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • android/app/build.gradle (0 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 (2 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java (1 hunks)
  • android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java (2 hunks)
  • ios/AppDelegate.swift (1 hunks)
  • ios/Podfile (1 hunks)
  • ios/RocketChatRN-Bridging-Header.h (1 hunks)
  • ios/RocketChatRN.xcodeproj/project.pbxproj (42 hunks)
  • package.json (1 hunks)
💤 Files with no reviewable changes (1)
  • android/app/build.gradle
🚧 Files skipped from review as they are similar to previous changes (3)
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
  • ios/RocketChatRN-Bridging-Header.h
  • android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java
🧰 Additional context used
🧬 Code graph analysis (4)
android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java (1)
  • NotificationHelper (10-40)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (2)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt (2)
  • createNotificationChannel (54-77)
  • showIncomingCall (85-183)
android/app/src/main/java/chat/rocket/reactnative/notification/LoadNotification.java (1)
  • Notification (24-57)
ios/AppDelegate.swift (1)
ios/Shared/Extensions/FileManager+Extensions.swift (1)
  • groupDir (4-12)
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)
🪛 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). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (16)
android/app/src/main/java/chat/rocket/reactnative/notification/ReplyBroadcast.java (1)

84-84: Good addition of User-Agent header.

The User-Agent header standardizes API requests across notification flows and aligns with the pattern established in other notification handlers. Based on learnings, NotificationHelper.getUserAgent() safely returns a formatted string.

package.json (1)

72-72: expo-notifications version is correct.

The expo-notifications version (^0.32.14) is compatible with Expo SDK 53 and requires no changes.

ios/AppDelegate.swift (1)

34-35: LGTM - correct initialization order.

The ReplyNotification.configure() call is correctly positioned after MMKV initialization and before React Native setup, aligning with the migration to expo-notifications as described in the PR objectives.

android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt (4)

25-25: LGTM! Import follows established pattern.

The import addition is consistent with other TurboPackage imports in the file and supports the package registration at line 47.


27-38: Helpful documentation of the hybrid notification architecture.

The documentation clearly explains the division of responsibilities between the JS layer (expo-notifications) and native layer (FCM + CustomPushNotification). This hybrid approach appears intentional to leverage expo-notifications for token management while retaining native FCM capabilities for advanced features like E2E encryption and direct reply.


39-39: Removal of INotificationsApplication interface is safe.

The interface removal is clean—no remaining references to INotificationsApplication or calls to getPushNotification exist in the codebase. This change is consistent with the migration to expo-notifications.


47-47: VideoConfTurboPackage is properly implemented. The class correctly extends TurboReactPackage and implements the required methods getModule() and getReactModuleInfoProvider(). The registration at line 47 follows the established pattern.

android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (9)

65-72: LGTM: Simplified constructor.

The constructor refactor removes external dependencies and creates the notification channel immediately, which is good practice for ensuring notifications can be displayed.


109-150: LGTM: Robust React initialization waiting.

The polling logic with timeout ensures notifications are processed even if React initialization is delayed, which is good for reliability.


224-280: LGTM: Well-structured E2E handling.

The fast-path optimization for immediate decryption and the fallback to async processing is well-designed. The separation of concerns with E2ENotificationProcessor is good architecture.


323-351: LGTM: Video conference handling.

The logic correctly handles incoming calls (status 0) and ended calls (status 4), with proper fallback from caller to sender for caller ID extraction.


373-430: LGTM: Proper PendingIntent flags.

The code correctly uses FLAG_IMMUTABLE for Android S+ (line 394) and handles API level differences appropriately.


432-453: LGTM: Fallback notification cleanup.

The logic correctly identifies and cancels previous fallback notifications from the same server, with proper error handling.


455-492: LGTM: Avatar loading with fallback.

The avatar loading logic properly handles errors and provides a reasonable fallback to the app icon.


522-591: LGTM: Notification styling.

The code properly handles different Android API levels and uses MessagingStyle for modern Android versions, which is appropriate for a chat application.


593-637: LGTM: Reply and dismiss actions.

The code correctly uses FLAG_MUTABLE for the reply action (which needs to be updated with user input) and FLAG_IMMUTABLE for the dismiss action, following Android best practices.

Comment on lines +1552 to 1593
407D3EDE3DABEE15D27BD87D /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh",
"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
"$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n";
showEnvVarsInLog = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Extract the shell script phase and surrounding context
cd ios/RocketChatRN.xcodeproj
sed -n '1545,1575p' project.pbxproj

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 2481


🏁 Script executed:

#!/bin/bash

# Look for other shell script phases to compare patterns
cd ios/RocketChatRN.xcodeproj
grep -n "PBXShellScriptBuildPhase" project.pbxproj | head -20

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 887


🏁 Script executed:

#!/bin/bash

# Get more context around the target configuration - examine the inputPaths script
cd ios/RocketChatRN.xcodeproj
sed -n '1552,1568p' project.pbxproj | cat -A

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 919


Incorrect output path target in shell script build phase.

The shell script phase at lines 1552-1568 references the RocketChatRN frameworks script (Pods-defaults-RocketChatRN-frameworks.sh) as input, but declares an output path for the NotificationService target (Pods-defaults-NotificationService-checkManifestLockResult.txt). The outputPaths should reference the RocketChatRN target instead:

"$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt"

This configuration mismatch will cause incorrect dependency tracking between build phases and targets.

🤖 Prompt for AI Agents
In ios/RocketChatRN.xcodeproj/project.pbxproj around lines 1552 to 1568 the
ShellScript build phase lists the RocketChatRN frameworks script as an input but
the outputPaths entry points to the NotificationService target
checkManifestLockResult file; replace the incorrect output path with the
RocketChatRN variant so the outputPaths becomes the RocketChatRN
checkManifestLockResult file (i.e. update
"$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt"
to "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt")
to align the input and output target for proper dependency tracking.

Comment on lines 2487 to 2488
"$(SRCROOT)/../node_modules/react-native-mmkv-storage/ios/**",
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if MMKV storage is properly installed
if [ -d "node_modules/react-native-mmkv-storage/ios" ]; then
  echo "✓ MMKV storage iOS directory exists"
  echo "Directory contents:"
  ls -la node_modules/react-native-mmkv-storage/ios/
else
  echo "✗ MMKV storage iOS directory not found"
  echo "Checking if node_modules exists:"
  ls -la node_modules/ | head -20
fi

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 227


🏁 Script executed:

# Check if the dependency is listed in package.json
grep -A 5 "mmkv" package.json || echo "mmkv not found in package.json"

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 306


🏁 Script executed:

# Check all the referenced lines in the pbxproj file to ensure they all reference MMKV storage
grep -n "react-native-mmkv-storage" ios/RocketChatRN.xcodeproj/project.pbxproj

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 515


Fix package name mismatch in header search paths.

The iOS build configuration references react-native-mmkv-storage/ios/**, but package.json declares the dependency as react-native-mmkv (3.3.3). The path in the build configuration must match the actual package name, otherwise header search will fail during iOS build. Update all six occurrences at lines 2487, 2553, 2632, 2709, 3064, and 3128 to reference the correct package path.

🤖 Prompt for AI Agents
In ios/RocketChatRN.xcodeproj/project.pbxproj around lines 2487, 2553, 2632,
2709, 3064, and 3128, the header search paths reference the incorrect package
folder "react-native-mmkv-storage/ios/**"; update each occurrence to the correct
package path "react-native-mmkv/ios/**" so the header search paths match
package.json (react-native-mmkv v3.3.3) and the iOS build can locate headers.

Comment on lines +3226 to +3271
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unnecessary empty space entry in OTHER_LDFLAGS.

The OTHER_LDFLAGS configuration includes an empty space string " " which serves no purpose and could potentially cause unexpected behavior during the linking phase.

Apply this change to remove the empty entry:

 OTHER_LDFLAGS = (
     "$(inherited)",
-    " ",
 );

Also applies to: 3293-3296

🤖 Prompt for AI Agents
In ios/RocketChatRN.xcodeproj/project.pbxproj around lines 3226-3229 and
3293-3296, the OTHER_LDFLAGS arrays contain an unnecessary empty string entry ("
"); remove that empty entry from both locations so the arrays only include
"$(inherited)" (keeping commas/format consistent with surrounding entries) to
avoid potential linker issues.

@diegolmello diegolmello had a problem deploying to upload_experimental_android December 16, 2025 16:24 — with GitHub Actions Error
@diegolmello diegolmello had a problem deploying to upload_experimental_android December 16, 2025 17:31 — with GitHub Actions Error
@diegolmello diegolmello had a problem deploying to upload_experimental_android December 16, 2025 18:53 — with GitHub Actions Error
@diegolmello diegolmello had a problem deploying to upload_experimental_android December 16, 2025 20:52 — with GitHub Actions Error
@diegolmello diegolmello temporarily deployed to experimental_android_build December 18, 2025 13:50 — with GitHub Actions Inactive
@diegolmello diegolmello requested a deployment to official_android_build December 18, 2025 13:50 — with GitHub Actions Waiting
@diegolmello diegolmello temporarily deployed to experimental_ios_build December 18, 2025 13:50 — with GitHub Actions Inactive
@diegolmello diegolmello requested a deployment to upload_experimental_android December 18, 2025 14:24 — with GitHub Actions Waiting
@github-actions
Copy link

Android Build Available

Rocket.Chat Experimental 4.68.0.107907

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNSTcBau19k3HjGPeJbjQWsuDBrCXF59dw38LeTFPmfXBjrL-U71NYfZGw3qx2c3h2T9Q08prtt5akS4-lfp

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.68.0.107909

Copy link
Contributor

@OtavioStasiak OtavioStasiak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incoming call notification is not being displayed on iOS and Android when the app is in background.

version tested:
iOS - Rocket.Chat Experimental 4.68.0.107909
android - Rocket.Chat Experimental 4.68.0.107907

Copy link
Contributor

@OtavioStasiak OtavioStasiak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants