Created
March 18, 2021 22:06
-
-
Save Bogidon/f107df4e55636399038b0d7a02dba50f to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const State = { | |
cancelMigrationConfirmation: 'cancelMigrationConfirmation', | |
done: 'done', | |
duplicatePortal: 'duplicatePortal', | |
initial: 'initial', | |
migrationAccountsMismatch: 'migrationAccountsMismatch', | |
migrationDisclosure: 'migrationDisclosure', | |
plaidFlow: 'plaidFlow', | |
portalError: 'portalError', | |
selectInstitution: 'selectInstitution', | |
smsVerification: 'smsVerification', | |
unsupportedInstitution: 'unsupportedInstitution', | |
yodleeFlow: 'yodleeFlow', | |
}; | |
const Transition = { | |
BACK: 'BACK', | |
CONTINUE: 'CONTINUE', | |
SELECT_INSTITUTION: 'SELECT_INSTITUTION', | |
GO_TO_MIGRATION: 'GO_TO_MIGRATION', | |
}; | |
const Action = { | |
BEGIN_MIGRATION: 'BEGIN_MIGRATION', | |
SELECT_INSTITUTION: 'SELECT_INSTITUTION', | |
UPDATE_PORTAL: 'UPDATE_PORTAL', | |
UPDATE_MIGRATION_DETAILS: 'UPDATE_MIGRATION_DETAILS', | |
}; | |
const Condition = { | |
HAS_SELECTED_PLAID_PORTAL: 'HAS_SELECTED_PLAID_PORTAL', | |
HAS_SELECTED_YODLEE_PORTAL: 'HAS_SELECTED_YODLEE_PORTAL', | |
IS_ACCOUNTS_MISMATCH: 'IS_ACCOUNTS_MISMATCH', | |
IS_DUPLICATE_LINK: 'IS_DUPLICATE_LINK', | |
IS_FIXING_PORTAL: 'IS_FIXING_PORTAL', | |
IS_MIGRATION: 'IS_MIGRATION', | |
IS_PLAID_PORTAL: 'IS_PLAID_PORTAL', | |
IS_SELECTING_PLAID_PORTAL: 'IS_SELECTING_PLAID_PORTAL', | |
IS_SELECTING_YODLEE_PORTAL: 'IS_SELECTING_YODLEE_PORTAL', | |
IS_UNSUPPORTED_INSTITUTION: 'IS_UNSUPPORTED_INSTITUTION', | |
IS_YODLEE_MFA: 'IS_YODLEE_MFA', | |
IS_YODLEE_PORTAL: 'IS_YODLEE_PORTAL', | |
REQUIRES_SMS_VERIFICATION: 'REQUIRES_SMS_VERIFICATION', | |
}; | |
const Context = { | |
INSTITUTION: 'institution', | |
MIGRATION_DETAILS: 'migrationDetails', | |
PORTAL: 'portal', | |
REQUIRES_SMS_VERIFICATION: 'requiresSmsVerification', | |
}; | |
const YodleeLinkState = { | |
auth: 'yodleeAuth', | |
done: 'yodleeDone', | |
initial: 'yodleeInitial', | |
mfa: 'yodleeMfa', | |
}; | |
const YodleeLinkTransition = { | |
BACK: 'yodleeBack', | |
CONTINUE: 'yodleeContinue', | |
UPDATE_PORTAL: 'yodleeUpdatePortal', | |
}; | |
const yodleeStates = { | |
initial: YodleeLinkState.initial, | |
states: { | |
[YodleeLinkState.initial]: { | |
always: [ | |
{ | |
cond: Condition.IS_YODLEE_MFA, | |
target: YodleeLinkState.mfa, | |
}, | |
{ | |
target: YodleeLinkState.auth, | |
}, | |
], | |
}, | |
// todo: I'm probably missing complexity here | |
[YodleeLinkState.auth]: { | |
on: { | |
[YodleeLinkTransition.CONTINUE]: [ | |
{ | |
cond: Condition.IS_YODLEE_MFA, | |
target: YodleeLinkState.mfa, | |
actions: [Action.UPDATE_PORTAL], | |
}, | |
{ | |
target: YodleeLinkState.done, | |
actions: [Action.UPDATE_PORTAL], | |
}, | |
], | |
[YodleeLinkTransition.BACK]: { | |
target: YodleeLinkState.done, | |
}, | |
}, | |
}, | |
[YodleeLinkState.mfa]: { | |
on: { | |
[YodleeLinkTransition.CONTINUE]: { | |
target: YodleeLinkState.done, | |
}, | |
[YodleeLinkTransition.BACK]: { | |
target: YodleeLinkState.back, | |
}, | |
}, | |
}, | |
[YodleeLinkState.done]: { | |
type: 'final', | |
}, | |
}, | |
}; | |
const PlaidLinkState = { | |
done: 'plaidDone', | |
initial: 'plaidInitial', | |
link: 'plaidLink', | |
relink: 'plaidRelink', | |
}; | |
const PlaidLinkTransition = { | |
BACK: 'plaidBack', | |
CONTINUE: 'plaidContinue', | |
}; | |
const plaidStates = { | |
initial: PlaidLinkState.initial, | |
states: { | |
[PlaidLinkState.initial]: { | |
always: [ | |
{ | |
cond: Condition.IS_PLAID_PORTAL, | |
target: PlaidLinkState.relink, | |
}, | |
{ | |
target: PlaidLinkState.link, | |
}, | |
], | |
}, | |
[PlaidLinkState.link]: { | |
on: { | |
[PlaidLinkTransition.BACK]: { | |
target: PlaidLinkState.done, | |
}, | |
[PlaidLinkTransition.CONTINUE]: { | |
actions: [Action.UPDATE_MIGRATION_DETAILS], | |
target: PlaidLinkState.done, | |
}, | |
}, | |
}, | |
[PlaidLinkState.relink]: { | |
on: { | |
[PlaidLinkTransition.BACK]: { | |
target: PlaidLinkState.done, | |
}, | |
[PlaidLinkTransition.CONTINUE]: { | |
target: PlaidLinkState.done, | |
}, | |
}, | |
}, | |
[PlaidLinkState.done]: { | |
type: 'final', | |
}, | |
}, | |
}; | |
const isYodleePortalInMfaRequired = ({ portal } = {}) => { | |
return portal?.authState === 'USER_ACTION_REQUIRED' && portal?.primarySource === 'YODLEE'; | |
}; | |
const isAccountsMismatch = ({ migrationDetails } = {}) => { | |
return migrationDetails?.oldAccountIds?.length > migrationDetails?.newAccountIds?.length; | |
}; | |
const accountAggregationMachine = Machine( | |
{ | |
id: 'accountAggregation', | |
initial: State.initial, | |
context: { | |
// the currently selected institution | |
[Context.INSTITUTION]: undefined, | |
// if we are attempting to migrate the primary source of the portal | |
[Context.IS_MIGRATION]: undefined, | |
// the current portal being fixed or linked | |
// todo: how to differentiate if this a linked portal or not? | |
[Context.PORTAL]: undefined, | |
// whether the user requires SMS verification or not | |
[Context.REQUIRES_SMS_VERIFICATION]: undefined, | |
}, | |
states: { | |
[State.initial]: { | |
always: [ | |
{ | |
target: [State.portalError], | |
cond: Condition.IS_FIXING_PORTAL, | |
}, | |
{ | |
target: [State.selectInstitution], | |
}, | |
], | |
}, | |
[State.selectInstitution]: { | |
on: { | |
[Transition.SELECT_INSTITUTION]: [ | |
{ | |
actions: [Action.SELECT_INSTITUTION], | |
cond: Condition.IS_DUPLICATE_LINK, | |
target: State.duplicatePortal, | |
}, | |
{ | |
actions: [Action.SELECT_INSTITUTION], | |
cond: Condition.IS_UNSUPPORTED_INSTITUTION, | |
target: State.unsupportedInstitution, | |
}, | |
{ | |
actions: [Action.SELECT_INSTITUTION], | |
cond: Condition.REQUIRES_SMS_VERIFICATION, | |
target: State.smsVerification, | |
}, | |
{ | |
actions: [Action.SELECT_INSTITUTION], | |
cond: Condition.IS_MIGRATION, | |
target: State.migrationDisclosure, | |
}, | |
{ | |
actions: [Action.SELECT_INSTITUTION], | |
cond: Condition.IS_SELECTING_PLAID_PORTAL, | |
target: State.plaidFlow, | |
}, | |
{ | |
actions: [Action.SELECT_INSTITUTION], | |
cond: Condition.IS_SELECTING_YODLEE_PORTAL, | |
target: State.yodleeFlow, | |
}, | |
], | |
}, | |
}, | |
[State.duplicatePortal]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.selectInstitution, | |
}, | |
}, | |
}, | |
[State.unsupportedInstitution]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.selectInstitution, | |
}, | |
}, | |
}, | |
[State.smsVerification]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.selectInstitution, | |
}, | |
[Transition.CONTINUE]: [ | |
{ | |
cond: Condition.HAS_SELECTED_PLAID_PORTAL, | |
target: State.plaidFlow, | |
}, | |
{ | |
cond: Condition.HAS_SELECTED_YODLEE_PORTAL, | |
target: State.plaidFlow, | |
}, | |
], | |
}, | |
}, | |
// todo: do we need separate error screens for Yodlee and Plaid? Logic today is pretty different | |
// but maybe can be simplified? | |
[State.portalError]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.done, | |
}, | |
[Transition.CONTINUE]: [ | |
{ | |
actions: [Action.UPDATE_PORTAL], | |
cond: Condition.IS_ACCOUNTS_MISMATCH, | |
target: State.migrationAccountsMismatch, | |
}, | |
{ | |
actions: [Action.UPDATE_PORTAL], | |
cond: Condition.IS_MIGRATION, | |
target: State.plaidFlow, | |
}, | |
{ | |
// actions: [Action.UPDATE_PORTAL], | |
cond: Condition.IS_PLAID_PORTAL, | |
target: State.plaidFlow, | |
}, | |
{ | |
// actions: [Action.UPDATE_PORTAL], | |
cond: Condition.IS_YODLEE_PORTAL, | |
target: State.yodleeFlow, | |
}, | |
], | |
}, | |
}, | |
[State.migrationDisclosure]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.portalError, | |
}, | |
[Transition.CONTINUE]: { | |
target: State.plaidFlow, | |
}, | |
}, | |
}, | |
[State.migrationAccountsMismatch]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.portalError, | |
}, | |
[Transition.CONTINUE]: { | |
target: State.done, | |
}, | |
}, | |
}, | |
[State.cancelMigrationConfirmation]: { | |
on: { | |
[Transition.BACK]: { | |
target: State.migrationAccountsMismatch, | |
}, | |
[Transition.CONTINUE]: { | |
target: State.done, | |
}, | |
}, | |
}, | |
[State.plaidFlow]: { | |
on: { | |
[Transition.BACK]: [ | |
{ | |
cond: Condition.IS_FIXING_PORTAL, | |
target: State.portalError, | |
}, | |
{ | |
target: State.selectInstitution, | |
}, | |
], | |
[Transition.CONTINUE]: [ | |
{ | |
cond: Condition.IS_ACCOUNTS_MISMATCH, | |
target: State.migrationAccountsMismatch, | |
}, | |
{ | |
target: State.done, | |
}, | |
], | |
}, | |
...plaidStates, | |
}, | |
[State.yodleeFlow]: { | |
on: { | |
[Transition.BACK]: [ | |
{ | |
cond: Condition.IS_FIXING_PORTAL, | |
target: State.portalError, | |
}, | |
], | |
[Transition.CONTINUE]: { | |
target: State.done, | |
}, | |
[Transition.GO_TO_MIGRATION]: { | |
actions: [Action.BEGIN_MIGRATION], | |
target: State.plaidFlow, | |
}, | |
}, | |
...yodleeStates, | |
}, | |
[State.done]: { | |
type: 'final', | |
}, | |
}, | |
}, | |
{ | |
actions: { | |
[Action.BEGIN_MIGRATION]: assign({ | |
[Context.MIGRATION_DETAILS]: () => {}, | |
}), | |
[Action.SELECT_INSTITUTION]: assign({ | |
[Context.INSTITUTION]: (_context, event) => event.institution, | |
}), | |
[Action.UPDATE_MIGRATION_DETAILS]: assign({ | |
[Context.MIGRATION_DETAILS]: (_context, event) => event.migrationDetails, | |
}), | |
[Action.UPDATE_PORTAL]: assign({ | |
[Context.PORTAL]: (_context, event) => event.portal, | |
}), | |
}, | |
guards: { | |
[Condition.IS_FIXING_PORTAL]: (context) => { | |
return !!context.portal?.id; | |
}, | |
[Condition.IS_PLAID_PORTAL]: (context) => { | |
return context.portal?.primarySource === 'PLAID'; | |
}, | |
[Condition.IS_YODLEE_PORTAL]: (context) => { | |
return context.portal?.primarySource === 'YODLEE'; | |
}, | |
[Condition.IS_SELECTING_PLAID_PORTAL]: (_context, event) => { | |
return event?.institution?.aggregator === 'PLAID'; | |
}, | |
[Condition.IS_SELECTING_YODLEE_PORTAL]: (_context, event) => { | |
return event?.institution?.aggregator === 'YODLEE'; | |
}, | |
[Condition.IS_DUPLICATE_LINK]: (_context, event) => { | |
// todo: update check of action against context | |
// will context know which institutions have been linked already? | |
return !!event.isDuplicate; | |
}, | |
[Condition.IS_UNSUPPORTED_INSTITUTION]: (_context, event) => { | |
// todo: update check of action | |
return !!event.isUnsupported; | |
}, | |
[Condition.IS_YODLEE_MFA]: (context, event) => { | |
return isYodleePortalInMfaRequired(event) || isYodleePortalInMfaRequired(context); | |
}, | |
[Condition.REQUIRES_SMS_VERIFICATION]: (context) => { | |
return !!context.requiresSmsVerification; | |
}, | |
[Condition.HAS_SELECTED_PLAID_PORTAL]: (context) => { | |
return context?.institution?.aggregator === 'PLAID'; | |
}, | |
[Condition.HAS_SELECTED_YODLEE_PORTAL]: (context) => { | |
return context?.institution?.aggregator === 'YODLEE'; | |
}, | |
[Condition.IS_MIGRATION]: (context, event) => { | |
return !!context?.migrationDetails || !!event?.migrationDetails; | |
}, | |
[Condition.IS_ACCOUNTS_MISMATCH]: (context, event) => { | |
return isAccountsMismatch(event) || isAccountsMismatch(context); | |
}, | |
}, | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment