From fee5d4745535bc71fb6badaf248b2b3fcfb847d3 Mon Sep 17 00:00:00 2001 From: CPatchane Date: Tue, 7 May 2019 13:53:53 +0200 Subject: [PATCH] feat(harvest): :nail_care: Let KonnectorJob handle statuses --- .../src/components/TriggerLauncher.jsx | 126 +++++++----------- .../src/models/KonnectorJob.js | 117 +++++++++++++++- 2 files changed, 156 insertions(+), 87 deletions(-) diff --git a/packages/cozy-harvest-lib/src/components/TriggerLauncher.jsx b/packages/cozy-harvest-lib/src/components/TriggerLauncher.jsx index 1722cb0ccd..3d7444a3cb 100644 --- a/packages/cozy-harvest-lib/src/components/TriggerLauncher.jsx +++ b/packages/cozy-harvest-lib/src/components/TriggerLauncher.jsx @@ -7,17 +7,14 @@ import TwoFAForm from './TwoFAForm' import { accountsMutations } from '../connections/accounts' import { triggersMutations } from '../connections/triggers' -import KonnectorJob from '../models/KonnectorJob' - -// Statuses -const ERRORED = 'ERRORED' -const IDLE = 'IDLE' -const LOGIN_SUCCESS = 'LOGIN_SUCCESS' -const TWO_FA_REQUEST = 'TWO_FA_REQUEST' -const TWO_FA_MISMATCH = 'TWO_FA_MISMATCH' -const PENDING = 'PENDING' -const SUCCESS = 'SUCCESS' -const RUNNING_TWOFA = 'RUNNING_TWOFA' +import { + withKonnectorJob, + KonnectorJobPropTypes, + ERROR_EVENT, + SUCCESS_EVENT, + TWO_FA_REQUEST_EVENT, + TWO_FA_MISMATCH_EVENT +} from '../models/KonnectorJob' /** * Trigger Launcher renders its children with following props: @@ -38,86 +35,55 @@ const RUNNING_TWOFA = 'RUNNING_TWOFA' * TriggerManager for example */ export class TriggerLauncher extends Component { - state = { - status: IDLE - } - constructor(props, context) { super(props, context) + this.state = { showTwoFAModal: false } - this.launch = this.launch.bind(this) - this.handleTwoFAModalDismiss = this.handleTwoFAModalDismiss.bind(this) - this.handle2FACode = this.handle2FACode.bind(this) - this.handleError = this.handleError.bind(this) - this.handleSuccess = this.handleSuccess.bind(this) - this.handleLoginSuccess = this.handleLoginSuccess.bind(this) - this.handleTwoFARequest = this.handleTwoFARequest.bind(this) - } - - handleTwoFAModalDismiss() { - // TODO: Make the modale not closable, or offer possibility to re-open it. - this.setState({ showTwoFAModal: false }) - } - - handleError() { - this.setState({ status: ERRORED, showTwoFAModal: false }) - } - - handleSuccess() { - this.setState({ status: SUCCESS, showTwoFAModal: false }) - } - - handleLoginSuccess() { - this.setState({ status: LOGIN_SUCCESS }) + this.dismissTwoFAModal = this.dismissTwoFAModal.bind(this) + this.displayTwoFAModal = this.displayTwoFAModal.bind(this) } - handleTwoFARequest() { - this.setState({ status: TWO_FA_REQUEST, showTwoFAModal: true }) + componentDidMount() { + this.props.konnectorJob + .on(ERROR_EVENT, this.dismissTwoFAModal) + .on(SUCCESS_EVENT, this.dismissTwoFAModal) + .on(TWO_FA_REQUEST_EVENT, this.displayTwoFAModal) + .on(TWO_FA_MISMATCH_EVENT, this.displayTwoFAModal) } - handleTwoFAMismatch() { - this.setState({ status: TWO_FA_MISMATCH, showTwoFAModal: true }) - } - - handle2FACode(code) { - this.setState({ status: RUNNING_TWOFA }) - this.konnectorJob.sendTwoFACode(code) + dismissTwoFAModal() { + // TODO: Make the modal not closable, or offer possibility to re-open it. + this.setState({ showTwoFAModal: false }) } - async launch() { - const { trigger } = this.props - const { client } = this.context - - this.setState({ status: PENDING }) - - this.konnectorJob = new KonnectorJob(client, trigger) - this.konnectorJob - .on('error', this.handleError) - .on('loginSuccess', this.handleLoginSuccess) - .on('success', this.handleSuccess) - .on('twoFARequest', this.handleTwoFARequest) - .on('twoFAMismatch', this.handleTwoFAMismatch) - this.konnectorJob.launch() + displayTwoFAModal() { + this.setState({ showTwoFAModal: true }) } render() { - const { showTwoFAModal, status } = this.state - const { account, children, konnector, submitting } = this.props + const { showTwoFAModal } = this.state + const { + account, + children, + konnector, + konnectorJob, + submitting + } = this.props return (
{children({ - launch: this.launch, - running: ![ERRORED, IDLE, SUCCESS].includes(status) || submitting + launch: konnectorJob.launch, + running: konnectorJob.isRunning() || submitting })} {showTwoFAModal && ( )} @@ -132,19 +98,19 @@ TriggerLauncher.propTypes = { */ children: PropTypes.func.isRequired, /** - * Indicates if trigger is already runnning + * Account document to use for 2FA */ - submitting: PropTypes.bool, + account: PropTypes.object.isRequired, /** - * The trigger to launch + * Konnector required to fetch app icon */ - trigger: PropTypes.object.isRequired -} - -TriggerLauncher.contextTypes = { - client: PropTypes.object.isRequired + konnector: PropTypes.object.isRequired, + /** + * Indicates if trigger is already runnning + */ + submitting: PropTypes.bool } -export default withMutations(accountsMutations, triggersMutations)( - TriggerLauncher +export default withKonnectorJob( + withMutations(accountsMutations, triggersMutations)(TriggerLauncher) ) diff --git a/packages/cozy-harvest-lib/src/models/KonnectorJob.js b/packages/cozy-harvest-lib/src/models/KonnectorJob.js index 1aa32f2767..08b99a9ee3 100644 --- a/packages/cozy-harvest-lib/src/models/KonnectorJob.js +++ b/packages/cozy-harvest-lib/src/models/KonnectorJob.js @@ -1,3 +1,4 @@ +import React, { PureComponent } from 'react' import MicroEE from 'microee' import accounts from '../helpers/accounts' @@ -6,12 +7,25 @@ import triggers from '../helpers/triggers' import accountsMutations from '../connections/accounts' import triggersMutations from '../connections/triggers' +import PropTypes from 'prop-types' + +// events export const ERROR_EVENT = 'error' export const LOGIN_SUCCESS_EVENT = 'loginSuccess' export const SUCCESS_EVENT = 'success' export const TWO_FA_REQUEST_EVENT = 'twoFARequest' export const TWO_FA_MISMATCH_EVENT = 'twoFAMismatch' +// statuses +export const IDLE = 'IDLE' +export const PENDING = 'PENDING' +export const ERRORED = 'ERRORED' +export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' +export const SUCCESS = 'SUCCESS' +export const RUNNING_TWOFA = 'RUNNING_TWOFA' +export const TWO_FA_MISMATCH = 'TWO_FA_MISMATCH' +export const TWO_FA_REQUEST = 'TWO_FA_REQUEST' + // helpers export const prepareTriggerAccount = async (trigger, accountsMutations) => { const { findAccount, updateAccount } = accountsMutations @@ -31,6 +45,36 @@ export class KonnectorJob { // Bind methods used as callbacks this.handleTwoFA = this.handleTwoFA.bind(this) this.sendTwoFACode = this.sendTwoFACode.bind(this) + this.handleLegacyEvent = this.handleLegacyEvent.bind(this) + this.launch = this.launch.bind(this) + + // status and setter/getters + this._status = IDLE + this.setStatus = this.setStatus.bind(this) + this.getStatus = this.getStatus.bind(this) + this.isRunning = this.isRunning.bind(this) + this.isTwoFARunning = this.isTwoFARunning.bind(this) + this.isTwoFARetry = this.isTwoFARetry.bind(this) + } + + setStatus(status) { + this._status = status + } + + getStatus() { + return this._status + } + + isRunning() { + return ![ERRORED, IDLE, SUCCESS].includes(this.getStatus()) + } + + isTwoFARunning() { + return this.getStatus() === RUNNING_TWOFA + } + + isTwoFARetry() { + return this.getStatus() === TWO_FA_MISMATCH } // TODO: Pass updated account as parameter @@ -41,28 +85,47 @@ export class KonnectorJob { this.account.state = state if (accounts.isTwoFANeeded(state)) { + this.setStatus(TWO_FA_REQUEST) this.emit(TWO_FA_REQUEST_EVENT, this.account) } else if (accounts.isTwoFARetry(state)) { + this.setStatus(TWO_FA_MISMATCH) this.emit(TWO_FA_MISMATCH_EVENT, this.account) } } /** - * Re-emit an event received from an internal object + * Legacy events use the KonnectorJobWatcher unstil it has been merged into + * KonnectorJob so we need to re-emit the events */ - reEmit(event) { - return (...args) => this.emit(event, ...args) + handleLegacyEvent(event) { + return (...args) => { + switch (event) { + case ERROR_EVENT: + this.setStatus(ERRORED) + break + case LOGIN_SUCCESS_EVENT: + this.setStatus(LOGIN_SUCCESS) + break + case SUCCESS_EVENT: + this.setStatus(SUCCESS) + break + } + this.emit(event, ...args) + } } /** * Send Two FA Code, i.e. save it into account */ async sendTwoFACode(code) { + this.setStatus(RUNNING_TWOFA) const { updateAccount } = this.accountsMutations try { await updateAccount(accounts.updateTwoFaCode(this.account, code)) } catch (error) { - return this.handleError(error) + // eslint-disable-next-line no-console + console.error(error) + this.setStatus(ERRORED) } } @@ -70,6 +133,7 @@ export class KonnectorJob { * Launch the job and set up everything to follow execution. */ async launch() { + this.setStatus(PENDING) const { watchKonnectorAccount } = this.accountsMutations const { launchTrigger, watchKonnectorJob } = this.triggersMutations @@ -85,12 +149,51 @@ export class KonnectorJob { const konnectorJob = watchKonnectorJob(await launchTrigger(this.trigger)) // Temporary reEmitting until merging of KonnectorJobWatcher and // KonnectorAccountWatcher into KonnectorJob - konnectorJob.on(ERROR_EVENT, this.reEmit(ERROR_EVENT)) - konnectorJob.on(LOGIN_SUCCESS_EVENT, this.reEmit(LOGIN_SUCCESS_EVENT)) - konnectorJob.on(SUCCESS_EVENT, this.reEmit(SUCCESS_EVENT)) + konnectorJob.on(ERROR_EVENT, this.handleLegacyEvent(ERROR_EVENT)) + konnectorJob.on( + LOGIN_SUCCESS_EVENT, + this.handleLegacyEvent(LOGIN_SUCCESS_EVENT) + ) + konnectorJob.on(SUCCESS_EVENT, this.handleLegacyEvent(SUCCESS_EVENT)) } } MicroEE.mixin(KonnectorJob) +export const KonnectorJobPropTypes = { + /** + * The trigger to launch + */ + trigger: PropTypes.object.isRequired, + /** + * The konnectorJob instance provided by withKonnectorJob + */ + konnectorJob: PropTypes.object.isRequired +} + +export const withKonnectorJob = WrappedComponent => { + class ComponentWithKonnectorJob extends PureComponent { + constructor(props, context) { + super(props, context) + this.konnectorJob = new KonnectorJob(context.client, props.trigger) + } + render() { + return ( + + ) + } + } + ComponentWithKonnectorJob.contextTypes = { + client: PropTypes.object.isRequired + } + ComponentWithKonnectorJob.propTypes = { + /** + * KonnectorJob required and provided props + */ + ...KonnectorJobPropTypes, + ...(WrappedComponent.propTypes || {}) + } + return ComponentWithKonnectorJob +} + export default KonnectorJob