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

Ml/conv 1130 inconsistent notification deep link to chat #1325

Conversation

technoplato
Copy link
Collaborator

@technoplato technoplato commented Dec 6, 2024

Fixes #1130
Fixes #1182

https://www.loom.com/share/3db3a0cd75524e08b9b4752e0a5784cd?sid=0bb5c799-330e-4db4-adf2-2c2160cd848d

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced logging for debugging during account hydration and notification handling.
    • Streamlined navigation by adjusting how topics are managed.
  • Improvements

    • Simplified logic for splash screen management and deep link handling.
    • Improved error handling and initialization processes for account management.
  • Bug Fixes

    • Adjusted notification handling to prevent alerts from being shown to the sender.

@technoplato technoplato requested a review from a team as a code owner December 6, 2024 21:46
Copy link
Contributor

coderabbitai bot commented Dec 6, 2024

Walkthrough

The pull request includes modifications across several components, primarily focusing on improving logging, simplifying control flows, and enhancing error handling. Key changes involve the HydrationStateHandler, InitialStateHandler, and notification handling functions. Notably, the handling of deep links and notifications has been streamlined, and debug logging has been added to better track the state of accounts during hydration. The changes aim to improve the robustness and clarity of the application’s navigation and state management processes.

Changes

File Path Change Summary
components/StateHandlers/HydrationStateHandler.tsx Commented out unused import; adjusted useEffect logic for accounts; added comment regarding getInstalledWallets.
components/StateHandlers/InitialStateHandler.tsx Added imports for logger; simplified splash screen logic; enhanced logging for deep link handling.
data/store/accountsStore.ts Enhanced error handling and logging in onRehydrateStorage; updated setCurrentAccount logic for new accounts.
features/notifications/utils/handleForegroundNotification.ts Updated logging and refined logic for showing alerts based on sender and recipient accounts.
features/notifications/utils/onInteractWithNotification.ts Made function asynchronous; removed unused import; streamlined navigation logic and reset notifications.
utils/navigation.ts Updated topicToNavigateTo type to string or undefined; modified setTopicToNavigateTo function signature.

Assessment against linked issues

