fix(messaging, android): ignore non-message broadcasts in messaging receiver#9105
Conversation
…eceiver ReactNativeFirebaseMessagingReceiver listens on the raw com.google.android.c2dm.intent.RECEIVE channel and converts every broadcast into a RemoteMessage emitted to JS. That channel also carries control broadcasts (deleted_messages, send_event, send_error) and, since firebase-android-sdk#5410, the wrapped NOTIFICATION_DISMISS analytics intent that FCM attaches as deleteIntent to notifications it displays itself - so every notification swipe surfaces an empty message to JS. Reproduce the two entry guards of the official FirebaseMessagingService routing: ignore broadcasts with a non-gcm message_type, and ignore broadcasts without a google.message_id (every FCM-delivered message carries one, server-stamped). Fixes invertase#8040
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses an issue where the Android messaging receiver incorrectly processes non-message broadcasts as valid FCM messages. By introducing routing guards that verify the message type and the presence of a message ID, the change ensures that only legitimate FCM messages are surfaced to the JavaScript layer, significantly reducing noise and error logs in production environments. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces routing guards to ReactNativeFirebaseMessagingReceiver to ignore non-message intents and control broadcasts, aligning its behavior with the official FirebaseMessagingService. The feedback suggests using intent.getStringExtra(key) instead of chaining intent.getExtras().getString(key) to make the code more idiomatic and concise.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Address review feedback: intent.getStringExtra(key) is the idiomatic form and equivalent to intent.getExtras().getString(key). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikehardy
left a comment
There was a problem hiding this comment.
interesting - this looks valid to me - thanks for taking the time to post a PR - I appreciate it
CI chewing on it now
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #9105 +/- ##
============================================
- Coverage 65.11% 65.11% -0.00%
+ Complexity 1833 1832 -1
============================================
Files 503 503
Lines 39180 39187 +7
Branches 5807 5745 -62
============================================
+ Hits 25510 25512 +2
+ Misses 12235 12200 -35
- Partials 1435 1475 +40
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Description
ReactNativeFirebaseMessagingReceiverlistens on the rawcom.google.android.c2dm.intent.RECEIVEaction and blindly converts every broadcast it receives into aRemoteMessageemitted to JS (onMessageReceived/ background handler). That channel is not message-only, and this produces phantom empty messages in JS.Root cause (verified in both SDKs' sources). The official
FirebaseMessagingService(handleMessageIntent) applies two routing guards before ever callingonMessageReceived:message_typepresent and ≠"gcm"→ routed to dedicated callbacks (onDeletedMessages,onMessageSent,onSendError) — these are control broadcasts, not messages;google.message_id.The RNFB receiver has neither guard. The most visible consequence: since firebase/firebase-android-sdk#5410 (firebase-messaging 23.3.0+, late 2023), when FCM displays a notification itself, it attaches a
NOTIFICATION_DISMISSanalytics intent asdeleteIntent, wrapped as an implicit package-scoped broadcast on that same c2dm action. So every notification swipe-to-dismiss fabricates an emptyRemoteMessage(nomessageId, nodata, nonotification) delivered to the app's JS message handlers. This is exactly what #8040 reported (closed stale without a fix).Real-world impact: in our production app (~25k Android devices), these phantom messages hit our push payload validation and accounted for ~600k error logs / 30 days — ~23% of all app error logs — drowning real signal.
Fix. Reproduce the two entry guards of the official routing at the top of
onReceive:message_typepresent and ≠"gcm"→ ignore;google.message_idextra → ignore (not an FCM-delivered message).The guards are safe by construction: every FCM-delivered message (data-only, notification, high/normal priority, console tests) carries
google.message_id. The official pipeline (notification display, dismiss analytics,onDeletedMessages) is untouched — it flows through the Firebase SDK's ownFirebaseInstanceIdReceiver, which keeps receiving the same broadcasts.Related issues
Release Summary
fix(messaging, android): ignore non-message c2dm broadcasts (control broadcasts, notification-dismiss analytics intents) instead of emitting them to JS as empty messages
Checklist
AndroidiOSOther(macOS, web)e2etests added or updated inpackages/\*\*/e2ejesttests added or updated inpackages/\*\*/__tests__Test Plan
No automated tests added: the package has no Android unit-test harness, and the e2e suite has no mechanism to inject
com.google.android.c2dm.intent.RECEIVEbroadcasts (the dismissdeleteIntentis fired by the OS notification tray, which Detox cannot drive). Happy to add coverage if you can point me at a preferred approach.Manual repro (matches #8040):
onMessageReceivedfires with an emptyRemoteMessage(nomessageId, nodata, nonotification). After: nothing is emitted, and logcat showsbroadcast ignored, no google.message_id (not an FCM message).Field evidence — we ship this exact change as a
patch-packagepatch on@react-native-firebase/messaging@25.1.0in our app; verified on real devices:google.message_id.onDeletedMessagesstill works: the Firebase SDK's ownFirebaseInstanceIdReceiverreceives the same broadcasts and routesdeleted_messagesthroughFirebaseMessagingService, which RNFB subclasses.🔥