Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Notification Subscriptions forV3 #1258

Merged
merged 1 commit into from
Nov 26, 2024

Conversation

alexrisch
Copy link
Contributor

@alexrisch alexrisch commented Nov 26, 2024

Added notification subscribing for V3 only

Summary by CodeRabbit

  • New Features

    • Enhanced notification subscription logic with improved error handling and logging.
    • Updated conversation fetching process for better synchronization.
  • Bug Fixes

    • Improved error handling during synchronization to ensure responsiveness to connection issues.
  • Refactor

    • Simplified notification handling and conversation tracking within the chat store.
    • Updated method signatures and types for clarity and maintainability.
  • Chores

    • Removed legacy code and unnecessary subscriptions to streamline functionality.

Added notification subscribing for V3 only
@alexrisch alexrisch requested a review from a team as a code owner November 26, 2024 18:48
Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

Walkthrough

The pull request introduces significant changes across several files, primarily focusing on the restructuring of notification handling and conversation management within the chat and settings stores. Key alterations include the removal of certain subscription functions, modifications to how conversations are tracked, and enhancements to the notification subscription logic. Additionally, updates to method signatures and type definitions improve clarity and maintainability. The changes reflect a comprehensive refactoring aimed at streamlining interactions and improving error handling in the notification and synchronization processes.

Changes

File Change Summary
data/store/chatStore.ts - Removed subscribeToNotifications import.
- Updated setTopicsData to eliminate immediate notification subscription.
- Replaced conversations with conversationsMapping.
- Commented out setConversationsLastNotificationSubscribePeriod.
data/store/settingsStore.ts - Removed subscribeToNotifications call from setPeersStatus.
utils/notifications.ts - Replaced _subscribeToNotifications with subscribeToNotifications accepting conversations and account.
- Streamlined topic updates and improved error logging.
- Renamed buildUserGroupInviteTopic to buildUserV3InviteTopic.
- Added SubscribeToNotificationsParams type.
utils/xmtpRN/api.ts - Updated getXmtpApiHeaders to use evmHelpers.toChecksumAddress(account).
utils/xmtpRN/sync.ts - Replaced prefetchConversationListQuery with fetchConversationListQuery.
- Updated syncClientConversationList to integrate new fetching method.
- Enhanced error handling in onSyncLost.

Possibly related PRs

  • fix: Sync Account on Add #1104: The changes in chatStore.ts regarding notification handling may relate to the subscription logic introduced in XmtpEngine.tsx, which also involves managing subscriptions for account synchronization.
  • fix: Fixed Buttons for Certain cases #1127: The modifications in chatStore.ts and settingsStore.ts regarding notification subscriptions align with the updates in button functionalities, which may impact user interactions related to notifications.
  • fix: rainbow connect + translations + remove unused code #1128: The mention of translations in this PR could connect to the changes in notification handling and conversation management, as both areas may require consistent user-facing text updates.

Suggested reviewers

  • thierryskoda
  • nmalzieu

🐰 In the chat store, changes abound,
Notifications now wear a new crown.
Conversations mapped, clear and bright,
Errors logged, all feels just right.
With each little tweak, we hop and play,
A smoother experience, hip-hip-hooray! 🥕


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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Comment on lines 45 to 48
return {
"xmtp-api-signature": "123",
"xmtp-api-address": account,
"xmtp-api-address": evmHelpers.toChecksumAddress(account),
};
Copy link

Choose a reason for hiding this comment

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

The hardcoded API signature "123" should be removed to allow the proper signature generation logic below to execute. This appears to be debug/test code that was accidentally committed. The checksummed address change looks correct, but bypassing authentication is a security risk in production.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
utils/xmtpRN/api.ts (1)

Critical: Fix unreachable code and incorrect signature handling

The verification confirms this is a critical issue that needs immediate attention. The getXmtpApiHeaders function is used extensively across the codebase for API authentication in critical operations like:

  • User authentication and existence checks
  • Peer status and consent management
  • Topic management and pinning
  • USDC transfer authorization
  • Group management and invites

