Skip to content

Commit 2d1d0f3

Browse files
forrest57Vangaorth
andauthored
feat: [IOCOM-2545] handlePendingMessageState refactor (#7342)
## Short description refactored said function to handle stored Universal/AppLinks ## List of changes proposed in this pull request - refactor of said function - moved linking feature to its own folder, for cleanliness and ease of expansion - required tests ## How to test using the io-dev-api-server on the latest version and reactotron to check the app state, make sure that: - opening a link like `https://cittadini.dev.notifichedigitali.it/io?aar=asd123` opens the app and displays the SEND flow ( after an authentication screen where necessary) - if the app was already open, but in background, then the state key `features.backgroundLinking` is an empty object - if it was closed, the same state key displays the open link while in an authentication screen - after authentication, said key should be empty again - the app behaves as normal when woken up by a push notification interaction - different URLs, like - `https://cittadini.uat.notifichedigitali.it/io?aar=asd123` - `https://cittadini.notifichedigitali.it/io?aar=asd123` - `https://cittadini.dev.notifichedigitali.it` - `https://cittadini.dev.notifichedigitali.it/io` do not wake or open the app. - URLs also wake the app up when they are the result of a QRcode scan automated tests should also pass --------- Co-authored-by: Andrea <[email protected]>
1 parent 847b5de commit 2d1d0f3

File tree

26 files changed

+464
-229
lines changed

26 files changed

+464
-229
lines changed

ts/features/barcode/screens/BarcodeScanScreen.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ import {
55
VSpacer
66
} from "@pagopa/io-app-design-system";
77
import { useNavigation } from "@react-navigation/native";
8+
import I18n from "i18next";
89
import { Alert, View } from "react-native";
910
import ReactNativeHapticFeedback, {
1011
HapticFeedbackTypes
1112
} from "react-native-haptic-feedback";
12-
import I18n from "i18next";
1313
import { useHardwareBackButton } from "../../../hooks/useHardwareBackButton";
1414
import { useOpenDeepLink } from "../../../hooks/useOpenDeepLink";
1515
import { mixpanelTrack } from "../../../mixpanel";
1616
import {
1717
AppParamsList,
1818
IOStackNavigationProp
1919
} from "../../../navigation/params/AppParamsList";
20-
import { useIOSelector } from "../../../store/hooks";
20+
import { useIOSelector, useIOStore } from "../../../store/hooks";
2121
import {
2222
barcodesScannerConfigSelector,
2323
isPnRemoteEnabledSelector
@@ -33,6 +33,7 @@ import { usePagoPaPayment } from "../../payments/checkout/hooks/usePagoPaPayment
3333
import { PaymentsCheckoutRoutes } from "../../payments/checkout/navigation/routes";
3434
import { paymentAnalyticsDataSelector } from "../../payments/history/store/selectors";
3535
import * as paymentsAnalytics from "../../payments/home/analytics";
36+
import { navigateToSendAarFlow } from "../../pn/aar/utils/deepLinking.ts";
3637
import * as analytics from "../analytics";
3738
import { BarcodeScanBaseScreenComponent } from "../components/BarcodeScanBaseScreenComponent";
3839
import { useIOBarcodeFileReader } from "../hooks/useIOBarcodeFileReader";
@@ -47,15 +48,14 @@ import {
4748
} from "../types/IOBarcode";
4849
import { BarcodeFailure } from "../types/failure";
4950
import { getIOBarcodesByType } from "../utils/getBarcodesByType";
50-
import { MESSAGES_ROUTES } from "../../messages/navigation/routes.ts";
51-
import PN_ROUTES from "../../pn/navigation/routes.ts";
5251

5352
const BarcodeScanScreen = () => {
5453
const navigation = useNavigation<IOStackNavigationProp<AppParamsList>>();
5554
const openDeepLink = useOpenDeepLink();
5655
const isIdPayEnabled = useIOSelector(isIdPayLocallyEnabledSelector);
5756
const paymentAnalyticsData = useIOSelector(paymentAnalyticsDataSelector);
5857
const isSendEnabled = useIOSelector(isPnRemoteEnabledSelector);
58+
const store = useIOStore();
5959

6060
const { startPaymentFlowWithRptId } = usePagoPaPayment();
6161

@@ -167,15 +167,7 @@ const BarcodeScanScreen = () => {
167167
});
168168
break;
169169
case "SEND":
170-
navigation.navigate(MESSAGES_ROUTES.MESSAGES_NAVIGATOR, {
171-
screen: PN_ROUTES.MAIN,
172-
params: {
173-
screen: PN_ROUTES.QR_SCAN_FLOW,
174-
params: {
175-
aarUrl: barcode.qrCodeContent
176-
}
177-
}
178-
});
170+
navigateToSendAarFlow(store.getState(), barcode.qrCodeContent);
179171
break;
180172
}
181173
};