Objective Addressed Explanation
Ensure navigation to chat for messages is consistent after push notifications (#[1130])
Await hydration state to ensure it is complete before handling notifications (#[1130]) No explicit awaiting of hydration state found.

Possibly related PRs

Suggested reviewers

  • alexrisch
  • thierryskoda

Poem

🐇 In the meadow where rabbits play,
Changes hop in, brightening the day.
With logs that guide and paths made clear,
Navigation's smooth, let’s give a cheer!
For every push and every link,
We leap ahead, no time to blink! 🌼


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 +17 to +18
const senderAccount =
/* TODO notification.request.content.data?.["sender"]; */ "todo";
Copy link

Choose a reason for hiding this comment

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

Setting senderAccount to the hardcoded string "todo" will cause incorrect notification behavior. The userIsNotSender check will evaluate to true whenever currentAccount() is not "todo", rather than comparing against the actual sender's address. Consider using notification.request.content.data?.["sender"] directly, or if that data isn't available yet, implement proper sender tracking before enabling this check.

Spotted by Graphite Reviewer

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

Copy link
Contributor

github-actions bot commented Dec 6, 2024

Performance Comparison Report

  • Current: 0edfab6 - 2024-12-09 22:08:21Z
  • Baseline: release/3.0.0 (5b29bf9) - 2024-12-09 22:07:01Z

Significant Changes To Duration

There are no entries

Meaningless Changes To Duration

Show entries
Name Type Duration Count
Avatar Image 10 runs render 1.1 ms → 1.3 ms (+0.2 ms, +18.2%) 🔴 1 → 1
Avatar Image 50 runs render 1.1 ms → 1.1 ms (-0.1 ms, -5.4%) 1 → 1
Empty Avatar 10 runs render 0.8 ms → 0.9 ms (+0.1 ms, +12.5%) 1 → 1
Empty Avatar 50 runs render 0.7 ms → 0.8 ms (+0.1 ms, +21.2%) 🔴 1 → 1
Text Component with color prop - 10 runs render 0.4 ms → 0.4 ms 1 → 1
Text Component with default props - 10 runs render 0.5 ms → 0.3 ms (-0.2 ms, -40.0%) 🟢 1 → 1
Text Component with translation key - 10 runs render 0.3 ms → 0.4 ms (+0.1 ms, +33.3%) 🔴 1 → 1
Text Component with weight and size - 10 runs render 0.3 ms → 0.4 ms (+0.1 ms, +33.3%) 🔴 1 → 1
Show details
Name Type Duration Count
Avatar Image 10 runs render Baseline
Mean: 1.1 ms
Stdev: 0.3 ms (28.7%)
Runs: 1 1 1 1 1 1 1 1 2 1
Warmup runs: 3

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

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

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

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

Current
Mean: 0.9 ms
Stdev: 0.3 ms (35.1%)
Runs: 0 1 1 1 1 1 1 1 1 1
Warmup runs: 3
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

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

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

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

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

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

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

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

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

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

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

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

Render Count Changes

There are no entries

Render Issues

There are no entries

Added Scenarios

There are no entries

Removed Scenarios

There are no entries

Generated by 🚫 dangerJS against 6e291f5

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

🧹 Outside diff range and nitpick comments (2)
components/StateHandlers/HydrationStateHandler.tsx (1)

Line range hint 21-31: Add error handling and improve hydration completion tracking

The hydration process is critical for notification handling but lacks:

  1. Error handling for getXmtpClient calls
  2. Clear indication when all XMTP clients are ready
  3. Proper sequencing with notification handling

This could explain the 50% success rate mentioned in the PR objectives.

Consider this implementation:

-      accounts.map((a) => getXmtpClient(a));
+      try {
+        await Promise.all(accounts.map(async (a) => {
+          await getXmtpClient(a);
+        }));
+        logger.debug('[Hydration] All XMTP clients ready');
+      } catch (error) {
+        logger.error('[Hydration] Failed to initialize XMTP clients:', error);
+      }
components/StateHandlers/InitialStateHandler.tsx (1)

11-14: Remove unused imports

The setTopicToNavigateTo and topicToNavigateTo imports appear to be unused. If they're intended for notification handling, their implementation is incomplete.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ac1eeaa and e6bc40c.

📒 Files selected for processing (6)
  • components/StateHandlers/HydrationStateHandler.tsx (1 hunks)
  • components/StateHandlers/InitialStateHandler.tsx (3 hunks)
  • data/store/accountsStore.ts (1 hunks)
  • features/notifications/utils/handleForegroundNotification.ts (1 hunks)
  • features/notifications/utils/onInteractWithNotification.ts (2 hunks)
  • utils/navigation.ts (1 hunks)
🔇 Additional comments (6)
features/notifications/utils/handleForegroundNotification.ts (1)

17-18: ⚠️ Potential issue

Critical: Fix hardcoded sender address

The hardcoded "todo" value for senderAccount will cause incorrect notification behavior, as previously noted.

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

61-63: Good addition: Ensuring conversation list is fetched before navigation

This is a crucial fix that addresses the hydration issue by ensuring the conversation data is available before navigation.


66-68: Good cleanup: Proper state management after navigation

The addition of setTopicToNavigateTo(undefined) and resetNotifications() ensures clean state after navigation.


58-60: 🛠️ Refactor suggestion

Consider handling potential undefined account more robustly

The fallback to currentAccount could be problematic if the store isn't properly hydrated yet.

Consider adding a guard clause:

-    const account =
-      notificationData["account"] || useAccountsStore.getState().currentAccount;
+    const account = notificationData["account"];
+    if (!account) {
+      const currentAccount = useAccountsStore.getState().currentAccount;
+      if (!currentAccount || currentAccount === TEMPORARY_ACCOUNT_NAME) {
+        logger.warn("[Notification] No valid account found for notification");
+        return;
+      }
+      account = currentAccount;
+    }
utils/navigation.ts (1)

29-32: Good improvement: Better navigation state management

The change to allow undefined as a valid state for topicToNavigateTo improves the state management lifecycle and helps prevent stale navigation targets.

data/store/accountsStore.ts (1)

204-209: Good addition: Enhanced hydration debugging

The added debug logging will help track and diagnose hydration-related issues, which is particularly valuable given the notification deep linking problems were related to hydration state.

Comment on lines +18 to 20
// note(lustig) I don't think this does anything?
getInstalledWallets(false);
}
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 6, 2024

Choose a reason for hiding this comment

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

💡 Codebase verification

The getInstalledWallets call is redundant and should be removed

The implementation shows that getInstalledWallets caches its results using the hasCheckedInstalled flag. When called with refresh: false, it returns the cached results without performing any checks. In the HydrationStateHandler, the second call with false is redundant since it's called immediately after the first awaited call, also with false. This second call will simply return the cached results without any effect.

  • Remove the redundant call at line 20 in components/StateHandlers/HydrationStateHandler.tsx
🔗 Analysis chain

Remove or justify the questionable getInstalledWallets call

The commented note suggests uncertainty about this call's purpose. If it's unnecessary, it should be removed to avoid potential performance impact during hydration.


Let me analyze the usage of getInstalledWallets further to understand its purpose and the impact of the call.


Let me try a different pattern to understand the implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other uses of getInstalledWallets to understand its purpose
rg "getInstalledWallets" -A 5

Length of output: 2646


Script:

#!/bin/bash
# Check the implementation and usage of getInstalledWallets
ast-grep --pattern 'const getInstalledWallets = async ($$$) => {
  $$$
}'

Length of output: 75


Script:

#!/bin/bash
# Get the content of the file containing getInstalledWallets implementation
cat components/Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets.ts

Length of output: 6987

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is inaccurate. there's an if else

Copy link
Contributor

Choose a reason for hiding this comment

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

@technoplato, thank you for pointing out the oversight in my analysis. You're correct; the calls to getInstalledWallets(false); are in separate branches of the if-else statement, and they are necessary in both scenarios. I apologize for the confusion.


✏️ Learnings added
Learnt from: technoplato
PR: ephemeraHQ/converse-app#1325
File: components/StateHandlers/HydrationStateHandler.tsx:18-20
Timestamp: 2024-12-09T14:51:51.030Z
Learning: In `components/StateHandlers/HydrationStateHandler.tsx`, the calls to `getInstalledWallets(false);` are in separate branches of an `if-else` statement and are required for both cases when `accounts.length === 0` and when `accounts.length > 0`. I should carefully examine conditional structures before suggesting code is redundant.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +55 to +62
if (isDevelopmentClientURL(initialURL)) {
return;
}

Linking.openURL(initialURL);
// Once opened, let's remove so we don't navigate twice
// when logging out / relogging in
initialURL = "";
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

Potential race condition with notification deep links

The current implementation clears initialURL after opening, which could interfere with notification-triggered navigation if the notification is processed during app startup.

Consider:

  1. Coordinating between notification and deep link handling
  2. Preserving navigation intent from notifications during hydration
-        Linking.openURL(initialURL);
-        // Once opened, let's remove so we don't navigate twice
-        // when logging out / relogging in
-        initialURL = "";
+        if (initialURL) {
+          try {
+            await Linking.openURL(initialURL);
+            logger.debug('[Navigation] Successfully opened initial URL:', initialURL);
+          } catch (error) {
+            logger.error('[Navigation] Failed to open initial URL:', error);
+          } finally {
+            // Store the last processed URL to prevent duplicate navigation
+            lastProcessedURL = initialURL;
+            initialURL = "";
+          }
+        }

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

Comment on lines +21 to +28
const isViewingNotifiedConversation = /* TODO */ false;
const hackSoWeDontShowNotificationToSender =
/* todo(lustig): remove once we get senderAddress */ recipientAccount !==
currentAccount();
const shouldShowAlert =
userIsNotSender &&
!isViewingNotifiedConversation &&
hackSoWeDontShowNotificationToSender;
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

Implement proper conversation state tracking

Several issues in the notification logic:

  1. isViewingNotifiedConversation is hardcoded to false
  2. The hack to prevent sender notifications is unreliable
  3. Multiple TODOs indicate incomplete implementation

This could contribute to the inconsistent notification behavior.

Consider this implementation:

-  const isViewingNotifiedConversation = /* TODO */ false;
-  const hackSoWeDontShowNotificationToSender =
-    /* todo(lustig): remove once we get senderAddress */ recipientAccount !==
-    currentAccount();
+  const conversationId = notification.request.content.data?.["conversationId"];
+  const isViewingNotifiedConversation = conversationId === getCurrentConversationId();
+  const senderAddress = notification.request.content.data?.["sender"];
+  const userIsNotSender = senderAddress && senderAddress !== currentAccount();

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