The current implementation with the early return and hardcoded "123" signature will break all these operations. Additionally, the codebase shows both mobile platforms (iOS and Android) expect proper XMTP API signatures for authentication.

Fix needed as suggested in the review:

export const getXmtpApiHeaders = async (account: string) => {
-  return {
-    "xmtp-api-signature": "123",
-    "xmtp-api-address": evmHelpers.toChecksumAddress(account),
-  };
  if (account in xmtpSignatureByAccount)
    return {
      "xmtp-api-signature": xmtpSignatureByAccount[account],
-      "xmtp-api-address": account,
+      "xmtp-api-address": evmHelpers.toChecksumAddress(account),
    };
  const xmtpApiSignature = await getXmtpApiSignature(account, "XMTP_IDENTITY");
  xmtpSignatureByAccount[account] = xmtpApiSignature;
  return {
    "xmtp-api-signature": xmtpApiSignature,
-    "xmtp-api-address": account,
+    "xmtp-api-address": evmHelpers.toChecksumAddress(account),
  };
};
🔗 Analysis chain

Line range hint 46-60: Critical: Fix unreachable code and incorrect signature handling

There's a severe control flow issue in the getXmtpApiHeaders function:

  1. The early return with hard-coded "123" signature makes all subsequent code unreachable
  2. The XMTP signature generation and caching logic is completely bypassed
  3. This will break authentication as the signature is invalid

Apply this fix to restore the proper authentication flow:

export const getXmtpApiHeaders = async (account: string) => {
-  return {
-    "xmtp-api-signature": "123",
-    "xmtp-api-address": evmHelpers.toChecksumAddress(account),
-  };
  if (account in xmtpSignatureByAccount)
    return {
      "xmtp-api-signature": xmtpSignatureByAccount[account],
-      "xmtp-api-address": account,
+      "xmtp-api-address": evmHelpers.toChecksumAddress(account),
    };
  const xmtpApiSignature = await getXmtpApiSignature(account, "XMTP_IDENTITY");
  xmtpSignatureByAccount[account] = xmtpApiSignature;
  return {
    "xmtp-api-signature": xmtpApiSignature,
-    "xmtp-api-address": account,
+    "xmtp-api-address": evmHelpers.toChecksumAddress(account),
  };
};

Let's verify the usage of this authentication header across the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for places where getXmtpApiHeaders is used to ensure they handle the async nature
rg -l "getXmtpApiHeaders" | xargs rg -A 5 "getXmtpApiHeaders"

# Search for any direct XMTP API calls to ensure they use proper authentication
rg -A 5 "xmtp-api-(signature|address)"

Length of output: 8586

utils/xmtpRN/sync.ts (1)

Line range hint 177-184: Add notification subscription cleanup.

The deleteXmtpClient function should clean up notification subscriptions when removing a client.

