Skip to content

Mobile: Deep links and push notifications are not wired into one routing system, so taps go nowhere #509

Description

@Emmy123222

Goal this serves — Donors arriving from a link or notification reaching the right screen

Why this matters

Two of the main ways a donor returns to the app — following a shared project link, and tapping a notification about a project they support — depend on routing that is only partly built. A donor who taps a notification about a milestone should land on that project; today the machinery to do so is not connected.

Both paths also accept externally supplied input, which makes them a security boundary as well as a usability one.

Evidence

mobile/utils/notifications.ts exports registerDeviceToken and setupNotificationListener, and a search across app/, src/, components/ and hooks/ finds no caller for either. So the backend never receives a device token, and no listener exists to respond to a notification tap.

Deep link parsing exists separately in mobile/hooks/useDeepLink.ts with its own validation, and the two have no shared notion of a destination. There is no Android notification channel, which that platform requires before notification presentation settings take effect.

Why this is hard

Cold start versus warm start differ. A tap on a cold app must hold the destination until navigation is ready, and getting this wrong produces a link that works only when the app is already open — a bug that reproduces inconsistently and wastes enormous debugging time.

External input reaches routing. Both a link and a notification payload are attacker-influenceable, so destinations must be validated against an allowlist rather than constructed from received data.

Permission timing. Requesting notification permission at the wrong moment gets it denied permanently, and Android and iOS differ in how a denial can be recovered.

Token lifecycle. Device tokens rotate, and must be tied to wallet connection and disconnection so notifications do not follow a device after the donor disconnects.

Testing is genuinely hard. Cold-start routing and notification taps need harnesses that do not exist here yet.

Suggested approach

Define one destination model that both entry points resolve into, validated against an allowlist so no externally supplied value becomes a route directly.

Handle cold start explicitly by holding the pending destination until navigation is ready.

Wire token registration into the wallet lifecycle, create the Android channel before any notification arrives, and request permission on explicit user intent with a rationale shown first.

Acceptance criteria

  • A single destination model resolves both deep links and notification taps.
  • Destinations are validated against an allowlist; no externally supplied value becomes a route directly.
  • Cold start and warm start both route correctly, each covered by a test.
  • Device tokens are registered with the backend and tied to wallet connect and disconnect.
  • Token rotation is handled without leaving stale registrations.
  • The Android notification channel is created before any notification is received.
  • Permission is requested on explicit user intent with a rationale shown first, and denial is recoverable.
  • Malformed and hostile link payloads are rejected safely, covered by fuzz tests.
  • Tapping a donation or milestone notification lands on the correct screen on both platforms, verified on devices.

Scope

Roughly 5,000–7,000 lines, including tests.

Relevant files

  • mobile/utils/notifications.ts
  • mobile/hooks/useDeepLink.ts
  • mobile/app/_layout.tsx
  • mobile/app.json

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaignarea: mobileExpo/React Native appcomplexity: highSubstantial design/implementation work, not a quick fixsecuritySecurity-relevant

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions