text: import ObservableStore from 'obs-store'
  
  /* eslint-disable import/first,import/order */
  const Box = process.env.IN_TEST
    ? require('../../../development/mock-3box')
    : require('3box')
  /* eslint-enable import/order */
  
  import log from 'loglevel'
  import JsonRpcEngine from 'json-rpc-engine'
  import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
  import Migrator from '../lib/migrator'
  import migrations from '../migrations'
  import createOriginMiddleware from '../lib/createOriginMiddleware'
  import createMetamaskMiddleware from './network/createMetamaskMiddleware'
  /* eslint-enable import/first */
  
  const SYNC_TIMEOUT = 60 * 1000 // one minute
  
  export default class ThreeBoxController {
    constructor (opts = {}) {
      const {
        preferencesController,
        keyringController,
        addressBookController,
        version,
        getKeyringControllerState,
      } = opts
  
      this.preferencesController = preferencesController
      this.addressBookController = addressBookController
      this.keyringController = keyringController
      this.provider = this._createProvider({
        version,
        getAccounts: async ({ origin }) => {
          if (origin !== '3Box') {
            return []
          }
          const { isUnlocked } = getKeyringControllerState()
  
          const accounts = await this.keyringController.getAccounts()
  
          if (isUnlocked && accounts[0]) {
            const appKeyAddress = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io')
            return [appKeyAddress]
          }
          return []
        },
        processPersonalMessage: async (msgParams) => {
          const accounts = await this.keyringController.getAccounts()
          return keyringController.signPersonalMessage({ ...msgParams, from: accounts[0] }, {
            withAppKeyOrigin: 'wallet://3box.metamask.io',
          })
        },
      })
  
      const initState = {
        threeBoxSyncingAllowed: false,
        showRestorePrompt: true,
        threeBoxLastUpdated: 0,
        ...opts.initState,
        threeBoxAddress: null,
        threeBoxSynced: false,
        threeBoxDisabled: false,
      }
      this.store = new ObservableStore(initState)
      this.registeringUpdates = false
      this.lastMigration = migrations.sort((a, b) => a.version - b.version).slice(-1)[0]
  
      if (initState.threeBoxSyncingAllowed) {
        this.init()
      }
    }
  
    async init () {
      const accounts = await this.keyringController.getAccounts()
      this.address = accounts[0]
      if (this.address && !(this.box && this.store.getState().threeBoxSynced)) {
        await this.new3Box()
      }
    }
  
    async _update3Box () {
      try {
        const { threeBoxSyncingAllowed, threeBoxSynced } = this.store.getState()
        if (threeBoxSyncingAllowed && threeBoxSynced) {
          const newState = {
            preferences: this.preferencesController.store.getState(),
            addressBook: this.addressBookController.state,
            lastUpdated: Date.now(),
            lastMigration: this.lastMigration,
          }
  
          await this.space.private.set('metamaskBackup', JSON.stringify(newState))
          await this.setShowRestorePromptToFalse()
        }
      } catch (error) {
        console.error(error)
      }
    }
  
    _createProvider (providerOpts) {
      const metamaskMiddleware = createMetamaskMiddleware(providerOpts)
      const engine = new JsonRpcEngine()
      engine.push(createOriginMiddleware({ origin: '3Box' }))
      engine.push(metamaskMiddleware)
      const provider = providerFromEngine(engine)
      return provider
    }
  
    _waitForOnSyncDone () {
      return new Promise((resolve) => {
        this.box.onSyncDone(() => {
          log.debug('3Box box sync done')
          return resolve()
        })
      })
    }
  
    async new3Box () {
      const accounts = await this.keyringController.getAccounts()
      this.address = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io')
      let backupExists
      try {
        const threeBoxConfig = await Box.getConfig(this.address)
        backupExists = threeBoxConfig.spaces && threeBoxConfig.spaces.metamask
      } catch (e) {
        if (e.message.match(/^Error: Invalid response \(404\)/u)) {
          backupExists = false
        } else {
          throw e
        }
      }
      if (this.getThreeBoxSyncingState() || backupExists) {
        this.store.updateState({ threeBoxSynced: false })
  
        let timedOut = false
        const syncTimeout = setTimeout(() => {
          log.error(`3Box sync timed out after ${SYNC_TIMEOUT} ms`)
          timedOut = true
          this.store.updateState({
            threeBoxDisabled: true,
            threeBoxSyncingAllowed: false,
          })
        }, SYNC_TIMEOUT)
        try {
          this.box = await Box.openBox(this.address, this.provider)
          await this._waitForOnSyncDone()
          this.space = await this.box.openSpace('metamask', {
            onSyncDone: async () => {
              const stateUpdate = {
                threeBoxSynced: true,
                threeBoxAddress: this.address,
              }
              if (timedOut) {
                log.info(`3Box sync completed after timeout; no longer disabled`)
                stateUpdate.threeBoxDisabled = false
              }
  
              clearTimeout(syncTimeout)
              this.store.updateState(stateUpdate)
  
              log.debug('3Box space sync done')
            },
          })
        } catch (e) {
          console.error(e)
          throw e
        }
      }
    }
  
    async getLastUpdated () {
      const res = await this.space.private.get('metamaskBackup')
      const parsedRes = JSON.parse(res || '{}')
      return parsedRes.lastUpdated
    }
  
    async migrateBackedUpState (backedUpState) {
      const migrator = new Migrator({ migrations })
      const { preferences, addressBook } = JSON.parse(backedUpState)
      const formattedStateBackup = {
        PreferencesController: preferences,
        AddressBookController: addressBook,
      }
      const initialMigrationState = migrator.generateInitialState(formattedStateBackup)
      const migratedState = await migrator.migrateData(initialMigrationState)
      return {
        preferences: migratedState.data.PreferencesController,
        addressBook: migratedState.data.AddressBookController,
      }
    }
  
    async restoreFromThreeBox () {
      const backedUpState = await this.space.private.get('metamaskBackup')
      const {
        preferences,
        addressBook,
      } = await this.migrateBackedUpState(backedUpState)
      this.store.updateState({ threeBoxLastUpdated: backedUpState.lastUpdated })
      preferences && this.preferencesController.store.updateState(preferences)
      addressBook && this.addressBookController.update(addressBook, true)
      this.setShowRestorePromptToFalse()
    }
  
    turnThreeBoxSyncingOn () {
      this._registerUpdates()
    }
  
    turnThreeBoxSyncingOff () {
      this.box.logout()
    }
  
    setShowRestorePromptToFalse () {
      this.store.updateState({ showRestorePrompt: false })
    }
  
    setThreeBoxSyncingPermission (newThreeboxSyncingState) {
      if (this.store.getState().threeBoxDisabled) {
        return
      }
      this.store.updateState({
        threeBoxSyncingAllowed: newThreeboxSyncingState,
      })
  
      if (newThreeboxSyncingState && this.box) {
        this.turnThreeBoxSyncingOn()
      }
  
      if (!newThreeboxSyncingState && this.box) {
        this.turnThreeBoxSyncingOff()
      }
    }
  
    getThreeBoxSyncingState () {
      return this.store.getState().threeBoxSyncingAllowed
    }
  
    _registerUpdates () {
      if (!this.registeringUpdates) {
        const updatePreferences = this._update3Box.bind(this)
        this.preferencesController.store.subscribe(updatePreferences)
        const updateAddressBook = this._update3Box.bind(this)
        this.addressBookController.subscribe(updateAddressBook)
        this.registeringUpdates = true
      }
    }
  }
  