aboutsummaryrefslogblamecommitdiffstats
path: root/ui/app/reducers/app.js
blob: 5c86d397d08957b0fdd64ede1b0652926fac88d2 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                     
                                               
                               


                          
 
                                    
                                             
                       
                                                        
                                                    
                       
                        
                          
   
 
                         
                                                                  
                   

   
                     
         
                     
                             


                       
                                          

                                
              
   
 
                  
                         
                       
                    



                   
                  
        

                           
        
      




                         

                       
                     
                               
                                                        


                              





                                          
                
                       
                        
                       

                               
                                 
      


                        




                                       
 




                                        


                               



                          



                               



                              
        
 
                    











                                   
                         




                                  
 

                            

                                                    
                               







                                                               



                               


                               
                                                    
                                                           
          
        
 
                         










                                     
 







                                   
 





                                    
                                
        
 
                                 
                                         
                                        
        
 







                                




                                 
 







                                                
 








                                                








                                                





                                  
                      

        

















                                          







                                                
 
                                            







                               
 








                                      
 








                                                









                                                









                                                



                                   
                                                



                           
           
 
                                 
                               
                                                                                           








                                 
                            


                      













                                     
                                 



                               



                                          
                      
                                   
          
                           

                      



                                      
                               












                                                   
                            
                      



                                     
                                                                                           
                      
                                
                                
          

                                  

                                
          
                            
        
 














                                        






                                                               
                      
                              
                                 

        





                               







                                   
                         
                                                                     
          
                                          
                      
                         




                                    
                                                               


                           
                      
                         


                              
                                                               





























                                                                 









































                                                                           
                                                                  

        




                                  








                                                      


                               
                                     






                               








                                             














                                       
                         




































                                                
                              


                               
                         
                                             
          
                                                          
                  
                              
                          
                                   






                              









                                                          

                                  
                  
                              



                              

                                                  




                                    
                  
                                





                                                  

                                                                             




                               
                  
                                



                                                
                                                               
            

                                                  



                        
                         
                               

                           
 

                                        
                                  

          









                                                


                                  





                                      







                                      
        
 




                                   
            
                     

   
 







                                                      
                                                                               
 
                                                                                                                            
                         
 
 
                                        



                                                         
 
 


                                             
const extend = require('xtend')
const actions = require('../actions')
const txHelper = require('../../lib/tx-helper')
const log = require('loglevel')

module.exports = reduceApp


