-
Notifications
You must be signed in to change notification settings - Fork 7
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
Conversation
Performance Comparison ReportSignificant Changes To DurationThere are no entries Meaningless Changes To DurationShow entries
Show details
Render Count ChangesThere are no entries Render IssuesThere are no entries Added ScenariosThere are no entries Removed ScenariosThere are no entries |
fcfdee4
to
cffc384
Compare
* feat: Emoji Picker Performance Improvements Moves Emoji Picker to bottom sheet design system Improves performance for filtering emojis * Update to use Design System
23fdf43
to
07b66ae
Compare
WIP Need to figure out background notifications, they are getting overridden somewhere and not handled by notifee
Bumps SDK to support dual sending
667bcae
to
7d8192c
Compare
WalkthroughThis 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
Possibly related PRs
Suggested reviewers
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 identifiersTo 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 constantThe 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 UXTo improve the user experience and reduce unnecessary permission checks:
- Consider caching the permission status with a TTL
- Implement a retry mechanism for failed permission checks
- 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 safetyConsider 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:
- Documenting how this signing utility relates to the notification system
- Adding comments explaining the authentication flow for notifications
- Ensuring the signing implementation aligns with notification security requirements
features/notifications/utils/setupAndroidNotificationChannel.ts (2)
5-8
: Consider enhancing the channel configurationThe 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 codeThe 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 clarityThe 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 mechanismGiven 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:
- Store notifications in AsyncStorage before processing
- Implement a notification queue with retry mechanism
- 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:
- Maximum length constraints for
contentTopic
andmessage
to prevent potential memory issues- 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 implicationsGiven 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:
- Documenting the relationship between installations and notification handling
- Adding telemetry to track installation-related notification issues
- 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 safetyThe 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:
- Add a reasonable staleTime to the query to reduce unnecessary refetches
- Consider implementing background prefetching for active groups
- 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:
- Remove the debug console.log
- 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:
- Return types are inconsistent (string | undefined)
- Some code paths implicitly return undefined
- 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:
- Complex nested conditions could be simplified
- Magic string "group_join_request" should be a constant
- 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:
- Direct store access in event handlers could cause issues if the store isn't initialized
- No error handling for navigation failures
- Comment suggests a race condition with app loading
Consider these improvements:
- 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; + }
- Add error handling for navigation:
if (conversations[conversationTopic]) { + try { navigateToConversation(conversations[conversationTopic]); + } catch (error) { + console.error('Navigation failed:', error); + }
- 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 conversionThe
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 handlingGiven the PR's focus on Android notifications and the mentioned background notification issues, consider the following improvements:
- The current Android permission flow might need additional handling for background notifications specifically
- 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 handlingThe 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 supportThe 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 dataThe async member fetching and subsequent data transformation could impact performance. Consider implementing a caching strategy for member data to reduce unnecessary fetches.
Suggestions:
- Add a timestamp-based cache invalidation
- Implement optimistic updates for member changes
- 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:
- Moving notification-related types and constants to the same feature directory
- Creating a facade/index file to expose the public API of the notifications feature
Line range hint
71-81
: Consider improving Android permission handlingThe current implementation has a comment indicating special handling for Android 13's permission behavior, but it might need enhancement:
- The code opens settings immediately after a "denied" status
- 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:
The
setAuthStatus("signedIn")
is called in thefinally
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
)This behavior is inconsistent with the "Later" button flow, where
setAuthStatus("signedIn")
is called only after successfully settingshowNotificationScreen: 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 flowThe 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 typescriptLength 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 tsxLength 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 tsxLength 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
andblockGroup
functions handle multiple state updates and protocol interactions without proper error handling. Consider the following improvements:
- Add error handling for protocol interactions
- Consider using a transaction-like pattern for multiple state updates
- 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:
The retry mechanism with
setTimeout
could block the UI thread. Consider usingrequestAnimationFrame
for better performance on mobile devices.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:
- Adding a parameter to distinguish between background and foreground navigation
- Implementing different loading strategies for each scenario
- 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:
- Consider checking notification permissions before allowing the "say hi" action
- Potentially integrate with the new notification utilities mentioned in the AI summary
- 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:
- Using
setImmediate
within setState could lead to race conditions- No error handling for notification subscription
- 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:
- Add a flag to track background notification state
- Implement retry logic for failed notification subscriptions
- 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:
- Adding telemetry to track which storage path is being used (MMKV vs keychain)
- Monitoring error rates during the migration period
- 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 unsubscriptionThe 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 operationsThe notification cleanup operations are scattered. Consider grouping them together and executing them in a specific order to prevent race conditions:
- Unsubscribe from notifications
- Delete subscribed topics
- 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 logicThe 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
andsentryAddBreadcrumb
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 foundCurrently, 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 thatinboxId
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 refactoringwaitForLoadingSavedNotifications
to avoid recursionThe 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 ofany
for better type safetyUsing
any
reduces type safety and can lead to runtime errors. Define specific interfaces formessage
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 readabilityThe 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 misspelledCorrect 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 logicThe code for resetting
loadingSavedNotifications
and clearing notifications is duplicated in bothtry
andcatch
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 inneedToUpdateConversationSubscription
Currently, the
update
flag is set totrue
unconditionally, which may cause unnecessary processing and network requests. Consider using the original conditional checkperiod !== 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 supportThere'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 operationsThe
await
calls insidePromise.all
may fail if any promise is rejected. Consider wrapping the asynchronous operations in atry-catch
block or handling rejections to ensure errors are managed appropriately.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ 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) andexpo-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 Notifeeindex.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 inNotificationsStateHandler.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:
- Making the chain ID configurable
- 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:
- The chain ID is only used for XMTP message signing, where EIP-6492 specifically makes chain ID irrelevant for signature verification
- The codebase handles chain IDs properly elsewhere for actual transactions, using configurable values and proper switching mechanisms
- 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:
- Handles HTTP errors with detailed logging of status codes and responses
- Handles network errors when no response is received
- Handles request setup errors
- Uses a logger for consistent error reporting
- 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
andAndroidVisibility
from '@notifee/react-native'getGroupIdFromTopic
from '@utils/groupUtils/groupId'ConverseXmtpClientType
andgetXmtpClient
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 ingroupMessageNotification.ts
for displaying notificationsgetXmtpClient
andConverseXmtpClientType
are used inprotocolNotification.ts
- Profile utilities (
getProfilesStore
,getPreferredName
,getProfile
) are used ingroupMessageNotification.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
:
Several critical improvements needed in the handler function.
The current implementation has several potential issues that should be addressed:
- Missing error handling for XMTP client retrieval
- Group message handling is fire-and-forget without error handling
- Non-group messages are silently ignored
- 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:
- The XMTP client retrieval needs error handling
- Group message handling is fire-and-forget without error handling
- Non-group messages are silently ignored
- 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 throughhandleGroupMessageNotification
- 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 viaonInstallationRevoked
- 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:
-
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
-
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:
- Is this update intentional or should it be in a separate PR?
- 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:
- When the app opens
- When the app becomes active
- 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:
- Android background notifications are processed in JS via a dedicated background task (
BACKGROUND_NOTIFICATION_TASK
) - iOS background notifications are handled natively in Swift
- 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
:
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:
- Ensure there's an alternative mechanism in place for handling FCM messages
- Verify that background notifications still work as expected
- 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 handlingfeatures/notifications/utils/setupAndroidNotificationChannel.ts
: Configures Android notification channelfeatures/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:
unsubscribeFromNotifications
is called during the execution of logout tasks (line 119) with proper API headersdeleteSubscribedTopics
is called separately during the account cleanup phase (line 234)- 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
andserviceExtensionTimeWillExpire
) - Notification content modifications are handled systematically through various methods like
handleGroupMessage
,handleNewConversationFirstMessage
, andhandleOngoingConversationMessage
- 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:
- Messages with identical timestamps might lead to inconsistent ordering
- No validation of message timestamps
- 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
export const isGroupWelcomeContentTopic = (contentTopic: string) => { | ||
return contentTopic.startsWith("/xmtp/mls/1/w-"); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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-"); | |
}; |
export const resetNotifications = async ( | ||
timeout: number = 0 | ||
): Promise<void> => { | ||
setTimeout(async () => { | ||
await Notifications.dismissAllNotificationsAsync(); | ||
await Notifications.setBadgeCountAsync(0); | ||
mmkv.set("notifications-badge", 0); | ||
}, timeout); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Promise resolution and error handling issues
The current implementation has several issues that need to be addressed:
- The function doesn't properly return a Promise that resolves after the timeout and async operations complete
- There's no error handling for the async operations
- 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.
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); | |
}); | |
}; |
export const deleteSubscribedTopics = (account: string) => { | ||
if (account in subscribedOnceByAccount) { | ||
delete subscribedOnceByAccount[account]; | ||
} | ||
if (account in subscribingByAccount) { | ||
delete subscribingByAccount[account]; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation, error handling, and logging for better reliability
The current implementation could be more robust with the following improvements:
- Input validation for the account parameter
- Error handling for delete operations
- Logging for debugging and monitoring
- 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.
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; | |
} | |
}; |
export const getNotificationsPermissionStatus = async (): Promise< | ||
NotificationPermissionStatus | undefined | ||
> => { | ||
await setupAndroidNotificationChannel(); | ||
|
||
const { status } = await Notifications.getPermissionsAsync(); | ||
return status; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and platform-specific logic
The current implementation has several areas for improvement:
- No error handling for
setupAndroidNotificationChannel
orgetPermissionsAsync
- Android-specific setup is called on all platforms
- 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.
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; | |
} | |
}; |
export const saveNotificationsStatus = async () => { | ||
const notificationsStatus = await getNotificationsPermissionStatus(); | ||
if ( | ||
notificationsStatus === "undetermined" || | ||
notificationsStatus === "granted" || | ||
notificationsStatus === "denied" | ||
) { | ||
useAppStore | ||
.getState() | ||
.setNotificationsPermissionStatus(notificationsStatus); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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 | |
} | |
}; |
const members = await group.members(); | ||
saveMemberInboxIds(account, members); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
[await protocolGroupToStateConversation(account, group)], | ||
true | ||
); | ||
const updatedMembers = await group.membersList(); | ||
const updatedMembers = await group.members(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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 atry-catch
block to handle exceptions and prevent unhandled errors.
members.forEach((m) => { | ||
const firstAddress = getCleanAddress(m.addresses[0]); | ||
if (firstAddress) { | ||
groupMembersAddresses.push(firstAddress); | ||
if (m.inboxId === group.creatorInboxId) { | ||
if (m.inboxId === creatorInboxId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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) { |
const newMembers = await g.members(); | ||
const newMembersSet = new Set( | ||
g.members.map((m) => getCleanAddress(m.addresses[0])) | ||
newMembers.map((m) => getCleanAddress(m.addresses[0])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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])) |
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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle potential errors and ensure 'addresses[0]' is defined
- The
await
call to retrieve members may fail. Consider wrapping it in atry-catch
block to handle potential errors. - When accessing
m.addresses[0]
in thesome
method, ensure thatm.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.
07b66ae
to
514e4b8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
📒 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add 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.
const messageContent = message.content(); | |
let messageContent; | |
try { | |
messageContent = message.content(); | |
} catch (error) { | |
console.error('Failed to decode message content:', error); | |
return; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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:
- Add timestamps to stored notification IDs
- Implement a periodic cleanup job
- 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:
- Add format validation for
contentTopic
to ensure it matches expected patterns- Add size limits for
message
to prevent potential memory issues- 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 groupsThe 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 mechanismThe 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 constantThe 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 issuesThere are two TODO comments that should be tracked:
- Making inboxId a first-class citizen
- 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 configurationThe 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:
- Binary scoring might miss nuanced spam patterns
- No maximum score limit could lead to unexpected behavior
- URL and restricted words have equal weight
- 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:
- Consider implementing a rule-based system where spam indicators and their weights can be configured
- Add telemetry to track false positives/negatives
- 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 torefreshConsentList
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 functionThe 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 themCurrently, 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
⛔ 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
andcomputeSpamScoreGroupMessage
) are separate, specialized functions for group-related notifications that properly integrate with the newcomputeMessageContentSpamScore
🏁 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.
const setShownNotificationIds = (ids: string[]): void => { | ||
try { | ||
const jsonData = JSON.stringify(ids); | ||
mmkv.set("notification-ids", jsonData); | ||
} catch { | ||
// Handle error silently | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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); | |
} | |
}; |
const getShownNotificationIds = (): string[] => { | ||
const jsonData = mmkv.getString("notification-ids"); | ||
if (jsonData) { | ||
try { | ||
return JSON.parse(jsonData); | ||
} catch { | ||
return []; | ||
} | ||
} | ||
return []; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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 []; | |
}; |
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; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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; | |
}; |
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); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Add proper error handling and async/await.
Several issues need to be addressed:
- Missing error handling for
getXmtpClient
- Notification handlers are called synchronously without
await
- No error handling for notification handlers
- 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.
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); | |
} | |
} | |
}; |
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; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling and improve type safety
The function has potential issues:
- Missing error handling for sync operations
- The reduce operation might be unsafe if groups array is modified during execution
- 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.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
data: notification, | |
data: { | |
contentTopic: notification.contentTopic, | |
messageId: notification.message.id, | |
account: notification.account, | |
}, |
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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; | |
} |
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()) | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving restricted words implementation.
Several improvements could enhance maintainability and efficiency:
- Move restricted words to a configuration file for easier updates
- Consider using a more efficient search algorithm for large lists
- Review generic terms that might cause false positives (e.g., "offer", "restriction")
- 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[ | ||
inviterAddress | ||
]; | ||
|
||
return senderSpamScore; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle 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.
const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[ | |
inviterAddress | |
]; | |
return senderSpamScore; | |
const senderSpamScore = (await getSendersSpamScores([inviterAddress]))[ | |
inviterAddress | |
] ?? 0; | |
return senderSpamScore; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
📒 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 infeatures/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
:
Add type safety and error handling for notification data
The current implementation has several potential issues:
- Unsafe type casting with
as any
- Complex data extraction logic with fallbacks
- 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.
export const getGroup = async ( | ||
xmtpClient: ConverseXmtpClientType, | ||
groupId: string | ||
) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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"); | |
} |
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", | ||
}, | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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, | |
}, | |
}); | |
} |
// Add other converse notifications here, with different "type" values | ||
export const ConverseNotificationSchema = z.discriminatedUnion("type", [ | ||
GroupJoinRequestNotificationSchema, | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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');
}
};
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); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical issues in notification handling implementation.
Several issues need to be addressed:
- Missing await on notification handler
- No error handling for XMTP client initialization
- No handling for unknown notification types
- 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.
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}`); | |
} | |
}; |
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 | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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 | |
}); |
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; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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:
- Use the existing
GroupJoinRequestStatus
type for the status parameter:
- status: string;
+ status: GroupJoinRequestStatus;
- 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.
sentryTrackMessage("No client found for account", { | ||
extras: { | ||
account: notification.account, | ||
}, | ||
}); | ||
|
||
return false; // Don't show notification if any error occurs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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 | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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 | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (2)
features/notifications/utils/background/groupJoinRequestNotification.ts (2)
64-73
: Enhance the notification content for better user guidanceWhen 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 storageWhen 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
📒 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:
- The API implementation details
- Whether there are UI components handling group notifications
- How similar errors are handled elsewhere in the codebase
This will help us understand:
- The complete notification flow and error handling strategy
- Whether notifications are intentionally suppressed in certain scenarios
- 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:
-
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
-
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
- The primary action (adding member to group) has already succeeded when
🏁 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
* 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]>
WIP
Need to figure out background notifications, they are getting overridden somewhere and not handled by notifee
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Chores
package.json
for better functionality and compatibility.Refactor