Consider adding cleanup for notification subscriptions:

 export const deleteXmtpClient = async (account: string) => {
   if (account in xmtpClientByAccount) {
     stopStreamingAllMessage(account);
     stopStreamingConversations(account);
+    try {
+      await subscribeToNotifications({ conversations: [], account });
+    } catch (e) {
+      logger.error(e, {
+        context: `Failed to cleanup notifications for ${account}`,
+      });
+    }
   }
   delete xmtpClientByAccount[account];
   delete xmtpSignatureByAccount[account];
utils/notifications.ts (2)

67-71: Avoid unnecessary type assertion

Casting the result of getXmtpClient(account) to ConverseXmtpClientType using as might mask potential issues. Consider refining getXmtpClient to return the correct type or properly handle the possibility of an undefined client.

If getXmtpClient can return null or undefined, adjust its return type accordingly and handle it without type assertion.

-const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
+const client = await getXmtpClient(account);
if (!client) {
  logger.error("[subscribeToNotifications] no client");
  return;
}
// Now `client` is guaranteed to be of type `ConverseXmtpClientType`

99-108: Ensure conversation.topic and conversation.state are defined

Add checks to handle cases where conversation.topic or conversation.state might be undefined to prevent potential runtime errors.

Modify the loop to include validation:

for (const conversation of conversations) {
+  if (!conversation.topic || !conversation.state) {
+    logger.warn("[subscribeToNotifications] Conversation missing topic or state", conversation);
+    continue;
+  }
  const topic = conversation.topic;
  const conversationState = conversation.state;
  if (conversationState === "allowed") {
    dataToHash.push.push(topic);
    topicsToUpdateForPeriod[topic] = {
      status: "PUSH",
    };
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b46a58a and f0573a9.

📒 Files selected for processing (5)
  • data/store/chatStore.ts (0 hunks)
  • data/store/settingsStore.ts (0 hunks)
  • utils/notifications.ts (3 hunks)
  • utils/xmtpRN/api.ts (2 hunks)
  • utils/xmtpRN/sync.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • data/store/chatStore.ts
  • data/store/settingsStore.ts
🔇 Additional comments (7)
utils/xmtpRN/api.ts (2)

6-6: LGTM: Import statement is correctly placed

The new import for evmHelpers is properly structured and follows the existing import organization.


47-47: LGTM: Improved address handling with checksum

Using evmHelpers.toChecksumAddress() is a good practice as it ensures consistent address formatting and helps prevent errors in address handling.

utils/xmtpRN/sync.ts (1)

21-24: Verify performance impact of switching from prefetch to fetch.

The change from prefetchConversationListQuery to fetchConversationListQuery might affect the application's performance characteristics, particularly regarding data loading timing.

Let's analyze the implementation differences:

✅ Verification successful

No performance concerns with fetch vs prefetch change

The change from prefetchConversationListQuery to fetchConversationListQuery is an intentional refactoring that doesn't impact performance. Both functions use the same underlying query function v3ConversationListQueryFn with different caller identifiers. The key differences are:

  • prefetchConversationListQuery uses queryClient.prefetchQuery
  • fetchConversationListQuery uses queryClient.fetchQuery

Both methods are valid React Query patterns, and the change aligns with the immediate data needs in sync.ts where the conversation list is required synchronously for notification subscription.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining prefetch implementations and compare with new fetch pattern
rg "prefetchConversationList" -A 5
rg "fetchConversationList" -A 5

Length of output: 4029

utils/notifications.ts (4)

7-7: Import statement is necessary and correctly added

The import saveNotificationsSubscribe from ./api is appropriately added and used in the code.


11-16: New imports are correctly utilized

The imports from xmtpRN modules (getXmtpClient, ConversationWithCodecsType, ConverseXmtpClientType) and logger are correctly added and used within the code.


49-51: Type definition SubscribeToNotificationsParams enhances clarity

Defining SubscribeToNotificationsParams improves type safety and readability of the subscribeToNotifications function parameters.


29-30: 🛠️ Refactor suggestion

Validate installationId before constructing the topic

If installationId is an empty string, the constructed topic may be invalid (e.g., /xmtp/mls/1/w-/proto). Consider adding validation to ensure installationId is not empty before using it.

You could modify the code to check for a valid installationId:

const buildUserV3InviteTopic = (installationId: string): string => {
+  if (!installationId) {
+    logger.error("Installation ID is missing");
+    throw new Error("Installation ID is required");
+  }
  return `/xmtp/mls/1/w-${installationId}/proto`;
};

And ensure that client.installationId is defined before calling:

const client = await getXmtpClient(account);
if (!client) {
  logger.error("[subscribeToNotifications] no client");
  return;
}
+if (!client.installationId) {
+  logger.error("[subscribeToNotifications] client.installationId is missing");
+  return;
+}
const userGroupInviteTopic = buildUserV3InviteTopic(client.installationId);

Comment on lines +149 to +150
const conversationList = await fetchConversationListQuery(account);
subscribeToNotifications({ conversations: conversationList, account });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling and consider race conditions.

The current implementation has two potential issues:

  1. No error handling for subscribeToNotifications
  2. Possible race condition between fetching conversations and subscribing to notifications

Consider wrapping the subscription in a try-catch and implementing a more atomic operation:

-    const conversationList = await fetchConversationListQuery(account);
-    subscribeToNotifications({ conversations: conversationList, account });
+    try {
+      const conversationList = await fetchConversationListQuery(account);
+      await subscribeToNotifications({ conversations: conversationList, account });
+    } catch (e) {
+      logger.error(e, {
+        context: `Failed to subscribe to notifications for ${account}`,
+      });
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const conversationList = await fetchConversationListQuery(account);
subscribeToNotifications({ conversations: conversationList, account });
try {
const conversationList = await fetchConversationListQuery(account);
await subscribeToNotifications({ conversations: conversationList, account });
} catch (e) {
logger.error(e, {
context: `Failed to subscribe to notifications for ${account}`,
});
}

Comment on lines +110 to +118
const nativeTokenQuery = await Notifications.getDevicePushTokenAsync();
nativePushToken = nativeTokenQuery.data;
if (nativePushToken) {
savePushToken(nativePushToken);
} else {
logger.error("[subscribeToNotifications] no native push token");
delete subscribingByAccount[account];
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle possible undefined nativeTokenQuery

Ensure nativeTokenQuery and nativeTokenQuery.data are properly checked to avoid runtime errors if getDevicePushTokenAsync fails.

Adjust the code to handle undefined nativeTokenQuery:

const nativeTokenQuery = await Notifications.getDevicePushTokenAsync();
-nativePushToken = nativeTokenQuery.data;
+nativePushToken = nativeTokenQuery?.data;

if (nativePushToken) {
  savePushToken(nativePushToken);
} else {
  logger.error("[subscribeToNotifications] no native push token");
  delete subscribingByAccount[account];
  return;
}

This change ensures that if nativeTokenQuery is undefined, nativePushToken will be undefined, and the subsequent check will handle it appropriately.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const nativeTokenQuery = await Notifications.getDevicePushTokenAsync();
nativePushToken = nativeTokenQuery.data;
if (nativePushToken) {
savePushToken(nativePushToken);
} else {
logger.error("[subscribeToNotifications] no native push token");
delete subscribingByAccount[account];
return;
}
const nativeTokenQuery = await Notifications.getDevicePushTokenAsync();
nativePushToken = nativeTokenQuery?.data;
if (nativePushToken) {
savePushToken(nativePushToken);
} else {
logger.error("[subscribeToNotifications] no native push token");
delete subscribingByAccount[account];
return;
}

const thirtyDayPeriodsSinceEpoch = Math.floor(
Date.now() / 1000 / 60 / 60 / 24 / 30
);
const client = (await getXmtpClient(account)) as ConverseXmtpClientType;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see we do A LOT of those const client = (await getXmtpClient(account)) as ConverseXmtpClientType; could we make it simpler and at least not need the as ... just curious

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I agree we don't need it, it's there because of old web stuff, so we should probably remove it

Comment on lines +127 to +133
await saveNotificationsSubscribe(
account,
nativePushToken,
nativeTokenQuery.type,
nativeTokenQuery.type === "android" ? "converse-notifications" : null,
topicsToUpdateForPeriod
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Many params , would love a object instead 👀

const conversationList = await prefetchConversationListQuery(account);
// TODO: Update notification topics
const conversationList = await fetchConversationListQuery(account);
subscribeToNotifications({ conversations: conversationList, account });
} catch (e) {
logger.error(e, {
context: `Failed to fetch persisted conversation list for ${account}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

would be cool to start handling errors better. Maybe worth waiting until we have a clear strategy tho

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants