import {AnyEventObject, AssignAction, MachineConfig, assign, spawn, Machine, Actor} from 'xstate'; import {filter, map} from 'rxjs/operators'; import {statesEqual, calculateChannelId, State} from '@statechannels/wallet-core'; import {Store} from '../store'; const WORKFLOW = 'support-state'; export type Init = {state: State; observer?: Actor}; type HasChannelId = Init & {channelId: string}; /* TODO What happens if sendState fails? Do we abort? Or do we try to reach consensus on a later state? */ export const config: MachineConfig = { key: WORKFLOW, initial: 'signState', states: { signState: { entry: [ assign({channelId: ({state}) => calculateChannelId(state)}), 'spawnObserver' ], invoke: {src: 'signState'}, on: {SUPPORTED: 'success'} }, success: {type: 'final'} } }; type Services = {signState(ctx: HasChannelId, event): any}; type Options = { services: Services; actions: {spawnObserver: AssignAction}; }; const signState = (store: Store) => async ({state}: HasChannelId) => store.supportState(state); const notifyWhenSupported = (store: Store, {state, channelId}: HasChannelId) => store.channelUpdatedFeed(channelId).pipe( filter(({isSupported}) => isSupported), filter(entry => statesEqual(state, entry.supported)), map(() => 'SUPPORTED') ); const options = (store: Store): Options => ({ services: { signState: signState(store) }, actions: { spawnObserver: assign((ctx: HasChannelId) => ({ ...ctx, observer: !ctx.observer ? spawn(notifyWhenSupported(store, ctx)) : ctx.observer })) } }); export const machine = (store: Store) => Machine(config, options(store));