Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JS Android Notifications #1214

Merged
merged 14 commits into from
Nov 27, 2024
Merged

Conversation

alexrisch
Copy link
Contributor

@alexrisch alexrisch commented Nov 18, 2024

WIP
Need to figure out background notifications, they are getting overridden somewhere and not handled by notifee

Summary by CodeRabbit

  • New Features

    • Introduced a structured notification permission handling system, including a new type for permission status.
    • Added functions for managing group message notifications and background notifications.
    • Implemented a utility for handling foreground notifications and user interactions with notifications.
    • Enhanced notification subscription management with new utilities for subscribing and unsubscribing.
    • Introduced functions for computing spam scores for group messages and welcome notifications.
    • Added a function to manage previously shown notifications to prevent duplicates.
    • Added a new function to retrieve the current notification permission status.
  • Bug Fixes

    • Improved error handling in various notification-related functions, ensuring better logging and tracking.
  • Documentation

    • Updated import paths for notification utilities to a more organized structure.
  • Chores

    • Updated dependencies in package.json for better functionality and compatibility.
  • Refactor

    • Refactored components and hooks to streamline notification permission requests and handling.

Copy link
Contributor

github-actions bot commented Nov 18, 2024

Performance Comparison Report

  • Current: 340c9e3 - 2024-11-21 15:28:43Z
  • Baseline: release/3.0.0 (fb58aa4) - 2024-11-21 15:27:38Z

Significant Changes To Duration

There are no entries

Meaningless Changes To Duration

Show entries
Name Type Duration Count
Avatar Image 10 runs render 1.0 ms → 1.4 ms (+0.4 ms, +40.0%) 🔴 1 → 1
Avatar Image 50 runs render 1.1 ms → 1.0 ms (-0.0 ms, -3.7%) 1 → 1
Empty Avatar 10 runs render 0.8 ms → 1.0 ms (+0.2 ms, +25.0%) 🔴 1 → 1
Empty Avatar 50 runs render 0.6 ms → 0.7 ms (+0.1 ms, +16.1%) 🔴 1 → 1
Text Component with color prop - 10 runs render 0.3 ms → 0.0 ms (-0.3 ms, -100.0%) 🟢 1 → 1
Text Component with default props - 10 runs render 0.4 ms → 0.2 ms (-0.2 ms, -50.0%) 🟢 1 → 1
Text Component with translation key - 10 runs render 0.4 ms → 0.3 ms (-0.1 ms, -25.0%) 🟢 1 → 1
Text Component with weight and size - 10 runs render 0.0 ms → 0.1 ms (+0.1 ms, +Infinity%) 🔴 1 → 1
Show details
Name Type Duration Count
Avatar Image 10 runs render Baseline
Mean: 1.0 ms
Stdev: 0.5 ms (47.1%)
Runs: 1 1 1 1 1 1 0 2 1 1
Warmup runs: 2

Current
Mean: 1.4 ms
Stdev: 0.5 ms (36.9%)
Runs: 2 1 1 1 1 1 2 2 2 1
Warmup runs: 2
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Avatar Image 50 runs render Baseline
Mean: 1.1 ms
Stdev: 0.3 ms (31.5%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 2 1 1 2 1 1 2 1 0 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1
Warmup runs: 1

Current
Mean: 1.0 ms
Stdev: 0.3 ms (33.4%)
Runs: 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 2 1 1 1 1 1 1 1 1 2 1 0 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 10 runs render Baseline
Mean: 0.8 ms
Stdev: 0.4 ms (52.7%)
Runs: 1 1 1 1 0 1 0 1 1 1
Warmup runs: 3

Current
Mean: 1.0 ms
Stdev: 0.0 ms (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Warmup runs: 8
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 50 runs render Baseline
Mean: 0.6 ms
Stdev: 0.5 ms (79.1%)
Runs: 1 1 1 1 1 1 0 0 1 0 0 1 0 1 1 0 1 1 1 1 1 1 1 0 1 0 1 1 0 1 0 0 1 1 0 0 1 1 0 1 1 0 1 0 0 1 1 0 0 1
Warmup runs: 1

Current
Mean: 0.7 ms
Stdev: 0.5 ms (63.0%)
Runs: 1 0 1 0 1 0 1 1 1 1 0 1 1 0 1 1 1 1 1 0 1 1 1 1 1 0 1 0 1 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 0 1 0 1 1
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with color prop - 10 runs render Baseline
Mean: 0.3 ms
Stdev: 0.5 ms (161.0%)
Runs: 1 0 1 0 0 0 0 1 0 0
Warmup runs: 1

Current
Mean: 0.0 ms
Stdev: 0.0 ms (NaN%)
Runs: 0 0 0 0 0 0 0 0 0 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with default props - 10 runs render Baseline
Mean: 0.4 ms
Stdev: 0.5 ms (129.1%)
Runs: 0 0 0 1 0 1 0 1 1 0
Warmup runs: 0

Current
Mean: 0.2 ms
Stdev: 0.4 ms (210.8%)
Runs: 1 0 0 0 1 0 0 0 0 0
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with translation key - 10 runs render Baseline
Mean: 0.4 ms
Stdev: 0.5 ms (129.1%)
Runs: 1 1 0 1 0 0 0 0 0 1
Warmup runs: 0

Current
Mean: 0.3 ms
Stdev: 0.5 ms (161.0%)
Runs: 0 1 1 0 1 0 0 0 0 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with weight and size - 10 runs render Baseline
Mean: 0.0 ms
Stdev: 0.0 ms (NaN%)
Runs: 0 0 0 0 0 0 0 0 0 0
Warmup runs: 0

Current
Mean: 0.1 ms
Stdev: 0.3 ms (316.2%)
Runs: 0 0 0 0 0 0 1 0 0 0
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Render Count Changes

There are no entries

Render Issues

There are no entries

Added Scenarios

There are no entries

Removed Scenarios

There are no entries

Generated by 🚫 dangerJS against 667bcae

@alexrisch alexrisch changed the base branch from release/3.0.0 to feature/v3-split November 19, 2024 15:02
@alexrisch alexrisch changed the base branch from feature/v3-split to release/3.0.0 November 21, 2024 14:40
* feat: Emoji Picker Performance Improvements

Moves Emoji Picker to bottom sheet design system
Improves performance for filtering emojis

* Update to use Design System
alexrisch and others added 6 commits November 22, 2024 09:37
WIP
Need to figure out background notifications, they are getting overridden somewhere and not handled by notifee
Bumps SDK to support dual sending
@alexrisch alexrisch force-pushed the ar/android-js-notifications branch from 667bcae to 7d8192c Compare November 22, 2024 14:40
Copy link
Contributor

coderabbitai bot commented Nov 22, 2024

Walkthrough

This pull request introduces significant changes primarily centered around the notification handling system within the application. It includes the addition of new utility functions for managing notifications, the restructuring of existing notification-related imports, and the removal of several files related to previous notification implementations. Notably, new types and functions are introduced to improve notification permission management and background notification processing. The changes aim to enhance the organization and maintainability of the notification feature while ensuring that existing functionalities remain intact.

Changes

File Change Summary
App.tsx New import for notification utilities added: import "./features/notifications/utils";
android/app/src/main/AndroidManifest.xml Commented out two <meta-data> entries and a <service> declaration for push notifications, indicating a shift in handling notifications.
android/app/src/main/java/com/converse/AsyncStorage.kt File deleted; contained functions for asynchronous storage management.
android/app/src/main/java/com/converse/BackendApi.kt File deleted; managed HTTP PUT requests for group invites.
android/app/src/main/java/com/converse/Datatypes.kt File deleted; included data classes for notifications and user profiles.
android/app/src/main/java/com/converse/Keychain.kt File deleted; provided secure keychain storage functionality.
android/app/src/main/java/com/converse/MessageNotification.kt File deleted; handled message notifications customization.
android/app/src/main/java/com/converse/Mmkv.kt File deleted; managed persistent storage with MMKV library.
android/app/src/main/java/com/converse/NotificationParser.kt File deleted; parsed notification payloads.
android/app/src/main/java/com/converse/NotificationUtils.kt File deleted; utility functions for notification management.
android/app/src/main/java/com/converse/Profile.kt File deleted; managed user profile retrieval and updates.
android/app/src/main/java/com/converse/PushNotificationsService.kt File deleted; handled push notifications.
android/app/src/main/java/com/converse/Sentry.kt File deleted; integrated Sentry error tracking.
android/app/src/main/java/com/converse/Spam.kt File deleted; managed spam scoring for messages.
android/app/src/main/java/com/converse/Sqlite.kt File deleted; managed SQLite database connections.
android/app/src/main/java/com/converse/Utils.kt File deleted; contained various utility functions.
android/app/src/main/java/com/converse/xmtp/Client.kt File deleted; managed XMTP client functionality.
android/app/src/main/java/com/converse/xmtp/Conversations.kt File deleted; managed conversation subscriptions.
android/app/src/main/java/com/converse/xmtp/Messages.kt File deleted; handled messaging notifications.
components/AccountSettingsButton.tsx Updated import paths for notification permissions handling.
components/Chat/ChatPlaceholder/GroupChatPlaceholder.tsx Logic updated to improve group creator identification.
components/Chat/ConsentPopup/GroupConsentPopup.tsx Updated access method for groupCreator from the hook.
components/StateHandlers/HydrationStateHandler.tsx Updated import path for loading saved notification messages.
components/StateHandlers/NotificationsStateHandler.tsx Updated import paths for notification reset and status saving functions.
components/XmtpEngine.tsx Updated import path for loading saved notification messages.
data/store/chatStore.ts Updated import paths for zustand storage and notification subscription functions.
data/store/settingsStore.ts Updated import paths for notification subscription functions.
features/notifications/types/Notifications.types.ts New type added: NotificationPermissionStatus.
features/notifications/utils/background/groupMessageNotification.ts New file; handles group message notifications.
features/notifications/utils/background/groupWelcomeNotification.ts New file; checks for group welcome message topics.
features/notifications/utils/background/handleBackgroundNotification.ts New file; processes background notifications.
features/notifications/utils/background/notificationContent.ts New file; handles notification content types.
features/notifications/utils/background/protocolNotification.ts New file; handles protocol notifications.
features/notifications/utils/deleteSubscribedTopics.ts New file; deletes subscribed topics for accounts.
features/notifications/utils/getNotificationsPermissionStatus.ts New file; retrieves current notification permission status.
features/notifications/utils/handleForegroundNotification.ts New file; manages foreground notifications.
features/notifications/utils/index.ts New file; sets up notification handlers for foreground and background notifications.
features/notifications/utils/lastNotifSubscribeByAccount.ts New constant; tracks last notification subscriptions by account.
features/notifications/utils/loadSavedNotificationMessagesToContext.ts New file; loads saved notification messages into context.
features/notifications/utils/onInteractWithNotification.ts New file; handles user interactions with notifications.
features/notifications/utils/requestPushNotificationsPermissions.ts New file; requests permissions for push notifications.
features/notifications/utils/resetNotifications.ts New file; resets notification states.
features/notifications/utils/saveNotificationsStatus.ts New file; saves notifications permission status.
features/notifications/utils/setupAndroidNotificationChannel.ts New file; sets up Android notification channels.
features/notifications/utils/subscribeToNotifications.ts New file; manages user subscriptions to notifications.
features/notifications/utils/subscribedOnceByAccount.ts New constant; tracks subscription status by account.
features/notifications/utils/subscribingByAccount.ts New constant; tracks subscribing status by account.
features/notifications/utils/unsubscribeFromNotifications.ts New file; handles unsubscription from notifications.
hooks/useGroupConsent.ts Updated type declaration and group creator access method.
hooks/useGroupCreator.ts Updated to use useQuery for fetching group creator data.
ios/ConverseNotificationExtension/Xmtp/Conversations.swift Enhanced error handling in conversation functions.
ios/ConverseNotificationExtension/Xmtp/Messages.swift Improved error logging and message processing.
ios/Podfile Updated XMTP dependency version from 0.15.0 to 0.16.2.
package.json Added new dependencies and updated existing ones.
queries/useGroupMembersQuery.ts Updated method for retrieving group members.
queries/useGroupPendingMessages.ts File deleted; custom hook for fetching pending messages.
queries/useGroupQuery.ts Updated method for retrieving group members.
screens/NotificationsScreen.tsx Updated import path for notification permissions handling.
screens/Onboarding/OnboardingNotificationsScreen.tsx Updated import path for notification permissions handling.
screens/Profile.tsx Updated import paths for notification permissions handling.
utils/conversation.ts Updated import path for notification subscription function.
utils/logout/index.tsx Updated import paths for notification management functions.
utils/navigation.ts Updated import path for loading saved notification messages.
utils/notifications.ts File deleted; comprehensive notification management system removed.
utils/xmtpRN/conversations.ts Enhanced asynchronous handling of group data.
utils/xmtpRN/messages.ts Improved group member data handling.
utils/xmtpRN/revoke.ts Updated signer processing in revocation function.
utils/xmtpRN/signIn.ts Updated signer processing in client instantiation.
utils/xmtpRN/signer.ts New file; converts ethers signer to xmtp signer.

Possibly related PRs

  • feat: Notification Subscriptions forV3 #1258: The changes in this PR involve the introduction of notification subscriptions, which directly relate to the new import of notification utilities in the main PR. This indicates a connection in the handling of notifications across the application.

Suggested reviewers

  • thierryskoda

🐇 In a world of notifications bright,
New paths and functions take their flight.
With permissions clear and messages bold,
Our app's new story is ready to be told!
So hop along, let's celebrate,
For every change, we elevate! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Outside diff range and nitpick comments (62)
features/notifications/types/Notifications.types.ts (1)

1-4: Add JSDoc documentation for better type clarity.

The type definition is well-structured and follows good naming conventions. Consider adding JSDoc documentation to explain the purpose and usage of each permission status.

+/**
+ * Represents the possible states of notification permissions.
+ * @property granted - User has explicitly granted permission
+ * @property undetermined - Permission hasn't been requested yet
+ * @property denied - User has explicitly denied permission
+ */
 export type NotificationPermissionStatus =
   | "granted"
   | "undetermined"
   | "denied";
features/notifications/utils/background/groupWelcomeNotification.ts (1)

1-3: Consider adding constants for protocol identifiers

To maintain consistency and make future protocol version updates easier, consider extracting the protocol prefix into a constant.

+const XMTP_MLS_V1_WELCOME_PREFIX = "/xmtp/mls/1/w-";
+
 export const isGroupWelcomeContentTopic = (contentTopic: string) => {
   if (!contentTopic) {
     throw new Error('Content topic cannot be null or undefined');
   }
-  return contentTopic.startsWith("/xmtp/mls/1/w-");
+  return contentTopic.startsWith(XMTP_MLS_V1_WELCOME_PREFIX);
 };
features/notifications/utils/resetNotifications.ts (1)

10-10: Consider extracting the storage key as a constant

The storage key "notifications-badge" is hardcoded. Consider extracting it to a constants file to maintain consistency and prevent typos.

+const NOTIFICATIONS_BADGE_KEY = "notifications-badge";
+
 export const resetNotifications = async (
   timeout: number = 0
 ): Promise<void> => {
   return new Promise((resolve, reject) => {
     setTimeout(async () => {
       try {
         await Notifications.dismissAllNotificationsAsync();
         await Notifications.setBadgeCountAsync(0);
-        mmkv.set("notifications-badge", 0);
+        mmkv.set(NOTIFICATIONS_BADGE_KEY, 0);
         resolve();
       } catch (error) {
         reject(error);
       }
     }, timeout);
   });
 };
features/notifications/utils/getNotificationsPermissionStatus.ts (1)

5-12: Consider caching and retry mechanism for better UX

To improve the user experience and reduce unnecessary permission checks:

  1. Consider caching the permission status with a TTL
  2. Implement a retry mechanism for failed permission checks
  3. Add events to track permission status changes

Would you like me to provide an implementation example with these improvements?

features/notifications/utils/saveNotificationsStatus.ts (1)

1-2: Add type imports for better type safety

Consider importing the NotificationPermissionStatus type to ensure type safety throughout the function.

 import { useAppStore } from "@data/store/appStore";
 import { getNotificationsPermissionStatus } from "./getNotificationsPermissionStatus";
+import { NotificationPermissionStatus } from "../types/Notifications.types";
utils/xmtpRN/signer.ts (1)

1-14: Clarify the relationship between signing and notifications.

This file introduces signing functionality in a PR focused on Android notifications. Consider:

  1. Documenting how this signing utility relates to the notification system
  2. Adding comments explaining the authentication flow for notifications
  3. Ensuring the signing implementation aligns with notification security requirements
features/notifications/utils/setupAndroidNotificationChannel.ts (2)

5-8: Consider enhancing the channel configuration

The channel configuration could benefit from additional properties that are commonly used in Android notification channels, such as description and importance level.

 export const androidChannel = {
   id: "converse-notifications",
   name: "Converse Notifications",
+  description: "Receive notifications from Converse",
+  importance: "default",
 };

16-21: Remove commented code

The commented Expo implementation should be removed as it's no longer needed and could cause confusion.

features/notifications/utils/background/handleBackgroundNotification.ts (2)

16-18: Add return type annotation for clarity

The function's return type should be explicitly defined for better type safety and documentation.

 export const handleBackgroundNotification = async (
   rawBody: string | undefined
-) => {
+): Promise<void> => {

16-37: Consider adding notification persistence mechanism

Given the PR objective mentions background notifications being overridden, consider implementing a notification queue or persistence mechanism to prevent notification loss. This could help track and debug when notifications are being overridden.

Potential approaches:

  1. Store notifications in AsyncStorage before processing
  2. Implement a notification queue with retry mechanism
  3. Add telemetry to track notification lifecycle

Would you like me to provide a detailed implementation for any of these approaches?

features/notifications/utils/background/protocolNotification.ts (1)

11-15: Consider adding additional validation constraints.

While the current schema provides basic validation, consider adding:

  1. Maximum length constraints for contentTopic and message to prevent potential memory issues
  2. A more comprehensive Ethereum address validation that includes checksum verification
 export const ProtocolNotificationSchema = z.object({
   account: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
-  contentTopic: z.string(),
-  message: z.string(),
+  contentTopic: z.string().max(1000),
+  message: z.string().max(10000),
 });
utils/xmtpRN/revoke.ts (1)

Line range hint 1-48: Consider notification persistence implications

Given the PR's context about background notifications being overridden, this installation management could be related. Multiple active installations might affect notification delivery and persistence.

Consider:

  1. Documenting the relationship between installations and notification handling
  2. Adding telemetry to track installation-related notification issues
  3. Implementing a mechanism to verify notification delivery after installation changes
features/notifications/utils/handleForegroundNotification.ts (1)

1-6: Consider adding type imports for better type safety

The imports are well-organized, but consider importing types explicitly for better type safety and code clarity.

+import type { Notification } from "expo-notifications";
 import * as Notifications from "expo-notifications";
queries/useGroupMembersQuery.ts (2)

36-38: Consider implementing multiple addresses support.

The TODO comment indicates that multiple addresses support is pending. This could be a limitation in the current implementation as it only uses the first address from the member's addresses array.

Would you like me to help implement multiple addresses support or create a GitHub issue to track this enhancement?


33-38: Consider caching strategy for notification performance.

Since this query is likely used in the context of notifications (based on PR objectives), consider implementing a caching strategy to optimize notification delivery performance. This could help prevent potential delays in notification processing, especially for large groups.

Some suggestions:

  1. Add a reasonable staleTime to the query to reduce unnecessary refetches
  2. Consider implementing background prefetching for active groups
  3. Add error boundaries to handle failed member fetches gracefully
features/notifications/utils/background/notificationContent.ts (2)

24-25: Remove debug code and implement or remove commented logic.

The console.log statement and commented code should be addressed:

  1. Remove the debug console.log
  2. Either implement or remove the commented referencedMessageId assignment
-    console.log({ replyContent });
-    // referencedMessageId = replyContent.reference;
+    referencedMessageId = replyContent.reference;

13-54: Consider improving type safety and function clarity.

The function has several areas that could be improved:

  1. Return types are inconsistent (string | undefined)
  2. Some code paths implicitly return undefined
  3. The reaction handling logic could be simplified

Consider this refactor:

type NotificationContent = string | undefined;

export const getNotificationContent = async (
  group: GroupWithCodecsType,
  message: DecodedMessageWithCodecsType
): Promise<NotificationContent> => {
  const supportedContentType = !!getMessageContentType(message.contentTypeId);
  if (!supportedContentType) return undefined;

  const contentType = message.contentTypeId;
  const messageContent = message.content();

  const contentHandlers: Record<string, () => Promise<NotificationContent>> = {
    reply: async () => {
      const replyContent = messageContent as ReplyContent;
      return "REPLY";
    },
    text: async () => messageContent as string,
    remoteAttachment: async () => "📎 Media",
    transactionReference: async () => "💸 Transaction",
    reaction: async () => {
      const { action, reference, schema, content } = messageContent as ReactionContent;
      if (action !== "added" || schema !== "unicode") return undefined;
      
      const isFromMe = await isGroupMessageFromMe(group.client, reference);
      if (!isFromMe) return undefined;
      
      return content ? `Reacted ${content} to a message` : "Reacted to a message";
    },
    readReceipt: async () => undefined
  };

  const handler = Object.entries(contentHandlers).find(([type]) => 
    isContentType(type, contentType)
  )?.[1];

  return handler ? handler() : undefined;
};
features/notifications/utils/onInteractWithNotification.ts (2)

27-49: Improve code structure and maintainability.

The current implementation has several areas for improvement:

  1. Complex nested conditions could be simplified
  2. Magic string "group_join_request" should be a constant
  3. Redundant else block with return

Consider this refactoring:

+const NOTIFICATION_TYPES = {
+  GROUP_JOIN_REQUEST: 'group_join_request'
+} as const;
+
+const getPayloadFromTrigger = (trigger: unknown) => {
+  return trigger &&
+    typeof trigger === "object" &&
+    "payload" in trigger
+      ? trigger.payload
+      : undefined;
+};

   if (!notificationData) {
-    const payload =
-      event.notification?.request.trigger &&
-      typeof event.notification.request.trigger === "object" &&
-      "payload" in event.notification.request.trigger
-        ? event.notification.request.trigger.payload
-        : undefined;
+    const payload = getPayloadFromTrigger(event.notification?.request.trigger);
     if (!payload) {
       return;
     }
     const payloadType = payload["type"];
-    if (payloadType === "group_join_request") {
+    if (payloadType === NOTIFICATION_TYPES.GROUP_JOIN_REQUEST) {
       const groupId = payload["groupId"] as string;
       if (typeof groupId === "string") {
         return navigate("Group", {
           topic: getTopicFromGroupId(groupId),
         });
-      } else {
-        return;
       }
+      return;
     }
   }

50-66: Address potential race conditions and improve error handling.

Several concerns in the conversation navigation logic:

  1. Direct store access in event handlers could cause issues if the store isn't initialized
  2. No error handling for navigation failures
  3. Comment suggests a race condition with app loading

Consider these improvements:

  1. Add error boundaries around store access:
   if (conversationTopic) {
+    try {
       useAccountsStore.getState().setCurrentAccount(account, false);
       const conversations = getChatStore(account).getState().conversations;
+    } catch (error) {
+      console.error('Failed to access store:', error);
+      return;
+    }
  1. Add error handling for navigation:
   if (conversations[conversationTopic]) {
+    try {
       navigateToConversation(conversations[conversationTopic]);
+    } catch (error) {
+      console.error('Navigation failed:', error);
+    }
  1. Consider implementing a more robust solution for the app loading race condition:
// Consider implementing a queue system for pending navigations
const pendingNavigations = new Set<string>();
// When app is ready, process all pending navigations
const processPendingNavigations = () => {
  pendingNavigations.forEach(topic => {
    // Process navigation
  });
};
utils/xmtpRN/signIn.ts (1)

Line range hint 20-71: Review error handling for signer conversion

The getXmtpBase64KeyFromSigner function handles various critical operations but might need additional error handling for the new signer conversion step.

Consider wrapping the client creation in a try-catch block:

-  const client = await Client.create(ethersSignerToXmtpSigner(signer), {
+  let client;
+  try {
+    const xmtpSigner = ethersSignerToXmtpSigner(signer);
+    client = await Client.create(xmtpSigner, {
+      ...options,
+      preCreateIdentityCallback,
+      preEnableIdentityCallback,
+      preAuthenticateToInboxCallback,
+    });
+  } catch (error) {
+    logger.error('Failed to create XMTP client:', error);
+    await deleteLibXmtpDatabaseForInboxId(inboxId);
+    throw new Error('Failed to initialize XMTP client');
+  }
screens/NotificationsScreen.tsx (2)

Line range hint 34-43: Consider enhancing Android notification permission handling

Given the PR's focus on Android notifications and the mentioned background notification issues, consider the following improvements:

  1. The current Android permission flow might need additional handling for background notifications specifically
  2. Consider adding a check for background notification permissions separately from general notification permissions

Here's a suggested enhancement:

  onPress={async () => {
    const newStatus = await requestPushNotificationsPermissions();
    if (!newStatus) return;
    if (newStatus === "denied" && Platform.OS === "android") {
+     // Check for background notification permissions specifically
+     const backgroundStatus = await checkBackgroundNotificationPermissions();
+     if (backgroundStatus === "denied") {
+       // Show a dialog explaining the need for background permissions
+       Alert.alert(
+         "Background Notifications",
+         "Please enable background notifications in settings for the best experience",
+         [
+           { text: "Later", style: "cancel" },
+           { text: "Open Settings", onPress: () => Linking.openSettings() }
+         ]
+       );
+       return;
+     }
      Linking.openSettings();
    } else {
      setNotificationsSettings({ showNotificationScreen: false });
    }
    setNotificationsPermissionStatus(newStatus);
  }}

Line range hint 1-90: Consider adding error boundaries for notification permission handling

The notification permission request is a critical user flow. Consider wrapping it in an error boundary to gracefully handle any permission-related failures.

Would you like me to provide an example implementation of an error boundary component specifically for notification permissions?

queries/useGroupQuery.ts (2)

Line range hint 57-59: Address the TODO comment for multiple addresses support

The comment indicates incomplete functionality for handling multiple addresses. This could be a limitation for users with multiple wallet addresses.

Would you like me to help implement multiple address support or create a GitHub issue to track this enhancement?


Line range hint 50-63: Consider caching optimization for member data

The async member fetching and subsequent data transformation could impact performance. Consider implementing a caching strategy for member data to reduce unnecessary fetches.

Suggestions:

  1. Add a timestamp-based cache invalidation
  2. Implement optimistic updates for member changes
  3. Consider using stale-while-revalidate pattern
screens/Onboarding/OnboardingNotificationsScreen.tsx (3)

16-16: Good architectural change moving to feature-based structure!

Moving notification-related code to a dedicated features directory improves code organization and maintainability.

Consider completing the feature isolation by:

  1. Moving notification-related types and constants to the same feature directory
  2. Creating a facade/index file to expose the public API of the notifications feature

Line range hint 71-81: Consider improving Android permission handling

The current implementation has a comment indicating special handling for Android 13's permission behavior, but it might need enhancement:

  1. The code opens settings immediately after a "denied" status
  2. This might interrupt the natural Android 13 permission flow where a second prompt could still appear

Consider this approach:

 if (newStatus === "denied" && Platform.OS === "android") {
-  // Android 13 always show denied first but sometimes
-  // it will still show the popup. If not, go to Settings!
-  Linking.openSettings();
+  // Android 13 might show a second prompt after initial denial
+  const retryStatus = await requestPushNotificationsPermissions();
+  if (retryStatus === "denied") {
+    // Only open settings if we're definitely denied
+    Linking.openSettings();
+  } else if (retryStatus) {
+    setNotificationsSettings({ showNotificationScreen: false });
+    setNotificationsPermissionStatus(retryStatus);
+  }
 } else {
   setNotificationsSettings({ showNotificationScreen: false });
 }

Auth status should be set conditionally based on notification permission result

The review comment is correct. Looking at the implementation:

  1. The setAuthStatus("signedIn") is called in the finally block, which means it will be set even when:

    • The permission request fails (catch block)
    • The user denies permission (newStatus === "denied")
    • The permission status is undefined (if (!newStatus) return)
  2. This behavior is inconsistent with the "Later" button flow, where setAuthStatus("signedIn") is called only after successfully setting showNotificationScreen: false.

The suggested fix to move setAuthStatus("signedIn") inside the success path is the correct approach, as it ensures the auth status is only updated when the notification permission flow completes successfully.

🔗 Analysis chain

Line range hint 82-89: Review error handling flow

The current implementation always sets auth status to "signedIn" in the finally block, even if permission request fails or user denies permission.

Let's verify if this is the intended behavior:

Consider moving the setAuthStatus call inside the success path:

 try {
   const newStatus = await requestPushNotificationsPermissions();
   if (!newStatus) return;
   if (newStatus === "denied" && Platform.OS === "android") {
     Linking.openSettings();
   } else {
     setNotificationsSettings({ showNotificationScreen: false });
   }
   setNotificationsPermissionStatus(newStatus);
+  setAuthStatus("signedIn");
 } catch (error) {
   sentryTrackError(error);
-} finally {
-  setAuthStatus("signedIn");
 }
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other notification permission flows to compare behavior
rg -A 5 "requestPushNotificationsPermissions|setAuthStatus" --type typescript

Length of output: 114


Script:

#!/bin/bash
# Search for notification permission flows and auth status updates with broader file patterns
rg -A 5 "requestPushNotificationsPermissions|setAuthStatus" --type ts --type tsx

Length of output: 110


Script:

#!/bin/bash
# Look for the implementation of requestPushNotificationsPermissions
ast-grep --pattern 'const requestPushNotificationsPermissions = $$$'

Length of output: 1575


Script:

#!/bin/bash
# Search for similar auth status handling patterns
rg -A 5 "authStatus|notificationPermission" --type ts --type tsx

Length of output: 94


Script:

#!/bin/bash
# Try searching with just file extension
rg -A 5 "authStatus|notificationPermission" "*.ts" "*.tsx"

Length of output: 151


Script:

#!/bin/bash
# Look for the file content directly
cat "screens/Onboarding/OnboardingNotificationsScreen.tsx"

Length of output: 3605


Script:

#!/bin/bash
# Search for auth status related code in any text file
rg -t txt -A 5 "setAuthStatus"

Length of output: 179

hooks/useGroupConsent.ts (2)

Line range hint 41-61: Consider improving error handling and state management.

The allowGroup and blockGroup functions handle multiple state updates and protocol interactions without proper error handling. Consider the following improvements:

  1. Add error handling for protocol interactions
  2. Consider using a transaction-like pattern for multiple state updates
  3. Extract the peer status update logic into a separate function

Here's a suggested refactor for better error handling and separation of concerns:

const updatePeerStatuses = async (
  inboxIds: InboxId[],
  status: 'allow' | 'deny',
  options: OnConsentOptions
) => {
  try {
    if (inboxIds.length === 0) return;
    
    await consentToInboxIdsOnProtocol(currentAccount(), inboxIds, status);
    const statusMap = inboxIds.reduce((acc, id) => ({
      ...acc,
      [id]: status === 'allow' ? 'allowed' : 'denied'
    }), {});
    setInboxIdPeerStatus(statusMap);
  } catch (error) {
    console.error(`Failed to update peer statuses: ${error}`);
    throw error;
  }
};

const allowGroup = useCallback(
  async (options: OnConsentOptions) => {
    try {
      await allowGroupMutation();
      setGroupStatus({ [getGroupIdFromTopic(topic).toLowerCase()]: "allowed" });
      
      const inboxIdsToAllow: InboxId[] = [
        ...(options.includeAddedBy && group?.addedByInboxId ? [group.addedByInboxId] : []),
        ...(options.includeCreator && groupCreator ? [groupCreator] : [])
      ];
      
      await updatePeerStatuses(inboxIdsToAllow, 'allow', options);
    } catch (error) {
      console.error(`Failed to allow group: ${error}`);
      throw error;
    }
  },
  [/* ... existing dependencies ... */]
);

Also applies to: 70-90


Line range hint 41-90: Consider implementing rollback mechanism.

The functions perform multiple state updates without a way to rollback if one operation fails. This could leave the application in an inconsistent state.

Consider implementing a transaction-like pattern or maintaining a queue of operations that can be rolled back if any step fails. This would help maintain data consistency across the application.

utils/navigation.ts (2)

Line range hint 43-57: Consider performance improvements and configuration options.

While the code formatting improves readability, there are two potential improvements:

  1. The retry mechanism with setTimeout could block the UI thread. Consider using requestAnimationFrame for better performance on mobile devices.

  2. The retry attempts (10) and delay (250ms) should be configurable constants rather than hard-coded values.

Here's a suggested improvement:

+const TOPIC_RETRY_ATTEMPTS = 10;
+const TOPIC_RETRY_DELAY = 250;

 export const navigateToTopicWithRetry = async () => {
   if (!topicToNavigateTo) return;
   
   let conversationToNavigateTo =
     getChatStore(currentAccount()).getState().conversations[topicToNavigateTo];
   let currentAttempt = 0;

   while (
     !conversationToNavigateTo &&
-    currentAttempt < 10 &&
+    currentAttempt < TOPIC_RETRY_ATTEMPTS &&
     topicToNavigateTo
   ) {
     currentAttempt += 1;
-    await new Promise((r) => setTimeout(r, 250));
+    await new Promise((resolve) => 
+      requestAnimationFrame(() => setTimeout(resolve, TOPIC_RETRY_DELAY))
+    );
     conversationToNavigateTo =
       getChatStore(currentAccount()).getState().conversations[
         topicToNavigateTo
       ];
   }

Line range hint 34-38: Consider separating background and foreground navigation flows.

Given the PR's focus on fixing background notification issues, the navigateToConversation function should handle background and foreground navigation scenarios differently. Currently, it loads notification messages before every navigation, which might contribute to the notification override issues mentioned in the PR.

Consider:

  1. Adding a parameter to distinguish between background and foreground navigation
  2. Implementing different loading strategies for each scenario
  3. Adding logging to track the navigation source

Example approach:

export const navigateToConversation = async (
  conversation: XmtpConversation,
  source: 'background' | 'foreground' = 'foreground'
) => {
  logger.debug(`[Navigation] Navigating to conversation from ${source}`);
  
  if (source === 'foreground') {
    await loadSavedNotificationMessagesToContext();
  }
  // Add specific handling for background navigation if needed
  
  navigate("Conversation", { topic: conversation.topic });
};
components/Chat/ChatPlaceholder/GroupChatPlaceholder.tsx (1)

Line range hint 52-62: Consider integrating notification setup with chat initialization.

Given that this PR focuses on Android notifications, consider ensuring proper notification setup when initializing group chats.

Suggestions:

  1. Consider checking notification permissions before allowing the "say hi" action
  2. Potentially integrate with the new notification utilities mentioned in the AI summary
  3. Handle cases where notifications might be disabled

Example integration:

import { getNotificationsPermissionStatus } from '@features/notifications/utils';

const handleSend = useCallback(async () => {
  if (!conversation) {
    return;
  }
  
  const notificationStatus = await getNotificationsPermissionStatus();
  if (notificationStatus !== 'granted') {
    // Handle notification permission state
    return;
  }
  
  sendMessage({
    conversation,
    content: "👋",
    contentType: "xmtp.org/text:1.0",
  });
}, [conversation]);
components/StateHandlers/NotificationsStateHandler.tsx (2)

Line range hint 32-32: Consider adding delay configuration for notification reset.

The notification reset delay (500ms) is hardcoded in some places but not others. Consider making this configurable to ensure consistent behavior across different app states.

-resetNotifications(500);
+const NOTIFICATION_RESET_DELAY = 500;
+resetNotifications(NOTIFICATION_RESET_DELAY);

Also applies to: 47-47


Line range hint 45-50: Add error handling for user data save operation.

The user data save operation during app state change lacks error handling, which could affect the notification state if the save operation fails.

-if (userAddress) {
-  saveUser(userAddress, privyAccountId[userAddress] as string);
-}
+if (userAddress) {
+  try {
+    await saveUser(userAddress, privyAccountId[userAddress] as string);
+  } catch (error) {
+    console.error('Failed to save user data:', error);
+    // Consider retrying or handling the error appropriately
+  }
+}
data/store/settingsStore.ts (1)

Line range hint 71-84: Improve notification subscription handling.

The current implementation has several potential issues:

  1. Using setImmediate within setState could lead to race conditions
  2. No error handling for notification subscription
  3. No mechanism to track or debug background notification issues

Consider applying these improvements:

 setPeersStatus: (peersStatus: {
   [peerAddress: string]: "blocked" | "consented";
 }) =>
-  set((state) => {
-    setImmediate(() => {
-      subscribeToNotifications(account);
-    });
+  set(async (state) => {
+    try {
+      await subscribeToNotifications(account);
+    } catch (error) {
+      logger.error('Failed to subscribe to notifications:', error);
+    }
     return {
       peersStatus: {
         ...state.peersStatus,

Consider adding these improvements to better handle background notifications:

  1. Add a flag to track background notification state
  2. Implement retry logic for failed notification subscriptions
  3. Add telemetry to monitor notification delivery success rates

Would you like me to provide a detailed implementation for any of these suggestions?

components/AccountSettingsButton.tsx (3)

Line range hint 89-89: Extract notification permission logic to a helper function.

The TODO comment suggests this logic should be moved to a helper. This would improve reusability and maintain consistency across the app.

Consider creating a new helper function in the notifications feature directory:

+ // features/notifications/utils/handleNotificationPermissions.ts
+ export const handleNotificationPermissions = async (
+   currentStatus: NotificationPermissionStatus,
+   onStatusChange: (status: NotificationPermissionStatus) => void
+ ) => {
+   if (currentStatus === "denied") {
+     if (Platform.OS === "android") {
+       const newStatus = await requestPushNotificationsPermissions();
+       if (newStatus === "denied") {
+         await Linking.openSettings();
+       } else if (newStatus) {
+         onStatusChange(newStatus);
+       }
+     } else {
+       await Linking.openSettings();
+     }
+   } else if (currentStatus === "undetermined") {
+     const newStatus = await requestPushNotificationsPermissions();
+     if (newStatus) {
+       onStatusChange(newStatus);
+     }
+   }
+ };

Line range hint 90-109: Improve error handling and add error boundaries.

The current implementation lacks proper error handling for failed permission requests or settings navigation.

Consider wrapping the async operations in try-catch blocks:

- requestPushNotificationsPermissions().then(
-   (newStatus: NotificationPermissionStatus | undefined) => {
-     if (newStatus === "denied") {
-       Linking.openSettings();
-     } else if (newStatus) {
-       setNotificationsPermissionStatus(newStatus);
-     }
-   }
- );
+ try {
+   const newStatus = await requestPushNotificationsPermissions();
+   if (newStatus === "denied") {
+     await Linking.openSettings();
+   } else if (newStatus) {
+     setNotificationsPermissionStatus(newStatus);
+   }
+ } catch (error) {
+   console.error('Failed to handle notification permissions:', error);
+   // Consider showing a user-friendly error message
+ }

Line range hint 90-116: Consider adding platform-specific notification channels for Android.

Android notifications typically require channel configuration for proper categorization and user control.

Consider implementing notification channels for Android:

// Example notification channel configuration
if (Platform.OS === 'android') {
  await notifee.createChannel({
    id: 'default',
    name: 'Default Channel',
    importance: AndroidImportance.HIGH,
  });
}
ios/ConverseNotificationExtension/Xmtp/Conversations.swift (3)

Line range hint 86-90: LGTM! Consider enhancing error context.

The addition of try keyword is correct and necessary for proper error handling. The error is appropriately caught and tracked.

Consider adding more context to the error tracking by including the content topic:

-          sentryTrackError(error: error, extras: ["message": "Could not import topic data in XMTP Client"])
+          sentryTrackError(error: error, extras: [
+            "message": "Could not import topic data in XMTP Client",
+            "contentTopic": contentTopic
+          ])

Line range hint 109-113: LGTM! Track migration timeline.

The error handling improvement is correct. However, since this is a migration path marked with "TODO => remove this a bit later", it would be helpful to track when this legacy code can be safely removed.

Would you like me to create a GitHub issue to track the removal of this legacy keychain-based persistence code? This would help ensure the cleanup is not forgotten after the migration period.


Line range hint 86-113: Monitor storage migration impact on notifications.

Since this code is critical for background notifications and is undergoing a storage migration (keychain to MMKV), it's important to monitor any potential impact on notification delivery and performance.

Consider:

  1. Adding telemetry to track which storage path is being used (MMKV vs keychain)
  2. Monitoring error rates during the migration period
  3. Setting up alerts for increased error rates or notification delivery issues

This will help ensure a smooth migration and quickly identify any notification-related issues.

utils/logout/index.tsx (3)

Line range hint 102-104: Add error handling for notification unsubscription

The notification unsubscription is called without error handling. If it fails, the error will be silently ignored and the task will be removed anyway.

-      if (task.apiHeaders) {
-        unsubscribeFromNotifications(task.apiHeaders);
-      }
+      if (task.apiHeaders) {
+        try {
+          await unsubscribeFromNotifications(task.apiHeaders);
+        } catch (error) {
+          logger.error(error, {
+            context: `[Logout] Failed to unsubscribe from notifications for ${account}`,
+          });
+          throw error; // Propagate error to prevent task removal
+        }
+      }

Line range hint 223-225: Consider reordering cleanup operations

The notification cleanup operations are scattered. Consider grouping them together and executing them in a specific order to prevent race conditions:

  1. Unsubscribe from notifications
  2. Delete subscribed topics
  3. Clear notification tracking state
-  deleteXmtpClient(account);
-  deleteSubscribedTopics(account);
-  delete importedTopicsDataForAccount[account];
-  delete secureMmkvByAccount[account];
-  delete lastNotifSubscribeByAccount[account];
+  // Clean up notifications first
+  deleteSubscribedTopics(account);
+  delete lastNotifSubscribeByAccount[account];
+
+  // Then clean up XMTP and other data
+  deleteXmtpClient(account);
+  delete importedTopicsDataForAccount[account];
+  delete secureMmkvByAccount[account];

Line range hint 1-255: Consider extracting notification cleanup logic

The file currently handles both logout and notification cleanup responsibilities. Consider extracting the notification cleanup logic into a separate module (e.g., features/notifications/utils/cleanup.ts) to improve modularity and make the code more maintainable.

This would also make it easier to test and modify notification-related functionality independently of the logout flow.

ios/ConverseNotificationExtension/Xmtp/Messages.swift (3)

289-291: LGTM! Consider adding group ID to the breadcrumb.

The breadcrumb addition for group syncing improves observability. Consider including the group ID in the breadcrumb message for better debugging context.

-        sentryAddBreadcrumb(message: "[NotificationExtension] Syncing Group")
+        sentryAddBreadcrumb(message: "[NotificationExtension] Syncing Group \(group.id)")

293-295: LGTM! Consider adding message metadata to successful decode breadcrumb.

The breadcrumbs effectively track the message decoding flow. For better debugging, consider adding basic message metadata (e.g., message ID) to the success breadcrumb.

-        sentryAddBreadcrumb(message:"[NotificationExtension] Group message decoded!")
+        sentryAddBreadcrumb(message:"[NotificationExtension] Group message decoded! ID: \(decodedMessage.id)")

299-299: Consider consolidating error tracking approach.

The code currently uses both sentryTrackMessage and sentryAddBreadcrumb for error tracking. Consider using a consistent approach for error tracking to maintain uniformity across the codebase.

-        sentryTrackMessage(message: "NOTIFICATION_DECODING_ERROR", extras: ["error": error, "envelope": envelope])
-        sentryAddBreadcrumb(message: "[NotificationExtension] ERROR WHILE DECODING \(error)")
+        sentryTrackMessage(message: "NOTIFICATION_DECODING_ERROR", extras: [
+          "error": error,
+          "envelope": envelope,
+          "errorMessage": error.localizedDescription
+        ])
screens/Profile.tsx (2)

87-88: Consider co-locating related imports.

The notification-related imports could be grouped with other feature-related imports for better organization and maintainability.

Consider moving these imports near other feature imports around line 77:

 import { ExternalWalletPicker } from "../features/ExternalWalletPicker/ExternalWalletPicker";
 import { ExternalWalletPickerContextProvider } from "../features/ExternalWalletPicker/ExternalWalletPicker.context";
+import { requestPushNotificationsPermissions } from "../features/notifications/utils/requestPushNotificationsPermissions";
+import { NotificationPermissionStatus } from "../features/notifications/types/Notifications.types";

Line range hint 1-924: Consider breaking down the ProfileScreenImpl component.

The ProfileScreenImpl component is quite large and handles multiple responsibilities. Consider breaking it down into smaller, more focused components for better maintainability and testing.

Suggested structure:

// components/Profile/ProfileHeader.tsx
const ProfileHeader = ({ socials, peerAddress, shouldShowError }) => {
  // Avatar, title, and error display logic
};

// components/Profile/ProfileActions.tsx
const ProfileActions = ({ isMyProfile, notificationsPermissionStatus, ...props }) => {
  // Action items logic
};

// components/Profile/ProfileSocials.tsx
const ProfileSocials = ({ socials }) => {
  // Social items logic
};

// screens/Profile.tsx
function ProfileScreenImpl() {
  // Core logic and composition of smaller components
  return (
    <ScrollView>
      <ProfileHeader {...headerProps} />
      <ProfileActions {...actionProps} />
      <ProfileSocials {...socialProps} />
    </ScrollView>
  );
}
data/store/chatStore.ts (2)

Line range hint 365-476: Consider refactoring setMessages to improve separation of concerns.

The function currently handles multiple responsibilities and contains scattered side effects. This could lead to maintenance challenges and potential race conditions.

Consider splitting the function into smaller, focused functions:

- setMessages: (messagesToSet) =>
+ async setMessages: (messagesToSet) =>
   set((state) => {
-    let shouldResubscribe = false;
-    let shouldRefreshBalance = false;
+    const effects = {
+      shouldResubscribe: false,
+      shouldRefreshBalance: false,
+      conversationsToHandleSpamScore: {},
+    };
     // ... message processing logic ...
-    if (shouldResubscribe) {
-      setImmediate(() => {
-        subscribeToNotifications(account);
-      });
-    }
-    if (shouldRefreshBalance) {
-      refreshBalanceForAccount(account);
-    }
+    // Handle side effects after state update
+    handleMessageEffects(effects);
     return newState;
   }),
+
+ const handleMessageEffects = async (effects) => {
+   if (effects.shouldResubscribe) {
+     await subscribeToNotifications(account);
+   }
+   if (effects.shouldRefreshBalance) {
+     await refreshBalanceForAccount(account);
+   }
+   if (Object.keys(effects.conversationsToHandleSpamScore).length > 0) {
+     await computeConversationsSpamScores(
+       account,
+       Object.values(effects.conversationsToHandleSpamScore)
+     );
+   }
+ };

Line range hint 731-773: Enhance error handling for critical operations.

The current implementation lacks robust error handling for critical state operations. This could lead to inconsistent state or silent failures.

Consider adding error boundaries and retry mechanisms:

 migrate: (persistedState: any, version: number): ChatStoreType => {
+  const MAX_RETRIES = 3;
+  const migrate = async (attempt = 1) => {
     try {
       logger.debug("Zustand migration version:", version);
       // Migration logic...
+      return persistedState as ChatStoreType;
     } catch (error) {
-      return persistedState as ChatStoreType;
+      if (attempt < MAX_RETRIES) {
+        logger.warn(`Migration attempt ${attempt} failed, retrying...`);
+        return migrate(attempt + 1);
+      }
+      logger.error('Migration failed after max retries:', error);
+      throw error;
     }
+  };
+  return migrate();
 }
features/notifications/utils/background/groupMessageNotification.ts (2)

18-24: Enhance error handling when the group is not found

Currently, if the group is not found after syncing, the function throws a generic error. Consider providing a more descriptive error message or handling the error gracefully to improve debuggability and user experience.

Apply this diff to improve error handling:

 if (!group) {
   await xmtpClient.conversations.syncGroups();
   group = await xmtpClient.conversations.findGroup(groupId);
-  if (!group) throw new Error("Group not found");
+  if (!group) {
+    console.error(`Group with ID ${groupId} not found after syncing.`);
+    return;
+  }
 }

29-29: Address the TODO comment regarding 'inboxId'

There is a @todo comment suggesting that inboxId should become a first-class citizen. Consider prioritizing this enhancement to improve the clarity and reliability of user identification throughout the codebase.

Would you like assistance in creating a plan to make inboxId a first-class citizen? I can help draft an implementation strategy or open a GitHub issue to track this task.

features/notifications/utils/loadSavedNotificationMessagesToContext.ts (5)

15-19: Consider refactoring waitForLoadingSavedNotifications to avoid recursion

The recursive implementation of waitForLoadingSavedNotifications could lead to a stack overflow if the loading takes a very long time. Refactoring it to use a loop can improve efficiency and prevent potential issues.

Apply this diff to refactor the function:

-const waitForLoadingSavedNotifications = async () => {
-  if (!loadingSavedNotifications) return;
-  await new Promise((r) => setTimeout(r, 100));
-  await waitForLoadingSavedNotifications();
-};
+const waitForLoadingSavedNotifications = async () => {
+  while (loadingSavedNotifications) {
+    await new Promise((r) => setTimeout(r, 100));
+  }
+};

85-85: Use specific types instead of any for better type safety

Using any reduces type safety and can lead to runtime errors. Define specific interfaces for message to enhance code reliability and maintainability.

Define an interface for messages:

interface NotificationMessage {
  account: string;
  id: string;
  senderAddress: string;
  sent: number;
  content: string;
  contentType?: string;
  topic: string;
  referencedMessageId?: string;
}

Then update the code:

-messages.forEach((message: any) => {
+messages.forEach((message: NotificationMessage) => {

43-46: Simplify the complex conditional statement for better readability

The condition in the if statement is complex and can be simplified to improve readability.

Simplify the condition using optional chaining and nullish coalescing:

-if (
-  c.context &&
-  (c.context.conversationId ||
-    (c.context.metadata && Object.keys(c.context.metadata).length > 0))
-) {
+if (c.context && (c.context.conversationId || Object.keys(c.context.metadata ?? {}).length > 0)) {

119-119: Typo in error message: 'occurred' is misspelled

Correct the spelling of 'occurred' in the error message to maintain professionalism.

Apply this diff to fix the typo:

-  error: "An error occured while loading saved notifications",
+  error: "An error occurred while loading saved notifications",

114-116: Avoid code duplication by abstracting reset logic

The code for resetting loadingSavedNotifications and clearing notifications is duplicated in both try and catch blocks. Consider extracting this into a separate function to follow the DRY (Don't Repeat Yourself) principle.

Define a reset function:

const resetLoadingState = () => {
  emptySavedNotificationsConversations();
  emptySavedNotificationsMessages();
  loadingSavedNotifications = false;
};

Then update the code:

In the try block:

-    emptySavedNotificationsConversations();
-    emptySavedNotificationsMessages();
-    loadingSavedNotifications = false;
+    resetLoadingState();

In the catch block:

-    emptySavedNotificationsConversations();
-    emptySavedNotificationsMessages();
-    loadingSavedNotifications = false;
+    resetLoadingState();

Also applies to: 123-125

features/notifications/utils/subscribeToNotifications.ts (1)

93-94: Optimize update checks in needToUpdateConversationSubscription

Currently, the update flag is set to true unconditionally, which may cause unnecessary processing and network requests. Consider using the original conditional check period !== c.lastNotificationsSubscribedPeriod to update only when the subscription period has changed.

Apply this diff to restore the conditional update:

             // update: period !== c.lastNotificationsSubscribedPeriod,
-            update: true,
+            update: period !== c.lastNotificationsSubscribedPeriod,
utils/xmtpRN/conversations.ts (3)

84-84: Offer assistance for multiple addresses support

There's a TODO comment indicating the need to support multiple addresses:

// TODO: Multiple addresses support

Would you like assistance in implementing this functionality or creating a plan to address it?


226-226: Ensure error handling when awaiting 'protocolGroupToStateConversation'

When calling await protocolGroupToStateConversation, consider adding error handling to manage potential promise rejections and prevent unhandled exceptions.


423-427: Add error handling for asynchronous operations

The await calls inside Promise.all may fail if any promise is rejected. Consider wrapping the asynchronous operations in a try-catch block or handling rejections to ensure errors are managed appropriately.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 07b66ae and 0a48bea.

⛔ 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 (69)
  • App.tsx (1 hunks)
  • android/app/src/main/AndroidManifest.xml (2 hunks)
  • android/app/src/main/java/com/converse/AsyncStorage.kt (0 hunks)
  • android/app/src/main/java/com/converse/BackendApi.kt (0 hunks)
  • android/app/src/main/java/com/converse/Datatypes.kt (0 hunks)
  • android/app/src/main/java/com/converse/Keychain.kt (0 hunks)
  • android/app/src/main/java/com/converse/MessageNotification.kt (0 hunks)
  • android/app/src/main/java/com/converse/Mmkv.kt (0 hunks)
  • android/app/src/main/java/com/converse/NotificationParser.kt (0 hunks)
  • android/app/src/main/java/com/converse/NotificationUtils.kt (0 hunks)
  • android/app/src/main/java/com/converse/Profile.kt (0 hunks)
  • android/app/src/main/java/com/converse/PushNotificationsService.kt (0 hunks)
  • android/app/src/main/java/com/converse/Sentry.kt (0 hunks)
  • android/app/src/main/java/com/converse/Spam.kt (0 hunks)
  • android/app/src/main/java/com/converse/Sqlite.kt (0 hunks)
  • android/app/src/main/java/com/converse/Utils.kt (0 hunks)
  • android/app/src/main/java/com/converse/xmtp/Client.kt (0 hunks)
  • android/app/src/main/java/com/converse/xmtp/Conversations.kt (0 hunks)
  • android/app/src/main/java/com/converse/xmtp/Messages.kt (0 hunks)
  • components/AccountSettingsButton.tsx (1 hunks)
  • components/Chat/ChatPlaceholder/GroupChatPlaceholder.tsx (1 hunks)
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx (1 hunks)
  • components/StateHandlers/HydrationStateHandler.tsx (1 hunks)
  • components/StateHandlers/NotificationsStateHandler.tsx (1 hunks)
  • components/XmtpEngine.tsx (1 hunks)
  • data/store/chatStore.ts (1 hunks)
  • data/store/settingsStore.ts (1 hunks)
  • features/notifications/types/Notifications.types.ts (1 hunks)
  • features/notifications/utils/background/groupMessageNotification.ts (1 hunks)
  • features/notifications/utils/background/groupWelcomeNotification.ts (1 hunks)
  • features/notifications/utils/background/handleBackgroundNotification.ts (1 hunks)
  • features/notifications/utils/background/notificationContent.ts (1 hunks)
  • features/notifications/utils/background/protocolNotification.ts (1 hunks)
  • features/notifications/utils/deleteSubscribedTopics.ts (1 hunks)
  • features/notifications/utils/getNotificationsPermissionStatus.ts (1 hunks)
  • features/notifications/utils/handleForegroundNotification.ts (1 hunks)
  • features/notifications/utils/index.ts (1 hunks)
  • features/notifications/utils/lastNotifSubscribeByAccount.ts (1 hunks)
  • features/notifications/utils/loadSavedNotificationMessagesToContext.ts (1 hunks)
  • features/notifications/utils/onInteractWithNotification.ts (1 hunks)
  • features/notifications/utils/requestPushNotificationsPermissions.ts (1 hunks)
  • features/notifications/utils/resetNotifications.ts (1 hunks)
  • features/notifications/utils/saveNotificationsStatus.ts (1 hunks)
  • features/notifications/utils/setupAndroidNotificationChannel.ts (1 hunks)
  • features/notifications/utils/subscribeToNotifications.ts (1 hunks)
  • features/notifications/utils/subscribedOnceByAccount.ts (1 hunks)
  • features/notifications/utils/subscribingByAccount.ts (1 hunks)
  • features/notifications/utils/unsubscribeFromNotifications.ts (1 hunks)
  • hooks/useGroupConsent.ts (1 hunks)
  • hooks/useGroupCreator.ts (1 hunks)
  • ios/ConverseNotificationExtension/Xmtp/Conversations.swift (2 hunks)
  • ios/ConverseNotificationExtension/Xmtp/Messages.swift (1 hunks)
  • ios/Podfile (1 hunks)
  • package.json (3 hunks)
  • queries/useGroupMembersQuery.ts (1 hunks)
  • queries/useGroupPendingMessages.ts (0 hunks)
  • queries/useGroupQuery.ts (1 hunks)
  • screens/NotificationsScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingNotificationsScreen.tsx (1 hunks)
  • screens/Profile.tsx (5 hunks)
  • utils/conversation.ts (1 hunks)
  • utils/logout/index.tsx (2 hunks)
  • utils/navigation.ts (3 hunks)
  • utils/notifications.ts (0 hunks)
  • utils/xmtpRN/conversations.ts (10 hunks)
  • utils/xmtpRN/messages.ts (1 hunks)
  • utils/xmtpRN/revoke.ts (2 hunks)
  • utils/xmtpRN/signIn.ts (2 hunks)
  • utils/xmtpRN/signer.ts (1 hunks)
💤 Files with no reviewable changes (19)
  • android/app/src/main/java/com/converse/AsyncStorage.kt
  • android/app/src/main/java/com/converse/BackendApi.kt
  • android/app/src/main/java/com/converse/Datatypes.kt
  • android/app/src/main/java/com/converse/Keychain.kt
  • android/app/src/main/java/com/converse/MessageNotification.kt
  • android/app/src/main/java/com/converse/Mmkv.kt
  • android/app/src/main/java/com/converse/NotificationParser.kt
  • android/app/src/main/java/com/converse/NotificationUtils.kt
  • android/app/src/main/java/com/converse/Profile.kt
  • android/app/src/main/java/com/converse/PushNotificationsService.kt
  • android/app/src/main/java/com/converse/Sentry.kt
  • android/app/src/main/java/com/converse/Spam.kt
  • android/app/src/main/java/com/converse/Sqlite.kt
  • android/app/src/main/java/com/converse/Utils.kt
  • android/app/src/main/java/com/converse/xmtp/Client.kt
  • android/app/src/main/java/com/converse/xmtp/Conversations.kt
  • android/app/src/main/java/com/converse/xmtp/Messages.kt
  • queries/useGroupPendingMessages.ts
  • utils/notifications.ts
✅ Files skipped from review due to trivial changes (7)
  • App.tsx
  • components/StateHandlers/HydrationStateHandler.tsx
  • components/XmtpEngine.tsx
  • features/notifications/utils/lastNotifSubscribeByAccount.ts
  • features/notifications/utils/subscribedOnceByAccount.ts
  • features/notifications/utils/subscribingByAccount.ts
  • utils/conversation.ts
🔇 Additional comments (64)
features/notifications/types/Notifications.types.ts (1)

1-4: Verify consistent usage of permission states.

Let's ensure this type is being used consistently across the notification handling logic.

✅ Verification successful

The NotificationPermissionStatus type is consistently used across the codebase

The verification shows that the NotificationPermissionStatus type with its three states ("granted" | "undetermined" | "denied") is consistently used throughout the codebase:

  • The type is properly imported and used in key notification-related files:
    • requestPushNotificationsPermissions.ts
    • getNotificationsPermissionStatus.ts
    • saveNotificationsStatus.ts
    • subscribeToNotifications.ts
  • The permission states are consistently handled in UI components:
    • AccountSettingsButton.tsx
    • Profile.tsx
    • NotificationsScreen.tsx
    • OnboardingNotificationsScreen.tsx
  • The type is correctly stored in the app state (appStore.ts) and used consistently for permission checks and state management

The implementation shows proper type safety and consistent handling of all three permission states across the notification system.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of NotificationPermissionStatus type and its values

# Check for type imports
echo "Checking NotificationPermissionStatus type usage:"
rg -t typescript "NotificationPermissionStatus" -A 2

# Check for string literal usage to ensure they match the type
echo "\nChecking permission status string usage:"
rg -t typescript '"granted"|"undetermined"|"denied"' --context 3

# Look for potential platform-specific permission handling
echo "\nChecking platform-specific permission handling:"
rg -t typescript "requestPermission|checkPermission|getPermissionStatus" --context 3

Length of output: 624


Script:

#!/bin/bash
# Let's try without typescript file type specification since it's not recognized
# Check for type imports and usage
echo "Checking NotificationPermissionStatus type usage:"
rg "NotificationPermissionStatus" -A 2

# Check for string literal usage to ensure they match the type
echo "\nChecking permission status string usage:"
rg '"granted"|"undetermined"|"denied"' --context 3

# Look for potential platform-specific permission handling
echo "\nChecking platform-specific permission handling:"
rg "requestPermission|checkPermission|getPermissionStatus" --context 3

# Also check for files that might be related to notifications
echo "\nChecking for notification-related files:"
fd "notification" -I

Length of output: 31497

features/notifications/utils/background/groupWelcomeNotification.ts (1)

1-3: Verify XMTP MLS protocol version and usage

Let's verify if this protocol version prefix is used consistently across the codebase and if there are any other related message types we should handle.

✅ Verification successful

XMTP MLS protocol version is consistently used as "/xmtp/mls/1/" across the codebase

The verification shows consistent usage of XMTP MLS protocol version 1 across the codebase with two specific message types:

  • Group messages using prefix /xmtp/mls/1/g-
  • Welcome messages using prefix /xmtp/mls/1/w-

The implementation is consistent across TypeScript/JavaScript and Swift codebases, with proper handling of both message types.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for XMTP MLS protocol version usage
echo "Searching for XMTP MLS protocol version patterns..."
rg -A 2 '"/xmtp/mls/\d+/' 

# Search for related message type handlers
echo "Searching for related message type handlers..."
fd -e ts -e js --exec ast-grep --pattern 'contentTopic.startsWith("/xmtp/mls")'

Length of output: 3582

features/notifications/utils/resetNotifications.ts (2)

1-2: LGTM: Imports look good

The imports are appropriate for the functionality being implemented.


4-12: Verify the impact on background notifications

Given the PR objectives mention issues with background notifications being overridden, we should verify how this reset function interacts with background notifications.

Let's search for background notification handling in the codebase:

✅ Verification successful

Let's gather more context about how background notifications are handled and their interaction with the reset function.


Based on the code analysis, I can now generate the final response:

The notification reset function is safely integrated with background notifications

The reset function is used appropriately in the notification flow:

  • Background notifications (handleBackgroundNotification.ts) operate independently and don't interact with the reset function
  • Reset is only called in foreground scenarios and app state changes:
    • When receiving foreground notifications
    • When app becomes active from background/inactive state
    • When app becomes inactive

The reset function's implementation won't interfere with background notification handling as they operate in separate contexts and the reset is only triggered during active app states.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for background notification handling
echo "Searching for background notification handling..."
rg -l "setBackgroundMessageHandler|onBackgroundMessage|background notification" --type ts

# Search for Notifee usage mentioned in PR description
echo "Searching for Notifee usage..."
rg -l "notifee" --type ts

# Search for other reset notification calls
echo "Searching for resetNotifications usage..."
rg "resetNotifications" --type ts

Length of output: 1554


Script:

#!/bin/bash
# Check background notification handling implementation
echo "Content of background notification handler:"
cat features/notifications/utils/background/handleBackgroundNotification.ts

# Check foreground notification handling to understand the reset usage
echo -e "\nContent of foreground notification handler:"
cat features/notifications/utils/handleForegroundNotification.ts

# Check notification state handler implementation
echo -e "\nContent of notification state handler:"
cat components/StateHandlers/NotificationsStateHandler.tsx

Length of output: 8160

features/notifications/utils/deleteSubscribedTopics.ts (2)

1-2: Consider using a more thread-safe approach for managing subscription state

The direct manipulation of shared subscription objects could lead to race conditions in concurrent scenarios. Consider using a more robust state management solution or implementing proper synchronization mechanisms.

Let's verify the implementation of these imported objects:


1-11: Verify integration with background notification handling

Given the PR's mention of background notification issues, we should verify how this cleanup function integrates with the notification lifecycle, especially during background operations.

Let's check the usage of this function:

✅ Verification successful

Integration with notification handling is properly implemented

The deleteSubscribedTopics function is correctly integrated within the logout flow and doesn't require additional verification with background notifications because:

  • It's used as part of the comprehensive cleanup in the logoutAccount function alongside other notification-related cleanups
  • The background notification handler (handleBackgroundNotification.ts) processes incoming notifications independently and doesn't maintain state that would be affected by topic subscriptions
  • The cleanup happens before the logout tasks are executed, ensuring proper order of operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find where this function is called, particularly in background notification handlers
rg -t typescript "deleteSubscribedTopics\(" -B 5 -A 5

# Check for background notification handlers
ast-grep --pattern 'onBackgroundMessage($$$)' 

Length of output: 138


Script:

#!/bin/bash
# Find where this function is called with different file extensions
rg "deleteSubscribedTopics\(" -B 5 -A 5

# Check for background notification handlers with different patterns
ast-grep --pattern 'onMessage($$$)'
ast-grep --pattern 'messaging.onMessage($$$)'
ast-grep --pattern 'messaging.onBackgroundMessage($$$)'

# Find files related to background notifications
fd -e ts -e js -e tsx -e jsx background

Length of output: 932


Script:

#!/bin/bash
# Check the content of background notification handlers
cat features/notifications/utils/background/handleBackgroundNotification.ts
cat utils/background.tsx

# Check the logout flow where deleteSubscribedTopics is used
cat utils/logout/index.tsx

Length of output: 11067

features/notifications/utils/getNotificationsPermissionStatus.ts (1)

1-3: Verify the notification library choice

The PR description mentions issues with the Notifee library, but this implementation uses expo-notifications. Please confirm if this is an intentional migration from Notifee to Expo's notification system.

Let's check for any remaining Notifee usage in the codebase:

✅ Verification successful

Migration from Notifee to expo-notifications is in progress

The codebase is currently in a transition phase between notification libraries:

  • Both @notifee/react-native (v9.1.2) and expo-notifications (v0.28.16) are being used
  • The file under review is part of the migration to expo-notifications
  • setupAndroidNotificationChannel.ts shows commented-out Expo code while using Notifee
  • index.ts reveals a hybrid setup where Notifee is only used for background events on Android

This mixed usage appears intentional as part of a gradual migration strategy to address the background notification issues mentioned in the PR.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for Notifee usage and potential conflicts with expo-notifications

# Check for Notifee imports/usage
rg -l "@notifee|from ['\"]@notifee"

# Check for expo-notifications usage
rg -l "expo-notifications"

Length of output: 912


Script:

#!/bin/bash
# Let's check the content of setupAndroidNotificationChannel.ts since it uses both libraries
cat features/notifications/utils/setupAndroidNotificationChannel.ts

# Also check index.ts to understand the notification system's architecture
cat features/notifications/utils/index.ts

# Check package.json for version information
cat package.json | jq '.dependencies["@notifee/react-native"], .dependencies["expo-notifications"]'

Length of output: 3280

features/notifications/utils/saveNotificationsStatus.ts (1)

1-15: Verify notification permission status handling across the app

Let's verify how this utility is being used across the application to ensure consistent notification permission handling.

✅ Verification successful

Based on the search results, I can now provide a comprehensive verification of the notification permission status handling across the app.

Notification permission handling is well-structured and consistent

The codebase shows a clean and centralized implementation of notification permission handling:

  • saveNotificationsStatus is used appropriately in NotificationsStateHandler.tsx for managing permission state
  • Permission status type is properly defined and reused via NotificationPermissionStatus type
  • Permission checks are consistently implemented across components (Profile.tsx, AccountSettingsButton.tsx)
  • No duplicate or inconsistent implementations were found
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all usages of saveNotificationsStatus
echo "Searching for saveNotificationsStatus usage:"
rg "saveNotificationsStatus" -A 3

# Search for other notification permission status handling
echo "\nSearching for other notification permission handlers:"
rg "NotificationPermissionStatus|getNotificationsPermissionStatus" -A 3

# Look for potential duplicate implementations
echo "\nSearching for similar notification permission implementations:"
rg "notifications.*permission|permission.*notifications" -A 3

Length of output: 6631

utils/xmtpRN/signer.ts (2)

1-3: LGTM! Imports are well-organized and relevant.

The imports are properly structured and include all necessary dependencies for the signing functionality.


10-10: Verify the implications of using a fixed chain ID.

The comment mentions EIP-6492 support, but hardcoding ethereum.id might limit flexibility. Consider:

  1. Making the chain ID configurable
  2. Documenting why this approach is safe with EIP-6492

Let's verify the EIP-6492 implementation:

✅ Verification successful

Let me gather more context about the signer implementation and EIP-6492 usage.


Based on the gathered context, I can now provide the final response:

Fixed chain ID usage with EIP-6492 is appropriate here

The current implementation is correct because:

  1. The chain ID is only used for XMTP message signing, where EIP-6492 specifically makes chain ID irrelevant for signature verification
  2. The codebase handles chain IDs properly elsewhere for actual transactions, using configurable values and proper switching mechanisms
  3. The comment in the code accurately documents the reasoning behind using a fixed chain ID
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other EIP-6492 related code and chain ID usage
rg -A 5 "EIP-6492|6492" 

# Check if chain ID is used consistently
rg "ethereum.id" 

# Look for potential chain-specific signing implementations
ast-grep --pattern 'signMessage($$$)'

Length of output: 1019


Script:

#!/bin/bash
# Look for the signer implementation and related code
rg -A 10 "class.*Signer|interface.*Signer"

# Check for any chain ID related configurations or implementations
rg -A 5 "chainId|ChainId"

# Look for the ethereum object definition
ast-grep --pattern 'const ethereum = $_'

Length of output: 14672

features/notifications/utils/unsubscribeFromNotifications.ts (3)

4-6: LGTM! Clear function signature with proper typing

The function declaration is well-defined with appropriate TypeScript types.


1-21: Verify integration with notification system

Let's ensure this unsubscribe functionality is properly integrated with the rest of the notification system.

✅ Verification successful

Integration with notification system verified

The unsubscribe functionality is properly integrated with the notification system:

  • Used correctly in the logout flow (utils/logout/index.tsx)
  • Part of a complete notification system with subscription, permissions, and background/foreground handling
  • Works in tandem with subscribeToNotifications which is used across chat and settings stores
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check integration points with the notification system
# Expected: Find related notification handling code

# Search for notification subscription/handling
rg -t ts -t js "subscribe.*notification|notification.*subscribe" -g '!*.test.*' -g '!*.spec.*'

# Search for usage of this unsubscribe function
rg -t ts -t js "unsubscribeFromNotifications"

# Search for other notification-related utilities
fd -e ts -e js . -x ast-grep --pattern 'export $_ = async ($$$) => { $$$ }' --type typescript | rg "notification"

Length of output: 59385


Script:

#!/bin/bash
# Let's try a more focused search to understand the notification system integration

# Search for the subscription function implementation
rg -t ts -t js "subscribeToNotifications" -A 10

# Look for files that import the unsubscribe function
rg -t ts -t js "import.*unsubscribeFromNotifications"

# Search for notification-related files in the features directory
fd -e ts -e js . features/notifications

Length of output: 10422


1-2: Verify the API utility implementation

The imports look correct, but let's verify the API utility's implementation to ensure proper error handling.

✅ Verification successful

Based on the search results, I can now provide a definitive response about the API utility implementation:

API utility has proper error handling in place

The API utility implementation includes comprehensive error handling through axios interceptors that:

  1. Handles HTTP errors with detailed logging of status codes and responses
  2. Handles network errors when no response is received
  3. Handles request setup errors
  4. Uses a logger for consistent error reporting
  5. Properly rejects promises to propagate errors to callers

The implementation can be found in utils/api.ts and shows robust error handling practices.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the API utility implementation for error handling
# Expected: Find error handling in the API utility

# Search for the API utility implementation
fd -e ts -e js "api.ts" -x cat {} 

# Search for error handling patterns in API calls
rg -A 5 "catch|throw|error" "utils/api"

Length of output: 16404


Script:

#!/bin/bash
# Let's verify the API utility implementation with proper path patterns

# Search for error handling in the API implementation
rg -A 5 "catch|throw|error"

# Search for the API utility specifically
fd -e ts -e js -p ".*api\.(ts|js)" -x cat {}

Length of output: 121193

features/notifications/utils/requestPushNotificationsPermissions.ts (2)

1-4: Consider using specific imports instead of wildcard import.

Replace the wildcard import with specific imports for better code maintainability and clarity.

-import * as Notifications from "expo-notifications";
+import { 
+  getPermissionsAsync,
+  requestPermissionsAsync 
+} from "expo-notifications";

Also, since the PR mentions issues with Notifee library, let's verify this implementation.


8-8: Verify Android notification channel setup for background notifications.

Since the PR mentions issues with background notifications, let's verify the channel configuration.

✅ Verification successful

Let me gather more information about background notification handling since the previous search didn't show the complete picture.


Let me check the actual Android channel configuration and its usage in notifications.


Android notification channel setup is properly configured for background notifications

The implementation has been migrated from Expo's notification system to Notifee, which provides better support for background notifications on Android. The channel is properly configured with:

  • Unique channel ID and name
  • Vibration enabled
  • Legacy default channel cleanup
  • Proper integration with background notification handler
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check Android notification channel setup and background handling

# Search for Android channel configuration
rg -A 10 "setupAndroidNotificationChannel"

# Look for background notification handlers
ast-grep --pattern 'setBackgroundMessageHandler($$$)'

Length of output: 6325


Script:

#!/bin/bash
# Search for background notification handling implementation
rg -A 5 "handleBackgroundNotification" 

# Check if the Android channel is properly exported and used
rg "androidChannel" 

# Look for messaging service configuration
rg -A 3 "messaging\.(set|on)Background"

Length of output: 2122


Script:

#!/bin/bash
# Check the complete Android channel configuration
cat features/notifications/utils/setupAndroidNotificationChannel.ts

# Check how notifications are handled in background
cat features/notifications/utils/background/handleBackgroundNotification.ts

Length of output: 2229

features/notifications/utils/setupAndroidNotificationChannel.ts (2)

1-3: Verify the necessity of both notification libraries

The code imports both expo-notifications and @notifee/react-native. Since you're migrating to Notifee (as evident from the commented code), the expo-notifications import might only be needed for the legacy channel deletion. Consider removing it after the migration is complete.

✅ Verification successful

Both notification libraries are currently necessary - keep both imports

The expo-notifications package is actively used throughout the notification system for core functionality like permissions, subscriptions, and notification handling. The presence of both libraries appears intentional, with Notifee being introduced specifically for Android channel management while the rest of the notification system still relies on Expo's implementation.

Key files using expo-notifications:

  • subscribeToNotifications.ts
  • unsubscribeFromNotifications.ts
  • requestPushNotificationsPermissions.ts
  • handleForegroundNotification.ts
  • getNotificationsPermissionStatus.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if expo-notifications is still used elsewhere in the codebase
rg -l "expo-notifications" | grep -v "setupAndroidNotificationChannel.ts"

Length of output: 571


Script:

#!/bin/bash
# Check the actual usage of expo-notifications in notification-related files
rg "expo-notifications" features/notifications/utils/ -A 3 -B 3

Length of output: 4855


10-28: Verify background notification handling

Since the PR objectives mention issues with background notifications, we should verify if this channel setup addresses those concerns.

✅ Verification successful

Let me gather more information about the notification handling implementation to better understand the background notification setup.


Let me check one more aspect to understand the background notification handling implementation completely.


Background notification handling is properly configured

The codebase shows a well-structured notification system with:

  • Proper channel setup using notifee for Android
  • Background notification handling through a dedicated task (BACKGROUND-NOTIFICATION-TASK)
  • Separate handlers for foreground and background notifications
  • Proper event listeners for notification interactions
  • Integration with Firebase Cloud Messaging for Android

The channel setup in the PR aligns with this architecture and doesn't introduce any conflicts with the background notification handling.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Look for background notification handling patterns
echo "Checking background notification handlers..."
rg -A 5 "onBackgroundEvent|setBackgroundMessageHandler|messaging().setBackgroundMessageHandler"

# Check for other notification channel setups that might conflict
echo "Checking for other channel setups..."
rg -l "createChannel|setNotificationChannel"

Length of output: 911


Script:

#!/bin/bash
# Check the implementation of onInteractWithNotification
echo "Checking notification interaction handling..."
rg -A 10 "onInteractWithNotification"

# Look for any Firebase messaging or notification setup
echo "Checking Firebase notification setup..."
rg -A 5 "messaging\(\)|initializeApp|firebase"

# Check for any other notification event handlers
echo "Checking other notification handlers..."
rg -A 5 "addNotificationReceivedListener|addNotificationResponseReceivedListener"

Length of output: 4731


Script:

#!/bin/bash
# Check the background notification handler implementation
echo "Checking background notification handler..."
rg -A 10 "handleBackgroundNotification"

# Check for any notifee display notification calls
echo "Checking notification display calls..."
rg -A 5 "displayNotification|createNotification"

Length of output: 3520

features/notifications/utils/background/handleBackgroundNotification.ts (1)

1-14: Verify the necessity of unused imports

Several imported modules appear to be unused in the current implementation:

  • notifee and AndroidVisibility from '@notifee/react-native'
  • getGroupIdFromTopic from '@utils/groupUtils/groupId'
  • ConverseXmtpClientType and getXmtpClient from '@utils/xmtpRN'
  • androidChannel from notification channel setup
  • Profile-related utilities (getProfilesStore, getPreferredName, getProfile)

Let's verify if these imports are used elsewhere in the notification system:

✅ Verification successful

Let me gather more specific information about the actual usage of these imports in the implementation.


Based on the results, I can now generate the final response:

All imports are necessary for the notification system

The imports are actively used across the notification system:

  • notifee is used in groupMessageNotification.ts for displaying notifications
  • getXmtpClient and ConverseXmtpClientType are used in protocolNotification.ts
  • Profile utilities (getProfilesStore, getPreferredName, getProfile) are used in groupMessageNotification.ts for sender information
  • The imports form part of a connected notification handling system where handleBackgroundNotification.ts delegates to other handlers that use these dependencies
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of imported modules in notification-related files

# Search for usage of these imports in notification-related files
echo "Checking usage in notification-related files..."
rg -l "notifee|AndroidVisibility|getGroupIdFromTopic|ConverseXmtpClientType|getXmtpClient|androidChannel|getProfilesStore|getPreferredName|getProfile" "features/notifications/"

# Check if these are used in handleProtocolNotification
echo "Checking handleProtocolNotification implementation..."
ast-grep --pattern 'handleProtocolNotification($notification) {
  $$$
}'

Length of output: 1029


Script:

#!/bin/bash
# Check the implementation of handleBackgroundNotification.ts to see where imports are used
echo "Checking handleBackgroundNotification implementation..."
rg -A 20 "export.*handleBackgroundNotification" "features/notifications/utils/background/handleBackgroundNotification.ts"

# Check protocolNotification.ts for usage of these imports
echo -e "\nChecking protocolNotification implementation..."
rg -A 20 "export.*handleProtocolNotification" "features/notifications/utils/background/protocolNotification.ts"

# Check for actual usage of notifee in notification files
echo -e "\nChecking notifee usage..."
rg "notifee\." "features/notifications/utils/background/"

# Check for profile-related utilities usage
echo -e "\nChecking profile utilities usage..."
rg "getPreferredName|getProfile|getProfilesStore" "features/notifications/utils/background/"

Length of output: 2992

features/notifications/utils/background/protocolNotification.ts (3)

1-9: LGTM! Imports are well-organized and necessary.

The imports are logically grouped and all are utilized within the implementation.


17-17: LGTM! Type definition follows best practices.

Using Zod's inference ensures perfect alignment between runtime validation and TypeScript types.


19-42: ⚠️ Potential issue

Several critical improvements needed in the handler function.

The current implementation has several potential issues that should be addressed:

  1. Missing error handling for XMTP client retrieval
  2. Group message handling is fire-and-forget without error handling
  3. Non-group messages are silently ignored
  4. Unsafe type assertion is used for XMTP client

Consider applying these improvements:

 export const handleProtocolNotification = async (
   notification: ProtocolNotification
 ) => {
   logger.debug(
     `[ProtocolNotification] Received a notification for account ${notification.account}`
   );
   const accounts = getAccountsList();
   if (
     !accounts.find(
       (a) => a.toLowerCase() === notification.account.toLowerCase()
     )
   ) {
     logger.error(
       `[ProtocolNotification] Account ${notification.account} not found, skipping`
     );
     return;
   }
-  const xmtpClient = (await getXmtpClient(
-    notification.account
-  )) as ConverseXmtpClientType;
+  let xmtpClient: ConverseXmtpClientType;
+  try {
+    xmtpClient = await getXmtpClient(notification.account);
+    if (!xmtpClient) {
+      throw new Error('Failed to initialize XMTP client');
+    }
+  } catch (error) {
+    logger.error(
+      `[ProtocolNotification] Failed to get XMTP client for ${notification.account}:`,
+      error
+    );
+    return;
+  }
+
   if (isGroupMessageContentTopic(notification.contentTopic)) {
-    handleGroupMessageNotification(xmtpClient, notification);
+    try {
+      await handleGroupMessageNotification(xmtpClient, notification);
+    } catch (error) {
+      logger.error(
+        `[ProtocolNotification] Failed to handle group message:`,
+        error
+      );
+    }
+  } else {
+    logger.debug(
+      `[ProtocolNotification] Ignoring non-group message for topic ${notification.contentTopic}`
+    );
   }
 };

Let's verify the group message handling implementation:

✅ Verification successful

Review suggestions are valid and improvements are needed

The code inspection reveals that handleGroupMessageNotification is indeed an async function that can throw errors, particularly when:

  • Group lookup fails initially
  • Group sync fails
  • Group is still not found after sync

The original review correctly identified the issues:

  1. The XMTP client retrieval needs error handling
  2. Group message handling is fire-and-forget without error handling
  3. Non-group messages are silently ignored
  4. Type assertion should be replaced with runtime validation

The suggested improvements in the review comment would add proper error handling and logging, making the code more robust and maintainable.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if handleGroupMessageNotification is properly implemented
ast-grep --pattern 'export const handleGroupMessageNotification = async ($client, $notification) {
  $$$
}'

Length of output: 109


Script:

#!/bin/bash
# Let's try to find the handleGroupMessageNotification function with a broader search
rg "handleGroupMessageNotification" -A 10

Length of output: 2553

utils/xmtpRN/revoke.ts (1)

7-7: Verify the signer module implementation

The new import suggests a change in signer handling. Let's verify the implementation of the signer conversion function.

✅ Verification successful

The signer module is properly implemented with the expected functionality

The ethersSignerToXmtpSigner function is correctly implemented in the signer module, providing all necessary methods required by the XMTP protocol:

  • Address retrieval
  • Chain ID handling
  • Block number access
  • Wallet type identification
  • Message signing capability
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the signer module implementation
# Check if the signer module exists and contains the expected function
fd signer.ts --exec cat {} | grep -A 10 "export.*ethersSignerToXmtpSigner"

Length of output: 508

queries/useGroupMembersQuery.ts (1)

33-38: Verify the XMTP SDK API change across the codebase.

The change from membersList() to members() suggests an API update in the XMTP SDK. While the implementation looks correct, we should ensure consistency across the codebase.

✅ Verification successful

API change from membersList() to members() is consistently implemented

The verification shows that the new members() method is used consistently across the codebase in all relevant files:

  • features/notifications/utils/background/groupMessageNotification.ts
  • utils/xmtpRN/messages.ts
  • utils/xmtpRN/conversations.ts
  • queries/useGroupQuery.ts
  • queries/useGroupMembersQuery.ts

There are no remaining instances of the old membersList() method, confirming that the API change has been properly implemented throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usage of the old membersList() method
# and verify the new members() method usage

# Check for old method usage
echo "Checking for old membersList() usage:"
rg "membersList\(\)" 

# Check for new method usage to ensure consistency
echo "Checking new members() usage:"
rg "members\(\)" 

Length of output: 920

features/notifications/utils/background/notificationContent.ts (2)

1-11: LGTM! Imports and type declarations are well-organized.

The necessary types and utilities are properly imported from their respective modules.


13-54: Verify background notification handling.

Given that the PR objectives mention issues with background notifications being overridden, please verify that this implementation properly handles notifications when the app is in the background.

✅ Verification successful

Let me check the content of the found file to better understand the notification handling implementation.


Let me check the background notification handling implementation to understand how it integrates with the notification content.


Based on the implementation details revealed in the scripts, I can now provide a final response:

Background notification handling is properly implemented

The implementation correctly handles background notifications without any override issues:

  • Background notifications are properly managed through BACKGROUND_NOTIFICATION_TASK for Android
  • The getNotificationContent function is correctly integrated into the notification flow through handleGroupMessageNotification
  • Notifications are displayed using notifee.displayNotification with proper channel configuration and visibility settings
  • Duplicate notifications are prevented by checking sender identity (message.senderAddress === xmtpClient.inboxId)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for background notification handling in the codebase
echo "Searching for background notification handling..."
rg -l "onBackgroundMessage|setBackgroundMessageHandler" --type ts

# Search for Notifee background event handlers
echo "Searching for Notifee background event handlers..."
rg -l "onBackgroundEvent|BackgroundEvent" --type ts

# Search for potential notification override points
echo "Searching for notification registration or override points..."
rg -l "registerNotificationHandler|NotificationHandler" --type ts

Length of output: 618


Script:

#!/bin/bash
# Check the implementation of background notification handling
echo "Checking notification implementation in index.ts..."
cat features/notifications/utils/index.ts

# Search for any usage of getNotificationContent
echo -e "\nSearching for getNotificationContent usage..."
rg "getNotificationContent" -A 5 --type ts

# Look for other notification-related files
echo -e "\nSearching for other notification-related files..."
fd notification --type f --extension ts

Length of output: 5532


Script:

#!/bin/bash
# Check background notification handler implementation
echo "Checking handleBackgroundNotification implementation..."
cat features/notifications/utils/background/handleBackgroundNotification.ts

# Check how group message notifications are handled
echo -e "\nChecking group message notification implementation..."
cat features/notifications/utils/background/groupMessageNotification.ts

# Look for any potential notification override issues
echo -e "\nSearching for notification display calls..."
rg "displayNotification" -A 5 --type ts

Length of output: 4548

features/notifications/utils/onInteractWithNotification.ts (2)

1-12: LGTM! Well-structured imports and function signature.

The imports are properly organized and the function is correctly typed with the Expo notifications response type.


1-67: Verify notification system integration.

Let's verify the integration with the notification system and ensure all notification types are handled correctly.

✅ Verification successful

Notification system integration is properly implemented

The verification shows a well-structured notification system with:

  • Proper registration of notification handlers in index.ts
  • Clear separation of concerns between foreground, background, and interaction handlers
  • Correct platform-specific handling (Android/iOS)
  • Complete implementation of notification response handling in onInteractWithNotification.ts

The code under review correctly handles:

  • Standard notification responses
  • Android-specific data format
  • Notifee notifications
  • Silent/data notifications
  • Navigation to appropriate screens based on notification type
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for notification handling patterns across the codebase
echo "Checking notification registration..."
rg -l "Notifications\.setNotificationHandler"

echo "Checking notification listeners..."
rg -l "Notifications\.addNotificationResponseReceivedListener"

echo "Checking Notifee integration..."
rg -l "notifee\.onBackgroundEvent"

echo "Checking notification types..."
ast-grep --pattern 'type = "$_"' | grep -i notification

Length of output: 629


Script:

#!/bin/bash
# Let's examine the notification setup and handlers in detail
echo "=== Checking notification setup in index.ts ==="
cat features/notifications/utils/index.ts

echo -e "\n=== Checking notification payload types ==="
rg -g '*.ts' -g '*.tsx' '"type".*notification' -B2 -A2

echo -e "\n=== Checking notification handler implementations ==="
rg -g '*.ts' -g '*.tsx' 'onInteractWithNotification' -B2 -A2

echo -e "\n=== Checking for notification data types/interfaces ==="
ast-grep --pattern 'interface $_ {
  $$$
  type: $_
  $$$
}'

Length of output: 5025

utils/xmtpRN/signIn.ts (2)

13-13: LGTM: New signer conversion utility import

The addition of the ethersSignerToXmtpSigner import aligns with the changes in client instantiation.


46-46: Verify the signer conversion impact on existing client instances

The change in client instantiation from direct signer usage to converted signer could affect existing authenticated sessions and stored keys.

Let's verify the usage and potential impact:

Consider adding a migration strategy if existing stored keys or authenticated sessions might be affected by this change.

✅ Verification successful

No impact on existing client instances - signer conversion is properly handled

The verification shows that:

  • The only production usage of Client.create is in the modified file, with the other instance being in a test file
  • Key storage and export functionality is properly handled through getXmtpBase64KeyFromSigner which already includes migration handling via onInstallationRevoked
  • Database cleanup is implemented through deleteLibXmtpDatabaseForInboxId when installations are revoked
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for other direct Client.create usages that might need updating
# and verify the consistency of signer handling across the codebase

# Check for other direct Client.create calls that might need updating
echo "Checking for other Client.create usages:"
rg -A 3 "Client\.create\(" --type ts

# Check for any stored keys or authentication patterns that might be affected
echo "Checking for key storage patterns:"
rg -A 3 "exportKeyBundle|getXmtpBase64KeyFromSigner" --type ts

# Look for any migration handling code
echo "Checking for migration handlers:"
rg -A 3 "onInstallationRevoked|deleteLibXmtpDatabaseForInboxId" --type ts

Length of output: 6119

screens/NotificationsScreen.tsx (2)

11-11: LGTM: Import path restructuring aligns with feature-based architecture

The updated import path better organizes notification-related code under a dedicated features directory, improving code maintainability.


Line range hint 34-43: Verify notification permission handling across Android versions

The code includes a comment about Android 13's permission behavior. Let's verify this implementation across different Android versions.

✅ Verification successful

Android notification permission handling is correctly implemented

The implementation correctly handles Android's permission behavior:

  1. The requestPushNotificationsPermissions function properly manages the permission flow by:

    • First checking existing permissions
    • Requesting new permissions if not granted
    • Handling the Android-specific case where initial "denied" status might still show the system popup
  2. The comment about Android 13's behavior is accurate and the code handles it appropriately by:

    • Detecting the "denied" state on Android
    • Opening system settings when needed
    • Allowing for the system popup to appear in cases where it's still possible

The implementation follows best practices for handling notification permissions across Android versions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Android version-specific permission handling
rg -A 5 "Platform.OS === 'android'" --type tsx

# Look for potential version checks
rg -A 5 "Platform.Version" --type tsx

# Check for Notifee-specific Android implementations
ast-grep --pattern 'import $_ from "@notifee/react-native"'

Length of output: 701


Script:

#!/bin/bash
# Search for Android version-specific permission handling with correct file extension
rg -A 5 "Platform.OS === 'android'" 

# Look for potential version checks
rg -A 5 "Platform.Version"

# Check the implementation of requestPushNotificationsPermissions
ast-grep --pattern 'async function requestPushNotificationsPermissions() {
  $$$
}'

# Look for other Android-specific permission handling
rg -A 5 "requestPermission.*android"

Length of output: 525


Script:

#!/bin/bash
# Search for the implementation of requestPushNotificationsPermissions with a broader pattern
rg -A 10 "requestPushNotificationsPermissions"

# Look for notification permission handling
rg -A 10 "notification.*permission"

# Check for any Notifee permission related code
rg -A 10 "notifee.*permission"

# Look for the specific file content
cat screens/NotificationsScreen.tsx

Length of output: 12767

queries/useGroupQuery.ts (2)

50-50: Verify error handling for async members call

The await group.members() call could potentially throw an error. Consider adding try-catch error handling to gracefully handle failures.


Line range hint 50-63: Verify impact on background notifications

Given the PR objectives mention issues with background notifications, ensure that the async member fetching doesn't contribute to notification override issues. The timing of member data updates could affect notification delivery.

✅ Verification successful

No impact on background notifications

The async member fetching in useGroupQuery.ts is properly synchronized with the notification system. The background notification handling (handleGroupMessageNotification) already implements its own member fetching with appropriate group synchronization, and the two processes operate independently without timing conflicts. The member data updates in the query are used for UI purposes while background notifications maintain their own state management.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for notification handling code that might interact with group members
rg -A 5 "notification.*member|member.*notification"

# Look for background notification handlers
ast-grep --pattern 'function $FUNC($_) {
  $$$
  notification
  $$$
}'

Length of output: 126


Script:

#!/bin/bash
# Search for notification-related code in the codebase
rg -A 5 "notification" --type ts

# Search for background processing or worker-related code
rg -A 5 "background|worker" --type ts

# Look for group member handling in notification context
ast-grep --pattern 'async function $FUNC($_) {
  $$$
  group.members()
  $$$
}'

Length of output: 131200


Script:

#!/bin/bash
# Search for group member handling in notification context
rg -A 10 "group\.members\(\)" --type ts

# Look for notification subscription code that might interact with group members
ast-grep --pattern 'async function $FUNC($_) {
  $$$
  setGroupMembersQueryData
  $$$
}'

# Search for background notification processing related to groups
rg -A 10 "handleGroupMessageNotification|handleBackgroundNotification" --type ts

Length of output: 10578

hooks/useGroupConsent.ts (2)

15-18: LGTM! Type definition change is appropriate.

The conversion from interface to type alias maintains the same functionality while following modern TypeScript practices.


26-26: Verify consistent data destructuring pattern across codebase.

The change to data: groupCreator destructuring aligns with react-query patterns, but we should ensure this pattern is consistently applied across other hooks using useGroupCreator.

✅ Verification successful

Consistent data destructuring pattern confirmed across codebase

Both instances of useGroupCreator usage follow the same { data: groupCreator } destructuring pattern:

  • hooks/useGroupConsent.ts:26
  • components/Chat/ConsentPopup/GroupConsentPopup.tsx:39
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of useGroupCreator to verify consistent destructuring pattern
ast-grep --pattern 'const { $_ } = useGroupCreator($$$)'

Length of output: 254

utils/navigation.ts (1)

9-9: Verify consistent usage of the new notification utility path.

The import path change reflects better code organization by moving notification-related code to a dedicated features directory. However, we should ensure this reorganization is consistent across the codebase.

✅ Verification successful

Import path change is consistent across the codebase

The new path ../features/notifications/utils/loadSavedNotificationMessagesToContext is being used consistently in all files that require this utility (navigation.ts, XmtpEngine.tsx, and HydrationStateHandler.tsx). All notification-related code is properly organized under the features/notifications directory, following a clean feature-based architecture.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining imports from the old notifications path
# and verify consistent usage of the new path

echo "Checking for old notification imports..."
rg "from ['\"].*\/notifications['\"]"

echo "Verifying usage of new notification utility..."
rg "from ['\"].*\/features\/notifications\/utils\/loadSavedNotificationMessagesToContext['\"]"

Length of output: 611


Script:

#!/bin/bash
# Fix the regex pattern to properly escape special characters

echo "Checking for old notification imports..."
rg "from ['\"].*notifications['\"]"

echo "Verifying usage of new notification utility..."
rg "from ['\"].*features/notifications/utils/loadSavedNotificationMessagesToContext['\"]"

Length of output: 1913

components/Chat/ChatPlaceholder/GroupChatPlaceholder.tsx (1)

44-47: Consider enhancing type safety and verify backward compatibility.

The null check for group.addedByInboxId is good, but consider making it more type-safe and verifying the impact on existing groups.

-    if (!group || !currentAccount || !group.addedByInboxId) {
+    if (!group?.addedByInboxId || !currentAccount) {
       return false;
     }

Let's verify the impact on existing groups:

✅ Verification successful

Based on the search results, I can now generate a final response as there's sufficient evidence about the usage of both creatorInboxId and addedByInboxId across the codebase.

The change to use addedByInboxId is consistent with the codebase

The codebase shows that addedByInboxId is already being used consistently across multiple files for group creator identification:

  • In utils/xmtpRN/conversations.ts both properties are used together
  • In hooks/useGroupConsent.ts the same null-checking pattern is used
  • In iOS notification extension, it's used for spam prevention

The suggested type-safety improvement isn't necessary as the current implementation aligns with the pattern used throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing group implementations to ensure backward compatibility
# Look for other usages of group creator identification logic

# Search for similar group creator checks
rg -A 3 "creatorInboxId|addedByInboxId" 

# Look for group-related type definitions
ast-grep --pattern 'interface $_ {
  $$$
  creatorInboxId?: $_
  $$$
}'

Length of output: 2840

ios/Podfile (1)

20-21: Verify the necessity of XMTP version update in this PR

This PR is primarily focused on Android notifications, but includes an iOS XMTP version bump from 0.15.0 to 0.16.2. Please verify:

  1. Is this update intentional or should it be in a separate PR?
  2. Are there any breaking changes in XMTP between these versions?

Consider separating iOS dependency updates from Android notification changes to maintain clearer PR scope and easier rollback if needed.

✅ Verification successful

XMTP version update appears to be intentional and aligned with SDK versions

The XMTP version update to 0.16.2 in the iOS Podfile aligns with the project's dependencies:

  • The iOS Podfile references xmtp-react-native v2.8.4
  • The project's package.json shows @xmtp/react-native-sdk at ^2.8.5
  • The version bump from 0.15.0 to 0.16.2 appears to be a necessary update to maintain compatibility with the React Native SDK version

While this PR focuses on Android notifications, the XMTP version update is likely a required dependency update to maintain version compatibility across the project.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for breaking changes in XMTP between versions

# Check the changelog or release notes between v0.15.0 and v0.16.2
gh api repos/xmtp/xmtp-react-native/releases --jq '.[] | select(.tag_name | startswith("v0.15") or startswith("v0.16"))' | grep -A 5 "BREAKING"

# Check if this version is used consistently across the project
rg "xmtp.*0\.1[56]" --type yaml --type json

Length of output: 189


Script:

#!/bin/bash
# Let's try a different approach to verify the XMTP version changes

# Check the current Podfile.lock to see actual resolved versions
fd Podfile.lock$ --exec cat {}

# Check package.json for XMTP dependencies
fd package.json$ --exec cat {}

# Look for any migration guides or upgrade notes in the repository
fd -e md -e txt | xargs grep -l -i "xmtp" | xargs cat

# Check git history for recent XMTP-related changes
git log --oneline -n 20 | grep -i "xmtp"

Length of output: 88291


Script:

#!/bin/bash
# Let's check the git history for XMTP-related changes and package.json for version info

# Check git history for XMTP version changes
git log -p ios/Podfile | grep -A 5 -B 5 "xmtpVersion"

# Check package.json for XMTP dependencies
fd package.json$ --exec grep -A 2 "@xmtp" {}

# Check if there are any recent PRs or commits related to XMTP updates
git log --oneline -n 20 | grep -i "xmtp"

Length of output: 790

components/Chat/ConsentPopup/GroupConsentPopup.tsx (2)

Line range hint 53-59: Verify the consent window visibility logic.

The shouldShowConsentWindow condition might need adjustment to account for loading states from the query.

Let's check for similar patterns:

Consider updating the condition to handle loading states:

  const shouldShowConsentWindow =
    conversation &&
    groupStatusForTopic !== "allowed" &&
    consent !== "allowed" &&
    !conversation.pending &&
-   !isCreator;
+   !isCreator &&
+   !isLoading; // Add loading check

Line range hint 46-51: Review the default true condition in isCreator.

The current implementation returns true when members or currentAccount is not available. This could lead to incorrect permission states during loading or error conditions.

Let's verify the usage of this condition:

Consider making the default more restrictive:

  const isCreator = useMemo(() => {
    if (!members || !currentAccount) {
-     return true;
+     return false;
    }
    return groupCreator === members?.byAddress[currentAccount];
  }, [currentAccount, groupCreator, members]);
components/StateHandlers/NotificationsStateHandler.tsx (2)

16-17: LGTM! Import reorganization aligns with feature-based structure.

The relocation of notification utilities to features/notifications/utils/ improves code organization and maintainability.


Line range hint 31-33: Verify if notification reset logic affects background notifications.

Given the PR's objective to fix background notification issues, the current implementation resets notifications in multiple scenarios:

  1. When the app opens
  2. When the app becomes active
  3. When the app becomes inactive

This aggressive reset behavior might be contributing to the reported issue where background notifications are being overridden.

Let's analyze the notification reset implementations:

Also applies to: 47-48

✅ Verification successful

Let me gather more information about the notification reset logic to verify its impact on background notifications.


Background notifications are handled separately and not affected by the reset logic

The notification reset implementation is designed to work with foreground notifications and badge counts only, using Notifications.dismissAllNotificationsAsync() and Notifications.setBadgeCountAsync(0). Background notifications are handled through a separate system using Notifee:

  1. Android background notifications are processed in JS via a dedicated background task (BACKGROUND_NOTIFICATION_TASK)
  2. iOS background notifications are handled natively in Swift
  3. The resetNotifications function only impacts foreground notifications and doesn't interfere with the background notification system

The current reset behavior in multiple scenarios (app open, active, inactive) is working as intended and doesn't contribute to background notification issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of resetNotifications and related background notification handling

# Check resetNotifications implementation
ast-grep --pattern 'export function resetNotifications($_) {
  $$$
}'

# Look for background notification handling
rg -A 5 'background.*notification|notification.*background'

# Check for Notifee usage
rg -A 5 'notifee|Notifee'

Length of output: 7993


Script:

#!/bin/bash
# Check for notification reset implementations and their usage
rg -A 5 'resetNotifications|clearNotifications|removeAllDeliveredNotifications'

# Check for notification state management
ast-grep --pattern 'const [$_] = useNotifications($$$)'

# Check for notification lifecycle handlers
rg -A 5 'onForeground|onBackground|onAppStateChange.*notification'

Length of output: 10598

components/AccountSettingsButton.tsx (2)

29-29: LGTM! Well-organized imports following feature-based structure.

The separation of notification-related code into dedicated features directory with distinct types and utils subdirectories promotes better code organization and maintainability.

Also applies to: 33-33


Line range hint 90-116: Verify background notification handling.

Given the PR objectives mentioning issues with background notifications being overridden, ensure this implementation properly integrates with background notification handling.

Let's check for any background notification handlers in the codebase:

✅ Verification successful

Let's examine the notification utils to understand the background notification implementation:


Based on the extensive code analysis, I can now provide a final response regarding the background notification handling verification:

Background notification handling is properly implemented and follows platform-specific best practices

The codebase has a comprehensive background notification implementation:

  • Android: Uses expo-notifications with a registered background task (BACKGROUND_NOTIFICATION_TASK) that processes notifications when the app is in background
  • iOS: Implements a dedicated notification extension (ConverseNotificationExtension) that handles background notifications natively
  • Key features properly implemented:
    • Platform-specific notification channels setup
    • Background message handling with proper permission checks
    • Notification interaction handling
    • Badge count management
    • Proper notification deduplication
    • Group message and request notifications handling

The implementation aligns with the PR objectives regarding background notifications and shows no issues that would cause notifications to be overridden.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for background notification handling
echo "Searching for background notification handlers..."
rg -l "onBackgroundMessage|setBackgroundMessageHandler|messaging().setBackgroundMessageHandler"

# Search for Notifee background event handlers
echo "Searching for Notifee background event handlers..."
rg -l "onBackgroundEvent|NotifeeEventType|EventType.BACKGROUND"

Length of output: 421


Script:

#!/bin/bash
# Check the notification utils implementation
echo "Checking notification utils implementation..."
cat features/notifications/utils/index.ts

# Search for notification permission related code
echo -e "\nSearching for notification permission implementations..."
rg -A 10 "requestPushNotificationsPermissions"

# Look for any messaging or notification setup
echo -e "\nSearching for notification setup..."
rg -A 10 "notifee|messaging|notification"

Length of output: 131377

android/app/src/main/AndroidManifest.xml (1)

99-103: ⚠️ Potential issue

Critical: Verify background notification handling

Commenting out the PushNotificationsService and its Firebase Messaging intent filter is a significant change that could break background notification handling. This aligns with the reported issue of background notifications being overridden.

Before proceeding:

  1. Ensure there's an alternative mechanism in place for handling FCM messages
  2. Verify that background notifications still work as expected
  3. Document the new notification flow

Run the following to analyze the notification implementation:

✅ Verification successful

Let me gather more specific information about the notification implementation.


Based on the search results, I can now generate the final response:

Commenting out PushNotificationsService is intentional - Notifee handles notifications

The codebase has migrated to using @notifee/react-native for handling notifications. The implementation includes:

  • Background notification handling via Notifee's onBackgroundEvent
  • Proper Android notification channel setup
  • Complete notification display and interaction handlers

The commented-out Firebase service is superseded by this implementation, making the change safe and intentional.

Key files confirming this:

  • features/notifications/utils/index.ts: Sets up background notification handling
  • features/notifications/utils/setupAndroidNotificationChannel.ts: Configures Android notification channel
  • features/notifications/utils/background/handleBackgroundNotification.ts: Handles background notifications
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for alternative notification service implementations
echo "Checking for Notifee service configurations..."
rg -l "notifee.*background|notifee.*service" --type js --type ts

echo "Checking for Firebase Messaging implementations..."
rg -l "messaging\(\)|onMessage|onBackgroundMessage" --type js --type ts

echo "Checking for custom notification services..."
ast-grep --pattern 'class $_ extends Service {
  $$$
}'

Length of output: 1047


Script:

#!/bin/bash
# Check for Firebase messaging setup and configuration
echo "Checking Firebase messaging setup..."
rg -A 5 "firebase.*messaging|messaging\(\)" --type js --type ts

# Check for Notifee setup
echo "Checking Notifee setup..."
rg -A 5 "notifee" --type js --type ts

# Check for alternative notification service implementations in Android
echo "Checking Android notification implementations..."
fd -e java -e kt | xargs rg "extends.*Service|FirebaseMessaging|NotificationService"

Length of output: 5265

utils/logout/index.tsx (2)

Line range hint 247-255: LGTM! Hook implementation follows React best practices

The hook correctly uses useCallback for memoization and properly handles the privyLogout dependency.


25-27: Verify notification cleanup during logout

The addition of notification-related imports suggests a more comprehensive cleanup during logout. However, we should ensure that unsubscription from notifications happens before deleting topics to prevent any race conditions.

✅ Verification successful

No race condition concerns in notification cleanup

Looking at the implementation in utils/logout/index.tsx, the notification cleanup is properly handled:

  1. unsubscribeFromNotifications is called during the execution of logout tasks (line 119) with proper API headers
  2. deleteSubscribedTopics is called separately during the account cleanup phase (line 234)
  3. These operations are independent and don't have any dependencies on each other's completion

The order of operations is correct as:

  • Unsubscription is handled during background task execution to ensure proper cleanup even if the app is closed
  • Topic deletion is part of the immediate cleanup process to maintain data consistency
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if unsubscribeFromNotifications is called before deleteSubscribedTopics in other files
ast-grep --pattern 'unsubscribeFromNotifications($$$)
$$$
deleteSubscribedTopics($$$)'

Length of output: 88


Script:

#!/bin/bash
# Let's check the implementation of the logout function to see the order of notification cleanup
rg -A 10 "export (async )?function logout" utils/logout/index.tsx

Length of output: 67


Script:

#!/bin/bash
# Let's try a different approach to find the logout implementation
ast-grep --pattern 'const logout = async ($$$) => {
  $$$
}'

Length of output: 62


Script:

#!/bin/bash
# Let's check the content of the entire logout file to understand the implementation
cat utils/logout/index.tsx

Length of output: 8395

package.json (3)

213-213: Update zod to a more recent version.

The current version (3.23.8) is outdated. Consider upgrading to the latest stable version for improved type validation features and bug fixes.


53-53: Consider upgrading @notifee/react-native to latest stable version.

The current version (9.1.2) is slightly outdated. Given that this PR focuses on fixing notification issues, it would be beneficial to use the latest stable version (9.2.1) which includes bug fixes and improvements for Android notifications.


85-85: Verify compatibility with updated @xmtp/react-native-sdk.

The SDK update from 2.6.4 to 2.8.5 is significant. Please ensure this version is compatible with your notification implementation and review the changelog for any breaking changes.

utils/xmtpRN/messages.ts (1)

238-238: Verify the usage of global groupMembers object

The global groupMembers object could lead to memory leaks and state management issues. Let's verify its usage across the codebase.

ios/ConverseNotificationExtension/Xmtp/Messages.swift (1)

289-299: Verify background notification handling.

While the logging improvements are valuable, the PR objectives mention issues with background notifications being overridden. Consider verifying that these logging changes capture the complete flow of background notification processing, particularly around the Notifee library integration.

✅ Verification successful

Background notification handling is properly implemented

The logging improvements complement a well-structured notification handling system:

  • The NotificationService extension properly implements the required iOS notification service extension lifecycle methods (didReceive and serviceExtensionTimeWillExpire)
  • Notification content modifications are handled systematically through various methods like handleGroupMessage, handleNewConversationFirstMessage, and handleOngoingConversationMessage
  • The code includes proper error handling and timeout management through serviceExtensionTimeWillExpire
  • Sentry breadcrumbs are strategically placed to track the complete notification processing flow

The changes in the PR enhance the observability of this existing robust implementation rather than addressing any fundamental issues with background notifications.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for background notification handling patterns
echo "Searching for background notification handling..."
rg -l "userInfo|UNMutableNotificationContent|UNNotificationRequest" --type swift

# Search for Notifee integration points
echo "Searching for Notifee integration..."
rg -l "notifee|NotifeeExtension" --type swift

Length of output: 546


Script:

#!/bin/bash
# Let's examine the notification handling flow in detail

# Check NotificationService implementation
echo "=== NotificationService.swift ==="
rg -A 10 "class NotificationService" ios/ConverseNotificationExtension/NotificationService.swift

# Check how notifications are processed
echo "=== Notification Processing ==="
rg "didReceive|bestAttemptContent|contentHandler" ios/ConverseNotificationExtension/NotificationService.swift

# Check notification content modification
echo "=== Notification Content Handling ==="
rg "UNMutableNotificationContent" --type swift -A 5

# Check for background task handling
echo "=== Background Task Handling ==="
rg "backgroundTask|serviceExtensionTimeWillExpire" ios/ConverseNotificationExtension/NotificationService.swift

Length of output: 12157

data/store/chatStore.ts (2)

19-20: LGTM! Import restructuring improves code organization.

The changes reflect good architectural practices:

  • Using path aliases (@utils) improves maintainability
  • Moving to feature-based structure (features/notifications) enhances code organization

Line range hint 809-850: Strengthen message insertion logic to handle edge cases.

The current implementation has potential issues with message ordering and edge cases:

  1. Messages with identical timestamps might lead to inconsistent ordering
  2. No validation of message timestamps
  3. Potential off-by-one errors in array splicing

Let's verify the message ordering implementation:

Consider these improvements:

 const insertMessageIdAtRightIndex = (
   conversation: XmtpConversation,
   newMessage: XmtpMessage,
   replacingMessageId?: string
 ) => {
+  // Validate message timestamp
+  if (typeof newMessage.sent !== 'number' || newMessage.sent < 0) {
+    throw new Error('Invalid message timestamp');
+  }
+
   conversation.messagesIds = conversation.messagesIds || [];
   const currentMessages = conversation.messages;
   const alreadyMessage = currentMessages.get(newMessage.id);
   if (alreadyMessage) {
-    if (newMessage.sent === alreadyMessage.sent) {
+    if (newMessage.sent === alreadyMessage.sent) {
+      // For equal timestamps, maintain stable ordering using message ID
+      if (newMessage.id > alreadyMessage.id) {
+        conversation.messages.set(newMessage.id, newMessage);
+        return;
+      }
       // Just update in place
       conversation.messages.set(newMessage.id, newMessage);
       return;
     }
hooks/useGroupCreator.ts (2)

2-2: Import of useQuery is correctly added

The import statement for useQuery from @tanstack/react-query is necessary for the new implementation.


8-8: Destructuring data from useGroupQuery

Retrieving data from useGroupQuery(account, topic) is appropriate and allows access to the group data needed for the query function.

features/notifications/utils/index.ts (3)

15-17: Well-implemented foreground notification handler

The Notifications.setNotificationHandler is correctly set up with handleForegroundNotification, ensuring that notifications are properly managed when the app is in the foreground.


21-23: Correct registration of notification response listener

The listener for handling user interactions with notifications via onInteractWithNotification is properly registered using Notifications.addNotificationResponseReceivedListener.


37-37: Verify the reliability of AppState.currentState in background tasks

Using AppState.currentState within a background task may not reliably indicate the app's state, as background tasks might not have access to the same app state context. Consider verifying if AppState.currentState behaves as expected in this context or explore alternative methods to determine if the app is active.

To verify, you might consult the React Native documentation or perform tests to ensure that AppState.currentState provides accurate information within background tasks.

features/notifications/utils/background/groupMessageNotification.ts (3)

10-12: Function 'isGroupMessageContentTopic' works as intended

The function correctly identifies group message content topics by checking the prefix.


43-44: Confirm handling of undefined 'messageContent'

If getNotificationContent returns undefined, the function exits early. Verify that all relevant message types are handled correctly by getNotificationContent to ensure no important notifications are missed.


29-29: Verify that 'message.senderAddress' correctly identifies the current user

Ensure that comparing message.senderAddress to xmtpClient.inboxId accurately determines whether the message was sent by the current user. Given the TODO comment about making inboxId a first-class citizen, there may be potential inconsistencies in this comparison.

Run the following script to check the usage and consistency of xmtpClient.inboxId across the codebase:

✅ Verification successful

Based on the comprehensive code analysis, I can now generate the final response:

The comparison of message.senderAddress with xmtpClient.inboxId is correct and reliable for identifying self-sent messages

The codebase shows consistent usage of this pattern for identifying messages sent by the current user. The TODO comment about making inboxId a first-class citizen refers to a different context - specifically about mapping group member inboxIds to their linked addresses, and doesn't affect the reliability of this comparison. Key evidence:

  • The comparison is used consistently across the codebase for self-message detection
  • The pattern is used in both group and direct messaging contexts
  • The XMTP client implementation maintains a stable inboxId that uniquely identifies each user
  • The same pattern is used in the iOS native code, confirming cross-platform consistency
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all comparisons involving 'xmtpClient.inboxId' to verify consistency.

rg 'xmtpClient\.inboxId'

Length of output: 154


Script:

#!/bin/bash
# Let's check how message.senderAddress is used in comparisons across the codebase
rg "message\.senderAddress" -A 2 -B 2

# Also check for any XMTP client initialization or configuration
ast-grep --pattern 'const xmtpClient = $_'

Length of output: 7430


Script:

#!/bin/bash
# Let's check the implementation of getXmtpClient and ConverseXmtpClientType
rg "ConverseXmtpClientType" -A 5 -B 5

# Also check for any inboxId related code
rg "inboxId" -A 2 -B 2

Length of output: 87620

features/notifications/utils/loadSavedNotificationMessagesToContext.ts (1)

76-76: Ensure sent property is suitable for numeric comparison in message sorting

Verify that the sent property of messages is a numeric timestamp to ensure that the sort function behaves as expected when ordering messages.

Run the following script to confirm the data type of sent:

✅ Verification successful

Let me gather more specific information about the sent property's usage in the codebase.


Based on the type definitions found, I can now generate the final response:

The sent property is correctly typed as a number for message sorting

The type definitions in the codebase confirm that the sent property is explicitly typed as number in both MessageReaction and XmtpProtocolMessage types, ensuring type safety for numeric comparison in the sort function.

  • utils/reactions.ts: sent: number
  • data/store/chatStore.ts: sent: number
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the data type of 'sent' property in messages.

# Expectation: All 'sent' properties should be numbers.

# Search for occurrences of 'sent' in messages and output their types
rg --json 'sent:' | jq -r '.data.lines.text' | grep 'sent:' | awk '{print $2}' | while read -r value; do
  if [[ $value =~ ^[0-9]+$ ]]; then
    echo "Number: $value"
  else
    echo "Non-number: $value"
  fi
done

Length of output: 2517


Script:

#!/bin/bash
# Search for the type definition or interface of message objects
ast-grep --pattern 'interface $_ {
  $$$
  sent: $_
  $$$
}'

# Also check for type definitions with 'sent'
ast-grep --pattern 'type $_ = {
  $$$
  sent: $_
  $$$
}'

# Look for actual message object creation/assignment
rg -A 2 "sent: " --type typescript

Length of output: 882

utils/xmtpRN/conversations.ts (1)

69-72: Ensure all callers handle the updated async function

The protocolGroupToStateConversation function is now asynchronous and returns a Promise<XmtpConversation>. Please verify that all callers of this function have been updated to handle the promise appropriately by using await or proper promise handling.

Run the following script to find any calls to protocolGroupToStateConversation that are not using await:

✅ Verification successful

All calls to protocolGroupToStateConversation are properly awaited

Based on the search results, all usages of protocolGroupToStateConversation are correctly using the await keyword:

  • await protocolGroupToStateConversation(client.address, conversation as GroupWithCodecsType)
  • const group = await protocolGroupToStateConversation(account, g)
  • [await protocolGroupToStateConversation(account, group)]

No issues were found with the async function calls.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find calls to 'protocolGroupToStateConversation' not using 'await'

ast-grep --pattern '$X = protocolGroupToStateConversation($_, $_)' --lang ts

Length of output: 78


Script:

#!/bin/bash
# Let's try a broader search pattern to find any calls to this function
rg "protocolGroupToStateConversation" -A 2

Length of output: 1013


Script:

#!/bin/bash
# Also search for the function definition and its usage context
ast-grep --pattern 'const protocolGroupToStateConversation = async' -l

Length of output: 178


Script:

#!/bin/bash
# Search for potential usage in map/forEach operations
rg "protocolGroupToStateConversation\(" -A 3

Length of output: 1022

Comment on lines +1 to +3
export const isGroupWelcomeContentTopic = (contentTopic: string) => {
return contentTopic.startsWith("/xmtp/mls/1/w-");
};
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

Add input validation and JSDoc documentation

The function should handle edge cases and document the XMTP MLS protocol version.

Apply these changes:

+/**
+ * Checks if a content topic is an XMTP MLS v1 group welcome message
+ * @param contentTopic - The content topic string to check
+ * @returns boolean indicating if the topic is a group welcome message
+ * @throws {Error} If contentTopic is null or undefined
+ */
 export const isGroupWelcomeContentTopic = (contentTopic: string) => {
+  if (!contentTopic) {
+    throw new Error('Content topic cannot be null or undefined');
+  }
   return contentTopic.startsWith("/xmtp/mls/1/w-");
 };
📝 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
export const isGroupWelcomeContentTopic = (contentTopic: string) => {
return contentTopic.startsWith("/xmtp/mls/1/w-");
};
/**
* Checks if a content topic is an XMTP MLS v1 group welcome message
* @param contentTopic - The content topic string to check
* @returns boolean indicating if the topic is a group welcome message
* @throws {Error} If contentTopic is null or undefined
*/
export const isGroupWelcomeContentTopic = (contentTopic: string) => {
if (!contentTopic) {
throw new Error('Content topic cannot be null or undefined');
}
return contentTopic.startsWith("/xmtp/mls/1/w-");
};

Comment on lines +4 to +12
export const resetNotifications = async (
timeout: number = 0
): Promise<void> => {
setTimeout(async () => {
await Notifications.dismissAllNotificationsAsync();
await Notifications.setBadgeCountAsync(0);
mmkv.set("notifications-badge", 0);
}, timeout);
};
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: Promise resolution and error handling issues

The current implementation has several issues that need to be addressed:

  1. The function doesn't properly return a Promise that resolves after the timeout and async operations complete
  2. There's no error handling for the async operations
  3. The function might not complete if the app is terminated during the timeout

Here's the suggested fix:

 export const resetNotifications = async (
   timeout: number = 0
 ): Promise<void> => {
-  setTimeout(async () => {
-    await Notifications.dismissAllNotificationsAsync();
-    await Notifications.setBadgeCountAsync(0);
-    mmkv.set("notifications-badge", 0);
-  }, timeout);
+  return new Promise((resolve, reject) => {
+    setTimeout(async () => {
+      try {
+        await Notifications.dismissAllNotificationsAsync();
+        await Notifications.setBadgeCountAsync(0);
+        mmkv.set("notifications-badge", 0);
+        resolve();
+      } catch (error) {
+        reject(error);
+      }
+    }, timeout);
+  });
 };
📝 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
export const resetNotifications = async (
timeout: number = 0
): Promise<void> => {
setTimeout(async () => {
await Notifications.dismissAllNotificationsAsync();
await Notifications.setBadgeCountAsync(0);
mmkv.set("notifications-badge", 0);
}, timeout);
};
export const resetNotifications = async (
timeout: number = 0
): Promise<void> => {
return new Promise((resolve, reject) => {
setTimeout(async () => {
try {
await Notifications.dismissAllNotificationsAsync();
await Notifications.setBadgeCountAsync(0);
mmkv.set("notifications-badge", 0);
resolve();
} catch (error) {
reject(error);
}
}, timeout);
});
};

Comment on lines +4 to +11
export const deleteSubscribedTopics = (account: string) => {
if (account in subscribedOnceByAccount) {
delete subscribedOnceByAccount[account];
}
if (account in subscribingByAccount) {
delete subscribingByAccount[account];
}
};
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

Add validation, error handling, and logging for better reliability

The current implementation could be more robust with the following improvements:

  1. Input validation for the account parameter
  2. Error handling for delete operations
  3. Logging for debugging and monitoring
  4. Return value to indicate success/failure

Consider this improved implementation:

-export const deleteSubscribedTopics = (account: string) => {
+export const deleteSubscribedTopics = (account: string): boolean => {
+  if (!account?.trim()) {
+    console.warn('deleteSubscribedTopics: Invalid account parameter');
+    return false;
+  }
+
+  try {
     if (account in subscribedOnceByAccount) {
       delete subscribedOnceByAccount[account];
     }
     if (account in subscribingByAccount) {
       delete subscribingByAccount[account];
     }
+    console.debug(`Successfully deleted subscriptions for account: ${account}`);
+    return true;
+  } catch (error) {
+    console.error('Error deleting subscribed topics:', error);
+    return false;
+  }
 };

Additionally, consider adding unit tests to verify the function's behavior.

Would you like me to help create unit tests for this utility function?

📝 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
export const deleteSubscribedTopics = (account: string) => {
if (account in subscribedOnceByAccount) {
delete subscribedOnceByAccount[account];
}
if (account in subscribingByAccount) {
delete subscribingByAccount[account];
}
};
export const deleteSubscribedTopics = (account: string): boolean => {
if (!account?.trim()) {
console.warn('deleteSubscribedTopics: Invalid account parameter');
return false;
}
try {
if (account in subscribedOnceByAccount) {
delete subscribedOnceByAccount[account];
}
if (account in subscribingByAccount) {
delete subscribingByAccount[account];
}
console.debug(`Successfully deleted subscriptions for account: ${account}`);
return true;
} catch (error) {
console.error('Error deleting subscribed topics:', error);
return false;
}
};

Comment on lines +5 to +12
export const getNotificationsPermissionStatus = async (): Promise<
NotificationPermissionStatus | undefined
> => {
await setupAndroidNotificationChannel();

const { status } = await Notifications.getPermissionsAsync();
return status;
};
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

Add error handling and platform-specific logic

The current implementation has several areas for improvement:

  1. No error handling for setupAndroidNotificationChannel or getPermissionsAsync
  2. Android-specific setup is called on all platforms
  3. Potential race condition with multiple simultaneous calls

Consider this improved implementation:

+import { Platform } from 'react-native';
+
+let setupPromise: Promise<void> | null = null;
+
 export const getNotificationsPermissionStatus = async (): Promise<
   NotificationPermissionStatus | undefined
 > => {
-  await setupAndroidNotificationChannel();
+  try {
+    // Only setup Android channel on Android platform
+    if (Platform.OS === 'android') {
+      // Ensure setup runs only once if multiple calls occur
+      if (!setupPromise) {
+        setupPromise = setupAndroidNotificationChannel();
+      }
+      await setupPromise;
+    }
 
-  const { status } = await Notifications.getPermissionsAsync();
-  return status;
+    const { status } = await Notifications.getPermissionsAsync();
+    return status;
+  } catch (error) {
+    console.error('Failed to get notification permission status:', error);
+    throw error;
+  }
 };
📝 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
export const getNotificationsPermissionStatus = async (): Promise<
NotificationPermissionStatus | undefined
> => {
await setupAndroidNotificationChannel();
const { status } = await Notifications.getPermissionsAsync();
return status;
};
import { Platform } from 'react-native';
let setupPromise: Promise<void> | null = null;
export const getNotificationsPermissionStatus = async (): Promise<
NotificationPermissionStatus | undefined
> => {
try {
// Only setup Android channel on Android platform
if (Platform.OS === 'android') {
// Ensure setup runs only once if multiple calls occur
if (!setupPromise) {
setupPromise = setupAndroidNotificationChannel();
}
await setupPromise;
}
const { status } = await Notifications.getPermissionsAsync();
return status;
} catch (error) {
console.error('Failed to get notification permission status:', error);
throw error;
}
};

Comment on lines +4 to +15
export const saveNotificationsStatus = async () => {
const notificationsStatus = await getNotificationsPermissionStatus();
if (
notificationsStatus === "undetermined" ||
notificationsStatus === "granted" ||
notificationsStatus === "denied"
) {
useAppStore
.getState()
.setNotificationsPermissionStatus(notificationsStatus);
}
};
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

Add error handling and simplify the condition

The function needs error handling and the condition check is redundant since it covers all valid states.

Here's a suggested improvement:

 export const saveNotificationsStatus = async () => {
-  const notificationsStatus = await getNotificationsPermissionStatus();
-  if (
-    notificationsStatus === "undetermined" ||
-    notificationsStatus === "granted" ||
-    notificationsStatus === "denied"
-  ) {
-    useAppStore
-      .getState()
-      .setNotificationsPermissionStatus(notificationsStatus);
-  }
+  try {
+    const notificationsStatus = await getNotificationsPermissionStatus();
+    useAppStore
+      .getState()
+      .setNotificationsPermissionStatus(notificationsStatus);
+  } catch (error) {
+    console.error('Failed to save notification status:', error);
+    // Consider adding error reporting service here
+  }
 };
📝 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
export const saveNotificationsStatus = async () => {
const notificationsStatus = await getNotificationsPermissionStatus();
if (
notificationsStatus === "undetermined" ||
notificationsStatus === "granted" ||
notificationsStatus === "denied"
) {
useAppStore
.getState()
.setNotificationsPermissionStatus(notificationsStatus);
}
};
export const saveNotificationsStatus = async () => {
try {
const notificationsStatus = await getNotificationsPermissionStatus();
useAppStore
.getState()
.setNotificationsPermissionStatus(notificationsStatus);
} catch (error) {
console.error('Failed to save notification status:', error);
// Consider adding error reporting service here
}
};

Comment on lines +698 to +699
const members = await group.members();
saveMemberInboxIds(account, members);
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

Handle errors when fetching group members

The await group.members() call may throw an exception if the promise is rejected. To improve reliability, wrap this call in a try-catch block to handle potential errors.

Comment on lines +714 to +717
[await protocolGroupToStateConversation(account, group)],
true
);
const updatedMembers = await group.membersList();
const updatedMembers = await group.members();
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

Ensure error handling for async calls

  • When calling await protocolGroupToStateConversation, consider adding error handling to manage potential promise rejections.
  • The await group.members() call may fail. Wrap it in a try-catch block to handle exceptions and prevent unhandled errors.

Comment on lines +95 to +99
members.forEach((m) => {
const firstAddress = getCleanAddress(m.addresses[0]);
if (firstAddress) {
groupMembersAddresses.push(firstAddress);
if (m.inboxId === group.creatorInboxId) {
if (m.inboxId === creatorInboxId) {
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

Ensure 'm.addresses[0]' is defined before accessing

When accessing m.addresses[0], there is an assumption that m.addresses contains at least one element. To prevent potential runtime errors, add a check to ensure m.addresses is not empty before accessing the first element.

Apply this diff to handle empty addresses arrays:

 members.forEach((m) => {
+  if (m.addresses.length > 0) {
     const firstAddress = getCleanAddress(m.addresses[0]);
     if (firstAddress) {
       groupMembersAddresses.push(firstAddress);
       if (m.inboxId === creatorInboxId) {
         groupCreator = firstAddress;
       }
       if (m.inboxId === groupAddedByInboxId) {
         groupAddedBy = firstAddress;
       }
     }
+  }
 });
📝 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
members.forEach((m) => {
const firstAddress = getCleanAddress(m.addresses[0]);
if (firstAddress) {
groupMembersAddresses.push(firstAddress);
if (m.inboxId === group.creatorInboxId) {
if (m.inboxId === creatorInboxId) {
members.forEach((m) => {
if (m.addresses.length > 0) {
const firstAddress = getCleanAddress(m.addresses[0]);
if (firstAddress) {
groupMembersAddresses.push(firstAddress);
if (m.inboxId === creatorInboxId) {

Comment on lines +399 to +401
const newMembers = await g.members();
const newMembersSet = new Set(
g.members.map((m) => getCleanAddress(m.addresses[0]))
newMembers.map((m) => getCleanAddress(m.addresses[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

Ensure 'm.addresses[0]' is defined when mapping over 'newMembers'

When creating newMembersSet, accessing m.addresses[0] assumes that m.addresses is not empty. To prevent potential runtime errors, check that m.addresses.length > 0 before accessing the first element.

Apply this diff to handle empty addresses arrays:

 const newMembersSet = new Set(
-  newMembers.map((m) => getCleanAddress(m.addresses[0]))
+  newMembers
+    .filter((m) => m.addresses.length > 0)
+    .map((m) => getCleanAddress(m.addresses[0]))
 );
📝 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
const newMembers = await g.members();
const newMembersSet = new Set(
g.members.map((m) => getCleanAddress(m.addresses[0]))
newMembers.map((m) => getCleanAddress(m.addresses[0]))
const newMembers = await g.members();
const newMembersSet = new Set(
newMembers
.filter((m) => m.addresses.length > 0)
.map((m) => getCleanAddress(m.addresses[0]))

Comment on lines +201 to +208
const members = isGroup
? await (conversation as GroupWithCodecsType).members()
: [];
// Temporary fix to stop receiving messages for groups
// we are not member of
const shouldSkip =
isGroup &&
!(conversation as GroupWithCodecsType).members.some(
!members.some(
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

Handle potential errors and ensure 'addresses[0]' is defined

  • The await call to retrieve members may fail. Consider wrapping it in a try-catch block to handle potential errors.
  • When accessing m.addresses[0] in the some method, ensure that m.addresses is not empty to prevent undefined access errors.

Apply this diff to address these issues:

 let members = [];
 if (isGroup) {
+  try {
     members = await (conversation as GroupWithCodecsType).members();
+  } catch (error) {
+    logger.error('Failed to retrieve group members', error);
+    members = [];
+  }
 }

 const shouldSkip =
   isGroup &&
-  !members.some(
-    (m) => m.addresses[0].toLowerCase() === client.address.toLowerCase()
-  );
+  !members.some(
+    (m) =>
+      m.addresses.length > 0 &&
+      m.addresses[0].toLowerCase() === client.address.toLowerCase()
+  );

Committable suggestion skipped: line range outside the PR's diff.

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

🧹 Outside diff range and nitpick comments (2)
features/notifications/utils/background/notificationContent.ts (2)

13-16: Consider splitting the function into smaller, focused handlers.

The function handles multiple content types which makes it complex and harder to maintain. Consider extracting each content type handler into a separate function.

+const handleTextContent = (content: unknown) => content as string;
+const handleReactionContent = async (
+  group: GroupWithCodecsType,
+  content: ReactionContent
+) => {
+  if (content.action !== 'added' || content.schema !== 'unicode') return;
+  const isFromMe = await isGroupMessageFromMe(group.client, content.reference);
+  if (!isFromMe) return;
+  return content.content ? `Reacted ${content.content} to a message` : "Reacted to a message";
+};

 export const getNotificationContent = async (
   group: GroupWithCodecsType,
   message: DecodedMessageWithCodecsType
 ) => {
   // ... existing type checks ...
   if (isContentType("text", contentType)) {
-    return messageContent as string;
+    return handleTextContent(messageContent);
   } else if (isContentType("reaction", contentType)) {
-    let { action, reference, schema, content } = messageContent as ReactionContent;
-    // ... existing reaction handling ...
+    return handleReactionContent(group, messageContent as ReactionContent);
   }
   // ... rest of the function ...
 };

22-28: Track the TODO implementation for replies.

The TODO comment indicates pending implementation dependent on an external issue. This should be tracked to ensure it's not forgotten.

Would you like me to create a GitHub issue to track this TODO implementation? I can link it to the referenced XMTP issue for better tracking.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0a48bea and 1e5a9f2.

📒 Files selected for processing (1)
  • features/notifications/utils/background/notificationContent.ts (1 hunks)
🔇 Additional comments (3)
features/notifications/utils/background/notificationContent.ts (3)

1-11: LGTM! Well-organized imports with proper type definitions.

The imports are cleanly structured and all the necessary types are properly imported.


57-63: Previous review comment about error handling is still applicable.

The function needs error handling as suggested in the previous review.


13-55: Verify background notification handling.

Given the PR objectives mention issues with background notifications being overridden, we should verify if this implementation properly handles background scenarios.

✅ Verification successful

Background notification handling is properly implemented

The implementation in getNotificationContent.ts is correctly integrated with the background notification system:

  • The content is properly processed and used by handleGroupMessageNotification.ts which safely displays notifications using Notifee
  • Background notifications are handled through a dedicated task manager on Android (BACKGROUND_NOTIFICATION_TASK)
  • The system properly prevents duplicate notifications by checking sender identity
  • Content type handling is properly integrated with the notification display logic
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for background notification handling patterns
echo "Searching for background notification handlers..."
rg -l "background.*notification|notification.*background" --type ts

# Search for Notifee library usage mentioned in PR objectives
echo "Checking Notifee library integration..."
rg -l "notifee" --type ts

# Look for potential notification override points
echo "Checking for potential notification override points..."
rg "override.*notification|notification.*override" --type ts

Length of output: 814


Script:

#!/bin/bash
# Let's examine the notification handling implementation in detail

# Check the content of background notification handler
echo "=== Background Notification Handler ==="
cat features/notifications/utils/background/handleBackgroundNotification.ts

# Check how notifications are set up
echo -e "\n=== Notification Setup ==="
cat features/notifications/utils/setupAndroidNotificationChannel.ts

# Check how the notification content is used
echo -e "\n=== Group Message Notification ==="
cat features/notifications/utils/background/groupMessageNotification.ts

# Check the main notification utilities
echo -e "\n=== Main Notification Utils ==="
cat features/notifications/utils/index.ts

Length of output: 7087

const supportedContentType = !!getMessageContentType(message.contentTypeId);
if (!supportedContentType) return;
let contentType = message.contentTypeId;
const messageContent = message.content();
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

Add error handling for content() call.

The content() method call could potentially throw an error and should be wrapped in a try-catch block.

-  const messageContent = message.content();
+  let messageContent;
+  try {
+    messageContent = message.content();
+  } catch (error) {
+    console.error('Failed to decode message content:', error);
+    return;
+  }
📝 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
const messageContent = message.content();
let messageContent;
try {
messageContent = message.content();
} catch (error) {
console.error('Failed to decode message content:', error);
return;
}

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

🧹 Outside diff range and nitpick comments (13)
features/notifications/utils/background/alreadyShown.ts (2)

24-24: Consider making MAX_STORED_IDS configurable.

The hard-coded limit of 10 stored IDs might need adjustment based on usage patterns or memory constraints.

-const MAX_STORED_IDS = 10;
+const MAX_STORED_IDS = process.env.MAX_NOTIFICATION_IDS || 10;

1-50: Consider adding a cleanup mechanism for old notification IDs.

The current implementation keeps a fixed number of recent notification IDs, but there's no mechanism to clear old entries based on time. Consider adding a timestamp-based cleanup:

  1. Add timestamps to stored notification IDs
  2. Implement a periodic cleanup job
  3. Clear notifications older than a certain threshold

Example structure:

interface StoredNotification {
  id: string;
  timestamp: number;
}

This would help manage memory usage more effectively and provide better insights into notification patterns.

features/notifications/utils/background/protocolNotification.ts (1)

15-21: Consider enhancing schema validation for better data integrity.

While the current schema provides basic validation, consider these improvements:

  1. Add format validation for contentTopic to ensure it matches expected patterns
  2. Add size limits for message to prevent potential memory issues
  3. Consider using a stricter Ethereum address validation that includes checksum verification

Example enhancement:

 export const ProtocolNotificationSchema = z.object({
   account: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
-  contentTopic: z.string(),
-  message: z.string(),
+  contentTopic: z.string().min(1).max(1000),
+  message: z.string().min(1).max(10000),
 });
features/notifications/utils/background/groupWelcomeNotification.ts (2)

71-71: Track the TODO comment about handling 1:1 DM MLS groups

The TODO comment indicates missing functionality for handling 1:1 DM MLS groups.

Would you like me to create a GitHub issue to track this TODO item? I can help draft the issue with the necessary context and requirements.


49-74: Consider implementing notification retry mechanism

The notification display is a critical operation that should be resilient to temporary failures.

Consider implementing a retry mechanism with exponential backoff for the notifee.displayNotification call, especially since this is a background operation where user experience depends on reliable notification delivery.

I can help provide an implementation of a retry mechanism if you'd like.

features/notifications/utils/background/groupMessageNotification.ts (3)

15-17: Consider extracting the prefix as a constant

The group message prefix could be extracted as a named constant for better maintainability and reusability.

+const GROUP_MESSAGE_PREFIX = "/xmtp/mls/1/g-";
+
 export const isGroupMessageContentTopic = (contentTopic: string) => {
-  return contentTopic.startsWith("/xmtp/mls/1/g-");
+  return contentTopic.startsWith(GROUP_MESSAGE_PREFIX);
 };

48-48: Track TODOs as GitHub issues

There are two TODO comments that should be tracked:

  1. Making inboxId a first-class citizen
  2. Handling 1:1 DM MLS groups

Would you like me to create GitHub issues to track these TODOs?

Also applies to: 89-89


75-90: Consider extracting notification style configuration

The Android notification style configuration is complex and could be moved to a separate utility function for better maintainability.

+const createAndroidNotificationStyle = (
+  groupName: string,
+  groupImage: string | undefined,
+  senderName: string,
+  notificationContent: string,
+  messageSentTime: number
+) => ({
+  type: AndroidStyle.MESSAGING,
+  person: {
+    name: groupName,
+    icon: groupImage,
+  },
+  messages: [
+    {
+      text: `${senderName}: ${notificationContent}`,
+      timestamp: messageSentTime,
+    },
+  ],
+  group: true, // todo => handle 1:1 DM MLS groups
+});
+
 await notifee.displayNotification({
   // ... other config
   android: {
     channelId: androidChannel.id,
     pressAction: {
       id: "default",
     },
     visibility: AndroidVisibility.PUBLIC,
-    style: {
-      type: AndroidStyle.MESSAGING,
-      person: {
-        name: groupName,
-        icon: groupImage,
-      },
-      messages: [
-        {
-          text: `${senderName}: ${notificationContent}`,
-          timestamp: message.sent,
-        },
-      ],
-      group: true,
-    },
+    style: createAndroidNotificationStyle(
+      groupName,
+      groupImage,
+      senderName,
+      notificationContent,
+      message.sent
+    ),
   },
 });
data/helpers/conversations/spamScore.ts (2)

128-144: Consider enhancing spam score computation logic.

The current implementation has several areas for improvement:

  1. Binary scoring might miss nuanced spam patterns
  2. No maximum score limit could lead to unexpected behavior
  3. URL and restricted words have equal weight
  4. URL_REGEX usage could be optimized

Consider this enhanced implementation:

+const MAX_SPAM_SCORE = 5;
+const WEIGHTS = {
+  URL: 1.5,
+  RESTRICTED_WORDS: 2.0
+};

 export const computeMessageContentSpamScore = (
   message: string,
   contentType: string
 ): number => {
   let spamScore: number = 0.0;

-  URL_REGEX.lastIndex = 0;
-
   if (isContentType("text", contentType)) {
-    if (URL_REGEX.test(message)) {
-      spamScore += 1;
+    const urlCount = (message.match(URL_REGEX) || []).length;
+    if (urlCount > 0) {
+      spamScore += Math.min(urlCount * WEIGHTS.URL, MAX_SPAM_SCORE);
     }
     if (containsRestrictedWords(message)) {
-      spamScore += 1;
+      spamScore += WEIGHTS.RESTRICTED_WORDS;
     }
   }

-  return spamScore;
+  return Math.min(spamScore, MAX_SPAM_SCORE);
 };

Line range hint 1-144: Consider implementing a more configurable spam detection system.

The current implementation works but could benefit from a more structured approach:

  1. Consider implementing a rule-based system where spam indicators and their weights can be configured
  2. Add telemetry to track false positives/negatives
  3. Consider using machine learning for more accurate spam detection in the future

Would you like assistance in designing a more extensible spam detection architecture?

features/notifications/utils/background/notificationSpamScore.ts (3)

16-16: Optimize calls to refreshConsentList

Calling await xmtpClient.contacts.refreshConsentList() in both functions may lead to performance issues if these functions are invoked frequently. Consider refreshing the consent list at a higher level or caching the consent list to avoid unnecessary network calls.

Also applies to: 76-76


39-60: Refactor repeated address checks into a helper function

The loops that check if addresses are denied or allowed are repeated in both functions. Refactor this logic into a shared helper function to improve maintainability and reduce code duplication.

Example helper function:

const checkAddressesForPermission = async (
  xmtpClient: ConverseXmtpClientType,
  addresses: string[]
): Promise<number | null> => {
  for (const address of addresses) {
    if (await xmtpClient.contacts.isDenied(address)) {
      return 1;
    }
  }
  for (const address of addresses) {
    if (await xmtpClient.contacts.isAllowed(address)) {
      return -1;
    }
  }
  return null;
};

Then use this function within your existing code.

Also applies to: 103-115


117-118: Consider logging errors instead of silently ignoring them

Currently, errors caught in the catch block are ignored. For better debugging and monitoring, consider logging the errors to help identify potential issues during runtime.

     } catch (error) {
-      // Ignore errors
+      console.error('Error computing spam score:', error);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1e5a9f2 and f498e1a.

⛔ Files ignored due to path filters (1)
  • ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • data/helpers/conversations/spamScore.ts (3 hunks)
  • features/notifications/utils/background/alreadyShown.ts (1 hunks)
  • features/notifications/utils/background/groupMessageNotification.ts (1 hunks)
  • features/notifications/utils/background/groupWelcomeNotification.ts (1 hunks)
  • features/notifications/utils/background/notificationSpamScore.ts (1 hunks)
  • features/notifications/utils/background/protocolNotification.ts (1 hunks)
🔇 Additional comments (7)
features/notifications/utils/background/protocolNotification.ts (2)

1-13: LGTM! Well-structured imports with clear separation of concerns.

The imports are properly organized and each import serves a specific purpose in the notification handling system.


23-48: Verify potential race conditions in background notification handling.

Given the PR objective mentioning background notifications being overridden, we should verify if there are any race conditions in the notification handling flow.

Let's check for other background notification handlers that might interfere:

✅ Verification successful

Let me gather more information about the notification handling flow to better understand potential race conditions.


Let me check the background notification handlers and their interaction patterns.


Based on the comprehensive analysis of the notification system, I can now provide the final response:

No race conditions found in background notification handling

The notification system has proper safeguards against notification overrides:

  • Duplicate notifications are prevented via notificationAlreadyShown checks
  • Each notification type (group message/welcome) has its own dedicated handler
  • Notifications are properly serialized with async/await patterns
  • The system includes spam protection and sender verification before displaying notifications
  • Platform-specific handling is properly separated (Android/iOS)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other background notification handlers
echo "Searching for other background notification handlers..."
rg -l "background.*notification" --type ts

# Search for Notifee usage
echo "Checking Notifee usage..."
rg "notifee\." --type ts -A 3

# Look for potential notification overrides
echo "Checking for potential notification overrides..."
rg "displayNotification|cancelNotification|removeAllDeliveredNotifications" --type ts

Length of output: 2228


Script:

#!/bin/bash
# Check the content of index.ts to understand the notification setup
echo "Checking notification setup in index.ts..."
cat features/notifications/utils/index.ts

# Look for any concurrent notification handling
echo "Checking for async/await patterns in notification handlers..."
rg "async.*notification" --type ts -A 5

# Check for any notification queuing or management
echo "Checking for notification management patterns..."
rg "notification.*queue|notification.*manager" --type ts

Length of output: 2669


Script:

#!/bin/bash
# Check the background notification handler implementation
echo "Checking background notification handler..."
cat features/notifications/utils/background/handleBackgroundNotification.ts

# Check the implementation of group notification handlers
echo "Checking group notification handlers..."
cat features/notifications/utils/background/groupMessageNotification.ts
cat features/notifications/utils/background/groupWelcomeNotification.ts

# Look for any notification state management
echo "Checking for notification state management..."
rg "notification.*state|notification.*store" --type ts

Length of output: 7738

features/notifications/utils/background/groupWelcomeNotification.ts (1)

11-13: Past review comment is still valid

The previous review comment about adding input validation and JSDoc documentation is still applicable.

features/notifications/utils/background/groupMessageNotification.ts (1)

1-13: LGTM! Well-organized imports

The imports are logically grouped and all are being utilized within the implementation.

data/helpers/conversations/spamScore.ts (2)

9-11: LGTM: Type definition change is appropriate.

The change from interface to type alias is suitable for this simple key-value structure.


Line range hint 97-103: Verify consistent usage of new spam score computation.

The function rename and usage look correct. Let's verify it's consistently used across the codebase.

✅ Verification successful

Let me verify the implementation details of both old and new functions to ensure they are being used correctly and consistently.


Let me try a different approach to check the implementation details.


Function rename and usage is consistent across the codebase

The verification shows that:

  • The old computeSpamScore function was not found in the codebase
  • The new computeMessageContentSpamScore is consistently used in both conversation scoring and group message notifications
  • The other spam score functions (computeSpamScoreGroupWelcome and computeSpamScoreGroupMessage) are separate, specialized functions for group-related notifications that properly integrate with the new computeMessageContentSpamScore
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining uses of old computeSpamScore function
# and verify consistent usage of new computeMessageContentSpamScore

# Check for old function name usage
rg "computeSpamScore[^C]"

# Check all usages of new function
rg "computeMessageContentSpamScore"

Length of output: 2128


Script:

#!/bin/bash
# Get implementation details of both functions
ast-grep --pattern 'export const computeMessageContentSpamScore = ($_) => {
  $$$
}'

ast-grep --pattern 'export const computeSpamScoreGroup$_ = async ($_) => {
  $$$
}'

Length of output: 172


Script:

#!/bin/bash
# Get content of relevant files to analyze implementations
cat data/helpers/conversations/spamScore.ts
cat features/notifications/utils/background/notificationSpamScore.ts

Length of output: 8197

features/notifications/utils/background/notificationSpamScore.ts (1)

65-65: Review the default spam score return value

The function returns 0 when none of the conditions are met. Confirm that returning 0 is the intended behavior, as it might affect the spam assessment logic.

Please ensure that a spam score of 0 appropriately represents messages that don't match any criteria.

Comment on lines +15 to +22
const setShownNotificationIds = (ids: string[]): void => {
try {
const jsonData = JSON.stringify(ids);
mmkv.set("notification-ids", jsonData);
} catch {
// Handle error silently
}
};
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

Improve error handling and add storage key constant.

The current implementation silently ignores errors and has a duplicated storage key:

+const NOTIFICATION_IDS_KEY = "notification-ids";
+
 const setShownNotificationIds = (ids: string[]): void => {
+  if (!Array.isArray(ids) || !ids.every(id => typeof id === 'string')) {
+    console.warn('Invalid notification IDs array');
+    return;
+  }
   try {
     const jsonData = JSON.stringify(ids);
-    mmkv.set("notification-ids", jsonData);
+    mmkv.set(NOTIFICATION_IDS_KEY, jsonData);
   } catch (error) {
-    // Handle error silently
+    console.warn('Failed to store notification IDs:', error);
   }
 };
📝 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
const setShownNotificationIds = (ids: string[]): void => {
try {
const jsonData = JSON.stringify(ids);
mmkv.set("notification-ids", jsonData);
} catch {
// Handle error silently
}
};
const NOTIFICATION_IDS_KEY = "notification-ids";
const setShownNotificationIds = (ids: string[]): void => {
if (!Array.isArray(ids) || !ids.every(id => typeof id === 'string')) {
console.warn('Invalid notification IDs array');
return;
}
try {
const jsonData = JSON.stringify(ids);
mmkv.set(NOTIFICATION_IDS_KEY, jsonData);
} catch (error) {
console.warn('Failed to store notification IDs:', error);
}
};

Comment on lines +3 to +13
const getShownNotificationIds = (): string[] => {
const jsonData = mmkv.getString("notification-ids");
if (jsonData) {
try {
return JSON.parse(jsonData);
} catch {
return [];
}
}
return [];
};
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

Add type safety and validation to JSON parsing.

The current implementation could be improved with better type safety and validation:

-const getShownNotificationIds = (): string[] => {
+const getShownNotificationIds = (): string[] => {
   const jsonData = mmkv.getString("notification-ids");
   if (jsonData) {
     try {
-      return JSON.parse(jsonData);
+      const parsed = JSON.parse(jsonData);
+      if (Array.isArray(parsed) && parsed.every(item => typeof item === 'string')) {
+        return parsed;
+      }
+      console.warn('Invalid notification IDs format in storage');
+      return [];
     } catch (error) {
+      console.warn('Failed to parse notification IDs:', error);
       return [];
     }
   }
   return [];
 };
📝 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
const getShownNotificationIds = (): string[] => {
const jsonData = mmkv.getString("notification-ids");
if (jsonData) {
try {
return JSON.parse(jsonData);
} catch {
return [];
}
}
return [];
};
const getShownNotificationIds = (): string[] => {
const jsonData = mmkv.getString("notification-ids");
if (jsonData) {
try {
const parsed = JSON.parse(jsonData);
if (Array.isArray(parsed) && parsed.every(item => typeof item === 'string')) {
return parsed;
}
console.warn('Invalid notification IDs format in storage');
return [];
} catch (error) {
console.warn('Failed to parse notification IDs:', error);
return [];
}
}
return [];
};

Comment on lines +25 to +50
export const notificationAlreadyShown = (messageId?: string): boolean => {
// Check if the messageId is not undefined/null and not an empty string
if (!messageId?.trim()) {
return true;
}

// If the id already exists in the list, don't show the notification
const existingIds = getShownNotificationIds();
if (existingIds.includes(messageId)) {
return true;
}

// Append the new id to the list
const updatedIds = [...existingIds, messageId];

// If the list exceeds the maximum limit, trim from the beginning
if (updatedIds.length > MAX_STORED_IDS) {
updatedIds.splice(0, updatedIds.length - MAX_STORED_IDS);
}

// Store the updated list of IDs back to storage
setShownNotificationIds(updatedIds);

// If all conditions are met, show the notification
return false;
};
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

Improve function naming and efficiency.

The function does more than checking if a notification was shown - it also manages state. Consider these improvements:

-export const notificationAlreadyShown = (messageId?: string): boolean => {
+export const shouldShowNotification = (messageId?: string): boolean => {
   if (!messageId?.trim()) {
     return true;
   }
 
   const existingIds = getShownNotificationIds();
   if (existingIds.includes(messageId)) {
     return true;
   }
 
-  const updatedIds = [...existingIds, messageId];
+  // More efficient than splice
+  const updatedIds = [...existingIds.slice(-MAX_STORED_IDS + 1), messageId];
 
-  if (updatedIds.length > MAX_STORED_IDS) {
-    updatedIds.splice(0, updatedIds.length - MAX_STORED_IDS);
-  }
-
   setShownNotificationIds(updatedIds);
 
-  return false;
+  return false; // Notification should be shown
 };

Additionally, consider adding a comment about potential race conditions in concurrent scenarios.

📝 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
export const notificationAlreadyShown = (messageId?: string): boolean => {
// Check if the messageId is not undefined/null and not an empty string
if (!messageId?.trim()) {
return true;
}
// If the id already exists in the list, don't show the notification
const existingIds = getShownNotificationIds();
if (existingIds.includes(messageId)) {
return true;
}
// Append the new id to the list
const updatedIds = [...existingIds, messageId];
// If the list exceeds the maximum limit, trim from the beginning
if (updatedIds.length > MAX_STORED_IDS) {
updatedIds.splice(0, updatedIds.length - MAX_STORED_IDS);
}
// Store the updated list of IDs back to storage
setShownNotificationIds(updatedIds);
// If all conditions are met, show the notification
return false;
};
export const shouldShowNotification = (messageId?: string): boolean => {
// Check if the messageId is not undefined/null and not an empty string
if (!messageId?.trim()) {
return true;
}
// If the id already exists in the list, don't show the notification
const existingIds = getShownNotificationIds();
if (existingIds.includes(messageId)) {
return true;
}
// Append the new id to the list
// More efficient than splice
const updatedIds = [...existingIds.slice(-MAX_STORED_IDS + 1), messageId];
// Store the updated list of IDs back to storage
setShownNotificationIds(updatedIds);
// Notification should be shown
return false;
};

Comment on lines +23 to +48
export const handleProtocolNotification = async (
notification: ProtocolNotification
) => {
logger.debug(
`[ProtocolNotification] Received a notification for account ${notification.account}`
);
const accounts = getAccountsList();
if (
!accounts.find(
(a) => a.toLowerCase() === notification.account.toLowerCase()
)
) {
logger.error(
`[ProtocolNotification] Account ${notification.account} not found, skipping`
);
return;
}
const xmtpClient = (await getXmtpClient(
notification.account
)) as ConverseXmtpClientType;
if (isGroupMessageContentTopic(notification.contentTopic)) {
handleGroupMessageNotification(xmtpClient, notification);
} else if (isGroupWelcomeContentTopic(notification.contentTopic)) {
handleGroupWelcomeNotification(xmtpClient, notification);
}
};
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: Add proper error handling and async/await.

Several issues need to be addressed:

  1. Missing error handling for getXmtpClient
  2. Notification handlers are called synchronously without await
  3. No error handling for notification handlers
  4. Unsafe type assertion for XMTP client

Apply these fixes:

 export const handleProtocolNotification = async (
   notification: ProtocolNotification
 ) => {
   logger.debug(
     `[ProtocolNotification] Received a notification for account ${notification.account}`
   );
   const accounts = getAccountsList();
   if (
     !accounts.find(
       (a) => a.toLowerCase() === notification.account.toLowerCase()
     )
   ) {
     logger.error(
       `[ProtocolNotification] Account ${notification.account} not found, skipping`
     );
     return;
   }
-  const xmtpClient = (await getXmtpClient(
-    notification.account
-  )) as ConverseXmtpClientType;
+  let xmtpClient: ConverseXmtpClientType;
+  try {
+    const client = await getXmtpClient(notification.account);
+    if (!client) {
+      throw new Error('Failed to initialize XMTP client');
+    }
+    xmtpClient = client;
+  } catch (error) {
+    logger.error(
+      `[ProtocolNotification] Failed to get XMTP client for ${notification.account}:`,
+      error
+    );
+    return;
+  }
+
   if (isGroupMessageContentTopic(notification.contentTopic)) {
-    handleGroupMessageNotification(xmtpClient, notification);
+    try {
+      await handleGroupMessageNotification(xmtpClient, notification);
+    } catch (error) {
+      logger.error('[ProtocolNotification] Failed to handle group message:', error);
+    }
   } else if (isGroupWelcomeContentTopic(notification.contentTopic)) {
-    handleGroupWelcomeNotification(xmtpClient, notification);
+    try {
+      await handleGroupWelcomeNotification(xmtpClient, notification);
+    } catch (error) {
+      logger.error('[ProtocolNotification] Failed to handle welcome message:', error);
+    }
   }
 };
📝 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
export const handleProtocolNotification = async (
notification: ProtocolNotification
) => {
logger.debug(
`[ProtocolNotification] Received a notification for account ${notification.account}`
);
const accounts = getAccountsList();
if (
!accounts.find(
(a) => a.toLowerCase() === notification.account.toLowerCase()
)
) {
logger.error(
`[ProtocolNotification] Account ${notification.account} not found, skipping`
);
return;
}
const xmtpClient = (await getXmtpClient(
notification.account
)) as ConverseXmtpClientType;
if (isGroupMessageContentTopic(notification.contentTopic)) {
handleGroupMessageNotification(xmtpClient, notification);
} else if (isGroupWelcomeContentTopic(notification.contentTopic)) {
handleGroupWelcomeNotification(xmtpClient, notification);
}
};
export const handleProtocolNotification = async (
notification: ProtocolNotification
) => {
logger.debug(
`[ProtocolNotification] Received a notification for account ${notification.account}`
);
const accounts = getAccountsList();
if (
!accounts.find(
(a) => a.toLowerCase() === notification.account.toLowerCase()
)
) {
logger.error(
`[ProtocolNotification] Account ${notification.account} not found, skipping`
);
return;
}
let xmtpClient: ConverseXmtpClientType;
try {
const client = await getXmtpClient(notification.account);
if (!client) {
throw new Error('Failed to initialize XMTP client');
}
xmtpClient = client;
} catch (error) {
logger.error(
`[ProtocolNotification] Failed to get XMTP client for ${notification.account}:`,
error
);
return;
}
if (isGroupMessageContentTopic(notification.contentTopic)) {
try {
await handleGroupMessageNotification(xmtpClient, notification);
} catch (error) {
logger.error('[ProtocolNotification] Failed to handle group message:', error);
}
} else if (isGroupWelcomeContentTopic(notification.contentTopic)) {
try {
await handleGroupWelcomeNotification(xmtpClient, notification);
} catch (error) {
logger.error('[ProtocolNotification] Failed to handle welcome message:', error);
}
}
};

Comment on lines +15 to +28
const getNewGroup = async (xmtpClient: ConverseXmtpClientType) => {
// Welcome envelopes are too large to send in a push, so sync to get latest group
await xmtpClient.conversations.syncGroups();
const groups = await xmtpClient.conversations.listGroups();
if (groups.length === 0) return;
const mostRecentGroup = groups.reduce((latest, current) => {
if (!latest || current.createdAt > latest.createdAt) {
return current;
}
return latest;
}, groups[0]);
await mostRecentGroup.sync();
return mostRecentGroup;
};
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

Add error handling and improve type safety

The function has potential issues:

  1. Missing error handling for sync operations
  2. The reduce operation might be unsafe if groups array is modified during execution
  3. No type checking for createdAt comparison

Consider applying these improvements:

 const getNewGroup = async (xmtpClient: ConverseXmtpClientType) => {
   // Welcome envelopes are too large to send in a push, so sync to get latest group
-  await xmtpClient.conversations.syncGroups();
-  const groups = await xmtpClient.conversations.listGroups();
+  try {
+    await xmtpClient.conversations.syncGroups();
+    const groups = await xmtpClient.conversations.listGroups();
+    
+    if (groups.length === 0) return;
+    
+    // Create a safe copy of the groups array
+    const safeGroups = [...groups];
+    const mostRecentGroup = safeGroups.reduce((latest, current) => {
+      // Ensure createdAt exists and is a valid date
+      const currentDate = new Date(current.createdAt);
+      const latestDate = latest ? new Date(latest.createdAt) : new Date(0);
+      
+      if (!isNaN(currentDate.getTime()) && currentDate > latestDate) {
+        return current;
+      }
+      return latest;
+    }, safeGroups[0]);
 
-  if (groups.length === 0) return;
-  const mostRecentGroup = groups.reduce((latest, current) => {
-    if (!latest || current.createdAt > latest.createdAt) {
-      return current;
+    await mostRecentGroup.sync();
+    return mostRecentGroup;
+  } catch (error) {
+    console.error('Failed to fetch or sync groups:', error);
+    throw error;
+  }
-    }
-    return latest;
-  }, groups[0]);
-  await mostRecentGroup.sync();
-  return mostRecentGroup;
 };
📝 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
const getNewGroup = async (xmtpClient: ConverseXmtpClientType) => {
// Welcome envelopes are too large to send in a push, so sync to get latest group
await xmtpClient.conversations.syncGroups();
const groups = await xmtpClient.conversations.listGroups();
if (groups.length === 0) return;
const mostRecentGroup = groups.reduce((latest, current) => {
if (!latest || current.createdAt > latest.createdAt) {
return current;
}
return latest;
}, groups[0]);
await mostRecentGroup.sync();
return mostRecentGroup;
};
const getNewGroup = async (xmtpClient: ConverseXmtpClientType) => {
// Welcome envelopes are too large to send in a push, so sync to get latest group
try {
await xmtpClient.conversations.syncGroups();
const groups = await xmtpClient.conversations.listGroups();
if (groups.length === 0) return;
// Create a safe copy of the groups array
const safeGroups = [...groups];
const mostRecentGroup = safeGroups.reduce((latest, current) => {
// Ensure createdAt exists and is a valid date
const currentDate = new Date(current.createdAt);
const latestDate = latest ? new Date(latest.createdAt) : new Date(0);
if (!isNaN(currentDate.getTime()) && currentDate > latestDate) {
return current;
}
return latest;
}, safeGroups[0]);
await mostRecentGroup.sync();
return mostRecentGroup;
} catch (error) {
console.error('Failed to fetch or sync groups:', error);
throw error;
}
};

title: groupName,
subtitle: senderName,
body: notificationContent,
data: notification,
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

Security: Limit sensitive data in notification payload

Passing the entire notification object as data could expose sensitive information.

-    data: notification,
+    data: {
+      contentTopic: notification.contentTopic,
+      messageId: notification.message.id,
+      account: notification.account,
+    },
📝 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
data: notification,
data: {
contentTopic: notification.contentTopic,
messageId: notification.message.id,
account: notification.account,
},

Comment on lines +23 to +31
const groupId = getGroupIdFromTopic(notification.contentTopic);
let group = await xmtpClient.conversations.findGroup(groupId);
if (!group) {
await xmtpClient.conversations.syncGroups();
group = await xmtpClient.conversations.findGroup(groupId);
if (!group) throw new Error("Group not found");
}
await group.sync();
const groupName = await group.groupName();
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

Enhance error handling for group synchronization

The group synchronization could benefit from more robust error handling and logging.

   const groupId = getGroupIdFromTopic(notification.contentTopic);
+  try {
     let group = await xmtpClient.conversations.findGroup(groupId);
     if (!group) {
       await xmtpClient.conversations.syncGroups();
       group = await xmtpClient.conversations.findGroup(groupId);
-      if (!group) throw new Error("Group not found");
+      if (!group) throw new Error(`Group not found for ID: ${groupId}`);
     }
     await group.sync();
     const groupName = await group.groupName();
+  } catch (error) {
+    console.error(`Failed to sync group ${groupId}:`, error);
+    throw error;
+  }
📝 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
const groupId = getGroupIdFromTopic(notification.contentTopic);
let group = await xmtpClient.conversations.findGroup(groupId);
if (!group) {
await xmtpClient.conversations.syncGroups();
group = await xmtpClient.conversations.findGroup(groupId);
if (!group) throw new Error("Group not found");
}
await group.sync();
const groupName = await group.groupName();
const groupId = getGroupIdFromTopic(notification.contentTopic);
try {
let group = await xmtpClient.conversations.findGroup(groupId);
if (!group) {
await xmtpClient.conversations.syncGroups();
group = await xmtpClient.conversations.findGroup(groupId);
if (!group) throw new Error(`Group not found for ID: ${groupId}`);
}
await group.sync();
const groupName = await group.groupName();
} catch (error) {
console.error(`Failed to sync group ${groupId}:`, error);
throw error;
}

Comment on lines +110 to +126
const restrictedWords = [
"Coinbase",
"Airdrop",
"voucher",
"offer",
"restriction",
"Congrats",
"$SHIB",
"$AERO",
];

const containsRestrictedWords = (searchString: string): boolean => {
const lowercasedSearchString = searchString.toLowerCase();
return restrictedWords.some((word) =>
lowercasedSearchString.includes(word.toLowerCase())
);
};
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

Consider improving restricted words implementation.

Several improvements could enhance maintainability and efficiency:

  1. Move restricted words to a configuration file for easier updates
  2. Consider using a more efficient search algorithm for large lists
  3. Review generic terms that might cause false positives (e.g., "offer", "restriction")
  4. Add word boundary checks to prevent partial matches

Consider this implementation:

-const restrictedWords = [
-  "Coinbase",
-  "Airdrop",
-  "voucher",
-  "offer",
-  "restriction",
-  "Congrats",
-  "$SHIB",
-  "$AERO",
-];

-const containsRestrictedWords = (searchString: string): boolean => {
-  const lowercasedSearchString = searchString.toLowerCase();
-  return restrictedWords.some((word) =>
-    lowercasedSearchString.includes(word.toLowerCase())
-  );
-};
+import { RESTRICTED_WORDS } from '../../../config/spam';
+
+const WORD_BOUNDARY = '\\b';
+const restrictedWordsRegex = new RegExp(
+  RESTRICTED_WORDS.map(word => 
+    `${WORD_BOUNDARY}${word}${WORD_BOUNDARY}`
+  ).join('|'),
+  'i'
+);
+
+const containsRestrictedWords = (searchString: string): boolean => {
+  return restrictedWordsRegex.test(searchString);
+};

Committable suggestion skipped: line range outside the PR's diff.

return -1;
}

const inviterInboxId = await group.addedByInboxId;
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

Remove unnecessary await on property access

In line 23, group.addedByInboxId appears to be a property, not a Promise. Using await on non-Promise values is unnecessary and could lead to unexpected behavior.

Apply this diff to fix the issue:

-    const inviterInboxId = await group.addedByInboxId;
+    const inviterInboxId = group.addedByInboxId;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +54 to +58
const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[
inviterAddress
];

return senderSpamScore;
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

Handle potential undefined senderSpamScore

If getSendersSpamScores does not return a spam score for the given inviterAddress, senderSpamScore might be undefined, which could lead to unexpected results when returning it as a number.

Consider providing a default value to ensure senderSpamScore is a number:

         const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[
           inviterAddress
-        ];
+        ] ?? 0;

         return senderSpamScore;
📝 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
const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[
inviterAddress
];
return senderSpamScore;
const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[
inviterAddress
] ?? 0;
return senderSpamScore;

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

🧹 Outside diff range and nitpick comments (1)
features/notifications/utils/background/getGroup.ts (1)

22-23: Consider logging null returns.

Silent failures returning null might make it difficult to track down notification issues in production.

+  if (!group) {
+    console.warn(`Group ${groupId} not found`);
+  }
   return null;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f498e1a and a6a74ee.

📒 Files selected for processing (6)
  • features/notifications/utils/background/converseNotification.ts (1 hunks)
  • features/notifications/utils/background/getGroup.ts (1 hunks)
  • features/notifications/utils/background/groupJoinRequestNotification.ts (1 hunks)
  • features/notifications/utils/background/handleBackgroundNotification.ts (1 hunks)
  • features/notifications/utils/index.ts (1 hunks)
  • utils/api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • features/notifications/utils/background/handleBackgroundNotification.ts
🔇 Additional comments (11)
features/notifications/utils/background/getGroup.ts (2)

1-2: LGTM! Imports are clean and appropriate.

The imports are well-organized and necessary for the function's requirements.


9-13: Verify sync operations in the context of background notifications.

Given the PR's context about background notifications being overridden, we should verify that the sync operations are working as expected and not causing any race conditions.

✅ Verification successful

Sync operations in background notifications are properly coordinated and follow a consistent pattern.

The sync operations are implemented consistently across the codebase:

  • syncGroups() is always called before attempting to find a group
  • Individual group.sync() is called after finding the group
  • The sync pattern in getGroup.ts matches the pattern used in other background notification handlers
  • No race conditions are evident as the operations are properly sequenced with await

The background notification handlers (groupMessageNotification.ts, groupWelcomeNotification.ts, and getGroup.ts) all follow this same synchronization pattern, ensuring consistent group state before processing notifications.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other sync operations that might interfere
rg -A 5 "syncGroups|group\.sync" --type ts

# Look for potential race conditions in notification handling
ast-grep --pattern 'await $_.sync()' 

Length of output: 7595

features/notifications/utils/background/converseNotification.ts (2)

16-16: LGTM!

Good use of type inference from the Zod schema.


1-41: Verify integration with Notifee and background handling.

The PR objectives mention issues with background notifications being overridden and problems with Notifee integration. Let's verify how this implementation connects to the notification system.

✅ Verification successful

Let me gather more information about the notification handling implementation to better understand the integration.


Let me check the background notification handler to complete the verification.


Notification system is properly integrated with no override issues

The implementation shows a well-structured notification system with proper handling of background, foreground, and interaction states:

  • Background notifications are correctly handled through both Expo's task manager and Notifee
  • Notifications are properly channeled and don't override each other:
    • Protocol notifications are handled by XMTP SDK when foregrounded
    • Converse notifications use dedicated handlers for different types
    • Different notification channels for Android
  • Proper integration with Notifee:
    • Custom Android channel setup
    • Background event handling
    • Notification display with proper configurations

The code in review (converseNotification.ts) fits well into this architecture and doesn't introduce any notification override issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Notifee integration and background notification handling
echo "Searching for Notifee configuration and background handlers..."
rg -l "@notifee|notifee" --type ts

echo "\nSearching for background notification setup..."
rg "onBackgroundEvent|setBackgroundMessageHandler" --type ts

echo "\nSearching for notification override patterns..."
rg "displayNotification|cancelNotification|removeAllDeliveredNotifications" --type ts

Length of output: 1311


Script:

#!/bin/bash
# Check the notification setup and background handling
echo "Checking notification index implementation..."
cat features/notifications/utils/index.ts

echo "\nChecking group join request notification implementation..."
cat features/notifications/utils/background/groupJoinRequestNotification.ts

echo "\nChecking Android notification channel setup..."
cat features/notifications/utils/setupAndroidNotificationChannel.ts

Length of output: 6062


Script:

#!/bin/bash
echo "Checking background notification handler..."
cat features/notifications/utils/background/handleBackgroundNotification.ts

echo "\nChecking foreground notification handler..."
cat features/notifications/utils/handleForegroundNotification.ts

echo "\nChecking notification interaction handler..."
cat features/notifications/utils/onInteractWithNotification.ts

Length of output: 5526

features/notifications/utils/index.ts (5)

13-17: LGTM! Clear and well-documented foreground notification setup.

The implementation follows Expo's recommended pattern for handling foreground notifications.


19-23: LGTM! Clear and well-documented notification interaction setup.

The implementation follows Expo's recommended pattern for handling notification interactions.


1-11: Verify potential conflicts between notification libraries

Using both expo-notifications and @notifee/react-native could lead to notification conflicts or race conditions. Ensure that these libraries don't compete for handling the same notifications.


27-59: Verify background notification handling

Given the PR objectives mentioning issues with background notifications being overridden, let's verify the implementation:

✅ Verification successful

Background notification handling implementation is correctly isolated

The verification shows that:

  • There is only one background notification task definition (BACKGROUND_NOTIFICATION_TASK) in the codebase, defined in features/notifications/utils/index.ts
  • The notifee.onBackgroundEvent handler is properly implemented and documented
  • Android notification channel is configured separately in setupAndroidNotificationChannel.ts
  • No conflicting or overriding background notification handlers were found

The implementation follows a clean separation between Android and iOS handling, with proper error logging and task registration.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other background notification handlers that might override this one
rg -t ts -t tsx --multiline -U "onBackgroundEvent|defineTask.*BACKGROUND.*NOTIFICATION" -A 5

# Search for Notifee channel configurations that might affect notification behavior
rg -t ts -t tsx "notifee\.createChannel|notifee\.getChannel"

Length of output: 213


Script:

#!/bin/bash
# Search for other background notification handlers that might override this one
rg -t typescript -t javascript "onBackgroundEvent|defineTask.*BACKGROUND.*NOTIFICATION" -A 5

# Search for Notifee channel configurations that might affect notification behavior
rg -t typescript -t javascript "notifee\.createChannel|notifee\.getChannel"

# Search for any other background notification task definitions
rg -t typescript -t javascript "BACKGROUND_NOTIFICATION_TASK" -B 2 -A 2

Length of output: 349


Script:

#!/bin/bash
# Search for background notification handlers without file type restrictions
rg "onBackgroundEvent|defineTask.*BACKGROUND.*NOTIFICATION" -A 5

# Search for Notifee channel configurations
rg "notifee\.createChannel|notifee\.getChannel"

# Search for background notification task definitions and usages
rg "BACKGROUND_NOTIFICATION_TASK" -B 2 -A 2

Length of output: 2690


28-41: ⚠️ Potential issue

Add type safety and error handling for notification data

The current implementation has several potential issues:

  1. Unsafe type casting with as any
  2. Complex data extraction logic with fallbacks
  3. No validation for extracted notification body

Consider applying this safer implementation:

-  defineTask(BACKGROUND_NOTIFICATION_TASK, async ({ data, error }) => {
+  defineTask(BACKGROUND_NOTIFICATION_TASK, async ({ data, error }: { data: unknown; error?: Error }) => {
     if (error) {
       logger.error(
         `${Platform.OS} ${BACKGROUND_NOTIFICATION_TASK}: Error! ${JSON.stringify(
           error
         )}`
       );
       return;
     }
-    const notificationBody =
-      ((data as any).notification.data.body as string | undefined) || // Protocol notifications use body (legacy, to support older versions of the app)
-      ((data as any).notification.data.data as string | undefined); // Converse notifications use data (legacy, to support older versions of the app)
+    if (!data || typeof data !== 'object') {
+      logger.error('Invalid notification data received');
+      return;
+    }
+    const notification = (data as any).notification;
+    if (!notification?.data) {
+      logger.error('Missing notification data');
+      return;
+    }
+    const notificationBody = notification.data.body || notification.data.data;
+    if (!notificationBody || typeof notificationBody !== 'string') {
+      logger.error('Invalid notification body format');
+      return;
+    }
     handleBackgroundNotification(notificationBody);
   });

Likely invalid or redundant comment.

utils/api.ts (1)

556-573: Verify notification handling for group join requests

Given that this PR addresses background notification issues in Android, please ensure that notifications triggered by group join request status updates are properly handled.

Let's verify the notification-related code:

Consider adding documentation to describe the notification flow for group join requests, especially regarding:

  • When notifications are triggered
  • How they are handled in background vs foreground
  • Any Android-specific handling
✅ Verification successful

Group join request notifications are properly implemented

The verification shows that group join request notifications are well-handled:

  • Proper type definitions exist for group join requests and their statuses
  • Background notification handling is implemented in features/notifications/utils/background/groupJoinRequestNotification.ts
  • Notifications are properly displayed using notifee.displayNotification with appropriate Android channel configuration
  • The notification system handles different group-related events (join requests, messages, welcome) separately
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for notification handling related to group join requests
echo "Searching for notification handlers..."
rg -l "notification.*group.*join|group.*join.*notification" --type ts

echo "Checking Notifee configuration..."
rg -l "notifee\.configure|NotifeeSettings" --type ts

echo "Checking background notification handlers..."
rg -l "onBackgroundEvent|backgroundMessageHandler" --type ts

Length of output: 554


Script:

#!/bin/bash
# Let's examine the notification handling implementation
echo "=== Checking converseNotification.ts for group join handling ==="
rg "group.*join" features/notifications/utils/background/converseNotification.ts -A 5 -B 5

echo -e "\n=== Checking background notification handler implementation ==="
rg "onBackgroundEvent|backgroundMessageHandler" features/notifications/utils/index.ts -A 10 -B 2

echo -e "\n=== Looking for group join request status handling ==="
rg "groupJoinRequest.*status" -A 5 -B 5

Length of output: 1185


Script:

#!/bin/bash
# Let's check the implementation of handleGroupJoinRequestNotification
echo "=== Checking handleGroupJoinRequestNotification implementation ==="
ast-grep --pattern 'function handleGroupJoinRequestNotification($_) { $$$ }'

# Let's also check for any group join request related types or interfaces
echo -e "\n=== Checking for group join request types ==="
rg "type.*GroupJoinRequest|interface.*GroupJoinRequest" --type ts -A 5

# Let's look for any notification display logic
echo -e "\n=== Checking notification display logic ==="
rg "displayNotification|showNotification|createNotification" --type ts -A 5

Length of output: 6829

features/notifications/utils/background/groupJoinRequestNotification.ts (1)

9-16: Schema definition is well-structured and validates notification data effectively.

The GroupJoinRequestNotificationSchema correctly defines the expected notification structure and includes validation for Ethereum addresses using regex patterns.

Comment on lines +4 to +7
export const getGroup = async (
xmtpClient: ConverseXmtpClientType,
groupId: string
) => {
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

Add input validation and improve type safety.

The function should validate the groupId parameter and explicitly type its return value.

+import { Group } from "@utils/xmtpRN/types";
+
 export const getGroup = async (
   xmtpClient: ConverseXmtpClientType,
-  groupId: string
-) => {
+  groupId: string
+): Promise<Group | null> => {
+  if (!groupId?.trim()) {
+    throw new Error("Invalid groupId provided");
+  }
📝 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
export const getGroup = async (
xmtpClient: ConverseXmtpClientType,
groupId: string
) => {
import { Group } from "@utils/xmtpRN/types";
export const getGroup = async (
xmtpClient: ConverseXmtpClientType,
groupId: string
): Promise<Group | null> => {
if (!groupId?.trim()) {
throw new Error("Invalid groupId provided");
}

Comment on lines +8 to +21
try {
await xmtpClient.conversations.syncGroups();
const group = await xmtpClient.conversations.findGroup(groupId);
if (group) {
await group.sync();
return group;
}
} catch (error) {
sentryTrackError(error, {
extras: {
message: "Could not get or sync group",
},
});
}
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

Improve error handling granularity.

The current error handling doesn't distinguish between different types of failures (sync failure vs. group not found). This could make debugging more difficult, especially in the context of background notification issues.

   try {
-    await xmtpClient.conversations.syncGroups();
+    await xmtpClient.conversations.syncGroups().catch((error) => {
+      throw new Error(`Failed to sync groups: ${error.message}`);
+    });
+
     const group = await xmtpClient.conversations.findGroup(groupId);
     if (group) {
-      await group.sync();
+      await group.sync().catch((error) => {
+        throw new Error(`Failed to sync group ${groupId}: ${error.message}`);
+      });
       return group;
     }
   } catch (error) {
     sentryTrackError(error, {
       extras: {
-        message: "Could not get or sync group",
+        message: `Could not get or sync group ${groupId}`,
+        operation: error.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
try {
await xmtpClient.conversations.syncGroups();
const group = await xmtpClient.conversations.findGroup(groupId);
if (group) {
await group.sync();
return group;
}
} catch (error) {
sentryTrackError(error, {
extras: {
message: "Could not get or sync group",
},
});
}
try {
await xmtpClient.conversations.syncGroups().catch((error) => {
throw new Error(`Failed to sync groups: ${error.message}`);
});
const group = await xmtpClient.conversations.findGroup(groupId);
if (group) {
await group.sync().catch((error) => {
throw new Error(`Failed to sync group ${groupId}: ${error.message}`);
});
return group;
}
} catch (error) {
sentryTrackError(error, {
extras: {
message: `Could not get or sync group ${groupId}`,
operation: error.message,
},
});
}

Comment on lines +11 to +14
// Add other converse notifications here, with different "type" values
export const ConverseNotificationSchema = z.discriminatedUnion("type", [
GroupJoinRequestNotificationSchema,
]);
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

Consider adding schema validation error handling.

The discriminated union schema is well-structured for extensibility. However, there's no explicit error handling for schema validation failures.

Consider wrapping the schema validation in a try-catch block when parsing notifications:

export const parseNotification = (data: unknown): ConverseNotification => {
  try {
    return ConverseNotificationSchema.parse(data);
  } catch (error) {
    logger.error('[ConverseNotification] Invalid notification format:', error);
    throw new Error('Invalid notification format');
  }
};

Comment on lines +18 to +41
export const handleConverseNotification = async (
notification: ConverseNotification
) => {
logger.debug(
`[ConverseNotification] Received a ${notification.type} notification for account ${notification.account}`
);
const accounts = getAccountsList();
if (
!accounts.find(
(a) => a.toLowerCase() === notification.account.toLowerCase()
)
) {
logger.error(
`[ConverseNotification] Account ${notification.account} not found, skipping`
);
return;
}
const xmtpClient = (await getXmtpClient(
notification.account
)) as ConverseXmtpClientType;
if (notification.type === "group_join_request") {
handleGroupJoinRequestNotification(xmtpClient, notification);
}
};
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 issues in notification handling implementation.

Several issues need to be addressed:

  1. Missing await on notification handler
  2. No error handling for XMTP client initialization
  3. No handling for unknown notification types
  4. Using type assertion instead of runtime validation

Apply these changes to fix the issues:

 export const handleConverseNotification = async (
   notification: ConverseNotification
 ) => {
   logger.debug(
     `[ConverseNotification] Received a ${notification.type} notification for account ${notification.account}`
   );
   const accounts = getAccountsList();
   if (
     !accounts.find(
       (a) => a.toLowerCase() === notification.account.toLowerCase()
     )
   ) {
     logger.error(
       `[ConverseNotification] Account ${notification.account} not found, skipping`
     );
     return;
   }
-  const xmtpClient = (await getXmtpClient(
-    notification.account
-  )) as ConverseXmtpClientType;
+  let xmtpClient: ConverseXmtpClientType;
+  try {
+    xmtpClient = await getXmtpClient(notification.account);
+    if (!xmtpClient) {
+      throw new Error('Failed to initialize XMTP client');
+    }
+  } catch (error) {
+    logger.error('[ConverseNotification] Failed to get XMTP client:', error);
+    return;
+  }
+
   if (notification.type === "group_join_request") {
-    handleGroupJoinRequestNotification(xmtpClient, notification);
+    try {
+      await handleGroupJoinRequestNotification(xmtpClient, notification);
+    } catch (error) {
+      logger.error('[ConverseNotification] Failed to handle group join request:', error);
+    }
+  } else {
+    logger.warn(`[ConverseNotification] Unknown notification type: ${notification.type}`);
   }
 };
📝 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
export const handleConverseNotification = async (
notification: ConverseNotification
) => {
logger.debug(
`[ConverseNotification] Received a ${notification.type} notification for account ${notification.account}`
);
const accounts = getAccountsList();
if (
!accounts.find(
(a) => a.toLowerCase() === notification.account.toLowerCase()
)
) {
logger.error(
`[ConverseNotification] Account ${notification.account} not found, skipping`
);
return;
}
const xmtpClient = (await getXmtpClient(
notification.account
)) as ConverseXmtpClientType;
if (notification.type === "group_join_request") {
handleGroupJoinRequestNotification(xmtpClient, notification);
}
};
export const handleConverseNotification = async (
notification: ConverseNotification
) => {
logger.debug(
`[ConverseNotification] Received a ${notification.type} notification for account ${notification.account}`
);
const accounts = getAccountsList();
if (
!accounts.find(
(a) => a.toLowerCase() === notification.account.toLowerCase()
)
) {
logger.error(
`[ConverseNotification] Account ${notification.account} not found, skipping`
);
return;
}
let xmtpClient: ConverseXmtpClientType;
try {
xmtpClient = await getXmtpClient(notification.account);
if (!xmtpClient) {
throw new Error('Failed to initialize XMTP client');
}
} catch (error) {
logger.error('[ConverseNotification] Failed to get XMTP client:', error);
return;
}
if (notification.type === "group_join_request") {
try {
await handleGroupJoinRequestNotification(xmtpClient, notification);
} catch (error) {
logger.error('[ConverseNotification] Failed to handle group join request:', error);
}
} else {
logger.warn(`[ConverseNotification] Unknown notification type: ${notification.type}`);
}
};

Comment on lines +53 to +58
notifee.onBackgroundEvent(async () => {
// This will get triggered for all background events,
// including displaying & clicking on notifications
// which is currently handled in onInteractWithNotification
// However this will silence the notifee warning so keeping it
});
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

Implement proper background event handling

The current empty Notifee background event handler might miss important events. Consider implementing proper event handling or documenting why events are intentionally ignored.

-  notifee.onBackgroundEvent(async () => {
+  notifee.onBackgroundEvent(async ({ type, detail }) => {
     // This will get triggered for all background events,
     // including displaying & clicking on notifications
     // which is currently handled in onInteractWithNotification
-    // However this will silence the notifee warning so keeping it
+    logger.debug(`Notifee background event: ${type}`, detail);
+    // TODO: Implement specific handling for different event types if needed
   });
📝 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
notifee.onBackgroundEvent(async () => {
// This will get triggered for all background events,
// including displaying & clicking on notifications
// which is currently handled in onInteractWithNotification
// However this will silence the notifee warning so keeping it
});
notifee.onBackgroundEvent(async ({ type, detail }) => {
// This will get triggered for all background events,
// including displaying & clicking on notifications
// which is currently handled in onInteractWithNotification
logger.debug(`Notifee background event: ${type}`, detail);
// TODO: Implement specific handling for different event types if needed
});

Comment on lines +556 to +573
export const putGroupInviteRequest = async ({
account,
status,
joinRequestId,
}: {
account: string;
status: string;
joinRequestId: string;
}): Promise<void> => {
const { data } = await api.put(
`/api/groupJoinRequest/${joinRequestId}`,
{ status },
{
headers: await getXmtpApiHeaders(account),
}
);
return data;
};
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

Consolidate duplicate group join request functionality

The new putGroupInviteRequest function appears to duplicate the functionality of the existing updateGroupJoinRequestStatus function (lines 482-493). Consider the following improvements:

  1. Use the existing GroupJoinRequestStatus type for the status parameter:
-  status: string;
+  status: GroupJoinRequestStatus;
  1. Consider removing this function and using updateGroupJoinRequestStatus instead, or if this function is needed for a specific use case, ensure consistent return types:
-}): Promise<void> => {
+}): Promise<Pick<GroupJoinRequest, "status" | "id">> => {
   const { data } = await api.put(
     `/api/groupJoinRequest/${joinRequestId}`,
     { status },
     {
       headers: await getXmtpApiHeaders(account),
     }
   );
   return data;

This will help maintain consistency and prevent potential issues with type mismatches.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 77 to 83
sentryTrackMessage("No client found for account", {
extras: {
account: notification.account,
},
});

return false; // Don't show notification if any error occurs
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

Update error message to accurately reflect when a group is not found.

The message "No client found for account" may be misleading, as the client is provided. If the group is not found after calling getGroup, consider updating the error message to reflect that the group was not found.

Apply this diff to update the error message and include groupId for better debugging:

-sentryTrackMessage("No client found for account", {
+sentrtTrackMessage("Group not found for groupId", {
   extras: {
-    account: notification.account,
+    groupId: groupId,
+    account: notification.account,
   },
 });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 22 to 84
export const handleGroupJoinRequestNotification = async (
client: ConverseXmtpClientType,
notification: GroupJoinRequestNotification
) => {
const groupId = mmkv.getString(
`group-invites-link-${notification.groupInviteId}`
);

// Don't handle if stored on different device
if (!groupId) {
return false;
}

try {
const group = await getGroup(client, groupId);

if (group) {
await group.addMembers([notification.address]);
try {
await putGroupInviteRequest({
account: notification.account,
status: "ACCEPTED",
joinRequestId: notification.joinRequestId,
});
} catch (error) {
sentryTrackError(error, {
extras: {
message: "PUT_GROUP_INVITE_REQUEST_FAILED",
},
});
}

return; // Don't show notification on success
}
} catch (error) {
sentryTrackError(error, {
extras: {
message: "Could not add member to group",
},
});
// Show notification on error
await notifee.displayNotification({
title: "New Group Join Request",
data: notification,
android: {
channelId: androidChannel.id,
pressAction: {
id: "default",
},
visibility: AndroidVisibility.PUBLIC,
},
});
return;
}

sentryTrackMessage("No client found for account", {
extras: {
account: notification.account,
},
});

return false; // Don't show notification if any error occurs
};
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

Ensure consistent return values for handleGroupJoinRequestNotification function.

The function currently has inconsistent return statements, sometimes returning false and other times returning undefined. For clarity and maintainability, consider standardizing the return statements to consistently indicate whether a notification should be shown.

Apply this diff to make return values consistent:

At line 54:

-      return; // Don't show notification on success
+      return false; // Don't show notification on success

At line 74:

-    return;
+    return true; // Notification displayed due to error

At line 83:

-  return false; // Don't show notification if any error occurs
+  return true; // Notification displayed due to error

Additionally, update the function signature to specify the return type explicitly:

-export const handleGroupJoinRequestNotification = async (
+export const handleGroupJoinRequestNotification = async (
   client: ConverseXmtpClientType,
   notification: GroupJoinRequestNotification
-) => {
+) : Promise<boolean> => {

This ensures that the function consistently returns a boolean, improving readability and reducing potential confusion about the function's behavior.

📝 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
export const handleGroupJoinRequestNotification = async (
client: ConverseXmtpClientType,
notification: GroupJoinRequestNotification
) => {
const groupId = mmkv.getString(
`group-invites-link-${notification.groupInviteId}`
);
// Don't handle if stored on different device
if (!groupId) {
return false;
}
try {
const group = await getGroup(client, groupId);
if (group) {
await group.addMembers([notification.address]);
try {
await putGroupInviteRequest({
account: notification.account,
status: "ACCEPTED",
joinRequestId: notification.joinRequestId,
});
} catch (error) {
sentryTrackError(error, {
extras: {
message: "PUT_GROUP_INVITE_REQUEST_FAILED",
},
});
}
return; // Don't show notification on success
}
} catch (error) {
sentryTrackError(error, {
extras: {
message: "Could not add member to group",
},
});
// Show notification on error
await notifee.displayNotification({
title: "New Group Join Request",
data: notification,
android: {
channelId: androidChannel.id,
pressAction: {
id: "default",
},
visibility: AndroidVisibility.PUBLIC,
},
});
return;
}
sentryTrackMessage("No client found for account", {
extras: {
account: notification.account,
},
});
return false; // Don't show notification if any error occurs
};
export const handleGroupJoinRequestNotification = async (
client: ConverseXmtpClientType,
notification: GroupJoinRequestNotification
) : Promise<boolean> => {
const groupId = mmkv.getString(
`group-invites-link-${notification.groupInviteId}`
);
// Don't handle if stored on different device
if (!groupId) {
return false;
}
try {
const group = await getGroup(client, groupId);
if (group) {
await group.addMembers([notification.address]);
try {
await putGroupInviteRequest({
account: notification.account,
status: "ACCEPTED",
joinRequestId: notification.joinRequestId,
});
} catch (error) {
sentryTrackError(error, {
extras: {
message: "PUT_GROUP_INVITE_REQUEST_FAILED",
},
});
}
return false; // Don't show notification on success
}
} catch (error) {
sentryTrackError(error, {
extras: {
message: "Could not add member to group",
},
});
// Show notification on error
await notifee.displayNotification({
title: "New Group Join Request",
data: notification,
android: {
channelId: androidChannel.id,
pressAction: {
id: "default",
},
visibility: AndroidVisibility.PUBLIC,
},
});
return true; // Notification displayed due to error
}
sentryTrackMessage("No client found for account", {
extras: {
account: notification.account,
},
});
return true; // Notification displayed due to error
};

@nmalzieu nmalzieu marked this pull request as ready for review November 27, 2024 16:44
@nmalzieu nmalzieu requested a review from a team as a code owner November 27, 2024 16:44
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

🧹 Outside diff range and nitpick comments (2)
features/notifications/utils/background/groupJoinRequestNotification.ts (2)

64-73: Enhance the notification content for better user guidance

When displaying the notification on error, consider adding more informative content or actionable steps to guide the user on resolving the issue.

For example, update the notification to include details about the error or instructions to try again:

         title: "New Group Join Request",
+        body: "There was an error processing the group join request. Please try again.",
         data: notification,

This can improve the user experience by providing clarity on what happened and what to do next.


3-3: Handle potential exceptions when accessing MMKV storage

When retrieving data from MMKV at line 26, consider handling potential exceptions that may occur if MMKV encounters issues. While MMKV generally handles storage efficiently, it's good practice to include error handling for robustness.

For example:

-const groupId = mmkv.getString(
+let groupId;
+try {
+  groupId = mmkv.getString(
     `group-invites-link-${notification.groupInviteId}`
-);
+  );
+} catch (e) {
+  sentryTrackError(e, {
+    extras: {
+      message: "Failed to retrieve groupId from MMKV",
+    },
+  });
+  return false; // Don't show notification due to storage error
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a6a74ee and ec3e41a.

📒 Files selected for processing (2)
  • features/notifications/utils/background/groupJoinRequestNotification.ts (1 hunks)
  • theme/useAppTheme.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • theme/useAppTheme.ts
🔇 Additional comments (4)
features/notifications/utils/background/groupJoinRequestNotification.ts (4)

77-81: Update error message to accurately reflect the encountered issue

The message "No client found for account" may be misleading, as the client is provided. If the group is not found or another error occurred, consider updating the error message to reflect the actual issue.

Apply this diff to update the error message and include groupId for better debugging:

-sentryTrackMessage("No client found for account", {
+sentryTrackMessage("Group not found or error occurred", {
+  extras: {
+    groupId: groupId,
     account: notification.account,
   },
 });

22-82: Ensure consistent return values for handleGroupJoinRequestNotification function

The function currently has inconsistent return statements, sometimes returning undefined and other times not returning anything explicitly. For clarity and maintainability, consider standardizing the return statements to consistently indicate whether a notification should be shown.

Apply this diff to make return values consistent:

At line 32:

       return;
+      return false; // Don't show notification when groupId is not found

At line 54:

       return; // Don't show notification on success
+      return false; // Don't show notification on success

At line 74:

       return;
+      return true; // Notification displayed due to error

Additionally, update the function signature to specify the return type explicitly:

-export const handleGroupJoinRequestNotification = async (
+export const handleGroupJoinRequestNotification = async (
    client: ConverseXmtpClientType,
    notification: GroupJoinRequestNotification
-) => {
+) : Promise<boolean> => {

This ensures that the function consistently returns a boolean, improving readability and reducing potential confusion about the function's behavior.


38-39: Ensure proper error handling when adding members to the group

If group.addMembers([notification.address]) fails, the error is caught in the catch block starting at line 56. However, consider whether the current error handling is sufficient. Should the user be notified of the failure to add a member? Is there a retry mechanism or alternative flow?

Please confirm whether additional error handling or user notification is needed when adding members fails.


46-52: Verify the impact of failing to update the group invite request status

In the catch block for putGroupInviteRequest, the error is logged to Sentry, but the function continues without notifying the user or taking additional action. It's important to verify whether failing to update the invite request status has any adverse effects on the application's functionality or user experience.

Run the following script to check for any dependencies or side effects related to putGroupInviteRequest:

This will help determine if additional error handling or user notification is necessary when the API call fails.

✅ Verification successful

This will help us understand:

  1. The API implementation details
  2. Whether there are UI components handling group notifications
  3. How similar errors are handled elsewhere in the codebase

This will help us understand:

  1. The complete notification flow and error handling strategy
  2. Whether notifications are intentionally suppressed in certain scenarios
  3. The broader notification display patterns in the codebase

No issue with current error handling approach

The current implementation is correct and intentional. Here's why:

  1. The function follows a clear notification strategy:

    • On successful group member addition, no notification is shown (explicit comment: "Don't show notification on success")
    • On group-related errors, a notification is displayed to the user
    • The putGroupInviteRequest failure is logged but doesn't affect the user experience since the member was already added successfully
  2. The error handling is appropriate because:

    • The primary action (adding member to group) has already succeeded when putGroupInviteRequest is called
    • The status update is a secondary operation that doesn't impact the user's ability to interact with the group
    • Errors are properly tracked in Sentry for monitoring and debugging
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of putGroupInviteRequest and analyze error handling.

# Search for all occurrences of putGroupInviteRequest in the codebase
rg 'putGroupInviteRequest' -A 5

# Optionally, check for any documentation or comments regarding its importance
rg 'putGroupInviteRequest' --type markdown --type yaml --type toml

Length of output: 3087


Script:

#!/bin/bash
# Let's analyze the API implementation and its usage context

# Check the API implementation
rg -A 10 "export const putGroupInviteRequest" utils/api.ts

# Check for any UI components or handlers that might show errors to users
rg -B 5 -A 10 "group.*notification" --type tsx --type ts

# Look for any group-related error handling patterns
rg "PUT_GROUP_INVITE_REQUEST_FAILED" -B 5 -A 5

Length of output: 3824


Script:

#!/bin/bash
# Let's examine the broader context of the notification handling

# Check the entire groupJoinRequestNotification file
cat features/notifications/utils/background/groupJoinRequestNotification.ts

# Look for any notification display or error handling patterns
rg -B 5 -A 5 "Don't show notification" --type ts
rg -B 5 -A 5 "showNotification" --type ts

Length of output: 9036

@alexrisch alexrisch merged commit ee7bec8 into release/3.0.0 Nov 27, 2024
6 of 7 checks passed
@alexrisch alexrisch deleted the ar/android-js-notifications branch November 27, 2024 20:37
@nmalzieu nmalzieu restored the ar/android-js-notifications branch December 12, 2024 14:09
technoplato pushed a commit that referenced this pull request Dec 17, 2024
* feat: Emoji Picker Performance Improvements (#1210)

* feat: Emoji Picker Performance Improvements

Moves Emoji Picker to bottom sheet design system
Improves performance for filtering emojis

* Update to use Design System

* feat: JS Android Notifications

WIP
Need to figure out background notifications, they are getting overridden somewhere and not handled by notifee

* Working wake up android JS for notification

* feat: Dual Sending

Bumps SDK to support dual sending

* Handle notification display & interaction for Android in JS

* 2.8.4 in Podfile

* Fix group foreach

* Multiple content types

* comment for replies

* Handling new group notification and already show

* Group invite notifications in JS

* Remove debug code / failing test

* return value

---------

Co-authored-by: Noé Malzieu <[email protected]>
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.

2 participants