Skip to content

chore: notification inbox public apis#566

Open
mrehan27 wants to merge 6 commits intofeature/message-inbox-mvpfrom
mbl-1550-notification-inbox
Open

chore: notification inbox public apis#566
mrehan27 wants to merge 6 commits intofeature/message-inbox-mvpfrom
mbl-1550-notification-inbox

Conversation

@mrehan27
Copy link
Contributor

@mrehan27 mrehan27 commented Feb 19, 2026

closes: MBL-1550

Summary

Implements Notification Inbox feature for React Native SDK, enabling customers to display and manage inbox messages. Includes TypeScript APIs, Android/iOS native implementations with real time change listeners, and native SDK topic filtering support.

Changes

  • Added NotificationInbox class in TypeScript with methods for managing inbox messages
  • Added InboxMessage and NotificationInboxChangeListener type definitions
  • Created subscribeToUpdates() method with EventEmitter for real time message updates
  • Implemented client side case-insensitive topic filtering
  • Added getMessages(), markMessageOpened(), markMessageUnopened(), markMessageDeleted(), and trackMessageClicked() methods
  • Extended TurboModule spec with inbox methods and subscribeToMessagesChanged EventEmitter
  • Implemented Android native inbox methods in NativeMessagingInAppModule
  • Created ReactNotificationInboxChangeListener singleton for Android with thread safe listener setup
  • Added NotificationInboxExtensions.kt with InboxMessage.toWritableMap() conversion helper
  • Implemented iOS native inbox methods in NativeMessagingInApp.swift
  • Addded ReactNotificationInboxChangeListener.swift to listen to notification inbox changes
  • Added InboxMessage+Conversion.swift extension for converting inbox messages to dictionaries
  • Updated Objective-C bridge to support new inbox methods
  • Implemented lazy listener setup triggered by first subscribeToUpdates() call

Notes

  • We are currently using feature branches for native SDKs and will revert to use live versions once available before pushing React Native to production

Note

Medium Risk
Introduces new cross-platform TurboModule methods and event emitters plus native listener lifecycle/serialization logic; bugs could surface as runtime bridge errors or missed/duplicated inbox updates.

Overview
Adds a new Notification Inbox public API to the React Native in-app module via CustomerIO.inAppMessaging.inbox(), including typed InboxMessage/NotificationInboxChangeListener and a NotificationInbox wrapper that can fetch messages, update read/deleted state, track clicks, and subscribe to real-time updates (with optional client-side topic filtering).

Extends the TurboModule spec and implements the new inbox bridge on Android and iOS, including lazy, one-time setup/teardown of native change listeners that emit subscribeToMessagesChanged events and message serialization helpers for RN interop (recursive map/array conversion and InboxMessage mapping). Also switches the sample/native dependency pins to a message-inbox feature snapshot/branch for testing.

Written by Cursor Bugbot for commit 2cba428. This will update automatically on new commits. Configure here.

@mrehan27 mrehan27 self-assigned this Feb 19, 2026
@mrehan27 mrehan27 requested a review from a team as a code owner February 19, 2026 17:53
@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

Sample app builds 📱

Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.


customerio.reactnative.targetSdkVersion=36
customerio.reactnative.minSdkVersion=21
customerio.reactnative.cioSDKVersionAndroid=4.15.2
customerio.reactnative.cioSDKVersionAndroid=feature-message-inbox-mvp-SNAPSHOT
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO: Update this before pushing live

# pod "customerio-reactnative/#{push_provider}", :path => cio_package_path
# install_non_production_ios_sdk_local_path(local_path: '~/code/customerio-ios/', is_app_extension: false, push_service: push_provider)
# install_non_production_ios_sdk_git_branch(branch_name: 'feature/wrappers-inline-support', is_app_extension: false, push_service: push_provider)
install_non_production_ios_sdk_git_branch(branch_name: 'feature/message-inbox-mvp', is_app_extension: false, push_service: push_provider)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO: Update this before pushing live

// Set flag after successful setup (allows retry if setup was called before SDK initialized)
self.isInboxChangeListenerSetup = true
}
}
Copy link

Choose a reason for hiding this comment

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

iOS listener setup has race condition with async flag

Medium Severity

setupInboxChangeListener checks isInboxChangeListenerSetup synchronously but sets it to true inside an asynchronous Task { @MainActor in }. If called twice before the first Task executes, both calls pass the guard and both add a listener, resulting in duplicate listeners and duplicate event emissions. The same pattern in clearInboxChangeListener means cleanup called before the setup Task runs will no-op, leaking the listener. The Android implementation correctly avoids this by using synchronized to make the check-and-set atomic.

Additional Locations (1)

Fix in Cursor Fix in Web

// Reset flag after cleanup completes
self.isInboxChangeListenerSetup = false
}
}
Copy link

Choose a reason for hiding this comment

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

iOS inbox listener cleanup has race condition

Medium Severity

The clearInboxChangeListener method has a race condition where the guard check on isInboxChangeListenerSetup happens on the calling thread, but the flag reset occurs asynchronously inside a Task on MainActor. If clearInboxChangeListener and setupInboxChangeListener are called concurrently during module invalidation and reinitialization, the flag state can become inconsistent, potentially leaving listeners registered but marked as unregistered, or blocking future listener setups.

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

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.

1 participant

Comments