From 2d6c3a8c571c0d47e86a9616f4711118cbd36855 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:01:40 +0100 Subject: [PATCH 01/15] fix: prevent multiple calls --- ts/features/linking/sagas/index.ts | 14 ++++++++++++++ ts/features/payments/common/saga/index.ts | 5 +---- ts/navigation/linkingSubscription.ts | 7 +++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ts/features/linking/sagas/index.ts b/ts/features/linking/sagas/index.ts index 59a6e9e640c..cd0b2e43334 100644 --- a/ts/features/linking/sagas/index.ts +++ b/ts/features/linking/sagas/index.ts @@ -3,12 +3,26 @@ import { isSendAARLink, navigateToSendAarFlowIfEnabled } from "../../pn/aar/utils/deepLinking"; +import { walletUpdate } from "../../wallet/store/actions"; import { clearLinkingUrl } from "../actions"; import { storedLinkingUrlSelector } from "../reducers"; +export const isExternalDeepLink = (url: string): boolean => + url.startsWith("https://continua.io.pagopa.it") || + url.startsWith("ioit://") || + url.includes("wallet") || + url.includes("payment") || + url.includes("idpay") || + url.includes("cgn"); + export function* handleStoredLinkingUrlIfNeeded() { const storedLinkingUrl = yield* select(storedLinkingUrlSelector); if (storedLinkingUrl !== undefined) { + // Check if it's an external deep link that should trigger wallet update + if (isExternalDeepLink(storedLinkingUrl)) { + yield* put(walletUpdate()); + } + const shouldNavigateToAAR = yield* select(isSendAARLink, storedLinkingUrl); if (shouldNavigateToAAR) { const state = yield* select(); diff --git a/ts/features/payments/common/saga/index.ts b/ts/features/payments/common/saga/index.ts index f772192fae4..5d48ef8ef71 100644 --- a/ts/features/payments/common/saga/index.ts +++ b/ts/features/payments/common/saga/index.ts @@ -1,5 +1,5 @@ import { SagaIterator } from "redux-saga"; -import { fork, put, select, takeLatest } from "typed-redux-saga/macro"; +import { fork, select, takeLatest } from "typed-redux-saga/macro"; import { isPagoPATestEnabledSelector } from "../../../../store/reducers/persistedPreferences"; import { walletApiBaseUrl, walletApiUatBaseUrl } from "../../../../config"; import { watchPaymentsOnboardingSaga } from "../../onboarding/saga"; @@ -14,7 +14,6 @@ import { createTransactionClient, createWalletClient } from "../api/client"; -import { walletUpdate } from "../../../wallet/store/actions"; import { handlePaymentsSessionToken } from "./handlePaymentsSessionToken"; import { handleResumePaymentsPendingActions } from "./handleResumePaymentsPendingActions"; import { cleanExpiredPaymentsOngoingFailed } from "./cleanExpiredPaymentsOngoingFailed"; @@ -48,6 +47,4 @@ export function* watchPaymentsSaga(walletToken: string): SagaIterator { yield* fork(watchPaymentsMethodDetailsSaga, walletClient); yield* fork(watchPaymentsReceiptSaga, transactionClient); yield* fork(watchPaymentsCheckoutSaga, paymentClient); - - yield* put(walletUpdate()); } diff --git a/ts/navigation/linkingSubscription.ts b/ts/navigation/linkingSubscription.ts index 091deebaadc..596e7d8b52e 100644 --- a/ts/navigation/linkingSubscription.ts +++ b/ts/navigation/linkingSubscription.ts @@ -2,6 +2,7 @@ import { Linking } from "react-native"; import { Action, Dispatch, Store } from "redux"; import { isLoggedIn } from "../features/authentication/common/store/utils/guards"; import { storeLinkingUrl } from "../features/linking/actions"; +import { isExternalDeepLink } from "../features/linking/sagas"; import { resetMessageArchivingAction } from "../features/messages/store/actions/archiving"; import { isArchivingDisabledSelector } from "../features/messages/store/reducers/archiving"; import { @@ -9,6 +10,7 @@ import { navigateToSendAarFlowIfEnabled } from "../features/pn/aar/utils/deepLinking"; import { processUtmLink } from "../features/utmLink"; +import { walletUpdate } from "../features/wallet/store/actions"; import { GlobalState } from "../store/reducers/types"; export const linkingSubscription = @@ -27,6 +29,11 @@ export const linkingSubscription = } if (isLoggedIn(state.authentication)) { + // Trigger wallet update for external deep links + if (isExternalDeepLink(url)) { + dispatch(walletUpdate()); + } + // only when logged in we can navigate to the AAR screen. if (isSendAARLink(state, url)) { navigateToSendAarFlowIfEnabled(state, url); From ac9f13068da88b379b65a654f807962d62163fe9 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:40:22 +0100 Subject: [PATCH 02/15] Revert "fix: prevent multiple calls" This reverts commit 2d6c3a8c571c0d47e86a9616f4711118cbd36855. --- ts/features/linking/sagas/index.ts | 14 -------------- ts/features/payments/common/saga/index.ts | 5 ++++- ts/navigation/linkingSubscription.ts | 7 ------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/ts/features/linking/sagas/index.ts b/ts/features/linking/sagas/index.ts index cd0b2e43334..59a6e9e640c 100644 --- a/ts/features/linking/sagas/index.ts +++ b/ts/features/linking/sagas/index.ts @@ -3,26 +3,12 @@ import { isSendAARLink, navigateToSendAarFlowIfEnabled } from "../../pn/aar/utils/deepLinking"; -import { walletUpdate } from "../../wallet/store/actions"; import { clearLinkingUrl } from "../actions"; import { storedLinkingUrlSelector } from "../reducers"; -export const isExternalDeepLink = (url: string): boolean => - url.startsWith("https://continua.io.pagopa.it") || - url.startsWith("ioit://") || - url.includes("wallet") || - url.includes("payment") || - url.includes("idpay") || - url.includes("cgn"); - export function* handleStoredLinkingUrlIfNeeded() { const storedLinkingUrl = yield* select(storedLinkingUrlSelector); if (storedLinkingUrl !== undefined) { - // Check if it's an external deep link that should trigger wallet update - if (isExternalDeepLink(storedLinkingUrl)) { - yield* put(walletUpdate()); - } - const shouldNavigateToAAR = yield* select(isSendAARLink, storedLinkingUrl); if (shouldNavigateToAAR) { const state = yield* select(); diff --git a/ts/features/payments/common/saga/index.ts b/ts/features/payments/common/saga/index.ts index 5d48ef8ef71..f772192fae4 100644 --- a/ts/features/payments/common/saga/index.ts +++ b/ts/features/payments/common/saga/index.ts @@ -1,5 +1,5 @@ import { SagaIterator } from "redux-saga"; -import { fork, select, takeLatest } from "typed-redux-saga/macro"; +import { fork, put, select, takeLatest } from "typed-redux-saga/macro"; import { isPagoPATestEnabledSelector } from "../../../../store/reducers/persistedPreferences"; import { walletApiBaseUrl, walletApiUatBaseUrl } from "../../../../config"; import { watchPaymentsOnboardingSaga } from "../../onboarding/saga"; @@ -14,6 +14,7 @@ import { createTransactionClient, createWalletClient } from "../api/client"; +import { walletUpdate } from "../../../wallet/store/actions"; import { handlePaymentsSessionToken } from "./handlePaymentsSessionToken"; import { handleResumePaymentsPendingActions } from "./handleResumePaymentsPendingActions"; import { cleanExpiredPaymentsOngoingFailed } from "./cleanExpiredPaymentsOngoingFailed"; @@ -47,4 +48,6 @@ export function* watchPaymentsSaga(walletToken: string): SagaIterator { yield* fork(watchPaymentsMethodDetailsSaga, walletClient); yield* fork(watchPaymentsReceiptSaga, transactionClient); yield* fork(watchPaymentsCheckoutSaga, paymentClient); + + yield* put(walletUpdate()); } diff --git a/ts/navigation/linkingSubscription.ts b/ts/navigation/linkingSubscription.ts index 596e7d8b52e..091deebaadc 100644 --- a/ts/navigation/linkingSubscription.ts +++ b/ts/navigation/linkingSubscription.ts @@ -2,7 +2,6 @@ import { Linking } from "react-native"; import { Action, Dispatch, Store } from "redux"; import { isLoggedIn } from "../features/authentication/common/store/utils/guards"; import { storeLinkingUrl } from "../features/linking/actions"; -import { isExternalDeepLink } from "../features/linking/sagas"; import { resetMessageArchivingAction } from "../features/messages/store/actions/archiving"; import { isArchivingDisabledSelector } from "../features/messages/store/reducers/archiving"; import { @@ -10,7 +9,6 @@ import { navigateToSendAarFlowIfEnabled } from "../features/pn/aar/utils/deepLinking"; import { processUtmLink } from "../features/utmLink"; -import { walletUpdate } from "../features/wallet/store/actions"; import { GlobalState } from "../store/reducers/types"; export const linkingSubscription = @@ -29,11 +27,6 @@ export const linkingSubscription = } if (isLoggedIn(state.authentication)) { - // Trigger wallet update for external deep links - if (isExternalDeepLink(url)) { - dispatch(walletUpdate()); - } - // only when logged in we can navigate to the AAR screen. if (isSendAARLink(state, url)) { navigateToSendAarFlowIfEnabled(state, url); From e2af20dcc8048c57b95750c0ca24f03ee8558671 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:59:31 +0100 Subject: [PATCH 03/15] fix: duplicated wallet calls --- ts/features/linking/sagas/index.ts | 9 +++++++++ .../wallet/saga/handleWalletUpdateSaga.ts | 3 +++ .../saga/watchExternalWalletUpdateSaga.ts | 19 ++++++++++++++++++ ts/features/wallet/store/actions/index.ts | 12 ++++++++++- ts/hooks/useOpenDeepLink.ts | 13 +++++++++++- ts/navigation/linkingSubscription.ts | 7 +++++++ ts/sagas/index.ts | 4 ++++ ts/sagas/startup.ts | 4 ---- ts/utils/deepLinkUtils.ts | 20 +++++++++++++++++++ 9 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts create mode 100644 ts/utils/deepLinkUtils.ts diff --git a/ts/features/linking/sagas/index.ts b/ts/features/linking/sagas/index.ts index 59a6e9e640c..44de4baff7a 100644 --- a/ts/features/linking/sagas/index.ts +++ b/ts/features/linking/sagas/index.ts @@ -3,8 +3,10 @@ import { isSendAARLink, navigateToSendAarFlowIfEnabled } from "../../pn/aar/utils/deepLinking"; +import { externalWalletUpdate } from "../../wallet/store/actions"; import { clearLinkingUrl } from "../actions"; import { storedLinkingUrlSelector } from "../reducers"; +import { shouldTriggerWalletUpdate } from "../../../utils/deepLinkUtils"; export function* handleStoredLinkingUrlIfNeeded() { const storedLinkingUrl = yield* select(storedLinkingUrlSelector); @@ -16,6 +18,13 @@ export function* handleStoredLinkingUrlIfNeeded() { yield* call(navigateToSendAarFlowIfEnabled, state, storedLinkingUrl); return true; } + + // Trigger wallet update for external Universal Links and specific internal paths + if (shouldTriggerWalletUpdate(storedLinkingUrl)) { + yield* put(externalWalletUpdate()); + yield* put(clearLinkingUrl()); + return true; + } } return false; } diff --git a/ts/features/wallet/saga/handleWalletUpdateSaga.ts b/ts/features/wallet/saga/handleWalletUpdateSaga.ts index 1845c359bac..53c7c890a52 100644 --- a/ts/features/wallet/saga/handleWalletUpdateSaga.ts +++ b/ts/features/wallet/saga/handleWalletUpdateSaga.ts @@ -1,5 +1,6 @@ import { put } from "typed-redux-saga/macro"; import { cgnDetails } from "../../bonus/cgn/store/actions/details"; +import { cgnEycaStatus } from "../../bonus/cgn/store/actions/eyca/details"; import { idPayWalletGet } from "../../idpay/wallet/store/actions"; import { getPaymentsWalletUserMethods } from "../../payments/wallet/store/actions"; @@ -14,4 +15,6 @@ export function* handleWalletUpdateSaga() { yield* put(idPayWalletGet.request()); // Updates the CGN details yield* put(cgnDetails.request()); + // Updates the CGN EYCA status + // yield* put(cgnEycaStatus.request()); } diff --git a/ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts b/ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts new file mode 100644 index 00000000000..fa8de5d611b --- /dev/null +++ b/ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts @@ -0,0 +1,19 @@ +import { SagaIterator } from "redux-saga"; +import { call, takeLatest } from "typed-redux-saga/macro"; +import { externalWalletUpdate } from "../store/actions"; +import { handleWalletUpdateSaga } from "./handleWalletUpdateSaga"; + +/** + * Saga that watches for external wallet update requests. + * This saga is triggered only by external deep links from web (Universal Links) + * and never by app startup or internal navigation. + */ +export function* watchExternalWalletUpdateSaga(): SagaIterator { + yield* takeLatest( + externalWalletUpdate, + function* handleExternalWalletUpdate() { + // Trigger the actual wallet update saga + yield* call(handleWalletUpdateSaga); + } + ); +} diff --git a/ts/features/wallet/store/actions/index.ts b/ts/features/wallet/store/actions/index.ts index 0d7672f406f..c2f0a840e22 100644 --- a/ts/features/wallet/store/actions/index.ts +++ b/ts/features/wallet/store/actions/index.ts @@ -8,7 +8,17 @@ import { WalletPreferencesActions } from "./preferences"; */ export const walletUpdate = createStandardAction("WALLET_UPDATE")(); -type WalletBaseActions = ActionType; +/** + * Action for external wallet update requests. + * This action is dispatched only when wallet updates are needed due to external deep links. + */ +export const externalWalletUpdate = createStandardAction( + "EXTERNAL_WALLET_UPDATE" +)(); + +type WalletBaseActions = ActionType< + typeof walletUpdate | typeof externalWalletUpdate +>; export type WalletActions = | WalletBaseActions diff --git a/ts/hooks/useOpenDeepLink.ts b/ts/hooks/useOpenDeepLink.ts index 73d2a26d9ac..2ba7ea2ae94 100644 --- a/ts/hooks/useOpenDeepLink.ts +++ b/ts/hooks/useOpenDeepLink.ts @@ -1,5 +1,8 @@ import { useLinkTo } from "@react-navigation/native"; +import { useIODispatch } from "../store/hooks"; +import { externalWalletUpdate } from "../features/wallet/store/actions"; import { handleInternalLink } from "../utils/internalLink"; +import { shouldTriggerWalletUpdate } from "../utils/deepLinkUtils"; /** * This hook handles deep links. It removes the prefix and navigates to the path using the linkTo function @@ -7,6 +10,14 @@ import { handleInternalLink } from "../utils/internalLink"; */ export const useOpenDeepLink = () => { const linkTo = useLinkTo(); + const dispatch = useIODispatch(); - return (url: string) => handleInternalLink(linkTo, url); + return (url: string) => { + // Trigger wallet update for external Universal Links and specific internal paths + if (shouldTriggerWalletUpdate(url)) { + dispatch(externalWalletUpdate()); + } + + handleInternalLink(linkTo, url); + }; }; diff --git a/ts/navigation/linkingSubscription.ts b/ts/navigation/linkingSubscription.ts index 091deebaadc..44a272446e1 100644 --- a/ts/navigation/linkingSubscription.ts +++ b/ts/navigation/linkingSubscription.ts @@ -9,6 +9,8 @@ import { navigateToSendAarFlowIfEnabled } from "../features/pn/aar/utils/deepLinking"; import { processUtmLink } from "../features/utmLink"; +import { externalWalletUpdate } from "../features/wallet/store/actions"; +import { shouldTriggerWalletUpdate } from "../utils/deepLinkUtils"; import { GlobalState } from "../store/reducers/types"; export const linkingSubscription = @@ -31,6 +33,11 @@ export const linkingSubscription = if (isSendAARLink(state, url)) { navigateToSendAarFlowIfEnabled(state, url); } + + // Trigger wallet update for external Universal Links and specific internal paths + if (shouldTriggerWalletUpdate(url)) { + dispatch(externalWalletUpdate()); + } } else { // If we are not logged in, we store the URL to be processed later dispatch(storeLinkingUrl(url)); diff --git a/ts/sagas/index.ts b/ts/sagas/index.ts index 2dd48bafad4..e2c54e2a5d8 100644 --- a/ts/sagas/index.ts +++ b/ts/sagas/index.ts @@ -11,6 +11,8 @@ import { watchUtmLinkSaga } from "../features/utmLink/saga"; import connectivityStatusSaga from "../features/connectivity/saga"; import { watchIdentification } from "../features/identification/sagas"; import { watchLogoutSaga } from "../features/authentication/common/saga/watchLogoutSaga"; +import { watchWalletSaga } from "../features/wallet/saga"; +import { watchExternalWalletUpdateSaga } from "../features/wallet/saga/watchExternalWalletUpdateSaga"; import backendStatusSaga from "./backendStatus"; import { watchContentSaga } from "./contentLoaders"; import { loadSystemPreferencesSaga } from "./preferences"; @@ -34,6 +36,8 @@ export default function* root() { call(watchPendingActionsSaga), call(watchUtmLinkSaga), call(watchLogoutSaga), + call(watchWalletSaga), + call(watchExternalWalletUpdateSaga), zendeskEnabled ? call(watchZendeskSupportSaga) : undefined ]); } diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index 76843e29925..5e69da2ad87 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -99,7 +99,6 @@ import { loadUserDataProcessing } from "../features/settings/common/store/action import { isProfileFirstOnBoarding } from "../features/settings/common/store/utils/guards"; import { handleApplicationStartupTransientError } from "../features/startup/sagas"; import { watchTrialSystemSaga } from "../features/trialSystem/store/sagas/watchTrialSystemSaga"; -import { watchWalletSaga } from "../features/wallet/saga"; import { watchGetZendeskTokenSaga, watchZendeskGetSessionSaga @@ -607,9 +606,6 @@ export function* initializeApplicationSaga( // active session login watcher yield* fork(watchActiveSessionLoginSaga); - // Start wathing new wallet sagas - yield* fork(watchWalletSaga); - // Here we can be sure that the session information is loaded and valid const bpdToken = maybeSessionInformation.value.bpdToken as string; diff --git a/ts/utils/deepLinkUtils.ts b/ts/utils/deepLinkUtils.ts new file mode 100644 index 00000000000..bfd17cf715d --- /dev/null +++ b/ts/utils/deepLinkUtils.ts @@ -0,0 +1,20 @@ +// Universal Links prefix for external web traffic +const IO_UNIVERSAL_LINK_PREFIX = "https://continua.io.pagopa.it"; + +// Internal deep link paths that require wallet update +const WALLET_UPDATE_PATHS = ["ioit://cgn-details/detail", "ioit://main/wallet"]; + +/** + * Check if the URL requires wallet update based on specific criteria + * - External Universal Links from web + * - Specific internal paths that need wallet refresh + */ +export const shouldTriggerWalletUpdate = (url: string): boolean => { + // External Universal Links always trigger wallet update + if (url.startsWith(IO_UNIVERSAL_LINK_PREFIX)) { + return true; + } + + // Check if internal path requires wallet update + return WALLET_UPDATE_PATHS.some(path => url.startsWith(path)); +}; From b28066385970be3fb9f86769b43b5909e079efe3 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:01:17 +0100 Subject: [PATCH 04/15] chore: removed comment --- ts/features/wallet/saga/handleWalletUpdateSaga.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/features/wallet/saga/handleWalletUpdateSaga.ts b/ts/features/wallet/saga/handleWalletUpdateSaga.ts index 53c7c890a52..8db06c3356f 100644 --- a/ts/features/wallet/saga/handleWalletUpdateSaga.ts +++ b/ts/features/wallet/saga/handleWalletUpdateSaga.ts @@ -16,5 +16,5 @@ export function* handleWalletUpdateSaga() { // Updates the CGN details yield* put(cgnDetails.request()); // Updates the CGN EYCA status - // yield* put(cgnEycaStatus.request()); + yield* put(cgnEycaStatus.request()); } From e0b52cc12b542d79af316f0978d9ec3f19f9ef99 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:17:13 +0100 Subject: [PATCH 05/15] chore: restore old navigation behavior --- ts/navigation/AppStackNavigator.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ts/navigation/AppStackNavigator.tsx b/ts/navigation/AppStackNavigator.tsx index 431c9119778..591ec51efa2 100644 --- a/ts/navigation/AppStackNavigator.tsx +++ b/ts/navigation/AppStackNavigator.tsx @@ -23,13 +23,13 @@ import { idPayLinkingOptions } from "../features/idpay/common/navigation/linking import { IngressScreen } from "../features/ingress/screens/IngressScreen"; import { ITW_ROUTES } from "../features/itwallet/navigation/routes"; import { useItwLinkingOptions } from "../features/itwallet/navigation/useItwLinkingOptions"; +import { storeLinkingUrl } from "../features/linking/actions"; import { MESSAGES_ROUTES } from "../features/messages/navigation/routes"; import { SERVICES_ROUTES } from "../features/services/common/navigation/routes"; import { SETTINGS_ROUTES } from "../features/settings/common/navigation/routes"; import { processUtmLink } from "../features/utmLink"; import { startApplicationInitialization } from "../store/actions/application"; import { setDebugCurrentRouteName } from "../store/actions/debug"; -import { storeLinkingUrl } from "../features/linking/actions"; import { useIODispatch, useIOSelector, useIOStore } from "../store/hooks"; import { trackScreen } from "../store/middlewares/navigation"; import { isCGNEnabledAfterLoadSelector } from "../store/reducers/backendStatus/remoteConfig"; @@ -149,15 +149,7 @@ const InnerNavigationContainer = (props: InnerNavigationContainerProps) => { [ROUTES.PAGE_NOT_FOUND]: "*" } }, - subscribe: linkingSubscription(dispatch, store), - getInitialURL: async () => { - const initialUrl = await Linking.getInitialURL(); - // check if the url contains main/wallet to remap the url - if (initialUrl && /\/main\/wallet(?:[/?#]|$)/.test(initialUrl)) { - return initialUrl.replace(/\/main\/wallet(?=\/|[?#]|$)/, ""); - } - return initialUrl; - } + subscribe: linkingSubscription(dispatch, store) }; /** From c04f79f134b1844e4f049cd623699a12dc6e0ffc Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:20:07 +0100 Subject: [PATCH 06/15] chore: removed unnecessary force update calls --- ts/hooks/useOpenDeepLink.ts | 13 +------------ ts/navigation/linkingSubscription.ts | 7 ------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/ts/hooks/useOpenDeepLink.ts b/ts/hooks/useOpenDeepLink.ts index 2ba7ea2ae94..73d2a26d9ac 100644 --- a/ts/hooks/useOpenDeepLink.ts +++ b/ts/hooks/useOpenDeepLink.ts @@ -1,8 +1,5 @@ import { useLinkTo } from "@react-navigation/native"; -import { useIODispatch } from "../store/hooks"; -import { externalWalletUpdate } from "../features/wallet/store/actions"; import { handleInternalLink } from "../utils/internalLink"; -import { shouldTriggerWalletUpdate } from "../utils/deepLinkUtils"; /** * This hook handles deep links. It removes the prefix and navigates to the path using the linkTo function @@ -10,14 +7,6 @@ import { shouldTriggerWalletUpdate } from "../utils/deepLinkUtils"; */ export const useOpenDeepLink = () => { const linkTo = useLinkTo(); - const dispatch = useIODispatch(); - return (url: string) => { - // Trigger wallet update for external Universal Links and specific internal paths - if (shouldTriggerWalletUpdate(url)) { - dispatch(externalWalletUpdate()); - } - - handleInternalLink(linkTo, url); - }; + return (url: string) => handleInternalLink(linkTo, url); }; diff --git a/ts/navigation/linkingSubscription.ts b/ts/navigation/linkingSubscription.ts index 44a272446e1..091deebaadc 100644 --- a/ts/navigation/linkingSubscription.ts +++ b/ts/navigation/linkingSubscription.ts @@ -9,8 +9,6 @@ import { navigateToSendAarFlowIfEnabled } from "../features/pn/aar/utils/deepLinking"; import { processUtmLink } from "../features/utmLink"; -import { externalWalletUpdate } from "../features/wallet/store/actions"; -import { shouldTriggerWalletUpdate } from "../utils/deepLinkUtils"; import { GlobalState } from "../store/reducers/types"; export const linkingSubscription = @@ -33,11 +31,6 @@ export const linkingSubscription = if (isSendAARLink(state, url)) { navigateToSendAarFlowIfEnabled(state, url); } - - // Trigger wallet update for external Universal Links and specific internal paths - if (shouldTriggerWalletUpdate(url)) { - dispatch(externalWalletUpdate()); - } } else { // If we are not logged in, we store the URL to be processed later dispatch(storeLinkingUrl(url)); From 707b9a7caf1dbc9cae26de8d834962452fd221ef Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:35:58 +0100 Subject: [PATCH 07/15] chore: [IOBP-2166] Saga improvements (#7618) ## Short description This pull request refactors how wallet updates triggered by deep links are handled, improving separation of concerns and simplifying the saga flow. The main change is moving wallet update logic for deep links out of general sagas and into a dedicated startup handler and navigation subscription. This eliminates the need for a special external wallet update action and saga, ensuring wallet updates are triggered in the correct context (either at startup or when the app is already running). ## List of changes proposed in this pull request - Added a new saga `handleDeepLinkStartupSaga` to process stored deep links during app startup, handling both AAR notification links and wallet update triggers directly within the startup flow - Removed the `externalWalletUpdate` action and the `watchExternalWalletUpdateSaga`, consolidating wallet update triggers to use the standard `walletUpdate` action and saga - Updated the navigation subscription (`linkingSubscription`) to trigger wallet updates via the `walletUpdate` action when appropriate deep links are detected while the app is running and the user is authenticated - Removed wallet update logic from the general linking saga (`handleStoredLinkingUrlIfNeeded`), delegating deep link handling to the new startup saga and navigation subscription ## How to test Ensure that the same behavior described in #7588 is reproducible --- .../__tests__/identificationSaga.test.ts | 2 +- ts/features/identification/sagas/index.ts | 2 +- ts/features/linking/sagas/index.ts | 7 ++-- .../sagas/__tests__/common.test.ts | 36 ++++++------------- ts/features/pushNotifications/sagas/common.ts | 35 +----------------- .../saga/watchExternalWalletUpdateSaga.ts | 19 ---------- ts/features/wallet/store/actions/index.ts | 14 ++------ ts/navigation/linkingSubscription.ts | 8 +++++ ts/sagas/backgroundActions.ts | 28 +++++++++++++++ ts/sagas/index.ts | 2 -- ts/sagas/startup.ts | 2 +- .../startup/watchApplicationActivitySaga.ts | 2 +- 12 files changed, 55 insertions(+), 102 deletions(-) delete mode 100644 ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts create mode 100644 ts/sagas/backgroundActions.ts diff --git a/ts/features/identification/sagas/__tests__/identificationSaga.test.ts b/ts/features/identification/sagas/__tests__/identificationSaga.test.ts index c00c4586023..94443b7f070 100644 --- a/ts/features/identification/sagas/__tests__/identificationSaga.test.ts +++ b/ts/features/identification/sagas/__tests__/identificationSaga.test.ts @@ -15,7 +15,7 @@ import { checkCurrentSession, sessionInvalid } from "../../../authentication/common/store/actions"; -import { maybeHandlePendingBackgroundActions } from "../../../pushNotifications/sagas/common"; +import { maybeHandlePendingBackgroundActions } from "../../../../sagas/backgroundActions"; import { isFastLoginEnabledSelector } from "../../../authentication/fastLogin/store/selectors"; import { startApplicationInitialization } from "../../../../store/actions/application"; import { PinString } from "../../../../types/PinString"; diff --git a/ts/features/identification/sagas/index.ts b/ts/features/identification/sagas/index.ts index a6a3d4c54cc..141847f1fb7 100644 --- a/ts/features/identification/sagas/index.ts +++ b/ts/features/identification/sagas/index.ts @@ -11,7 +11,7 @@ import { sessionInvalid } from "../../authentication/common/store/actions"; import { isFastLoginEnabledSelector } from "../../authentication/fastLogin/store/selectors/index"; -import { maybeHandlePendingBackgroundActions } from "../../pushNotifications/sagas/common"; +import { maybeHandlePendingBackgroundActions } from "../../../sagas/backgroundActions"; import { identificationCancel, identificationForceLogout, diff --git a/ts/features/linking/sagas/index.ts b/ts/features/linking/sagas/index.ts index 44de4baff7a..f9412c76af9 100644 --- a/ts/features/linking/sagas/index.ts +++ b/ts/features/linking/sagas/index.ts @@ -3,10 +3,10 @@ import { isSendAARLink, navigateToSendAarFlowIfEnabled } from "../../pn/aar/utils/deepLinking"; -import { externalWalletUpdate } from "../../wallet/store/actions"; import { clearLinkingUrl } from "../actions"; import { storedLinkingUrlSelector } from "../reducers"; import { shouldTriggerWalletUpdate } from "../../../utils/deepLinkUtils"; +import { walletUpdate } from "../../wallet/store/actions"; export function* handleStoredLinkingUrlIfNeeded() { const storedLinkingUrl = yield* select(storedLinkingUrlSelector); @@ -18,12 +18,9 @@ export function* handleStoredLinkingUrlIfNeeded() { yield* call(navigateToSendAarFlowIfEnabled, state, storedLinkingUrl); return true; } - - // Trigger wallet update for external Universal Links and specific internal paths if (shouldTriggerWalletUpdate(storedLinkingUrl)) { - yield* put(externalWalletUpdate()); yield* put(clearLinkingUrl()); - return true; + yield* put(walletUpdate()); } } return false; diff --git a/ts/features/pushNotifications/sagas/__tests__/common.test.ts b/ts/features/pushNotifications/sagas/__tests__/common.test.ts index 3ed3060c110..73dfbbe9f5a 100644 --- a/ts/features/pushNotifications/sagas/__tests__/common.test.ts +++ b/ts/features/pushNotifications/sagas/__tests__/common.test.ts @@ -1,5 +1,6 @@ import { testSaga } from "redux-saga-test-plan"; import NavigationService from "../../../../navigation/NavigationService"; +import { maybeHandlePendingBackgroundActions } from "../../../../sagas/backgroundActions"; import { navigateToMainNavigatorAction } from "../../../../store/actions/navigation"; import { handleStoredLinkingUrlIfNeeded } from "../../../linking/sagas"; import { resetMessageArchivingAction } from "../../../messages/store/actions/archiving"; @@ -16,14 +17,11 @@ import { checkNotificationPermissions } from "../../utils"; import { navigateToMessageRouterAction } from "../../utils/navigation"; import { checkAndUpdateNotificationPermissionsIfNeeded, - maybeHandlePendingBackgroundActions, - testable, + handlePushNotificationIfNeeded, updateNotificationPermissionsIfNeeded } from "../common"; describe("maybeHandlePendingBackgroundActions", () => { - const pushHandler = testable?.handlePushNotificationIfNeeded; - describe("main functionality", () => { const generateTestName = ( linkingUrlHandled: boolean, @@ -40,11 +38,6 @@ describe("maybeHandlePendingBackgroundActions", () => { [true, false].forEach(linkingUrlHandled => [true, false].forEach(pushNotifHandled => it(generateTestName(linkingUrlHandled, pushNotifHandled), () => { - if (pushHandler === undefined) { - fail( - "testable export does not contain handlePushNotificationIfNeeded" - ); - } const saga = testSaga(maybeHandlePendingBackgroundActions, false) .next() .call(handleStoredLinkingUrlIfNeeded) @@ -52,7 +45,10 @@ describe("maybeHandlePendingBackgroundActions", () => { if (linkingUrlHandled) { saga.isDone(); } else { - saga.call(pushHandler, false).next(pushNotifHandled).isDone(); + saga + .call(handlePushNotificationIfNeeded, false) + .next(pushNotifHandled) + .isDone(); } }) ) @@ -71,10 +67,7 @@ describe("maybeHandlePendingBackgroundActions", () => { fromNotification: true }); - if (pushHandler === undefined) { - fail("testable export does not contain handlePushNotificationIfNeeded"); - } - testSaga(pushHandler, false) + testSaga(handlePushNotificationIfNeeded, false) .next() .select(pendingMessageStateSelector) .next(mockedPendingMessageState) @@ -96,10 +89,7 @@ describe("maybeHandlePendingBackgroundActions", () => { fromNotification: true }); - if (pushHandler === undefined) { - fail("testable export does not contain pushHandler"); - } - testSaga(pushHandler, true) + testSaga(handlePushNotificationIfNeeded, true) .next() .select(pendingMessageStateSelector) .next(mockedPendingMessageState) @@ -123,10 +113,7 @@ describe("maybeHandlePendingBackgroundActions", () => { fromNotification: true }); - if (pushHandler === undefined) { - fail("testable export does not contain pushHandler"); - } - testSaga(pushHandler, false) + testSaga(handlePushNotificationIfNeeded, false) .next() .select(pendingMessageStateSelector) .next(mockedPendingMessageState) @@ -145,10 +132,7 @@ describe("maybeHandlePendingBackgroundActions", () => { }); it("does nothing if there are not pending messages", () => { - if (pushHandler === undefined) { - fail("testable export does not contain pushHandler"); - } - testSaga(pushHandler, false) + testSaga(handlePushNotificationIfNeeded, false) .next() .select(pendingMessageStateSelector) .next(null) diff --git a/ts/features/pushNotifications/sagas/common.ts b/ts/features/pushNotifications/sagas/common.ts index f5bc4769b54..a6ad31cc544 100644 --- a/ts/features/pushNotifications/sagas/common.ts +++ b/ts/features/pushNotifications/sagas/common.ts @@ -1,8 +1,6 @@ import { call, put, select } from "typed-redux-saga/macro"; import NavigationService from "../../../navigation/NavigationService"; import { navigateToMainNavigatorAction } from "../../../store/actions/navigation"; -import { isTestEnv } from "../../../utils/environment"; -import { handleStoredLinkingUrlIfNeeded } from "../../linking/sagas"; import { resetMessageArchivingAction } from "../../messages/store/actions/archiving"; import { isArchivingDisabledSelector } from "../../messages/store/reducers/archiving"; import { trackNotificationPermissionsStatus } from "../analytics"; @@ -52,32 +50,7 @@ export function* updateNotificationPermissionsIfNeeded( } } -/** - * this method is used to handle all actions that - * are triggered when the app is resumed from scratch or transitions from - * background to foreground, and also need to be handled - * later on in the app's life cycle. - * - * two examples are Universal/App Links and Push Notifications, which - * can transition the app from background or from a closed state to foreground, - * and need to be handled once the app has finished loading/initializing. - */ -export function* maybeHandlePendingBackgroundActions( - shouldResetToMainNavigator: boolean = false -) { - // check if we have a stored linking URL to process - if (yield* call(handleStoredLinkingUrlIfNeeded)) { - return true; - } - // Check if we have a pending notification message - if (yield* call(handlePushNotificationIfNeeded, shouldResetToMainNavigator)) { - return true; - } - - return false; -} - -function* handlePushNotificationIfNeeded( +export function* handlePushNotificationIfNeeded( shouldResetToMainNavigator: boolean = false ) { const pendingMessageState = yield* select(pendingMessageStateSelector); @@ -115,9 +88,3 @@ function* handlePushNotificationIfNeeded( } return false; } - -export const testable = isTestEnv - ? { - handlePushNotificationIfNeeded - } - : undefined; diff --git a/ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts b/ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts deleted file mode 100644 index fa8de5d611b..00000000000 --- a/ts/features/wallet/saga/watchExternalWalletUpdateSaga.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SagaIterator } from "redux-saga"; -import { call, takeLatest } from "typed-redux-saga/macro"; -import { externalWalletUpdate } from "../store/actions"; -import { handleWalletUpdateSaga } from "./handleWalletUpdateSaga"; - -/** - * Saga that watches for external wallet update requests. - * This saga is triggered only by external deep links from web (Universal Links) - * and never by app startup or internal navigation. - */ -export function* watchExternalWalletUpdateSaga(): SagaIterator { - yield* takeLatest( - externalWalletUpdate, - function* handleExternalWalletUpdate() { - // Trigger the actual wallet update saga - yield* call(handleWalletUpdateSaga); - } - ); -} diff --git a/ts/features/wallet/store/actions/index.ts b/ts/features/wallet/store/actions/index.ts index c2f0a840e22..0a8faefc365 100644 --- a/ts/features/wallet/store/actions/index.ts +++ b/ts/features/wallet/store/actions/index.ts @@ -4,21 +4,11 @@ import { WalletPlaceholdersActions } from "./placeholders"; import { WalletPreferencesActions } from "./preferences"; /** - * Action to trigger the update of the wallet screen content + * Action for requesting an update of the wallet info that can be called by any screen/saga */ export const walletUpdate = createStandardAction("WALLET_UPDATE")(); -/** - * Action for external wallet update requests. - * This action is dispatched only when wallet updates are needed due to external deep links. - */ -export const externalWalletUpdate = createStandardAction( - "EXTERNAL_WALLET_UPDATE" -)(); - -type WalletBaseActions = ActionType< - typeof walletUpdate | typeof externalWalletUpdate ->; +type WalletBaseActions = ActionType; export type WalletActions = | WalletBaseActions diff --git a/ts/navigation/linkingSubscription.ts b/ts/navigation/linkingSubscription.ts index 091deebaadc..b981fd753d7 100644 --- a/ts/navigation/linkingSubscription.ts +++ b/ts/navigation/linkingSubscription.ts @@ -9,6 +9,8 @@ import { navigateToSendAarFlowIfEnabled } from "../features/pn/aar/utils/deepLinking"; import { processUtmLink } from "../features/utmLink"; +import { walletUpdate } from "../features/wallet/store/actions"; +import { shouldTriggerWalletUpdate } from "../utils/deepLinkUtils"; import { GlobalState } from "../store/reducers/types"; export const linkingSubscription = @@ -31,6 +33,12 @@ export const linkingSubscription = if (isSendAARLink(state, url)) { navigateToSendAarFlowIfEnabled(state, url); } + + // Trigger wallet update for external Universal Links and specific internal paths + // when the user is authenticated and the app is already running + if (shouldTriggerWalletUpdate(url)) { + dispatch(walletUpdate()); + } } else { // If we are not logged in, we store the URL to be processed later dispatch(storeLinkingUrl(url)); diff --git a/ts/sagas/backgroundActions.ts b/ts/sagas/backgroundActions.ts new file mode 100644 index 00000000000..fdbc1cdbf6c --- /dev/null +++ b/ts/sagas/backgroundActions.ts @@ -0,0 +1,28 @@ +import { call } from "typed-redux-saga/macro"; +import { handleStoredLinkingUrlIfNeeded } from "../features/linking/sagas"; +import { handlePushNotificationIfNeeded } from "../features/pushNotifications/sagas/common"; + +/** + * this method is used to handle all actions that + * are triggered when the app is resumed from scratch or transitions from + * background to foreground, and also need to be handled + * later on in the app's life cycle. + * + * two examples are Universal/App Links and Push Notifications, which + * can transition the app from background or from a closed state to foreground, + * and need to be handled once the app has finished loading/initializing. + */ +export function* maybeHandlePendingBackgroundActions( + shouldResetToMainNavigator: boolean = false +) { + // check if we have a stored linking URL to process + if (yield* call(handleStoredLinkingUrlIfNeeded)) { + return true; + } + // Check if we have a pending notification message + if (yield* call(handlePushNotificationIfNeeded, shouldResetToMainNavigator)) { + return true; + } + + return false; +} diff --git a/ts/sagas/index.ts b/ts/sagas/index.ts index c9e040cbdfd..ff66cc1be1c 100644 --- a/ts/sagas/index.ts +++ b/ts/sagas/index.ts @@ -11,7 +11,6 @@ import connectivityStatusSaga from "../features/connectivity/saga"; import { watchIdentification } from "../features/identification/sagas"; import { watchUtmLinkSaga } from "../features/utmLink/saga"; import { watchWalletSaga } from "../features/wallet/saga"; -import { watchExternalWalletUpdateSaga } from "../features/wallet/saga/watchExternalWalletUpdateSaga"; import { watchZendeskSupportSaga } from "../features/zendesk/saga"; import backendStatusSaga from "./backendStatus"; import { watchContentSaga } from "./contentLoaders"; @@ -36,7 +35,6 @@ export default function* root() { call(watchUtmLinkSaga), call(watchLogoutSaga), call(watchWalletSaga), - call(watchExternalWalletUpdateSaga), zendeskEnabled ? call(watchZendeskSupportSaga) : undefined ]); } diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index c3001e71842..f6bed19f8aa 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -80,7 +80,6 @@ import { watchAbortOnboardingSaga } from "../features/onboarding/saga/watchAbort import { watchPaymentsSaga } from "../features/payments/common/saga"; import { watchAarFlowSaga } from "../features/pn/aar/saga/watchAARFlowSaga"; import { watchPnSaga } from "../features/pn/store/sagas/watchPnSaga"; -import { maybeHandlePendingBackgroundActions } from "../features/pushNotifications/sagas/common"; import { notificationPermissionsListener } from "../features/pushNotifications/sagas/notificationPermissionsListener"; import { profileAndSystemNotificationsPermissions } from "../features/pushNotifications/sagas/profileAndSystemNotificationsPermissions"; import { pushNotificationTokenUpload } from "../features/pushNotifications/sagas/pushNotificationTokenUpload"; @@ -142,6 +141,7 @@ import { showSessionExpirationBlockingScreenSelector } from "../features/authent import { watchCdcSaga } from "../features/bonus/cdc/common/saga"; import { setRefreshMessagesSection } from "../features/authentication/activeSessionLogin/store/actions"; import { watchMessagesSaga } from "../features/messages/saga"; +import { maybeHandlePendingBackgroundActions } from "./backgroundActions"; import { previousInstallationDataDeleteSaga } from "./installation"; import { askMixpanelOptIn, diff --git a/ts/sagas/startup/watchApplicationActivitySaga.ts b/ts/sagas/startup/watchApplicationActivitySaga.ts index 5f67b83c642..301ec02ccc0 100644 --- a/ts/sagas/startup/watchApplicationActivitySaga.ts +++ b/ts/sagas/startup/watchApplicationActivitySaga.ts @@ -10,7 +10,7 @@ import { StartupStatusEnum, isStartupLoaded } from "../../store/reducers/startup"; -import { maybeHandlePendingBackgroundActions } from "../../features/pushNotifications/sagas/common"; +import { maybeHandlePendingBackgroundActions } from "../backgroundActions"; import { areTwoMinElapsedFromLastActivity } from "../../features/authentication/fastLogin/store/actions/sessionRefreshActions"; import { isFastLoginEnabledSelector } from "../../features/authentication/fastLogin/store/selectors"; import { IdentificationBackActionType } from "../../features/identification/store/reducers"; From e1940448d325d1bab38fc2077f9f6ba4715e129b Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:54:03 +0100 Subject: [PATCH 08/15] chore: restore comment --- ts/features/wallet/store/actions/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/features/wallet/store/actions/index.ts b/ts/features/wallet/store/actions/index.ts index 0a8faefc365..0d7672f406f 100644 --- a/ts/features/wallet/store/actions/index.ts +++ b/ts/features/wallet/store/actions/index.ts @@ -4,7 +4,7 @@ import { WalletPlaceholdersActions } from "./placeholders"; import { WalletPreferencesActions } from "./preferences"; /** - * Action for requesting an update of the wallet info that can be called by any screen/saga + * Action to trigger the update of the wallet screen content */ export const walletUpdate = createStandardAction("WALLET_UPDATE")(); From cff94fa7e1681d879527c7ab448f1bf0b691721c Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:07:18 +0100 Subject: [PATCH 09/15] chore: removed duplicated const --- ts/utils/deepLinkUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/utils/deepLinkUtils.ts b/ts/utils/deepLinkUtils.ts index bfd17cf715d..519c83dfc9e 100644 --- a/ts/utils/deepLinkUtils.ts +++ b/ts/utils/deepLinkUtils.ts @@ -1,5 +1,4 @@ -// Universal Links prefix for external web traffic -const IO_UNIVERSAL_LINK_PREFIX = "https://continua.io.pagopa.it"; +import { IO_UNIVERSAL_LINK_PREFIX } from "./navigation"; // Internal deep link paths that require wallet update const WALLET_UPDATE_PATHS = ["ioit://cgn-details/detail", "ioit://main/wallet"]; From 1d5358bf4229319a60a908684a2dbfa6e0935dee Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:35:34 +0100 Subject: [PATCH 10/15] chore: removed wrong path --- ts/utils/deepLinkUtils.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ts/utils/deepLinkUtils.ts b/ts/utils/deepLinkUtils.ts index 519c83dfc9e..39bbb06b855 100644 --- a/ts/utils/deepLinkUtils.ts +++ b/ts/utils/deepLinkUtils.ts @@ -1,5 +1,3 @@ -import { IO_UNIVERSAL_LINK_PREFIX } from "./navigation"; - // Internal deep link paths that require wallet update const WALLET_UPDATE_PATHS = ["ioit://cgn-details/detail", "ioit://main/wallet"]; @@ -8,12 +6,5 @@ const WALLET_UPDATE_PATHS = ["ioit://cgn-details/detail", "ioit://main/wallet"]; * - External Universal Links from web * - Specific internal paths that need wallet refresh */ -export const shouldTriggerWalletUpdate = (url: string): boolean => { - // External Universal Links always trigger wallet update - if (url.startsWith(IO_UNIVERSAL_LINK_PREFIX)) { - return true; - } - - // Check if internal path requires wallet update - return WALLET_UPDATE_PATHS.some(path => url.startsWith(path)); -}; +export const shouldTriggerWalletUpdate = (url: string): boolean => + WALLET_UPDATE_PATHS.some(path => url.startsWith(path)); From 211717e68ad2b6a8ef889f9e7458f8bf0a232681 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:37:09 +0100 Subject: [PATCH 11/15] refactor: call eyca only if is CGN linking --- ts/features/linking/sagas/index.ts | 10 +++++++++- ts/features/wallet/saga/handleWalletUpdateSaga.ts | 5 +---- ts/utils/deepLinkUtils.ts | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ts/features/linking/sagas/index.ts b/ts/features/linking/sagas/index.ts index eb5a3b126c9..07fe08eabb6 100644 --- a/ts/features/linking/sagas/index.ts +++ b/ts/features/linking/sagas/index.ts @@ -3,8 +3,12 @@ import { initiateAarFlow } from "../../pn/aar/store/actions"; import { isSendAARLink } from "../../pn/aar/utils/deepLinking"; import { clearLinkingUrl } from "../actions"; import { storedLinkingUrlSelector } from "../reducers"; -import { shouldTriggerWalletUpdate } from "../../../utils/deepLinkUtils"; +import { + isCGNLinking, + shouldTriggerWalletUpdate +} from "../../../utils/deepLinkUtils"; import { walletUpdate } from "../../wallet/store/actions"; +import { cgnEycaStatus } from "../../bonus/cgn/store/actions/eyca/details"; export function* handleStoredLinkingUrlIfNeeded() { const storedLinkingUrl = yield* select(storedLinkingUrlSelector); @@ -19,6 +23,10 @@ export function* handleStoredLinkingUrlIfNeeded() { if (shouldTriggerWalletUpdate(storedLinkingUrl)) { yield* put(clearLinkingUrl()); yield* put(walletUpdate()); + // If the stored linking URL is a CGN linking, we also need to get EYCA status + if (isCGNLinking(storedLinkingUrl)) { + yield* put(cgnEycaStatus.request()); + } } } return false; diff --git a/ts/features/wallet/saga/handleWalletUpdateSaga.ts b/ts/features/wallet/saga/handleWalletUpdateSaga.ts index 2b825590727..fab0df8a98d 100644 --- a/ts/features/wallet/saga/handleWalletUpdateSaga.ts +++ b/ts/features/wallet/saga/handleWalletUpdateSaga.ts @@ -1,9 +1,8 @@ import { put } from "typed-redux-saga/macro"; +import { getCdcStatusWallet } from "../../bonus/cdc/wallet/store/actions"; import { cgnDetails } from "../../bonus/cgn/store/actions/details"; -import { cgnEycaStatus } from "../../bonus/cgn/store/actions/eyca/details"; import { idPayWalletGet } from "../../idpay/wallet/store/actions"; import { getPaymentsWalletUserMethods } from "../../payments/wallet/store/actions"; -import { getCdcStatusWallet } from "../../bonus/cdc/wallet/store/actions"; /** * This saga handles the update of the wallet screen @@ -16,8 +15,6 @@ export function* handleWalletUpdateSaga() { yield* put(idPayWalletGet.request()); // Updates the CGN details yield* put(cgnDetails.request()); - // Updates the CGN EYCA status - yield* put(cgnEycaStatus.request()); // Updates the CDC status yield* put(getCdcStatusWallet.request()); } diff --git a/ts/utils/deepLinkUtils.ts b/ts/utils/deepLinkUtils.ts index 39bbb06b855..a05d0fb2ee7 100644 --- a/ts/utils/deepLinkUtils.ts +++ b/ts/utils/deepLinkUtils.ts @@ -8,3 +8,6 @@ const WALLET_UPDATE_PATHS = ["ioit://cgn-details/detail", "ioit://main/wallet"]; */ export const shouldTriggerWalletUpdate = (url: string): boolean => WALLET_UPDATE_PATHS.some(path => url.startsWith(path)); + +export const isCGNLinking = (url: string): boolean => + url.startsWith("ioit://cgn-details/detail"); From 1541a50d783444322ae31da0e2189327d6ba49a5 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:44:02 +0100 Subject: [PATCH 12/15] chore: include idpay path --- ts/utils/deepLinkUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ts/utils/deepLinkUtils.ts b/ts/utils/deepLinkUtils.ts index a05d0fb2ee7..7db9d10853b 100644 --- a/ts/utils/deepLinkUtils.ts +++ b/ts/utils/deepLinkUtils.ts @@ -1,5 +1,9 @@ // Internal deep link paths that require wallet update -const WALLET_UPDATE_PATHS = ["ioit://cgn-details/detail", "ioit://main/wallet"]; +const WALLET_UPDATE_PATHS = [ + "ioit://cgn-details/detail", + "ioit://main/wallet", + "ioit://idpay/initiative" +]; /** * Check if the URL requires wallet update based on specific criteria From 8a507b7e5982c2b2a50141f26362e7745b54d147 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:04:18 +0100 Subject: [PATCH 13/15] feat: startup completed action --- ts/features/idpay/common/saga/index.ts | 5 ++++- ts/sagas/startup.ts | 24 +++++++++++++----------- ts/store/actions/startupCompleted.ts | 4 ++++ 3 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 ts/store/actions/startupCompleted.ts diff --git a/ts/features/idpay/common/saga/index.ts b/ts/features/idpay/common/saga/index.ts index 44477e1dfbc..89195299d5b 100644 --- a/ts/features/idpay/common/saga/index.ts +++ b/ts/features/idpay/common/saga/index.ts @@ -1,7 +1,7 @@ import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import { SagaIterator } from "redux-saga"; -import { fork, select } from "typed-redux-saga/macro"; +import { fork, select, take } from "typed-redux-saga/macro"; import { PreferredLanguageEnum } from "../../../../../definitions/backend/PreferredLanguage"; import { idPayApiBaseUrl, @@ -24,8 +24,11 @@ import { watchIDPayInitiativeConfigurationSaga } from "../../configuration/saga" import { watchIDPayBarcodeSaga } from "../../barcode/saga"; import { watchIdPayUnsubscriptionSaga } from "../../unsubscription/saga"; import { isIdPayCiePaymentCodeEnabledSelector } from "../../../../store/reducers/backendStatus/remoteConfig"; +import { startupCompleted } from "../../../../store/actions/startupCompleted"; export function* watchIDPaySaga(bpdToken: string): SagaIterator { + yield* take(startupCompleted); + const isPagoPATestEnabled = yield* select(isPagoPATestEnabledSelector); const baseUrl = isPagoPATestEnabled ? idPayApiUatBaseUrl : idPayApiBaseUrl; diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index f6bed19f8aa..ea05674214a 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -17,9 +17,13 @@ import { import { ActionType, getType } from "typesafe-actions"; import { UserDataProcessingChoiceEnum } from "../../definitions/backend/UserDataProcessingChoice"; import { UserDataProcessingStatusEnum } from "../../definitions/backend/UserDataProcessingStatus"; +import { backendClientManager } from "../api/BackendClientManager"; import { BackendClient } from "../api/backend"; import { apiUrlPrefix, zendeskEnabled } from "../config"; import { watchActiveSessionLoginSaga } from "../features/authentication/activeSessionLogin/saga"; +import { navigateToActiveSessionLogin } from "../features/authentication/activeSessionLogin/saga/navigateToActiveSessionLogin"; +import { setRefreshMessagesSection } from "../features/authentication/activeSessionLogin/store/actions"; +import { showSessionExpirationBlockingScreenSelector } from "../features/authentication/activeSessionLogin/store/selectors"; import { authenticationSaga } from "../features/authentication/common/saga/authenticationSaga"; import { loadSessionInformationSaga } from "../features/authentication/common/saga/loadSessionInformationSaga"; import { @@ -42,6 +46,7 @@ import { } from "../features/authentication/fastLogin/store/selectors"; import { shouldTrackLevelSecurityMismatchSaga } from "../features/authentication/login/cie/sagas/trackLevelSecuritySaga"; import { userFromSuccessLoginSelector } from "../features/authentication/loginInfo/store/selectors"; +import { watchCdcSaga } from "../features/bonus/cdc/common/saga"; import { watchBonusCgnSaga } from "../features/bonus/cgn/saga"; import { watchFciSaga } from "../features/fci/saga"; import { watchFimsSaga } from "../features/fims/common/saga"; @@ -73,12 +78,14 @@ import { watchEmailNotificationPreferencesSaga } from "../features/mailCheck/sag import { checkEmailSaga } from "../features/mailCheck/sagas/checkEmailSaga"; import { watchEmailValidationSaga } from "../features/mailCheck/sagas/emailValidationPollingSaga"; import { MESSAGES_ROUTES } from "../features/messages/navigation/routes"; +import { watchMessagesSaga } from "../features/messages/saga"; import { handleClearAllAttachments } from "../features/messages/saga/handleClearAttachments"; import { checkAcknowledgedFingerprintSaga } from "../features/onboarding/saga/biometric/checkAcknowledgedFingerprintSaga"; import { completeOnboardingSaga } from "../features/onboarding/saga/completeOnboardingSaga"; import { watchAbortOnboardingSaga } from "../features/onboarding/saga/watchAbortOnboardingSaga"; import { watchPaymentsSaga } from "../features/payments/common/saga"; import { watchAarFlowSaga } from "../features/pn/aar/saga/watchAARFlowSaga"; +import { checkShouldDisplaySendEngagementScreen } from "../features/pn/loginEngagement/sagas/checkShouldDisplaySendEngagementScreen"; import { watchPnSaga } from "../features/pn/store/sagas/watchPnSaga"; import { notificationPermissionsListener } from "../features/pushNotifications/sagas/notificationPermissionsListener"; import { profileAndSystemNotificationsPermissions } from "../features/pushNotifications/sagas/profileAndSystemNotificationsPermissions"; @@ -104,6 +111,10 @@ import { import { formatRequestedTokenString } from "../features/zendesk/utils"; import NavigationService from "../navigation/NavigationService"; import ROUTES from "../navigation/routes"; +import { + waitForMainNavigator, + waitForNavigatorServiceInitialization +} from "../navigation/saga/navigation"; import { applicationInitialized, startApplicationInitialization @@ -116,6 +127,7 @@ import { startupLoadSuccess, startupTransientError } from "../store/actions/startup"; +import { startupCompleted } from "../store/actions/startupCompleted"; import { isAarRemoteEnabled, isIdPayEnabledSelector, @@ -130,17 +142,6 @@ import { ReduxSagaEffect, SagaCallReturnType } from "../types/utils"; import { trackKeychainFailures } from "../utils/analytics"; import { isTestEnv } from "../utils/environment"; import { getPin } from "../utils/keychain"; -import { backendClientManager } from "../api/BackendClientManager"; -import { - waitForMainNavigator, - waitForNavigatorServiceInitialization -} from "../navigation/saga/navigation"; -import { checkShouldDisplaySendEngagementScreen } from "../features/pn/loginEngagement/sagas/checkShouldDisplaySendEngagementScreen"; -import { navigateToActiveSessionLogin } from "../features/authentication/activeSessionLogin/saga/navigateToActiveSessionLogin"; -import { showSessionExpirationBlockingScreenSelector } from "../features/authentication/activeSessionLogin/store/selectors"; -import { watchCdcSaga } from "../features/bonus/cdc/common/saga"; -import { setRefreshMessagesSection } from "../features/authentication/activeSessionLogin/store/actions"; -import { watchMessagesSaga } from "../features/messages/saga"; import { maybeHandlePendingBackgroundActions } from "./backgroundActions"; import { previousInstallationDataDeleteSaga } from "./installation"; import { @@ -748,6 +749,7 @@ export function* initializeApplicationSaga( actionsToWaitFor: [] }) ); + yield* put(startupCompleted()); } export function* startupSaga(): IterableIterator { diff --git a/ts/store/actions/startupCompleted.ts b/ts/store/actions/startupCompleted.ts new file mode 100644 index 00000000000..8739a0441bf --- /dev/null +++ b/ts/store/actions/startupCompleted.ts @@ -0,0 +1,4 @@ +import { createStandardAction } from "typesafe-actions"; + +export const startupCompleted = + createStandardAction("STARTUP_COMPLETED")(); From 76c6d3af7b8bfc912649172a5022fb85e39f5fa9 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Thu, 27 Nov 2025 10:33:44 +0100 Subject: [PATCH 14/15] Revert "feat: startup completed action" This reverts commit 8a507b7e5982c2b2a50141f26362e7745b54d147. --- ts/features/idpay/common/saga/index.ts | 5 +---- ts/sagas/startup.ts | 24 +++++++++++------------- ts/store/actions/startupCompleted.ts | 4 ---- 3 files changed, 12 insertions(+), 21 deletions(-) delete mode 100644 ts/store/actions/startupCompleted.ts diff --git a/ts/features/idpay/common/saga/index.ts b/ts/features/idpay/common/saga/index.ts index 89195299d5b..44477e1dfbc 100644 --- a/ts/features/idpay/common/saga/index.ts +++ b/ts/features/idpay/common/saga/index.ts @@ -1,7 +1,7 @@ import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import { SagaIterator } from "redux-saga"; -import { fork, select, take } from "typed-redux-saga/macro"; +import { fork, select } from "typed-redux-saga/macro"; import { PreferredLanguageEnum } from "../../../../../definitions/backend/PreferredLanguage"; import { idPayApiBaseUrl, @@ -24,11 +24,8 @@ import { watchIDPayInitiativeConfigurationSaga } from "../../configuration/saga" import { watchIDPayBarcodeSaga } from "../../barcode/saga"; import { watchIdPayUnsubscriptionSaga } from "../../unsubscription/saga"; import { isIdPayCiePaymentCodeEnabledSelector } from "../../../../store/reducers/backendStatus/remoteConfig"; -import { startupCompleted } from "../../../../store/actions/startupCompleted"; export function* watchIDPaySaga(bpdToken: string): SagaIterator { - yield* take(startupCompleted); - const isPagoPATestEnabled = yield* select(isPagoPATestEnabledSelector); const baseUrl = isPagoPATestEnabled ? idPayApiUatBaseUrl : idPayApiBaseUrl; diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index ea05674214a..f6bed19f8aa 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -17,13 +17,9 @@ import { import { ActionType, getType } from "typesafe-actions"; import { UserDataProcessingChoiceEnum } from "../../definitions/backend/UserDataProcessingChoice"; import { UserDataProcessingStatusEnum } from "../../definitions/backend/UserDataProcessingStatus"; -import { backendClientManager } from "../api/BackendClientManager"; import { BackendClient } from "../api/backend"; import { apiUrlPrefix, zendeskEnabled } from "../config"; import { watchActiveSessionLoginSaga } from "../features/authentication/activeSessionLogin/saga"; -import { navigateToActiveSessionLogin } from "../features/authentication/activeSessionLogin/saga/navigateToActiveSessionLogin"; -import { setRefreshMessagesSection } from "../features/authentication/activeSessionLogin/store/actions"; -import { showSessionExpirationBlockingScreenSelector } from "../features/authentication/activeSessionLogin/store/selectors"; import { authenticationSaga } from "../features/authentication/common/saga/authenticationSaga"; import { loadSessionInformationSaga } from "../features/authentication/common/saga/loadSessionInformationSaga"; import { @@ -46,7 +42,6 @@ import { } from "../features/authentication/fastLogin/store/selectors"; import { shouldTrackLevelSecurityMismatchSaga } from "../features/authentication/login/cie/sagas/trackLevelSecuritySaga"; import { userFromSuccessLoginSelector } from "../features/authentication/loginInfo/store/selectors"; -import { watchCdcSaga } from "../features/bonus/cdc/common/saga"; import { watchBonusCgnSaga } from "../features/bonus/cgn/saga"; import { watchFciSaga } from "../features/fci/saga"; import { watchFimsSaga } from "../features/fims/common/saga"; @@ -78,14 +73,12 @@ import { watchEmailNotificationPreferencesSaga } from "../features/mailCheck/sag import { checkEmailSaga } from "../features/mailCheck/sagas/checkEmailSaga"; import { watchEmailValidationSaga } from "../features/mailCheck/sagas/emailValidationPollingSaga"; import { MESSAGES_ROUTES } from "../features/messages/navigation/routes"; -import { watchMessagesSaga } from "../features/messages/saga"; import { handleClearAllAttachments } from "../features/messages/saga/handleClearAttachments"; import { checkAcknowledgedFingerprintSaga } from "../features/onboarding/saga/biometric/checkAcknowledgedFingerprintSaga"; import { completeOnboardingSaga } from "../features/onboarding/saga/completeOnboardingSaga"; import { watchAbortOnboardingSaga } from "../features/onboarding/saga/watchAbortOnboardingSaga"; import { watchPaymentsSaga } from "../features/payments/common/saga"; import { watchAarFlowSaga } from "../features/pn/aar/saga/watchAARFlowSaga"; -import { checkShouldDisplaySendEngagementScreen } from "../features/pn/loginEngagement/sagas/checkShouldDisplaySendEngagementScreen"; import { watchPnSaga } from "../features/pn/store/sagas/watchPnSaga"; import { notificationPermissionsListener } from "../features/pushNotifications/sagas/notificationPermissionsListener"; import { profileAndSystemNotificationsPermissions } from "../features/pushNotifications/sagas/profileAndSystemNotificationsPermissions"; @@ -111,10 +104,6 @@ import { import { formatRequestedTokenString } from "../features/zendesk/utils"; import NavigationService from "../navigation/NavigationService"; import ROUTES from "../navigation/routes"; -import { - waitForMainNavigator, - waitForNavigatorServiceInitialization -} from "../navigation/saga/navigation"; import { applicationInitialized, startApplicationInitialization @@ -127,7 +116,6 @@ import { startupLoadSuccess, startupTransientError } from "../store/actions/startup"; -import { startupCompleted } from "../store/actions/startupCompleted"; import { isAarRemoteEnabled, isIdPayEnabledSelector, @@ -142,6 +130,17 @@ import { ReduxSagaEffect, SagaCallReturnType } from "../types/utils"; import { trackKeychainFailures } from "../utils/analytics"; import { isTestEnv } from "../utils/environment"; import { getPin } from "../utils/keychain"; +import { backendClientManager } from "../api/BackendClientManager"; +import { + waitForMainNavigator, + waitForNavigatorServiceInitialization +} from "../navigation/saga/navigation"; +import { checkShouldDisplaySendEngagementScreen } from "../features/pn/loginEngagement/sagas/checkShouldDisplaySendEngagementScreen"; +import { navigateToActiveSessionLogin } from "../features/authentication/activeSessionLogin/saga/navigateToActiveSessionLogin"; +import { showSessionExpirationBlockingScreenSelector } from "../features/authentication/activeSessionLogin/store/selectors"; +import { watchCdcSaga } from "../features/bonus/cdc/common/saga"; +import { setRefreshMessagesSection } from "../features/authentication/activeSessionLogin/store/actions"; +import { watchMessagesSaga } from "../features/messages/saga"; import { maybeHandlePendingBackgroundActions } from "./backgroundActions"; import { previousInstallationDataDeleteSaga } from "./installation"; import { @@ -749,7 +748,6 @@ export function* initializeApplicationSaga( actionsToWaitFor: [] }) ); - yield* put(startupCompleted()); } export function* startupSaga(): IterableIterator { diff --git a/ts/store/actions/startupCompleted.ts b/ts/store/actions/startupCompleted.ts deleted file mode 100644 index 8739a0441bf..00000000000 --- a/ts/store/actions/startupCompleted.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createStandardAction } from "typesafe-actions"; - -export const startupCompleted = - createStandardAction("STARTUP_COMPLETED")(); From e04919b25977d888de566f0d2d28ef97b55a7b69 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:22:23 +0100 Subject: [PATCH 15/15] refactor: moved duplicated link in const --- ts/utils/deepLinkUtils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ts/utils/deepLinkUtils.ts b/ts/utils/deepLinkUtils.ts index 7db9d10853b..836f64fa08a 100644 --- a/ts/utils/deepLinkUtils.ts +++ b/ts/utils/deepLinkUtils.ts @@ -1,6 +1,7 @@ +const CGN_ROUTE_PATH = "ioit://cgn-details/detail"; // Internal deep link paths that require wallet update const WALLET_UPDATE_PATHS = [ - "ioit://cgn-details/detail", + CGN_ROUTE_PATH, "ioit://main/wallet", "ioit://idpay/initiative" ]; @@ -14,4 +15,4 @@ export const shouldTriggerWalletUpdate = (url: string): boolean => WALLET_UPDATE_PATHS.some(path => url.startsWith(path)); export const isCGNLinking = (url: string): boolean => - url.startsWith("ioit://cgn-details/detail"); + url.startsWith(CGN_ROUTE_PATH);