copay/src/pages/backup/backup-game/backup-game.ts

273 lines
7.8 KiB
TypeScript

import { Component, ViewChild } from '@angular/core';
import { AlertController, Navbar, NavController, NavParams, Slides } from 'ionic-angular';
import * as _ from 'lodash';
import { Logger } from '../../../providers/logger/logger';
// pages
import { DisclaimerPage } from '../../onboarding/disclaimer/disclaimer';
// providers
import { BwcProvider } from '../../../providers/bwc/bwc';
import { OnGoingProcessProvider } from '../../../providers/on-going-process/on-going-process';
import { ProfileProvider } from '../../../providers/profile/profile';
import { WalletProvider } from '../../../providers/wallet/wallet';
@Component({
selector: 'page-backup-game',
templateUrl: 'backup-game.html',
})
export class BackupGamePage {
@ViewChild(Slides) slides: Slides;
@ViewChild(Navbar) navBar: Navbar;
private fromOnboarding: boolean;
public currentIndex: number;
public deleted: boolean;
public mnemonicWords: string[];
public shuffledMnemonicWords: any[];
public password: string;
public customWords: any[];
public selectComplete: boolean;
public error: boolean;
public credentialsEncrypted: boolean;
private mnemonicHasPassphrase: any;
private walletId: string;
private wallet: any;
private keys: any;
private useIdeograms: any;
constructor(
private navCtrl: NavController,
private navParams: NavParams,
private alertCtrl: AlertController,
private logger: Logger,
private profileProvider: ProfileProvider,
private walletProvider: WalletProvider,
private bwcProvider: BwcProvider,
private onGoingProcessProvider: OnGoingProcessProvider
) {
this.walletId = this.navParams.get('walletId');
this.fromOnboarding = this.navParams.get('fromOnboarding');
this.wallet = this.profileProvider.getWallet(this.walletId);
this.credentialsEncrypted = this.wallet.isPrivKeyEncrypted();
this.deleted = this.isDeletedSeed();
if (this.deleted) {
this.logger.debug('no mnemonics');
return;
}
this.walletProvider.getKeys(this.wallet).then((keys) => {
if (_.isEmpty(keys)) {
this.logger.error('Empty keys');
}
this.credentialsEncrypted = false;
this.keys = keys;
this.setFlow();
}).catch((err) => {
this.logger.error('Could not get keys: ', err);
this.navCtrl.pop();
});
}
ngOnInit() {
this.currentIndex = 0;
this.navBar.backButtonClick = (e: UIEvent) => {
if (this.slides) this.slidePrev();
else this.navCtrl.pop();
}
}
ionViewDidLoad() {
if (this.slides) this.slides.lockSwipes(true);
}
private shuffledWords(words: string[]) {
var sort = _.sortBy(words);
return _.map(sort, (w) => {
return {
word: w,
selected: false
};
});
};
public addButton(index: number, item: any): void {
var newWord = {
word: item.word,
prevIndex: index
};
this.customWords.push(newWord);
this.shuffledMnemonicWords[index].selected = true;
this.shouldContinue();
};
public removeButton(index: number, item: any): void {
// if ($scope.loading) return;
this.customWords.splice(index, 1);
this.shuffledMnemonicWords[item.prevIndex].selected = false;
this.shouldContinue();
};
private shouldContinue(): void {
this.selectComplete =
this.customWords.length === this.shuffledMnemonicWords.length
? true
: false;
};
private showBackupResult(): void {
if (this.error) {
let alert = this.alertCtrl.create({
title: "Uh oh...",
subTitle: "It's important that you write your backup phrase down correctly. If something happens to your wallet, you'll need this backup to recover your money. Please review your backup and try again.",
buttons: [{
text: 'Ok',
role: 'cancel',
handler: () => {
this.setFlow();
}
}]
});
alert.present();
} else {
let opts = {
title: 'Your bitcoin wallet is backed up!',
message: 'Be sure to store your recovery phrase in a secure place. If this app is deleted, your money cannot be recovered without it.',
buttons: [{
text: 'Got it',
handler: () => {
if (this.fromOnboarding) {
this.navCtrl.push(DisclaimerPage);
} else {
this.navCtrl.popToRoot({ animate: false });
}
}
}],
}
this.alertCtrl.create(opts).present();
}
};
private isDeletedSeed(): boolean {
if (!this.wallet.credentials.mnemonic && !this.wallet.credentials.mnemonicEncrypted)
return true;
return false;
}
private slidePrev(): void {
this.slides.lockSwipes(false);
if (this.currentIndex == 0) this.navCtrl.pop();
else {
this.slides.slidePrev();
this.currentIndex = this.slides.getActiveIndex();
}
this.slides.lockSwipes(true);
}
public slideNext(): void {
if (this.currentIndex == 1 && !this.mnemonicHasPassphrase)
this.finalStep();
else {
this.slides.lockSwipes(false);
this.slides.slideNext();
}
this.currentIndex = this.slides.getActiveIndex();
this.slides.lockSwipes(true);
}
private setFlow(): void {
if (!this.keys) return;
let words = this.keys.mnemonic;
this.mnemonicWords = words.split(/[\u3000\s]+/);
this.shuffledMnemonicWords = this.shuffledWords(this.mnemonicWords);
this.mnemonicHasPassphrase = this.wallet.mnemonicHasPassphrase();
this.useIdeograms = words.indexOf("\u3000") >= 0;
this.password = '';
this.customWords = [];
this.selectComplete = false;
this.error = false;
words = _.repeat('x', 300);
if (this.currentIndex == 2) this.slidePrev();
};
public copyRecoveryPhrase(): string {
if (this.wallet.network == 'livenet') return null;
else if (!this.wallet.credentials.mnemonic) return null;
else return this.wallet.credentials.mnemonic;
}
private confirm(): Promise<any> {
return new Promise((resolve, reject) => {
this.error = false;
let customWordList = _.map(this.customWords, 'word');
if (!_.isEqual(this.mnemonicWords, customWordList)) {
return reject('Mnemonic string mismatch');
}
if (this.mnemonicHasPassphrase) {
let walletClient = this.bwcProvider.getClient();
let separator = this.useIdeograms ? '\u3000' : ' ';
let customSentence = customWordList.join(separator);
let password = this.password || '';
try {
walletClient.seedFromMnemonic(customSentence, {
network: this.wallet.credentials.network,
password,
account: this.wallet.credentials.account
});
} catch (err) {
walletClient.credentials.xPrivKey = _.repeat('x', 64);
return reject(err);
}
if (walletClient.credentials.xPrivKey.substr(walletClient.credentials.xPrivKey) != this.keys.xPrivKey) {
delete walletClient.credentials;
return reject('Private key mismatch');
}
}
this.profileProvider.setBackupFlag(this.wallet.credentials.walletId);
return resolve();
});
};
private finalStep(): void {
this.onGoingProcessProvider.set('validatingWords', true);
this.confirm().then(() => {
this.onGoingProcessProvider.set('validatingWords', false);
this.showBackupResult();
}).catch((err) => {
this.onGoingProcessProvider.set('validatingWords', false);
this.logger.error('Failed to verify backup: ', err);
this.error = true;
let showError = this.alertCtrl.create({
title: "Failed to verify backup",
subTitle: err,
buttons: [{
text: 'Try again',
role: 'cancel',
handler: () => {
this.setFlow();
}
}]
});
showError.present();
});
};
}