ts/features/common/store/reducers/__tests__/__snapshots__/index.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ exports[`featuresPersistor should match snapshot 1`] = `
99
"appearanceSettings": {
1010
"showAppearanceBanner": true,
1111
},
12+
"backgroundLinking": {},
1213
"connectivityStatus": {
1314
"isConnected": undefined,
1415
},

ts/features/common/store/reducers/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ import {
8080
activeSessionLoginReducer,
8181
ActiveSessionLoginState
8282
} from "../../../authentication/activeSessionLogin/store/reducer";
83+
import {
84+
BackgroundLinkingState,
85+
backgroundLinkingReducer
86+
} from "../../../linking/reducers";
8387

8488
type LoginFeaturesState = {
8589
testLogin: TestLoginState;
@@ -111,6 +115,7 @@ export type FeaturesState = {
111115
appFeedback: AppFeedbackState & PersistPartial;
112116
utmLink: UtmLinkState;
113117
connectivityStatus: ConnectivityState;
118+
backgroundLinking: BackgroundLinkingState;
114119
};
115120

116121
export type PersistedFeaturesState = FeaturesState & PersistPartial;
@@ -142,7 +147,8 @@ const rootReducer = combineReducers<FeaturesState, Action>({
142147
landingBanners: landingScreenBannersReducer,
143148
appFeedback: appFeedbackPersistor,
144149
utmLink: utmLinkReducer,
145-
connectivityStatus: connectivityStateReducer
150+
connectivityStatus: connectivityStateReducer,
151+
backgroundLinking: backgroundLinkingReducer
146152
});
147153

148154
const CURRENT_REDUX_FEATURES_STORE_VERSION = 1;

ts/features/identification/sagas/__tests__/identificationSaga.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
checkCurrentSession,
1616
sessionInvalid
1717
} from "../../../authentication/common/store/actions";
18-
import { handlePendingMessageStateIfAllowed } from "../../../pushNotifications/sagas/common";
18+
import { maybeHandlePendingBackgroundActions } from "../../../pushNotifications/sagas/common";
1919
import { isFastLoginEnabledSelector } from "../../../authentication/fastLogin/store/selectors";
2020
import { startApplicationInitialization } from "../../../../store/actions/application";
2121
import { PinString } from "../../../../types/PinString";
@@ -182,7 +182,7 @@ describe("Identification Saga", () => {
182182
.next()
183183
.call(testableModule.waitIdentificationResult)
184184
.next("success")
185-
.call(handlePendingMessageStateIfAllowed)
185+
.call(maybeHandlePendingBackgroundActions)
186186
.next()
187187
.isDone();
188188
});
@@ -253,7 +253,7 @@ describe("Identification Saga", () => {
253253
.next()
254254
.call(testableModule.waitIdentificationResult)
255255
.next("success")
256-
.call(handlePendingMessageStateIfAllowed)
256+
.call(maybeHandlePendingBackgroundActions)
257257
.next()
258258
.isDone();
259259
});

ts/features/identification/sagas/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
import { PinString } from "../../../types/PinString";
2525
import { ReduxSagaEffect, SagaCallReturnType } from "../../../types/utils";
2626
import { deletePin, getPin } from "../../../utils/keychain";
27-
import { handlePendingMessageStateIfAllowed } from "../../pushNotifications/sagas/common";
27+
import { maybeHandlePendingBackgroundActions } from "../../pushNotifications/sagas/common";
2828
import { isFastLoginEnabledSelector } from "../../authentication/fastLogin/store/selectors/index";
2929
import { isDevEnv } from "../../../utils/environment";
3030

@@ -141,8 +141,8 @@ function* startAndHandleIdentificationResult(
141141
if (identificationResult === IdentificationResult.pinreset) {
142142
yield* put(startApplicationInitialization());
143143
} else if (identificationResult === IdentificationResult.success) {
144-
// Check if we have a pending notification message
145-
yield* call(handlePendingMessageStateIfAllowed);
144+
// Check if we have any pending background actions to handle
145+
yield* call(maybeHandlePendingBackgroundActions);
146146
}
147147
}
148148

ts/store/actions/__tests__/__snapshots__/linking.test.ts.snap renamed to ts/features/linking/actions/__test__/__snapshots__/index.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`linking actions should create an action to clear the linking url 1`] = `
4+
{
5+
"meta": undefined,
6+
"payload": undefined,
7+
"type": "CLEAR_LINKING_URL",
8+
}
9+
`;
10+
311
exports[`linking actions should create an action to store a linking url 1`] = `
412
{
513
"meta": undefined,
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { storeLinkingUrl } from "../linking";
1+
import { clearLinkingUrl, storeLinkingUrl } from "..";
22

33
describe("linking actions", () => {
44
it("should create an action to store a linking url", () => {
55
const url = "https://example.com";
66
const action = storeLinkingUrl(url);
77
expect(action).toMatchSnapshot();
88
});
9+
it("should create an action to clear the linking url", () => {
10+
const action = clearLinkingUrl();
11+
expect(action).toMatchSnapshot();
12+
});
913
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ActionType, createStandardAction } from "typesafe-actions";
2+
3+
export const storeLinkingUrl =
4+
createStandardAction("STORE_LINKING_URL")<string>();
5+
export const clearLinkingUrl = createStandardAction("CLEAR_LINKING_URL")();
6+
7+
export type BackgroundLinkingActions = ActionType<
8+
typeof storeLinkingUrl | typeof clearLinkingUrl
9+
>;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { backgroundLinkingReducer, storedLinkingUrlSelector } from "..";
2+
import { clearLinkingUrl, storeLinkingUrl } from "../../actions";
3+
4+
describe("background linking reducer", () => {
5+
it("should return the initial state", () => {
6+
const expected = {};
7+
const reducer = backgroundLinkingReducer(undefined, {} as any);
8+
expect(reducer).toEqual(expected);
9+
});
10+
11+
it("should handle storing a linking url", () => {
12+
const url = "https://example.com";
13+
const expected = {
14+
linkingUrl: url
15+
};
16+
const reducer = backgroundLinkingReducer(undefined, storeLinkingUrl(url));
17+
expect(reducer).toEqual(expected);
18+
});
19+
it("should handle clearing the linking url", () => {
20+
const url = "https://example.com";
21+
const initialState = {
22+
linkingUrl: url
23+
};
24+
const expected = {};
25+
const reducer = backgroundLinkingReducer(initialState, clearLinkingUrl());
26+
expect(reducer).toEqual(expected);
27+
});
28+
describe("selectors", () => {
29+
["https://example.com", undefined].forEach(item =>
30+
it(`storedlinkingUrlSelector should return ${
31+
item === undefined
32+
? "undefined when no link is stored"
33+
: "an url that is stored in the reducer"
34+
}`, () => {
35+
const state = {
36+
features: {
37+
backgroundLinking:
38+
item === undefined
39+
? {}
40+
: {
41+
linkingUrl: item
42+
}
43+
}
44+
} as any;
45+
const linkingUrl = storedLinkingUrlSelector(state);
46+
expect(linkingUrl).toEqual(item);
47+
})
48+
);
49+
});
50+
});

ts/store/reducers/linking.ts renamed to ts/features/linking/reducers/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getType } from "typesafe-actions";
2-
import { storeLinkingUrl } from "../actions/linking";
3-
import { Action } from "../actions/types";
2+
import { clearLinkingUrl, storeLinkingUrl } from "../actions";
3+
import { Action } from "../../../store/actions/types";
4+
import { GlobalState } from "../../../store/reducers/types";
45

56
export type BackgroundLinkingState = {
67
linkingUrl?: string;
@@ -18,6 +19,11 @@ export const backgroundLinkingReducer = (
1819
...state,
1920
linkingUrl: action.payload
2021
};
22+
case getType(clearLinkingUrl):
23+
return INITIAL_STATE;
2124
}
2225
return state;
2326
};
27+
28+
export const storedLinkingUrlSelector = (state: GlobalState) =>
29+
state.features.backgroundLinking.linkingUrl;

0 commit comments

Comments
 (0)