You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
7.8 KiB
263 lines
7.8 KiB
import { ObservableStore } from '@metamask/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;
|
|
}
|
|
}
|
|
}
|
|
|