function reduceApp (state, action) {
  log.debug('App Reducer got ' + action.type)
  // clone and defaults
  const selectedAddress = state.metamask.selectedAddress
  const hasUnconfActions = checkUnconfActions(state)
  let name = 'accounts'
  if (selectedAddress) {
    name = 'accountDetail'
  }

  if (hasUnconfActions) {
    log.debug('pending txs detected, defaulting to conf-tx view.')
    name = 'confTx'
  }

  var defaultView = {
    name,
    detailView: null,
    context: selectedAddress,
  }

  // confirm seed words
  var seedWords = state.metamask.seedWords
  var seedConfView = {
    name: 'createVaultComplete',
    seedWords,
  }

  // default state
  var appState = extend({
    shouldClose: false,
    menuOpen: false,
    modal: {
      open: false,
      modalState: {
        name: null,
        props: {},
      },
      previousModalState: {
        name: null,
      },
    },
    sidebar: {
      isOpen: false,
      transitionName: '',
      type: '',
    },
    alertOpen: false,
    alertMessage: null,
    qrCodeData: null,
    networkDropdownOpen: false,
    currentView: seedWords ? seedConfView : defaultView,
    accountDetail: {
      subview: 'transactions',
    },
    // Used to render transition direction
    transForward: true,
    // Used to display loading indicator
    isLoading: false,
    // Used to display error text
    warning: null,
    buyView: {},
    isMouseUser: false,
    gasIsLoading: false,
    networkNonce: null,
    defaultHdPaths: {
      trezor: `m/44'/60'/0'/0`,
      ledger: `m/44'/60'/0'/0/0`,
    },
  }, state.appState)

  switch (action.type) {
    // dropdown methods
    case actions.NETWORK_DROPDOWN_OPEN:
      return extend(appState, {
        networkDropdownOpen: true,
      })

    case actions.NETWORK_DROPDOWN_CLOSE:
      return extend(appState, {
        networkDropdownOpen: false,
      })

    // sidebar methods
    case actions.SIDEBAR_OPEN:
      return extend(appState, {
        sidebar: {
          ...action.value,
          isOpen: true,
        },
      })

    case actions.SIDEBAR_CLOSE:
      return extend(appState, {
        sidebar: {
          ...appState.sidebar,
          isOpen: false,
        },
      })

    // alert methods
    case actions.ALERT_OPEN:
      return extend(appState, {
        alertOpen: true,
        alertMessage: action.value,
      })

    case actions.ALERT_CLOSE:
      return extend(appState, {
        alertOpen: false,
        alertMessage: null,
      })

    // qr scanner methods
    case actions.QR_CODE_DETECTED:
      return extend(appState, {
        qrCodeData: action.value,
      })


    // modal methods:
    case actions.MODAL_OPEN:
      const { name, ...modalProps } = action.payload

      return extend(appState, {
        modal: {
          open: true,
          modalState: {
            name: name,
            props: { ...modalProps },
          },
          previousModalState: { ...appState.modal.modalState },
        },
      })

    case actions.MODAL_CLOSE:
      return extend(appState, {
        modal: Object.assign(
          state.appState.modal,
          { open: false },
          { modalState: { name: null, props: {} } },
          { previousModalState: appState.modal.modalState},
        ),
      })

    // transition methods
    case actions.TRANSITION_FORWARD:
      return extend(appState, {
        transForward: true,
      })

    case actions.TRANSITION_BACKWARD:
      return extend(appState, {
        transForward: false,
      })

    // intialize

    case actions.SHOW_CREATE_VAULT:
      return extend(appState, {
        currentView: {
          name: 'createVault',
        },
        transForward: true,
        warning: null,
      })

    case actions.SHOW_RESTORE_VAULT:
      return extend(appState, {
        currentView: {
          name: 'restoreVault',
        },
        transForward: true,
        forgottenPassword: true,
      })

    case actions.FORGOT_PASSWORD:
      const newState = extend(appState, {
        forgottenPassword: action.value,
      })

      if (action.value) {
        newState.currentView = {
          name: 'restoreVault',
        }
      }

      return newState

    case actions.SHOW_INIT_MENU:
      return extend(appState, {
        currentView: defaultView,
        transForward: false,
      })

    case actions.SHOW_CONFIG_PAGE:
      return extend(appState, {
        currentView: {
          name: 'config',
          context: appState.currentView.context,
        },
        transForward: action.value,
      })

    case actions.SHOW_ADD_TOKEN_PAGE:
      return extend(appState, {
        currentView: {
          name: 'add-token',
          context: appState.currentView.context,
        },
        transForward: action.value,
      })

    case actions.SHOW_ADD_SUGGESTED_TOKEN_PAGE:
      return extend(appState, {
        currentView: {
          name: 'add-suggested-token',
          context: appState.currentView.context,
        },
        transForward: action.value,
      })

    case actions.SHOW_IMPORT_PAGE:
      return extend(appState, {
        currentView: {
          name: 'import-menu',
        },
        transForward: true,
        warning: null,
      })

    case actions.SHOW_NEW_ACCOUNT_PAGE:
      return extend(appState, {
        currentView: {
          name: 'new-account-page',
          context: action.formToSelect,
        },
        transForward: true,
        warning: null,
      })

    case actions.SET_NEW_ACCOUNT_FORM:
      return extend(appState, {
        currentView: {
          name: appState.currentView.name,
          context: action.formToSelect,
        },
      })

    case actions.SHOW_INFO_PAGE:
      return extend(appState, {
        currentView: {
          name: 'info',
          context: appState.currentView.context,
        },
        transForward: true,
      })

  case actions.CREATE_NEW_VAULT_IN_PROGRESS:
      return extend(appState, {
        currentView: {
          name: 'createVault',
          inProgress: true,
        },
        transForward: true,
        isLoading: true,
      })

    case actions.SHOW_NEW_VAULT_SEED:
      return extend(appState, {
        currentView: {
          name: 'createVaultComplete',
          seedWords: action.value,
        },
        transForward: true,
        isLoading: false,
      })

    case actions.NEW_ACCOUNT_SCREEN:
      return extend(appState, {
        currentView: {
          name: 'new-account',
          context: appState.currentView.context,
        },
        transForward: true,
      })

    case actions.SHOW_SEND_PAGE:
      return extend(appState, {
        currentView: {
          name: 'sendTransaction',
          context: appState.currentView.context,
        },
        transForward: true,
        warning: null,
      })

    case actions.SHOW_SEND_TOKEN_PAGE:
      return extend(appState, {
        currentView: {
          name: 'sendToken',
          context: appState.currentView.context,
        },
        transForward: true,
        warning: null,
      })

    case actions.SHOW_NEW_KEYCHAIN:
      return extend(appState, {
        currentView: {
          name: 'newKeychain',
          context: appState.currentView.context,
        },
        transForward: true,
      })

  // unlock

    case actions.UNLOCK_METAMASK:
      return extend(appState, {
        forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
        detailView: {},
        transForward: true,
        isLoading: false,
        warning: null,
      })

    case actions.LOCK_METAMASK:
      return extend(appState, {
        currentView: defaultView,
        transForward: false,
        warning: null,
      })

    case actions.BACK_TO_INIT_MENU:
      return extend(appState, {
        warning: null,
        transForward: false,
        forgottenPassword: true,
        currentView: {
          name: 'InitMenu',
        },
      })

    case actions.BACK_TO_UNLOCK_VIEW:
      return extend(appState, {
        warning: null,
        transForward: true,
        forgottenPassword: false,
        currentView: {
          name: 'UnlockScreen',
        },
      })
  // reveal seed words

    case actions.REVEAL_SEED_CONFIRMATION:
      return extend(appState, {
        currentView: {
          name: 'reveal-seed-conf',
        },
        transForward: true,
        warning: null,
      })

  // accounts

    case actions.SET_SELECTED_ACCOUNT:
      return extend(appState, {
        activeAddress: action.value,
      })

    case actions.GO_HOME:
      return extend(appState, {
        currentView: extend(appState.currentView, {
          name: 'accountDetail',
        }),
        accountDetail: {
          subview: 'transactions',
          accountExport: 'none',
          privateKey: '',
        },
        transForward: false,
        warning: null,
      })

    case actions.SHOW_ACCOUNT_DETAIL:
      return extend(appState, {
        forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
        currentView: {
          name: 'accountDetail',
          context: action.value,
        },
        accountDetail: {
          subview: 'transactions',
          accountExport: 'none',
          privateKey: '',
        },
        transForward: false,
      })

    case actions.BACK_TO_ACCOUNT_DETAIL:
      return extend(appState, {
        currentView: {
          name: 'accountDetail',
          context: action.value,
        },
        accountDetail: {
          subview: 'transactions',
          accountExport: 'none',
          privateKey: '',
        },
        transForward: false,
      })

    case actions.SHOW_ACCOUNTS_PAGE:
      return extend(appState, {
        currentView: {
          name: seedWords ? 'createVaultComplete' : 'accounts',
          seedWords,
        },
        transForward: true,
        isLoading: false,
        warning: null,
        scrollToBottom: false,
        forgottenPassword: false,
      })

    case actions.SHOW_NOTICE:
      return extend(appState, {
        transForward: true,
        isLoading: false,
      })

    case actions.REVEAL_ACCOUNT:
      return extend(appState, {
        scrollToBottom: true,
      })

    case actions.SHOW_CONF_TX_PAGE:
      return extend(appState, {
        currentView: {
          name: 'confTx',
          context: action.id ? indexForPending(state, action.id) : 0,
        },
        transForward: action.transForward,
        warning: null,
        isLoading: false,
      })

    case actions.SHOW_CONF_MSG_PAGE:
      return extend(appState, {
        currentView: {
          name: hasUnconfActions ? 'confTx' : 'account-detail',
          context: 0,
        },
        transForward: true,
        warning: null,
        isLoading: false,
      })

    case actions.COMPLETED_TX:
      log.debug('reducing COMPLETED_TX for tx ' + action.value)
      const otherUnconfActions = getUnconfActionList(state)
        .filter(tx => tx.id !== action.value)
      const hasOtherUnconfActions = otherUnconfActions.length > 0

      if (hasOtherUnconfActions) {
        log.debug('reducer detected txs - rendering confTx view')
        return extend(appState, {
          transForward: false,
          currentView: {
            name: 'confTx',
            context: 0,
          },
          warning: null,
        })
      } else {
        log.debug('attempting to close popup')
        return extend(appState, {
          // indicate notification should close
          shouldClose: true,
          transForward: false,
          warning: null,
          currentView: {
            name: 'accountDetail',
            context: state.metamask.selectedAddress,
          },
          accountDetail: {
            subview: 'transactions',
          },
        })
      }

    case actions.NEXT_TX:
      return extend(appState, {
        transForward: true,
        currentView: {
          name: 'confTx',
          context: ++appState.currentView.context,
          warning: null,
        },
      })

    case actions.VIEW_PENDING_TX:
      const context = indexForPending(state, action.value)
      return extend(appState, {
        transForward: true,
        currentView: {
          name: 'confTx',
          context,
          warning: null,
        },
      })

    case actions.PREVIOUS_TX:
      return extend(appState, {
        transForward: false,
        currentView: {
          name: 'confTx',
          context: --appState.currentView.context,
          warning: null,
        },
      })

    case actions.TRANSACTION_ERROR:
      return extend(appState, {
        currentView: {
          name: 'confTx',
          errorMessage: 'There was a problem submitting this transaction.',
        },
      })

    case actions.UNLOCK_FAILED:
      return extend(appState, {
        warning: action.value || 'Incorrect password. Try again.',
      })

    case actions.UNLOCK_SUCCEEDED:
      return extend(appState, {
        warning: '',
      })

    case actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH:
      const { device, path } = action.value
      const newDefaults = {...appState.defaultHdPaths}
      newDefaults[device] = path

      return extend(appState, {
        defaultHdPaths: newDefaults,
      })

    case actions.SHOW_LOADING:
      return extend(appState, {
        isLoading: true,
        loadingMessage: action.value,
      })

    case actions.HIDE_LOADING:
      return extend(appState, {
        isLoading: false,
      })

    case actions.SHOW_SUB_LOADING_INDICATION:
      return extend(appState, {
        isSubLoading: true,
      })

    case actions.HIDE_SUB_LOADING_INDICATION:
      return extend(appState, {
        isSubLoading: false,
      })
    case actions.CLEAR_SEED_WORD_CACHE:
      return extend(appState, {
        transForward: true,
        currentView: {},
        isLoading: false,
        accountDetail: {
          subview: 'transactions',
          accountExport: 'none',
          privateKey: '',
        },
      })

    case actions.DISPLAY_WARNING:
      return extend(appState, {
        warning: action.value,
        isLoading: false,
      })

    case actions.HIDE_WARNING:
      return extend(appState, {
        warning: undefined,
      })

    case actions.REQUEST_ACCOUNT_EXPORT:
      return extend(appState, {
        transForward: true,
        currentView: {
          name: 'accountDetail',
          context: appState.currentView.context,
        },
        accountDetail: {
          subview: 'export',
          accountExport: 'requested',
        },
      })

    case actions.EXPORT_ACCOUNT:
      return extend(appState, {
        accountDetail: {
          subview: 'export',
          accountExport: 'completed',
        },
      })

    case actions.SHOW_PRIVATE_KEY:
      return extend(appState, {
        accountDetail: {
          subview: 'export',
          accountExport: 'completed',
          privateKey: action.value,
        },
      })

    case actions.BUY_ETH_VIEW:
      return extend(appState, {
        transForward: true,
        currentView: {
          name: 'buyEth',
          context: appState.currentView.name,
        },
        identity: state.metamask.identities[action.value],
        buyView: {
          subview: 'Coinbase',
          amount: '15.00',
          buyAddress: action.value,
          formView: {
            coinbase: true,
            shapeshift: false,
          },
        },
      })

    case actions.ONBOARDING_BUY_ETH_VIEW:
      return extend(appState, {
        transForward: true,
        currentView: {
          name: 'onboardingBuyEth',
          context: appState.currentView.name,
        },
        identity: state.metamask.identities[action.value],
      })

    case actions.COINBASE_SUBVIEW:
      return extend(appState, {
        buyView: {
          subview: 'Coinbase',
          formView: {
            coinbase: true,
            shapeshift: false,
          },
          buyAddress: appState.buyView.buyAddress,
          amount: appState.buyView.amount,
        },
      })

    case actions.SHAPESHIFT_SUBVIEW:
      return extend(appState, {
        buyView: {
          subview: 'ShapeShift',
          formView: {
            coinbase: false,
            shapeshift: true,
            marketinfo: action.value.marketinfo,
            coinOptions: action.value.coinOptions,
          },
          buyAddress: action.value.buyAddress || appState.buyView.buyAddress,
          amount: appState.buyView.amount || 0,
        },
      })

    case actions.PAIR_UPDATE:
      return extend(appState, {
        buyView: {
          subview: 'ShapeShift',
          formView: {
            coinbase: false,
            shapeshift: true,
            marketinfo: action.value.marketinfo,
            coinOptions: appState.buyView.formView.coinOptions,
          },
          buyAddress: appState.buyView.buyAddress,
          amount: appState.buyView.amount,
          warning: null,
        },
      })

    case actions.SHOW_QR:
      return extend(appState, {
        qrRequested: true,
        transForward: true,

        Qr: {
          message: action.value.message,
          data: action.value.data,
        },
      })

    case actions.SHOW_QR_VIEW:
      return extend(appState, {
        currentView: {
          name: 'qr',
          context: appState.currentView.context,
        },
        transForward: true,
        Qr: {
          message: action.value.message,
          data: action.value.data,
        },
      })

    case actions.SET_MOUSE_USER_STATE:
      return extend(appState, {
        isMouseUser: action.value,
      })

    case actions.GAS_LOADING_STARTED:
      return extend(appState, {
        gasIsLoading: true,
      })

    case actions.GAS_LOADING_FINISHED:
      return extend(appState, {
        gasIsLoading: false,
      })

    case actions.SET_NETWORK_NONCE:
      return extend(appState, {
        networkNonce: action.value,
      })

    default:
      return appState
  }
}

function checkUnconfActions (state) {
  const unconfActionList = getUnconfActionList(state)
  const hasUnconfActions = unconfActionList.length > 0
  return hasUnconfActions
}

function getUnconfActionList (state) {
  const { unapprovedTxs, unapprovedMsgs,
    unapprovedPersonalMsgs, unapprovedTypedMessages, network } = state.metamask

  const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
  return unconfActionList
}

function indexForPending (state, txId) {
  const unconfTxList = getUnconfActionList(state)
  const match = unconfTxList.find((tx) => tx.id === txId)
  const index = unconfTxList.indexOf(match)
  return index
}

// function indexForLastPending (state) {
//   return getUnconfActionList(state).length
// }