Comment on lines +7 to 8
// note(lustig): these two handlers are the same - do we need both?
const handleiOSForegroundNotification = async (
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Consolidate notification handlers - iOS-specific logic is incomplete

The iOS handler contains incomplete TODOs and temporary hacks, while the default handler implements a simpler, complete version of the same logic. Specifically:

  • The iOS handler has unimplemented TODOs for senderAccount and isViewingNotifiedConversation
  • Contains a temporary hack hackSoWeDontShowNotificationToSender
  • Both handlers essentially check if the notification is for the current account

Recommendations:

  • Remove the iOS-specific handler and use the simpler handleDefaultForegroundNotification for all platforms
  • If iOS-specific features are needed in the future, implement them within a single handler using platform-specific conditionals
🔗 Analysis chain

Clarify the need for separate notification handlers

The comment questions whether both handlers are needed. This should be investigated and documented.


Let me gather more information about the implementation details of these handlers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for platform-specific notification handling differences
rg -A 10 "handleiOSForegroundNotification|handleDefaultForegroundNotification"

# Search for iOS-specific notification code
rg "notification.*ios" -g "!*.test.*"

Length of output: 2822


Script:

#!/bin/bash
# Search for the entire content of the handleForegroundNotification.ts file
cat features/notifications/utils/handleForegroundNotification.ts

# Look for any other files that might be importing or using these handlers
rg "handleiOSForegroundNotification|handleDefaultForegroundNotification" --type ts

Length of output: 2880

const recipientAccount = notification.request.content.data?.["account"];

const senderAccount =
/* TODO notification.request.content.data?.["sender"]; */ "todo";
Copy link
Contributor

Choose a reason for hiding this comment

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

Todo!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've left this half baked code in intentionally and was planning on a follow up, so wanted to leave breadcrumbs for the logic we need.

You cool leaving or want me to remove and make an Issue instead?

setTopicToNavigateTo(conversationTopic);
}
navigateToTopic(conversationTopic as ConversationTopic);
setTopicToNavigateTo(undefined);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this doing anything any more?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this lingering around was responsible for the bug where you'd come back into the app and it'd try and deep link you again, so yes!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
components/StateHandlers/InitialStateHandler.tsx (2)

Line range hint 31-35: Add error handling and consider using React state instead of global variable.

While the debug logging is a good addition, there are two concerns:

  1. Missing error handling for Linking.getInitialURL()
  2. Using a global initialURL variable could lead to race conditions and make the component's behavior unpredictable

Consider this improvement:

- export let initialURL = "";
+ const InitialStateHandler: React.FC = () => {
+   const [initialURL, setInitialURL] = useState<string>("");

  const handleInitialDeeplink = async () => {
    logger.debug("[InitialStateHandler] Handling initial deeplink");
-   let openedViaURL = (await Linking.getInitialURL()) || "";
-   openedViaURL = getSchemedURLFromUniversalURL(openedViaURL);
-   initialURL = openedViaURL;
+   try {
+     const url = await Linking.getInitialURL();
+     if (url) {
+       const schemedURL = getSchemedURLFromUniversalURL(url);
+       setInitialURL(schemedURL);
+       logger.debug("[InitialStateHandler] Initial URL processed:", schemedURL);
+     }
+   } catch (error) {
+     logger.error("[InitialStateHandler] Failed to process initial URL:", error);
+   }
  };

Line range hint 1-63: Consider implementing a unified navigation coordinator

To achieve the PR's objective of consistent navigation from notifications, consider implementing a dedicated navigation coordinator that:

  1. Maintains a single source of truth for navigation intents
  2. Coordinates between different navigation triggers (deep links, notifications)
  3. Ensures navigation happens only after hydration is complete
  4. Provides proper error handling and logging

Here's a suggested approach:

  1. Create a NavigationCoordinator class:
class NavigationCoordinator {
  private static instance: NavigationCoordinator;
  private pendingNavigations: Map<string, NavigationIntent> = new Map();
  
  static getInstance() {
    if (!this.instance) {
      this.instance = new NavigationCoordinator();
    }
    return this.instance;
  }
  
  addNavigation(intent: NavigationIntent) {
    this.pendingNavigations.set(intent.id, intent);
    this.processPendingNavigations();
  }
  
  private async processPendingNavigations() {
    if (!store.getState().hydrationDone) return;
    
    for (const [id, intent] of this.pendingNavigations) {
      try {
        await this.executeNavigation(intent);
      } catch (error) {
        logger.error(`Navigation failed for ${intent.source}:`, error);
      } finally {
        this.pendingNavigations.delete(id);
      }
    }
  }
}
  1. Update the notification handler to use the coordinator:
onNotification(notification) {
  NavigationCoordinator.getInstance().addNavigation({
    id: notification.id,
    url: notification.data.url,
    source: 'notification',
    timestamp: Date.now()
  });
}
  1. Update InitialStateHandler to use the coordinator:
handleInitialDeeplink() {
  const url = await Linking.getInitialURL();
  if (url) {
    NavigationCoordinator.getInstance().addNavigation({
      id: uuid(),
      url,
      source: 'deeplink',
      timestamp: Date.now()
    });
  }
}

This approach would:

  • Ensure consistent navigation handling
  • Prevent race conditions
  • Provide better debugging capabilities
  • Handle both notification and deep link scenarios uniformly
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e6bc40c and f5f485a.

📒 Files selected for processing (1)
  • components/StateHandlers/InitialStateHandler.tsx (3 hunks)
🔇 Additional comments (2)
components/StateHandlers/InitialStateHandler.tsx (2)

8-8: LGTM! Good addition of logging capabilities.

The addition of the logger import will help with debugging navigation issues, which is particularly relevant for the notification deep linking problems being addressed.

Also applies to: 10-10


48-55: ⚠️ Potential issue

Critical: Navigation handling needs improvement to prevent race conditions

The current implementation has several issues that could affect the reliability of notification deep linking:

  1. No error handling for Linking.openURL
  2. Immediate clearing of initialURL could interfere with notification handling
  3. No coordination between notification and deep link navigation

Consider this safer implementation:

- if (isDevelopmentClientURL(initialURL)) {
-   return;
- }
- Linking.openURL(initialURL);
- // Once opened, let's remove so we don't navigate twice
- // when logging out / relogging in
- initialURL = "";
+ if (initialURL && !isDevelopmentClientURL(initialURL)) {
+   try {
+     await Linking.openURL(initialURL);
+     logger.debug('[Navigation] Successfully opened URL:', initialURL);
+   } catch (error) {
+     logger.error('[Navigation] Failed to open URL:', error);
+   }
+ }

Additionally, consider implementing a navigation queue to handle both notification and deep link navigation intents:

type NavigationIntent = {
  url: string;
  source: 'notification' | 'deeplink';
  timestamp: number;
};

const navigationQueue = new Set<NavigationIntent>();

// Add to queue when receiving notification or deep link
const addNavigationIntent = (intent: NavigationIntent) => {
  navigationQueue.add(intent);
  processNavigationQueue();
};

// Process queue when hydration is complete
const processNavigationQueue = async () => {
  if (!hydrationDone) return;
  
  for (const intent of navigationQueue) {
    try {
      await Linking.openURL(intent.url);
      logger.debug(`[Navigation] Processed ${intent.source} navigation to: ${intent.url}`);
    } catch (error) {
      logger.error(`[Navigation] Failed to process ${intent.source} navigation:`, error);
    }
    navigationQueue.delete(intent);
  }
};

Let's verify the notification handling implementation:

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

# Look for other places where Linking.openURL is used
ast-grep --pattern 'Linking.openURL($_)'

@technoplato
Copy link
Collaborator Author

Android appears to be broken from the jump - unrelated to these changes. cc @alexrisch

@technoplato technoplato merged commit 02a23bd into release/3.0.0 Dec 9, 2024
5 checks passed
@technoplato technoplato deleted the ml/conv-1130-inconsistent-notification-deep-link-to-chat branch December 9, 2024 22:05
@coderabbitai coderabbitai bot mentioned this pull request Dec 11, 2024
technoplato added a commit that referenced this pull request Dec 17, 2024
* all

* fix-notification-opening-chats

* remove exploration

* Remove comments/unused code

---------

Co-authored-by: Alex Risch <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants