From bcd44828a35fd03f65aeadeec9d6988766014b7b Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Wed, 22 Jul 2026 21:49:03 -0700 Subject: [PATCH] fix(deeplink): stop malformed deep links from bricking every launch Two related defects surfaced from a banner action pezkuwiwallet://.../open/governance: 1. ReferendumDeepLinkHandler.matches() used path.startsWith("/open/gov"), so "/open/governance" wrongly matched the referendum prefix and then failed with ReferendumIsNotSpecified (no id). Use an exact path match instead. 2. RootDeepLinkHandler only cleared the persisted pending deep link on success or HandlerNotFound. A link that matched a handler but failed with a terminal DeepLinkHandlingException stayed in SharedPreferences and replayed on every launch ("Referendum not found" on each open). Clear pending on terminal handling failures in both the fresh-intent and pending-retry paths. This also auto-recovers already-stuck installs on update. --- .../presentation/handling/RootDeepLinkHandler.kt | 7 +++++++ .../details/deeplink/ReferendumDeepLinkHandler.kt | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/RootDeepLinkHandler.kt b/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/RootDeepLinkHandler.kt index 30f58e1c..7fa5446b 100644 --- a/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/RootDeepLinkHandler.kt +++ b/feature-deep-linking/src/main/java/io/novafoundation/nova/feature_deep_linking/presentation/handling/RootDeepLinkHandler.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_deep_linking.presentation.handling import android.net.Uri import io.novafoundation.nova.common.utils.onFailureInstance +import io.novafoundation.nova.feature_deep_linking.presentation.handling.common.DeepLinkHandlingException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.merge @@ -23,12 +24,18 @@ class RootDeepLinkHandler( return handleDeepLinkInternal(pendingDeepLink) .onSuccess { pendingDeepLinkProvider.clear() } + // A pending link that reaches a handler but fails with a terminal error (malformed/unresolvable), + // or matches no handler, will never succeed on retry - clear it so it does not replay on every launch. + .onFailureInstance { pendingDeepLinkProvider.clear() } + .onFailureInstance { pendingDeepLinkProvider.clear() } } override suspend fun handleDeepLink(data: Uri): Result { pendingDeepLinkProvider.save(data) return handleDeepLinkInternal(data) .onSuccess { pendingDeepLinkProvider.clear() } + // Same as above: a terminal handling failure must not be persisted, otherwise it bricks every launch. + .onFailureInstance { pendingDeepLinkProvider.clear() } .onFailureInstance { pendingDeepLinkProvider.clear() } // If we haven't find any handler - no need to save deep link } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/deeplink/ReferendumDeepLinkHandler.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/deeplink/ReferendumDeepLinkHandler.kt index 1c13551b..fb832850 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/deeplink/ReferendumDeepLinkHandler.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/deeplink/ReferendumDeepLinkHandler.kt @@ -31,7 +31,9 @@ class ReferendumDeepLinkHandler( override suspend fun matches(data: Uri): Boolean { val path = data.path ?: return false - return path.startsWith(ReferendumDetailsDeepLinkConfigurator.PREFIX) + // Exact match, not startsWith: otherwise "/open/governance" (and any "/open/gov*") would wrongly + // match the referendum prefix "/open/gov" and fail with ReferendumIsNotSpecified. + return path == ReferendumDetailsDeepLinkConfigurator.PREFIX } override suspend fun handleDeepLink(data: Uri) = runCatching {