1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-27 21:00:13 +01:00
metamask-extension/app/scripts/controllers/threebox.js

249 lines
7.5 KiB
JavaScript

const ObservableStore = require('obs-store')
const Box = process.env.IN_TEST
? require('../../../development/mock-3box')
: require('3box')
const log = require('loglevel')
const migrations = require('../migrations/')
const Migrator = require('../lib/migrator')
const JsonRpcEngine = require('json-rpc-engine')
const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine')
const createMetamaskMiddleware = require('./network/createMetamaskMiddleware')
const createOriginMiddleware = require('../lib/createOriginMiddleware')
const SYNC_TIMEOUT = 60 * 1000 // one minute
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().isUnlocked
const accounts = await this.keyringController.getAccounts()
if (isUnlocked && accounts[0]) {
const appKeyAddress = await this.keyringController.getAppKeyAddress(accounts[0], 'wallet://3box.metamask.io')
return [appKeyAddress]
} else {
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\)/)) {
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 formattedStateBackup = {
PreferencesController: backedUpState.preferences,
AddressBookController: backedUpState.addressBook,
}
const initialMigrationState = migrator.generateInitialState(formattedStateBackup)
const migratedState = await migrator.migrateData(initialMigrationState)
return {
preferences: migratedState.PreferencesController,
addressBook: migratedState.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(JSON.parse(preferences))
addressBook && this.addressBookController.update(JSON.parse(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
}
getThreeBoxAddress () {
return this.store.getState().threeBoxAddress
}
_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
}
}
}
module.exports = ThreeBoxController