A Metamask fork with Infura removed and default networks editable
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.
 
 
 
 
 
ciphermask/app/scripts/controllers/threebox.js

306 lines
8.7 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';
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,
trackMetaMetricsEvent,
} = 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',
},
);
},
});
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
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];
this._trackMetaMetricsEvent({
event: '3Box Initiated',
category: '3Box',
});
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)) {
this._trackMetaMetricsEvent({
event: '3Box Backup does not exist',
category: '3Box',
});
backupExists = false;
} else {
this._trackMetaMetricsEvent({
event: '3Box Config Error',
category: '3Box',
});
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');
this._trackMetaMetricsEvent({
event: '3Box Synced',
category: '3Box',
});
},
});
} catch (e) {
this._trackMetaMetricsEvent({
event: '3Box Initiation Error',
category: '3Box',
});
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();
this._trackMetaMetricsEvent({
event: '3Box Restored Data',
category: '3Box',
});
}
turnThreeBoxSyncingOn() {
this._trackMetaMetricsEvent({
event: '3Box Sync Turned On',
category: '3Box',
});
this._registerUpdates();
}
turnThreeBoxSyncingOff() {
this._trackMetaMetricsEvent({
event: '3Box Sync Turned Off',
category: '3Box',
});
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;
}
}
}