Skip to content

Commit

Permalink
3box Replacement (#15243)
Browse files Browse the repository at this point in the history
* Backup user data

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Tests for prependZero (utils.js)

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Fix advancedtab test

Signed-off-by: Akintayo A. Olusegun <[email protected]>

backup controller tests

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Lint fixes

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Backup controller don't have a store.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Restore from file.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Advanced Tab tests

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Lint fix

Signed-off-by: Akintayo A. Olusegun <[email protected]>

e2e tests for backup
unit tests for restore.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Fix comments on PR.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

restore style

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Lint fixes

Signed-off-by: Akintayo A. Olusegun <[email protected]>

We should move the exportAsFile to a utility file in the shared/ directory

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Move export as file to shared folder

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Refactor create download folder methods

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Lint fixes.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Move the backup/restore buttons closer to 3box

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Change descriptions
Add to search

Signed-off-by: Akintayo A. Olusegun <[email protected]>

refactor code to use if instead of &&

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Lint fixes

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Restore button should change cursor to pointer.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Fix restore not uploading same file twice.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

Do not backup these items in preferences
    identities
    lostIdentities
    selectedAddress

Signed-off-by: Akintayo A. Olusegun <[email protected]>

lint fixes.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Only update what is needed.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Fixed test for search

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* remove txError as it currently does nothing.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Remove dispatch, not needed since we're not dispatching any actions.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Event should be title case.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Make backup/restore normal async functions
rename event as per product suggestion.

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Use success Actionable message for success message and danger for error
message

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* change event name to match with backup

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* Lint fixes

Signed-off-by: Akintayo A. Olusegun <[email protected]>

* lint fixes
Signed-off-by: Akintayo A. Olusegun <[email protected]>

* fix e2e

Signed-off-by: Akintayo A. Olusegun <[email protected]>
  • Loading branch information
segun authored Aug 9, 2022
1 parent d255fcd commit 4f34e72
Show file tree
Hide file tree
Showing 20 changed files with 600 additions and 31 deletions.
21 changes: 21 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions app/scripts/controllers/backup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { exportAsFile } from '../../../shared/modules/export-utils';
import { prependZero } from '../../../shared/modules/string-utils';

export default class BackupController {
constructor(opts = {}) {
const {
preferencesController,
addressBookController,
trackMetaMetricsEvent,
} = opts;

this.preferencesController = preferencesController;
this.addressBookController = addressBookController;
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
}

async restoreUserData(jsonString) {
const existingPreferences = this.preferencesController.store.getState();
const { preferences, addressBook } = JSON.parse(jsonString);
if (preferences) {
preferences.identities = existingPreferences.identities;
preferences.lostIdentities = existingPreferences.lostIdentities;
preferences.selectedAddress = existingPreferences.selectedAddress;

this.preferencesController.store.updateState(preferences);
}

if (addressBook) {
this.addressBookController.update(addressBook, true);
}

if (preferences && addressBook) {
this._trackMetaMetricsEvent({
event: 'User Data Imported',
category: 'Backup',
});
}
}

async backupUserData() {
const userData = {
preferences: { ...this.preferencesController.store.getState() },
addressBook: { ...this.addressBookController.state },
};

/**
* We can remove these properties since we will won't be restoring identities from backup
*/
delete userData.preferences.identities;
delete userData.preferences.lostIdentities;
delete userData.preferences.selectedAddress;

const result = JSON.stringify(userData);

const date = new Date();

const prefixZero = (num) => prependZero(num, 2);

/*
* userData.YYYY_MM_DD_HH_mm_SS e.g userData.2022_01_13_13_45_56
* */
const userDataFileName = `MetaMaskUserData.${date.getFullYear()}_${prefixZero(
date.getMonth() + 1,
)}_${prefixZero(date.getDay())}_${prefixZero(date.getHours())}_${prefixZero(
date.getMinutes(),
)}_${prefixZero(date.getDay())}.json`;

exportAsFile(userDataFileName, result);

this._trackMetaMetricsEvent({
event: 'User Data Exported',
category: 'Backup',
});

return result;
}
}
118 changes: 118 additions & 0 deletions app/scripts/controllers/backup.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import BackupController from './backup';

function getMockController() {
const mcState = {
getSelectedAddress: sinon.stub().returns('0x01'),
selectedAddress: '0x01',
identities: {
'0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B': {
address: '0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B',
lastSelected: 1655380342907,
name: 'Account 3',
},
},
lostIdentities: {
'0xfd59bbe569376e3d3e4430297c3c69ea93f77435': {
address: '0xfd59bbe569376e3d3e4430297c3c69ea93f77435',
lastSelected: 1655379648197,
name: 'Ledger 1',
},
},
update: (store) => (mcState.store = store),
};

mcState.store = {
getState: sinon.stub().returns(mcState),
updateState: (store) => (mcState.store = store),
};

return mcState;
}

const jsonData = `{"preferences":{"frequentRpcListDetail":[{"chainId":"0x539","nickname":"Localhost 8545","rpcPrefs":{},"rpcUrl":"http://localhost:8545","ticker":"ETH"},{"chainId":"0x38","nickname":"Binance Smart Chain Mainnet","rpcPrefs":{"blockExplorerUrl":"https://bscscan.com"},"rpcUrl":"https://bsc-dataseed1.binance.org","ticker":"BNB"},{"chainId":"0x61","nickname":"Binance Smart Chain Testnet","rpcPrefs":{"blockExplorerUrl":"https://testnet.bscscan.com"},"rpcUrl":"https://data-seed-prebsc-1-s1.binance.org:8545","ticker":"tBNB"},{"chainId":"0x89","nickname":"Polygon Mainnet","rpcPrefs":{"blockExplorerUrl":"https://polygonscan.com"},"rpcUrl":"https://polygon-rpc.com","ticker":"MATIC"}],"useBlockie":false,"useNonceField":false,"usePhishDetect":true,"dismissSeedBackUpReminder":false,"useTokenDetection":false,"useCollectibleDetection":false,"openSeaEnabled":false,"advancedGasFee":null,"featureFlags":{"sendHexData":true,"showIncomingTransactions":true},"knownMethodData":{},"currentLocale":"en","forgottenPassword":false,"preferences":{"hideZeroBalanceTokens":false,"showFiatInTestnets":false,"showTestNetworks":true,"useNativeCurrencyAsPrimaryCurrency":true},"ipfsGateway":"dweb.link","infuraBlocked":false,"ledgerTransportType":"webhid","theme":"light","customNetworkListEnabled":false,"textDirection":"auto"},"addressBook":{"addressBook":{"0x61":{"0x42EB768f2244C8811C63729A21A3569731535f06":{"address":"0x42EB768f2244C8811C63729A21A3569731535f06","chainId":"0x61","isEns":false,"memo":"","name":""}}}}}`;

describe('BackupController', function () {
const getBackupController = () => {
return new BackupController({
preferencesController: getMockController(),
addressBookController: getMockController(),
trackMetaMetricsEvent: sinon.stub(),
});
};

describe('constructor', function () {
it('should setup correctly', async function () {
const backupController = getBackupController();
const selectedAddress =
backupController.preferencesController.getSelectedAddress();
assert.equal(selectedAddress, '0x01');
});

it('should restore backup', async function () {
const backupController = getBackupController();
backupController.restoreUserData(jsonData);
// check Preferences backup
assert.equal(
backupController.preferencesController.store.frequentRpcListDetail[0]
.chainId,
'0x539',
);
assert.equal(
backupController.preferencesController.store.frequentRpcListDetail[1]
.chainId,
'0x38',
);
// make sure identities are not lost after restore
assert.equal(
backupController.preferencesController.store.identities[
'0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B'
].lastSelected,
1655380342907,
);
assert.equal(
backupController.preferencesController.store.identities[
'0x295e26495CEF6F69dFA69911d9D8e4F3bBadB89B'
].name,
'Account 3',
);
assert.equal(
backupController.preferencesController.store.lostIdentities[
'0xfd59bbe569376e3d3e4430297c3c69ea93f77435'
].lastSelected,
1655379648197,
);
assert.equal(
backupController.preferencesController.store.lostIdentities[
'0xfd59bbe569376e3d3e4430297c3c69ea93f77435'
].name,
'Ledger 1',
);
// make sure selected address is not lost after restore
assert.equal(
backupController.preferencesController.store.selectedAddress,
'0x01',
);
// check address book backup
assert.equal(
backupController.addressBookController.store.addressBook['0x61'][
'0x42EB768f2244C8811C63729A21A3569731535f06'
].chainId,
'0x61',
);
assert.equal(
backupController.addressBookController.store.addressBook['0x61'][
'0x42EB768f2244C8811C63729A21A3569731535f06'
].address,
'0x42EB768f2244C8811C63729A21A3569731535f06',
);
assert.equal(
backupController.addressBookController.store.addressBook['0x61'][
'0x42EB768f2244C8811C63729A21A3569731535f06'
].isEns,
false,
);
});
});
});
16 changes: 16 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ import CachedBalancesController from './controllers/cached-balances';
import AlertController from './controllers/alert';
import OnboardingController from './controllers/onboarding';
import ThreeBoxController from './controllers/threebox';
import BackupController from './controllers/backup';
import IncomingTransactionsController from './controllers/incoming-transactions';
import MessageManager, { normalizeMsgData } from './lib/message-manager';
import DecryptMessageManager from './lib/decrypt-message-manager';
Expand Down Expand Up @@ -797,6 +798,14 @@ export default class MetamaskController extends EventEmitter {
),
});

this.backupController = new BackupController({
preferencesController: this.preferencesController,
addressBookController: this.addressBookController,
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),
});

this.txController = new TransactionController({
initState:
initState.TransactionController || initState.TransactionManager,
Expand Down Expand Up @@ -1047,6 +1056,7 @@ export default class MetamaskController extends EventEmitter {
PermissionLogController: this.permissionLogController.store,
SubjectMetadataController: this.subjectMetadataController,
ThreeBoxController: this.threeBoxController.store,
BackupController: this.backupController,
AnnouncementController: this.announcementController,
GasFeeController: this.gasFeeController,
TokenListController: this.tokenListController,
Expand Down Expand Up @@ -1085,6 +1095,7 @@ export default class MetamaskController extends EventEmitter {
PermissionLogController: this.permissionLogController.store,
SubjectMetadataController: this.subjectMetadataController,
ThreeBoxController: this.threeBoxController.store,
BackupController: this.backupController,
SwapsController: this.swapsController.store,
EnsController: this.ensController.store,
ApprovalController: this.approvalController,
Expand Down Expand Up @@ -1521,6 +1532,7 @@ export default class MetamaskController extends EventEmitter {
smartTransactionsController,
txController,
assetsContractController,
backupController,
} = this;

return {
Expand Down Expand Up @@ -1965,6 +1977,10 @@ export default class MetamaskController extends EventEmitter {
removePollingTokenFromAppState:
appStateController.removePollingToken.bind(appStateController),

// BackupController
backupUserData: backupController.backupUserData.bind(backupController),
restoreUserData: backupController.restoreUserData.bind(backupController),

// DetectTokenController
detectNewTokens: detectTokensController.detectNewTokens.bind(
detectTokensController,
Expand Down
19 changes: 19 additions & 0 deletions shared/modules/export-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getRandomFileName } from '../../ui/helpers/utils/util';

export function exportAsFile(filename, data, type = 'text/csv') {
// eslint-disable-next-line no-param-reassign
filename = filename || getRandomFileName();
// source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
const blob = new window.Blob([data], { type });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename);
} else {
const elem = window.document.createElement('a');
elem.target = '_blank';
elem.href = window.URL.createObjectURL(blob);
elem.download = filename;
document.body.appendChild(elem);
elem.click();
document.body.removeChild(elem);
}
}
4 changes: 4 additions & 0 deletions shared/modules/string-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export function isEqualCaseInsensitive(value1, value2) {
}
return value1.toLowerCase() === value2.toLowerCase();
}

export function prependZero(num, maxLength) {
return num.toString().padStart(maxLength, '0');
}
7 changes: 7 additions & 0 deletions test/e2e/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const path = require('path');
const { promises: fs } = require('fs');
const BigNumber = require('bignumber.js');
const mockttp = require('mockttp');
const createStaticServer = require('../../development/create-static-server');
Expand All @@ -17,6 +18,11 @@ const largeDelayMs = regularDelayMs * 2;
const veryLargeDelayMs = largeDelayMs * 2;
const dappBasePort = 8080;

const createDownloadFolder = async (downloadsFolder) => {
await fs.rm(downloadsFolder, { recursive: true, force: true });
await fs.mkdir(downloadsFolder, { recursive: true });
};

const convertToHexValue = (val) => `0x${new BigNumber(val, 10).toString(16)}`;

async function withFixtures(options, testSuite) {
Expand Down Expand Up @@ -330,4 +336,5 @@ module.exports = {
connectDappWithExtensionPopup,
completeImportSRPOnboardingFlow,
completeImportSRPOnboardingFlowWordByWord,
createDownloadFolder,
};
Loading

0 comments on commit 4f34e72

Please sign in to comment.