mirror of https://github.com/BTCPrivate/copay.git
Feat: Import wallet
This commit is contained in:
parent
07f06b22a7
commit
595971e907
|
@ -23,7 +23,7 @@
|
||||||
<div *ngIf="selectedTab == 'file'">
|
<div *ngIf="selectedTab == 'file'">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label stacked>Choose a backup file from your computer</ion-label>
|
<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>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<ion-item-divider color="light"></ion-item-divider>
|
<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">Show advanced options</ion-label>
|
||||||
<ion-label *ngIf="showAdvOpts">Hide advanced options</ion-label>
|
<ion-label *ngIf="showAdvOpts">Hide advanced options</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -1,40 +1,69 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { NavController, NavParams } from 'ionic-angular';
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
|
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 { 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 { 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({
|
@Component({
|
||||||
selector: 'page-import-wallet',
|
selector: 'page-import-wallet',
|
||||||
templateUrl: 'import-wallet.html'
|
templateUrl: 'import-wallet.html'
|
||||||
})
|
})
|
||||||
export class ImportWalletPage implements OnInit {
|
export class ImportWalletPage implements OnInit {
|
||||||
public fromOnboarding: boolean;
|
|
||||||
public formData: any;
|
|
||||||
public showAdvOpts: boolean;
|
|
||||||
public selectedTab: string;
|
|
||||||
public seedOptions: any;
|
|
||||||
|
|
||||||
private derivationPathByDefault: string;
|
private derivationPathByDefault: string;
|
||||||
private derivationPathForTestnet: string;
|
private derivationPathForTestnet: string;
|
||||||
private importForm: FormGroup;
|
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(
|
constructor(
|
||||||
public navCtrl: NavController,
|
private navCtrl: NavController,
|
||||||
private navParams: NavParams,
|
private navParams: NavParams,
|
||||||
private form: FormBuilder,
|
private form: FormBuilder,
|
||||||
private bwc: BwcProvider,
|
private bwcProvider: BwcProvider,
|
||||||
private pathHelper: DerivationPathHelperProvider,
|
private derivationPathHelperProvider: DerivationPathHelperProvider,
|
||||||
private walletProvider: WalletProvider,
|
private walletProvider: WalletProvider,
|
||||||
private configProvider: ConfigProvider,
|
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.fromOnboarding = this.navParams.data.fromOnboarding;
|
||||||
this.selectedTab = 'words';
|
this.selectedTab = 'words';
|
||||||
this.derivationPathByDefault = this.pathHelper.default;
|
this.derivationPathByDefault = this.derivationPathHelperProvider.default;
|
||||||
this.derivationPathForTestnet = this.pathHelper.defaultTestnet;
|
this.derivationPathForTestnet = this.derivationPathHelperProvider.defaultTestnet;
|
||||||
this.showAdvOpts = false;
|
this.showAdvOpts = false;
|
||||||
this.formData = {
|
this.formData = {
|
||||||
words: null,
|
words: null,
|
||||||
|
@ -43,8 +72,14 @@ export class ImportWalletPage implements OnInit {
|
||||||
filePassword: null,
|
filePassword: null,
|
||||||
derivationPath: this.derivationPathByDefault,
|
derivationPath: this.derivationPathByDefault,
|
||||||
testnet: false,
|
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() {
|
ngOnInit() {
|
||||||
|
@ -85,19 +120,259 @@ export class ImportWalletPage implements OnInit {
|
||||||
this.importForm.get('filePassword').updateValueAndValidity();
|
this.importForm.get('filePassword').updateValueAndValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
setDerivationPath() {
|
|
||||||
this.formData.derivationPath = this.formData.testnet ? this.derivationPathForTestnet : this.derivationPathByDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeMnemonic(words: string) {
|
normalizeMnemonic(words: string) {
|
||||||
if (!words || !words.indexOf) return words;
|
if (!words || !words.indexOf) return words;
|
||||||
var isJA = words.indexOf('\u3000') > -1;
|
var isJA = words.indexOf('\u3000') > -1;
|
||||||
var wordList = words.split(/[\u3000\s]+/);
|
var wordList = words.split(/[\u3000\s]+/);
|
||||||
|
|
||||||
return wordList.join(isJA ? '\u3000' : ' ');
|
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() {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ export class OnGoingProcessProvider {
|
||||||
content: processName
|
content: processName
|
||||||
});
|
});
|
||||||
this.loading.present();
|
this.loading.present();
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.loading.dismiss();
|
this.loading.dismiss();
|
||||||
|
|
|
@ -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) => {
|
return new Promise((resolve, reject) => {
|
||||||
prefs = prefs || {};
|
prefs = prefs ? prefs : {};
|
||||||
|
|
||||||
if (!lodash.isArray(clients))
|
if (!lodash.isArray(clients))
|
||||||
clients = [clients];
|
clients = [clients];
|
||||||
|
@ -723,13 +723,16 @@ export class WalletProvider {
|
||||||
this.logger.debug('Saving remote preferences', wallet.credentials.walletName, prefs);
|
this.logger.debug('Saving remote preferences', wallet.credentials.walletName, prefs);
|
||||||
|
|
||||||
wallet.savePreferences(prefs, (err: any) => {
|
wallet.savePreferences(prefs, (err: any) => {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
this.popupProvider.ionicAlert(this.bwcErrorProvider.msg(err, 'Could not save preferences on the server')); //TODO Gettextcatalog
|
this.popupProvider.ionicAlert(this.bwcErrorProvider.msg(err, 'Could not save preferences on the server')); //TODO Gettextcatalog
|
||||||
return reject(err);
|
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 (may come from arguments)
|
||||||
prefs.email = config.emailNotifications.email;
|
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
|
// prefs.unit = walletSettings.unitCode; // TODO: remove, not used
|
||||||
|
|
||||||
updateRemotePreferencesFor(lodash.clone(clients), prefs).then(() => {
|
updateRemotePreferencesFor(lodash.clone(clients), prefs).then(() => {
|
||||||
|
|
Loading…
Reference in New Issue