Feat: Import wallet

This commit is contained in:
Gabriel Masclef 2017-11-14 17:06:16 -03:00
parent 07f06b22a7
commit 595971e907
No known key found for this signature in database
GPG Key ID: DD6D7EAADE12280D
4 changed files with 306 additions and 29 deletions

View File

@ -23,7 +23,7 @@
<div *ngIf="selectedTab == 'file'">
<ion-item>
<ion-label stacked>Choose a backup file from your computer</ion-label>
<ion-input type="file" [(ngModel)]="formData.file" formControlName="file"></ion-input>
<ion-input type="file" accept="json" [(ngModel)]="formData.file" (change)="fileChangeEvent($event)" formControlName="file"></ion-input>
</ion-item>
<ion-item>
@ -34,7 +34,7 @@
<ion-item-divider color="light"></ion-item-divider>
<ion-item (click)="showAdvOpts = !showAdvOpts">
<ion-item (click)="toggleShowAdvOpts()">
<ion-label *ngIf="!showAdvOpts">Show advanced options</ion-label>
<ion-label *ngIf="showAdvOpts">Hide advanced options</ion-label>
</ion-item>

View File

@ -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<any> = 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);
}
}
}
}

View File

@ -17,7 +17,6 @@ export class OnGoingProcessProvider {
content: processName
});
this.loading.present();
}
else {
this.loading.dismiss();

View File

@ -709,9 +709,9 @@ export class WalletProvider {
});
}
public updateRemotePreferences(clients: any, prefs: any): Promise<any> {
public updateRemotePreferences(clients: any, prefs?: any): Promise<any> {
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(() => {