3box Replacement (#15243)
* Backup user data Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Tests for prependZero (utils.js) Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Fix advancedtab test Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> backup controller tests Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Lint fixes Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Backup controller don't have a store. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Restore from file. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Advanced Tab tests Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Lint fix Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> e2e tests for backup unit tests for restore. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Fix comments on PR. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> restore style Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Lint fixes Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> We should move the exportAsFile to a utility file in the shared/ directory Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Move export as file to shared folder Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Refactor create download folder methods Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Lint fixes. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Move the backup/restore buttons closer to 3box Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Change descriptions Add to search Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> refactor code to use if instead of && Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Lint fixes Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Restore button should change cursor to pointer. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Fix restore not uploading same file twice. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Do not backup these items in preferences identities lostIdentities selectedAddress Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> lint fixes. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Only update what is needed. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Fixed test for search Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * remove txError as it currently does nothing. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Remove dispatch, not needed since we're not dispatching any actions. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Event should be title case. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Make backup/restore normal async functions rename event as per product suggestion. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Use success Actionable message for success message and danger for error message Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * change event name to match with backup Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Lint fixes Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * lint fixes Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * fix e2e Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>feature/default_network_editable
parent
d255fcdefb
commit
4f34e72085
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
const { strict: assert } = require('assert'); |
||||||
|
const { promises: fs } = require('fs'); |
||||||
|
const { |
||||||
|
convertToHexValue, |
||||||
|
withFixtures, |
||||||
|
createDownloadFolder, |
||||||
|
} = require('../helpers'); |
||||||
|
|
||||||
|
const downloadsFolder = `${process.cwd()}/test-artifacts/downloads`; |
||||||
|
|
||||||
|
const backupExists = async () => { |
||||||
|
const date = new Date(); |
||||||
|
|
||||||
|
const prependZero = (num, maxLength) => { |
||||||
|
return num.toString().padStart(maxLength, '0'); |
||||||
|
}; |
||||||
|
|
||||||
|
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`;
|
||||||
|
|
||||||
|
try { |
||||||
|
const backup = `${downloadsFolder}/${userDataFileName}`; |
||||||
|
await fs.access(backup); |
||||||
|
return true; |
||||||
|
} catch (e) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
describe('Backup', function () { |
||||||
|
const ganacheOptions = { |
||||||
|
accounts: [ |
||||||
|
{ |
||||||
|
secretKey: |
||||||
|
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', |
||||||
|
balance: convertToHexValue(25000000000000000000), |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
it('should create backup for the account', async function () { |
||||||
|
await withFixtures( |
||||||
|
{ |
||||||
|
fixtures: 'imported-account', |
||||||
|
ganacheOptions, |
||||||
|
title: this.test.title, |
||||||
|
failOnConsoleError: false, |
||||||
|
}, |
||||||
|
async ({ driver }) => { |
||||||
|
await createDownloadFolder(downloadsFolder); |
||||||
|
await driver.navigate(); |
||||||
|
await driver.fill('#password', 'correct horse battery staple'); |
||||||
|
await driver.press('#password', driver.Key.ENTER); |
||||||
|
|
||||||
|
// Download user settings
|
||||||
|
await driver.clickElement('.account-menu__icon'); |
||||||
|
await driver.clickElement({ text: 'Settings', tag: 'div' }); |
||||||
|
await driver.clickElement({ text: 'Advanced', tag: 'div' }); |
||||||
|
await driver.clickElement({ |
||||||
|
text: 'Backup', |
||||||
|
tag: 'button', |
||||||
|
}); |
||||||
|
|
||||||
|
// Verify download
|
||||||
|
let fileExists; |
||||||
|
await driver.wait(async () => { |
||||||
|
fileExists = await backupExists(); |
||||||
|
return fileExists === true; |
||||||
|
}, 10000); |
||||||
|
assert.equal(fileExists, true); |
||||||
|
}, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue