From cf23a70e3d41d785a3922b0d856300b13bcbf2c5 Mon Sep 17 00:00:00 2001 From: Francesco Persico Date: Mon, 15 Oct 2018 16:43:42 +0200 Subject: [PATCH] Add code fix and improvements --- ts/IdentificationModal.tsx | 205 +++++++++--------- ts/sagas/identification.ts | 57 ++--- .../startup/watchApplicationActivitySaga.ts | 19 +- ts/store/reducers/wallet/payment.ts | 3 + 4 files changed, 147 insertions(+), 137 deletions(-) diff --git a/ts/IdentificationModal.tsx b/ts/IdentificationModal.tsx index 7c0baae793d..d38c82c106e 100644 --- a/ts/IdentificationModal.tsx +++ b/ts/IdentificationModal.tsx @@ -18,12 +18,18 @@ import { ReduxProps } from "./store/actions/types"; import { IdentificationState } from "./store/reducers/identification"; import { GlobalState } from "./store/reducers/types"; -type MapStateToProps = { - identification: IdentificationState; +type ReduxMappedStateProps = { + identificationState: IdentificationState; }; -type Props = MapStateToProps & ReduxProps; +type Props = ReduxMappedStateProps & ReduxProps; +/** + * Type used in the local state to save the result of Pinpad PIN matching. + * State is "unstarted" if the user still need to insert the PIN. + * State is "failure" when the PIN inserted by the user do not match the + * stored one. + */ type IdentificationByPinState = "unstarted" | "failure"; type State = { @@ -55,6 +61,11 @@ const renderIdentificationByPinState = ( return null; }; +/** + * A component used to identify the the user. + * The identification process can be activated calling a saga or dispatching the + * requestIdentification redux action. + */ class IdentificationModal extends React.PureComponent { constructor(props: Props) { super(props); @@ -65,102 +76,98 @@ class IdentificationModal extends React.PureComponent { } public render() { - const { identification, dispatch } = this.props; - - // If the identification state is started we need to show the modal - if (identification.kind === "started") { - const { - pin, - identificationCancelData, - identificationSuccessData - } = identification; - - const { identificationByPinState } = this.state; - - /** - * Create handlers merging default internal actions (to manage the identification state) - * with, if available, custom actions passed as props. - */ - const onIdentificationCancelHandler = () => { - if (identificationCancelData) { - dispatch(identificationCancelData.action); - } - dispatch(identificationCancel()); - }; - - const onIdentificationSuccessHandler = () => { - if (identificationSuccessData) { - dispatch(identificationSuccessData.action); - } - dispatch(identificationSuccess()); - }; - - const onIdentificationFailureHandler = () => { - dispatch(identificationFailure()); - }; - - const onPinResetHandler = () => { - dispatch(identificationPinReset()); - }; - - return ( - - - - - - - {I18n.t("pin_login.pin.pinInfo")} - - - this.onPinFullfill( - _, - __, - onIdentificationSuccessHandler, - onIdentificationFailureHandler - ) - } - clearOnInvalid={true} - /> - {renderIdentificationByPinState(identificationByPinState)} - - - {identificationCancelData !== undefined && ( - - )} - {identificationCancelData === undefined && ( - - )} - - {I18n.t("pin_login.pin.reset.tip")} - - - - - ); + const { identificationState, dispatch } = this.props; + + if (identificationState.kind !== "started") { + return null; } - return null; + // The identification state is started we need to show the modal + const { + pin, + identificationCancelData, + identificationSuccessData + } = identificationState; + + const { identificationByPinState } = this.state; + + /** + * Create handlers merging default internal actions (to manage the identification state) + * with, if available, custom actions passed as props. + */ + const onIdentificationCancelHandler = () => { + if (identificationCancelData) { + dispatch(identificationCancelData.action); + } + dispatch(identificationCancel()); + }; + + const onIdentificationSuccessHandler = () => { + if (identificationSuccessData) { + dispatch(identificationSuccessData.action); + } + dispatch(identificationSuccess()); + }; + + const onIdentificationFailureHandler = () => { + dispatch(identificationFailure()); + }; + + const onPinResetHandler = () => { + dispatch(identificationPinReset()); + }; + + return ( + + + + + + + {I18n.t("pin_login.pin.pinInfo")} + + + this.onPinFullfill( + _, + __, + onIdentificationSuccessHandler, + onIdentificationFailureHandler + ) + } + clearOnInvalid={true} + /> + {renderIdentificationByPinState(identificationByPinState)} + + + {identificationCancelData !== undefined && ( + + )} + {identificationCancelData === undefined && ( + + )} + + {I18n.t("pin_login.pin.reset.tip")} + + + + + ); } private onPinFullfill = ( @@ -184,8 +191,8 @@ class IdentificationModal extends React.PureComponent { }; } -const mapStateToProps = (state: GlobalState): MapStateToProps => ({ - identification: state.identification +const mapStateToProps = (state: GlobalState): ReduxMappedStateProps => ({ + identificationState: state.identification }); export default connect(mapStateToProps)(IdentificationModal); diff --git a/ts/sagas/identification.ts b/ts/sagas/identification.ts index 0ebd0ad5d40..3e55ab2889d 100644 --- a/ts/sagas/identification.ts +++ b/ts/sagas/identification.ts @@ -19,11 +19,9 @@ import { IdentificationResult, IdentificationSuccessData } from "../store/reducers/identification"; -import { - PendingMessageState, - pendingMessageStateSelector -} from "../store/reducers/notifications/pendingMessage"; +import { pendingMessageStateSelector } from "../store/reducers/notifications/pendingMessage"; import { GlobalState } from "../store/reducers/types"; +import { isPaymentOngoingSelector } from "../store/reducers/wallet/payment"; import { PinString } from "../types/PinString"; import { SagaCallReturnType } from "../types/utils"; import { deletePin } from "../utils/keychain"; @@ -41,28 +39,32 @@ export function* waitIdentificationResult(): Iterator< getType(identificationSuccess) ]); - // If the identification was cancelled just return false - if (resultAction.type === getType(identificationCancel)) { - return IdentificationResult.cancel; - } + switch (resultAction.type) { + case getType(identificationCancel): + return IdentificationResult.cancel; - // If the user decided to reset the pin perform needed actions than return false - if (resultAction.type === getType(identificationPinReset)) { - // Delete the PIN - // tslint:disable-next-line:saga-yield-return-type - yield call(deletePin); + case getType(identificationPinReset): { + // Invalidate the session + yield put(sessionInvalid()); - // Invalidate the session - yield put(sessionInvalid()); + // Delete the PIN + // tslint:disable-next-line:saga-yield-return-type + yield call(deletePin); - // Hide the identification screen - yield put(identificationReset()); + // Hide the identification screen + yield put(identificationReset()); - return IdentificationResult.pinreset; - } + return IdentificationResult.pinreset; + } - // Identification was success return true - return IdentificationResult.success; + case getType(identificationSuccess): { + return IdentificationResult.success; + } + + default: { + ((): never => resultAction)(); + } + } } /** @@ -103,11 +105,16 @@ export function* startAndHandleIdentificationResult( yield put(startApplicationInitialization()); } else if (identificationResult === IdentificationResult.success) { // Check if we have a pending notification message - const pendingMessageState: PendingMessageState = yield select( - pendingMessageStateSelector - ); + const pendingMessageState: ReturnType< + typeof pendingMessageStateSelector + > = yield select(pendingMessageStateSelector); + + // Check if there is a payment ongoing + const isPaymentOngoing: ReturnType< + typeof isPaymentOngoingSelector + > = yield select(isPaymentOngoingSelector); - if (pendingMessageState) { + if (!isPaymentOngoing && pendingMessageState) { // We have a pending notification message to handle const messageId = pendingMessageState.id; diff --git a/ts/sagas/startup/watchApplicationActivitySaga.ts b/ts/sagas/startup/watchApplicationActivitySaga.ts index 5c7f704bd6e..4f198418670 100644 --- a/ts/sagas/startup/watchApplicationActivitySaga.ts +++ b/ts/sagas/startup/watchApplicationActivitySaga.ts @@ -30,10 +30,12 @@ export function* watchApplicationActivitySaga(): IterableIterator { if (lastState !== "background" && newApplicationState === "background") { // Start the background timer - identificationBackgroundTimer = yield fork( - startIdentificationBackgroundTimer, - backgroundActivityTimeoutMillis - ); + identificationBackgroundTimer = yield fork(function*() { + // Start and wait the timer to fire + yield call(startTimer, backgroundActivityTimeoutMillis); + // Timer fired we need to identify the user + yield put(identificationRequest()); + }); } else if (lastState === "background" && newApplicationState === "active") { // Cancel the background timer if running if (identificationBackgroundTimer) { @@ -46,12 +48,3 @@ export function* watchApplicationActivitySaga(): IterableIterator { lastState = newApplicationState; }); } - -function* startIdentificationBackgroundTimer( - delay: number -): IterableIterator { - // Start and wait the timer to fire - yield call(startTimer, delay); - // Timer fired we need to identify the user - yield put(identificationRequest()); -} diff --git a/ts/store/reducers/wallet/payment.ts b/ts/store/reducers/wallet/payment.ts index 2fbf985496a..7b54b00f455 100644 --- a/ts/store/reducers/wallet/payment.ts +++ b/ts/store/reducers/wallet/payment.ts @@ -122,6 +122,9 @@ export type PaymentStateWithVerificaResponse = Readonly<{ stack: NonEmptyArray; }>; +export const isPaymentOngoingSelector = (state: GlobalState): boolean => + state.wallet.payment.stack !== null; + export const isPaymentRequestingPinLogin = (wallet: WalletState) => wallet.payment.stack !== null && wallet.payment.stack.head.kind === "PaymentStatePinLogin";