/* @flow */ import HDKey from 'hdkey'; import * as DISCOVERY from 'actions/constants/discovery'; import * as ACCOUNT from 'actions/constants/account'; import * as CONNECT from 'actions/constants/TrezorConnect'; import * as WALLET from 'actions/constants/wallet'; import type { Action, TrezorDevice } from 'flowtype'; import type { DiscoveryStartAction, DiscoveryWaitingAction, DiscoveryCompleteAction, } from 'actions/DiscoveryActions'; import type { Account } from './AccountsReducer'; export type Discovery = { network: string, basePath: Array, deviceState: string, accountIndex: number, interrupted: boolean, completed: boolean, waitingForDevice: boolean, waitingForBlockchain: boolean, fwNotSupported: boolean, fwOutdated: boolean, publicKey: string, // used in ethereum only chainCode: string, // used in ethereum only hdKey: HDKey, // used in ethereum only }; export type State = Array; const initialState: State = []; const defaultDiscovery: Discovery = { network: '', deviceState: '', basePath: [], accountIndex: 0, interrupted: false, completed: false, waitingForDevice: false, waitingForBlockchain: false, fwNotSupported: false, fwOutdated: false, publicKey: '', chainCode: '', hdKey: null, }; const findIndex = (state: State, network: string, deviceState: string): number => state.findIndex(d => d.network === network && d.deviceState === deviceState); const start = (state: State, action: DiscoveryStartAction): State => { const deviceState: string = action.device.state || '0'; const instance: Discovery = { ...defaultDiscovery, network: action.network.shortcut, deviceState, }; if (action.networkType === 'ethereum') { const hdKey = new HDKey(); hdKey.publicKey = Buffer.from(action.publicKey, 'hex'); hdKey.chainCode = Buffer.from(action.chainCode, 'hex'); instance.hdKey = hdKey; instance.publicKey = action.publicKey; instance.chainCode = action.chainCode; instance.basePath = action.basePath; } const newState: State = [...state]; const index: number = findIndex(state, action.network.shortcut, deviceState); if (index >= 0) { newState[index] = instance; } else { newState.push(instance); } return newState; }; const complete = (state: State, action: DiscoveryCompleteAction): State => { const index: number = findIndex(state, action.network, action.device.state || '0'); const newState: State = [...state]; newState[index] = { ...newState[index], completed: true }; return newState; }; const accountCreate = (state: State, account: Account): State => { const index: number = findIndex(state, account.network, account.deviceState); const newState: State = [...state]; // do not increment index when adding imported account // imported accounts should not interfere with the index used in discovery proccess. if (!account.imported) { newState[index].accountIndex++; } return newState; }; const forgetDiscovery = (state: State, device: TrezorDevice): State => state.filter(d => d.deviceState !== device.state); const clear = (state: State, devices: Array): State => { let newState: State = [...state]; devices.forEach(d => { newState = forgetDiscovery(newState, d); }); return newState; }; const stop = (state: State, device: TrezorDevice): State => { const affectedProcesses = state.filter(d => d.deviceState === device.state && !d.completed); const otherProcesses = state.filter(d => affectedProcesses.indexOf(d) === -1); const changedProcesses = affectedProcesses.map(d => ({ ...d, interrupted: true, waitingForDevice: false, waitingForBlockchain: false, })); return otherProcesses.concat(changedProcesses); }; const waitingForDevice = (state: State, action: DiscoveryWaitingAction): State => { const deviceState: string = action.device.state || '0'; const instance: Discovery = { ...defaultDiscovery, network: action.network, deviceState, waitingForDevice: true, }; const index: number = findIndex(state, action.network, deviceState); const newState: State = [...state]; if (index >= 0) { newState[index] = instance; } else { newState.push(instance); } return newState; }; const waitingForBlockchain = (state: State, action: DiscoveryWaitingAction): State => { const deviceState: string = action.device.state || '0'; const instance: Discovery = { ...defaultDiscovery, network: action.network, deviceState, waitingForBlockchain: true, }; const index: number = findIndex(state, action.network, deviceState); const newState: State = [...state]; if (index >= 0) { newState[index] = instance; } else { newState.push(instance); } return newState; }; const notSupported = (state: State, action: DiscoveryWaitingAction): State => { const affectedProcesses = state.filter( d => d.deviceState === action.device.state && d.network === action.network ); const otherProcesses = state.filter(d => affectedProcesses.indexOf(d) === -1); const changedProcesses = affectedProcesses.map(d => ({ ...d, fwOutdated: action.type === DISCOVERY.FIRMWARE_OUTDATED, fwNotSupported: action.type === DISCOVERY.FIRMWARE_NOT_SUPPORTED, })); return otherProcesses.concat(changedProcesses); }; export default function discovery(state: State = initialState, action: Action): State { switch (action.type) { case DISCOVERY.START: return start(state, action); case ACCOUNT.CREATE: return accountCreate(state, action.payload); case DISCOVERY.STOP: return stop(state, action.device); case DISCOVERY.COMPLETE: return complete(state, action); case DISCOVERY.WAITING_FOR_DEVICE: return waitingForDevice(state, action); case DISCOVERY.WAITING_FOR_BLOCKCHAIN: return waitingForBlockchain(state, action); case DISCOVERY.FIRMWARE_NOT_SUPPORTED: return notSupported(state, action); case DISCOVERY.FIRMWARE_OUTDATED: return notSupported(state, action); case DISCOVERY.FROM_STORAGE: return action.payload.map(d => { if (d.publicKey.length < 1) return d; // recreate ethereum discovery HDKey // deprecated: will be removed after switching to blockbook const hdKey: HDKey = new HDKey(); hdKey.publicKey = Buffer.from(d.publicKey, 'hex'); hdKey.chainCode = Buffer.from(d.chainCode, 'hex'); return { ...d, hdKey, interrupted: false, waitingForDevice: false, waitingForBlockchain: false, }; }); case CONNECT.FORGET: case CONNECT.FORGET_SINGLE: case CONNECT.FORGET_SILENT: case CONNECT.RECEIVE_WALLET_TYPE: return forgetDiscovery(state, action.device); case WALLET.CLEAR_UNAVAILABLE_DEVICE_DATA: return clear(state, action.devices); default: return state; } }