ts/store/reducers/index.ts
/* eslint-disable no-underscore-dangle */
/**
* Aggregates all defined reducers
*/
import AsyncStorage from "@react-native-async-storage/async-storage";
import { combineReducers, Reducer } from "redux";
import {
createMigrate,
MigrationManifest,
PersistConfig,
PersistedState,
PersistPartial,
persistReducer,
purgeStoredState
} from "redux-persist";
import { isActionOf } from "typesafe-actions";
import { versionInfoReducer } from "../../common/versionInfo/store/reducers/versionInfo";
import bonusReducer from "../../features/bonus/common/store/reducers";
import { featuresPersistor } from "../../features/common/store/reducers";
import { lollipopPersistor } from "../../features/lollipop/store";
import { initialLollipopState } from "../../features/lollipop/store/reducers/lollipop";
import {
logoutFailure,
logoutSuccess,
sessionExpired
} from "../actions/authentication";
import { Action } from "../actions/types";
import createSecureStorage from "../storages/keychain";
import { DateISO8601Transform } from "../transforms/dateISO8601Tranform";
import { whatsNewInitialState } from "../../features/whatsnew/store/reducers";
import { fastLoginOptInInitialState } from "../../features/fastLogin/store/reducers/optInReducer";
import { isDevEnv } from "../../utils/environment";
import { trialSystemActivationStatusReducer } from "../../features/trialSystem/store/reducers";
import { persistedNotificationsReducer } from "../../features/pushNotifications/store/reducers";
import { profileSettingsReducerInitialState } from "../../features/profileSettings/store/reducers";
import { itwIdentificationInitialState } from "../../features/itwallet/identification/store/reducers";
import { cieLoginInitialState } from "../../features/cieLogin/store/reducers";
import appStateReducer from "./appState";
import assistanceToolsReducer from "./assistanceTools";
import authenticationReducer, {
AuthenticationState,
INITIAL_STATE as authenticationInitialState
} from "./authentication";
import backendStatusReducer from "./backendStatus";
import backoffErrorReducer from "./backoffError";
import cieReducer from "./cie";
import contentReducer, {
initialContentState as contentInitialContentState
} from "./content";
import crossSessionsReducer from "./crossSessions";
import { debugPersistor } from "./debug";
import emailValidationReducer from "./emailValidation";
import entitiesReducer, {
entitiesPersistConfig,
EntitiesState
} from "./entities";
import identificationReducer, {
IdentificationState,
fillShowLockModal,
INITIAL_STATE as identificationInitialState
} from "./identification";
import installationReducer from "./installation";
import { navigationReducer } from "./navigation";
import onboardingReducer from "./onboarding";
import paymentsReducer from "./payments";
import persistedPreferencesReducer, {
initialPreferencesState
} from "./persistedPreferences";
import preferencesReducer from "./preferences";
import profileReducer from "./profile";
import searchReducer from "./search";
import startupReducer from "./startup";
import { GlobalState } from "./types";
import userDataProcessingReducer from "./userDataProcessing";
import walletReducer from "./wallet";
import { WALLETS_INITIAL_STATE as walletsInitialState } from "./wallet/wallets";
// A custom configuration to store the authentication into the Keychain
export const authenticationPersistConfig: PersistConfig = {
key: "authentication",
storage: createSecureStorage(),
blacklist: ["deepLink"]
};
export const IDENTIFICATION_STATE_MIGRATION_VERSION = 0;
export const identificationStateMigration: MigrationManifest = {
// version 0
// we added showLockModal
"0": (state: PersistedState) => {
const previousState = state as IdentificationState & PersistPartial;
const failData = previousState.fail
? fillShowLockModal(previousState.fail)
: undefined;
return {
...previousState,
fail: failData
} as PersistedState;
}
};
// A custom configuration to store the fail information of the identification section
export const identificationPersistConfig: PersistConfig = {
key: "identification",
storage: AsyncStorage,
blacklist: ["progress"],
transforms: [DateISO8601Transform],
version: IDENTIFICATION_STATE_MIGRATION_VERSION,
migrate: createMigrate(identificationStateMigration, { debug: isDevEnv })
};
/**
* Here we combine all the reducers.
* We use the best practice of separating UI state from the DATA state.
* UI state is mostly used to check what to show hide in the screens (ex.
* errors/spinners).
* DATA state is where we store real data fetched from the API (ex.
* profile/messages).
* More at
* @https://medium.com/statuscode/dissecting-twitters-redux-store-d7280b62c6b1
*/
export const appReducer: Reducer<GlobalState, Action> = combineReducers<
GlobalState,
Action
>({
//
// ephemeral state
//
appState: appStateReducer,
navigation: navigationReducer,
backoffError: backoffErrorReducer,
wallet: walletReducer,
versionInfo: versionInfoReducer,
backendStatus: backendStatusReducer,
preferences: preferencesReducer,
search: searchReducer,
cie: cieReducer,
bonus: bonusReducer,
assistanceTools: assistanceToolsReducer,
startup: startupReducer,
//
// persisted state
//
// custom persistor (uses secure storage)
authentication: persistReducer<AuthenticationState, Action>(
authenticationPersistConfig,
authenticationReducer
),
// standard persistor, see configureStoreAndPersistor.ts
identification: persistReducer<IdentificationState, Action>(
identificationPersistConfig,
identificationReducer
),
features: featuresPersistor,
onboarding: onboardingReducer,
notifications: persistedNotificationsReducer,
profile: profileReducer,
userDataProcessing: userDataProcessingReducer,
entities: persistReducer<EntitiesState, Action>(
entitiesPersistConfig,
entitiesReducer
),
debug: debugPersistor,
persistedPreferences: persistedPreferencesReducer,
installation: installationReducer,
payments: paymentsReducer,
content: contentReducer,
emailValidation: emailValidationReducer,
crossSessions: crossSessionsReducer,
lollipop: lollipopPersistor,
trialSystem: trialSystemActivationStatusReducer
});
export function createRootReducer(
persistConfigs: ReadonlyArray<PersistConfig>
) {
return (state: GlobalState | undefined, action: Action): GlobalState => {
if (isActionOf(sessionExpired, action)) {
persistConfigs.forEach(
pc => pc.key === "wallets" && purgeStoredState(pc)
);
}
// despite logout fails the user must be logged out
if (
isActionOf(logoutFailure, action) ||
isActionOf(logoutSuccess, action)
) {
// Purge the stored redux-persist state
persistConfigs.forEach(persistConfig => purgeStoredState(persistConfig));
/**
* We can't return undefined for nested persist reducer, we need to return
* the basic redux persist content.
*/
// for retro-compatibility
// eslint-disable-next-line no-param-reassign
state = state
? ({
authentication: {
...authenticationInitialState,
_persist: state.authentication._persist
},
// backend status must be kept
backendStatus: state.backendStatus,
// keep servicesMetadata from content section
content: {
...contentInitialContentState,
contextualHelp: state.content.contextualHelp
},
// keep hashed fiscal code
crossSessions: state.crossSessions,
// data should be kept across multiple sessions
entities: {
organizations: state.entities.organizations,
paymentByRptId: state.entities.paymentByRptId,
calendarEvents: state.entities.calendarEvents,
_persist: state.entities._persist
},
features: {
whatsNew: {
...whatsNewInitialState,
_persist: state.features.whatsNew._persist
},
loginFeatures: {
fastLogin: {
optIn: {
...fastLoginOptInInitialState,
_persist:
state.features.loginFeatures.fastLogin.optIn._persist
},
securityAdviceAcknowledged: {
acknowledged:
state.features.loginFeatures.fastLogin
.securityAdviceAcknowledged.acknowledged,
_persist:
state.features.loginFeatures.fastLogin
.securityAdviceAcknowledged._persist
}
},
cieLogin: {
...cieLoginInitialState,
isCieIDFeatureEnabled:
state.features.loginFeatures.cieLogin.isCieIDFeatureEnabled,
_persist: state.features.loginFeatures.cieLogin._persist
}
},
profileSettings: {
...profileSettingsReducerInitialState,
showProfileBanner:
state.features.profileSettings.showProfileBanner,
hasUserAcknowledgedSettingsBanner:
state.features.profileSettings
.hasUserAcknowledgedSettingsBanner,
_persist: state.features.profileSettings._persist
},
_persist: state.features._persist,
// IT Wallet must be kept
itWallet: {
identification: itwIdentificationInitialState,
issuance: state.features.itWallet.issuance,
lifecycle: state.features.itWallet.lifecycle,
credentials: state.features.itWallet.credentials,
_persist: state.features.itWallet._persist
}
},
identification: {
...identificationInitialState,
_persist: state.identification._persist
},
// notifications must be kept
notifications: {
...state.notifications,
_persist: state.notifications._persist
},
// payments must be kept
payments: {
...state.payments
},
// isMixpanelEnabled must be kept
// isFingerprintEnabled must be kept only if true
persistedPreferences: {
...initialPreferencesState,
isMixpanelEnabled: state.persistedPreferences.isMixpanelEnabled,
isFingerprintEnabled: state.persistedPreferences
.isFingerprintEnabled
? true
: undefined
},
wallet: {
wallets: {
...walletsInitialState,
_persist: state.wallet.wallets._persist
}
},
lollipop: {
...initialLollipopState,
keyTag: state.lollipop.keyTag,
_persist: state.lollipop._persist
}
} as GlobalState)
: state;
}
return appReducer(state, action);
};
}