From 595971e907d1b75f14be31b1fc651323d7b3146b Mon Sep 17 00:00:00 2001 From: Gabriel Masclef Date: Tue, 14 Nov 2017 17:06:16 -0300 Subject: [PATCH] Feat: Import wallet --- .../add/import-wallet/import-wallet.html | 6 +- src/pages/add/import-wallet/import-wallet.ts | 313 ++++++++++++++++-- .../on-going-process/on-going-process.ts | 1 - src/providers/wallet/wallet.ts | 15 +- 4 files changed, 306 insertions(+), 29 deletions(-) diff --git a/src/pages/add/import-wallet/import-wallet.html b/src/pages/add/import-wallet/import-wallet.html index 435984fa4..171d3684b 100644 --- a/src/pages/add/import-wallet/import-wallet.html +++ b/src/pages/add/import-wallet/import-wallet.html @@ -23,7 +23,7 @@
Choose a backup file from your computer - + @@ -34,7 +34,7 @@ - + Show advanced options Hide advanced options @@ -64,4 +64,4 @@
- + \ No newline at end of file diff --git a/src/pages/add/import-wallet/import-wallet.ts b/src/pages/add/import-wallet/import-wallet.ts index ec045908a..80e22ea2e 100644 --- a/src/pages/add/import-wallet/import-wallet.ts +++ b/src/pages/add/import-wallet/import-wallet.ts @@ -1,40 +1,69 @@ import { Component, OnInit } from '@angular/core'; import { NavController, NavParams } from 'ionic-angular'; import { Validators, FormBuilder, FormGroup } from '@angular/forms'; +import { Logger } from '@nsalaun/ng-logger'; +// Pages +import { HomePage } from '../../../pages/home/home'; + +// Providers import { BwcProvider } from '../../../providers/bwc/bwc'; -import { WalletProvider } from '../../../providers/wallet/wallet'; -import { DerivationPathHelperProvider } from '../../../providers/derivation-path-helper/derivation-path-helper'; import { ConfigProvider } from '../../../providers/config/config'; +import { DerivationPathHelperProvider } from '../../../providers/derivation-path-helper/derivation-path-helper'; +import { OnGoingProcessProvider } from '../../../providers/on-going-process/on-going-process'; +import { PlatformProvider } from '../../../providers/platform/platform'; +import { ProfileProvider } from '../../../providers/profile/profile'; +import { PopupProvider } from '../../../providers/popup/popup'; +import { WalletProvider } from '../../../providers/wallet/wallet'; @Component({ selector: 'page-import-wallet', templateUrl: 'import-wallet.html' }) export class ImportWalletPage implements OnInit { - public fromOnboarding: boolean; - public formData: any; - public showAdvOpts: boolean; - public selectedTab: string; - public seedOptions: any; private derivationPathByDefault: string; private derivationPathForTestnet: string; private importForm: FormGroup; + private reader: FileReader; + private defaults: any; + private config: any; + private errors: any; + private importErr: boolean; + + public fromOnboarding: boolean; + public formData: any; + public showAdvOpts: boolean; + public selectedTab: string; + public enableCash: boolean = false; + public isCordova: boolean; + public file: File; constructor( - public navCtrl: NavController, + private navCtrl: NavController, private navParams: NavParams, private form: FormBuilder, - private bwc: BwcProvider, - private pathHelper: DerivationPathHelperProvider, + private bwcProvider: BwcProvider, + private derivationPathHelperProvider: DerivationPathHelperProvider, private walletProvider: WalletProvider, private configProvider: ConfigProvider, + private popupProvider: PopupProvider, + private platformProvider: PlatformProvider, + private logger: Logger, + private onGoingProcessProvider: OnGoingProcessProvider, + private profileProvider: ProfileProvider ) { + this.reader = new FileReader(); + this.defaults = configProvider.getDefaults(); + this.config = configProvider.get(); + this.errors = bwcProvider.getErrors(); + + this.isCordova = this.platformProvider.isCordova; + this.importErr = false; this.fromOnboarding = this.navParams.data.fromOnboarding; this.selectedTab = 'words'; - this.derivationPathByDefault = this.pathHelper.default; - this.derivationPathForTestnet = this.pathHelper.defaultTestnet; + this.derivationPathByDefault = this.derivationPathHelperProvider.default; + this.derivationPathForTestnet = this.derivationPathHelperProvider.defaultTestnet; this.showAdvOpts = false; this.formData = { words: null, @@ -43,8 +72,14 @@ export class ImportWalletPage implements OnInit { filePassword: null, derivationPath: this.derivationPathByDefault, testnet: false, - bwsURL: this.configProvider.get()['bws']['url'], + bwsURL: this.defaults.bws.url, + coin: this.navParams.data.coin ? this.navParams.data.coin : null }; + + if (this.config.cashSupport) this.enableCash = true; + + if (this.navParams.data.code) + this.processWalletInfo(this.navParams.data.code); } ngOnInit() { @@ -85,19 +120,259 @@ export class ImportWalletPage implements OnInit { this.importForm.get('filePassword').updateValueAndValidity(); } - setDerivationPath() { - this.formData.derivationPath = this.formData.testnet ? this.derivationPathForTestnet : this.derivationPathByDefault; - } - normalizeMnemonic(words: string) { if (!words || !words.indexOf) return words; var isJA = words.indexOf('\u3000') > -1; var wordList = words.split(/[\u3000\s]+/); return wordList.join(isJA ? '\u3000' : ' '); - }; + } + + private processWalletInfo(code: string): void { + if (!code) return; + + this.importErr = false; + let parsedCode = code.split('|'); + + if (parsedCode.length != 5) { + /// Trying to import a malformed wallet export QR code + this.popupProvider.ionicAlert('Error', 'Incorrect code format'); //TODO gettextcatalog + return; + } + + let info = { + type: parsedCode[0], + data: parsedCode[1], + network: parsedCode[2], + derivationPath: parsedCode[3], + hasPassphrase: parsedCode[4] == 'true' ? true : false + }; + + if (info.type == '1' && info.hasPassphrase) + this.popupProvider.ionicAlert('Error', 'Password required. Make sure to enter your password in advanced options'); //TODO gettextcatalog + + this.formData.derivationPath = info.derivationPath; + this.formData.testnetEnabled = info.network == 'testnet' ? true : false; + this.formData.words = info.data; + } + + public switchTestnetOff(): void { + this.formData.testnetEnabled = false; + this.setDerivationPath(); + } + + private setDerivationPath(): void { + this.formData.derivationPath = this.formData.testnet ? this.derivationPathForTestnet : this.derivationPathByDefault; + } + + private importBlob(str: string, opts: any): void { + let str2: string; + let err: any = null; + try { + str2 = this.bwcProvider.getSJCL().decrypt(this.formData.filePassword, str); + } catch (e) { + err = 'Could not decrypt file, check your password'; //TODO gettextcatalog + this.logger.warn(e); + }; + + if (err) { + this.popupProvider.ionicAlert('Error', err); //TODO gettextcatalog + return; + } + + this.onGoingProcessProvider.set('importingWallet', true); + opts.compressed = null; + opts.password = null; + + setTimeout(() => { + this.profileProvider.importWallet(str2, opts).then((wallet: any) => { + this.onGoingProcessProvider.set('importingWallet', false); + this.finish(wallet); + }).catch((err: any) => { + this.onGoingProcessProvider.set('importingWallet', false); + this.popupProvider.ionicAlert('Error', err); //TODO gettextcatalog + return; + }); + }, 100); + } + + private finish(wallet: any): void { + this.walletProvider.updateRemotePreferences(wallet).then(() => { + this.profileProvider.setBackupFlag(wallet.credentials.walletId); + if (this.fromOnboarding) { + this.profileProvider.setDisclaimerAccepted().catch((err: any) => { + this.logger.error(err); + }); + } + this.navCtrl.setRoot(HomePage); + this.navCtrl.popToRoot(); + }).catch((err: any) => { + this.logger.warn(err); + }); + } + + private importExtendedPrivateKey(xPrivKey, opts) { + this.onGoingProcessProvider.set('importingWallet', true); + setTimeout(() => { + this.profileProvider.importExtendedPrivateKey(xPrivKey, opts).then((wallet: any) => { + this.onGoingProcessProvider.set('importingWallet', false); + this.finish(wallet); + }).catch((err: any) => { + this.onGoingProcessProvider.set('importingWallet', false); + if (err instanceof this.errors.NOT_AUTHORIZED) { + this.importErr = true; + } else { + this.popupProvider.ionicAlert('Error', err); // TODO: gettextcatalog + } + return; + }); + }, 100); + } + + /* + IMPORT FROM PUBLIC KEY - PENDING TODO from v1 + + var _importExtendedPublicKey = function(xPubKey, opts) { + ongoingProcess.set('importingWallet', true); + $timeout(function() { + profileService.importExtendedPublicKey(opts, function(err, walletId) { + ongoingProcess.set('importingWallet', false); + if (err) { + $scope.error = err; + return $timeout(function() { + $scope.$apply(); + }); + } + + profileService.setBackupFlag(walletId); + if ($stateParams.fromOnboarding) { + profileService.setDisclaimerAccepted(function(err) { + if (err) $log.error(err); + }); + } + + $state.go('tabs.home'); + }); + }, 100); + }; + */ + + private importMnemonic(words: string, opts: any): void { + this.onGoingProcessProvider.set('importingWallet', true); + setTimeout(() => { + this.profileProvider.importMnemonic(words, opts).then((wallet: any) => { + this.onGoingProcessProvider.set('importingWallet', false); + this.finish(wallet); + }).catch((err: any) => { + if (err instanceof this.errors.NOT_AUTHORIZED) { + this.importErr = true; + } else { + this.popupProvider.ionicAlert('Error', err); // TODO: gettextcatalog + } + }); + }, 100); + } + import() { - console.log(this.formData); + if (this.selectedTab === 'file') { + this.importFromFile(); + } else { + this.importFromMnemonic(); + } } + + public importFromFile(): void { + if (!this.importForm.valid) { + this.popupProvider.ionicAlert('Error', 'There is an error in the form'); // TODO: gettextcatalog + return; + } + + let backupFile = this.file; + let backupText = this.formData.backupText; + let password = this.formData.filePassword; + + if (!backupFile && !backupText) { + this.popupProvider.ionicAlert('Error', 'Please, select your backup file'); // TODO: gettextcatalog + return; + } + + if (backupFile) { + this.reader.readAsBinaryString(backupFile); + } else { + let opts: any = {}; + opts.bwsurl = this.formData.bwsurl; + opts.coin = this.formData.coin; + this.importBlob(backupText, opts); + } + } + + public importFromMnemonic(): void { + if (!this.importForm.valid) { + this.popupProvider.ionicAlert('Error', 'There is an error in the form'); // TODO: gettextcatalog + return; + } + + let opts: any = {}; + + if (this.formData.bwsurl) + opts.bwsurl = this.formData.bwsurl; + + let pathData: any = this.derivationPathHelperProvider.parse(this.formData.derivationPath); + + if (!pathData) { + this.popupProvider.ionicAlert('Error', 'Invalid derivation path'); // TODO: gettextcatalog + return; + } + + opts.account = pathData.account; + opts.networkName = pathData.networkName; + opts.derivationStrategy = pathData.derivationStrategy; + opts.coin = this.formData.coin; + + let words: string = this.formData.words || null; + + if (!words) { + this.popupProvider.ionicAlert('Error', 'Please enter the recovery phrase'); + return; + } else if (words.indexOf('xprv') == 0 || words.indexOf('tprv') == 0) { + return this.importExtendedPrivateKey(words, opts); + } else if (words.indexOf('xpub') == 0 || words.indexOf('tpuv') == 0) { + //return this.importExtendedPublicKey(words, opts); TODO + return this.logger.warn("TODO: this.importExtendedPublicKey(words, opts)"); + } else { + let wordList: Array = words.split(/[\u3000\s]+/); + + if ((wordList.length % 3) != 0) { + this.popupProvider.ionicAlert('Error', 'Wrong number of recovery words: ' + wordList.length); + return; + } + } + + opts.passphrase = this.formData.passphrase || null; + this.importMnemonic(words, opts); + } + + public toggleShowAdvOpts(): void { + this.showAdvOpts = !this.showAdvOpts; + } + + public fileChangeEvent($event: any) { + this.file = $event.target ? $event.target.files[0] : $event.srcElement.files[0]; + this.formData.file = $event.target.value; + this.getFile(); + } + + private getFile() { + // If we use onloadend, we need to check the readyState. + this.reader.onloadend = (evt: any) => { + if (evt.target.readyState == 2) { // DONE == 2 + let opts: any = {}; + opts.bwsurl = this.formData.bwsurl; + opts.coin = this.formData.coin; + this.importBlob(evt.target.result, opts); + } + } + } + } diff --git a/src/providers/on-going-process/on-going-process.ts b/src/providers/on-going-process/on-going-process.ts index 839fba31b..d5d4facc7 100644 --- a/src/providers/on-going-process/on-going-process.ts +++ b/src/providers/on-going-process/on-going-process.ts @@ -17,7 +17,6 @@ export class OnGoingProcessProvider { content: processName }); this.loading.present(); - } else { this.loading.dismiss(); diff --git a/src/providers/wallet/wallet.ts b/src/providers/wallet/wallet.ts index 57494b7b0..f8d2b1691 100644 --- a/src/providers/wallet/wallet.ts +++ b/src/providers/wallet/wallet.ts @@ -416,7 +416,7 @@ export class WalletProvider { if (lodash.isEmpty(txs)) { return resolve(localTxs); }; - + try { localTxs = JSON.parse(txs); } catch (ex) { @@ -709,9 +709,9 @@ export class WalletProvider { }); } - public updateRemotePreferences(clients: any, prefs: any): Promise { + public updateRemotePreferences(clients: any, prefs?: any): Promise { return new Promise((resolve, reject) => { - prefs = prefs || {}; + prefs = prefs ? prefs : {}; if (!lodash.isArray(clients)) clients = [clients]; @@ -723,13 +723,16 @@ export class WalletProvider { this.logger.debug('Saving remote preferences', wallet.credentials.walletName, prefs); wallet.savePreferences(prefs, (err: any) => { - if (err) { this.popupProvider.ionicAlert(this.bwcErrorProvider.msg(err, 'Could not save preferences on the server')); //TODO Gettextcatalog return reject(err); } - updateRemotePreferencesFor(clients, prefs); + updateRemotePreferencesFor(clients, prefs).then(() => { + return resolve(); + }).catch((err: any) => { + return reject(err); + }); }); }); }; @@ -740,7 +743,7 @@ export class WalletProvider { //prefs.email (may come from arguments) prefs.email = config.emailNotifications.email; - prefs.language = "English" // This line was hardcoded - TODO: prefs.language = uxLanguage.getCurrentLanguage(); + prefs.language = "en" // This line was hardcoded - TODO: prefs.language = uxLanguage.getCurrentLanguage(); // prefs.unit = walletSettings.unitCode; // TODO: remove, not used updateRemotePreferencesFor(lodash.clone(clients), prefs).then(() => {