You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wow-lite-wallet/src/main/wowrpc.js

546 lines
17 KiB

const fs = require('fs');
const os = require('os');
const path = require('path');
let childProcess = require('child_process');
let textEncoding = require('text-encoding');
let TextDecoder = textEncoding.TextDecoder;
import { platform, cliPath } from './binaries';
export class WowRpc {
// ps -ef | grep defunct | grep -v grep | cut -b8-20 | xargs kill -9
constructor(wowdir, cli_path) {
this._wowdir = wowdir;
this._cli_process = null;
this._cli_log_path = path.join(os.tmpdir(), 'wowlite-wallet.log');
if(!cli_path) {
this._cli_path = cliPath;
} else {
this._cli_path = cli_path;
}
console.log('WowRPC bin path: ' + this._cli_path);
this._wallet_path = '';
this._cli_wallet_password = '';
this._cli_wallet_address = null;
this._cli_daemon_address = '';
this._cli_wallet_selected_account = null;
this._cli_balance_unlocked = null;
this._cli_balance = null;
this._cli_txs = [];
this._version = "";
this._buffer = "";
this._sending = false;
// this will hold the newly created wallet details: address, view_key, seed, password
this._create_wallet = {};
this._create_wallet_tmp_path = path.join(os.tmpdir(), 'wowtmp');
this._cli_args_default = [
'--use-english-language-names',
'--log-file',
this._cli_log_path
]
this._cli_args_create_wallet = [
'--generate-new-wallet',
this._create_wallet_tmp_path
]
// '--restore-deterministic-wallet' recover from seed
// lil' state machine
// 0: not spawned
// 1: spawned - not authed
// 2: spawned - authed, not refreshed
// 3: spawned - authed/refreshed (shows prompt)
this._states = {
'cw': 'create wallet',
0: 'not spawned',
1: 'spawned - not authed',
2: 'spawned - authed',
3: 'spawned - starting refresh',
4: 'spawned - ended refresh',
5: 'spawned - showing prompt'
};
this._state = 0;
this._checkMemPoolTimeout = 10000
this.shown_transfers = false;
}
commitWallet(wallet_name) {
// moves `/tmp/tmpwallet to destination`
console.log(`moving into: ${this._wowdir}/${wallet_name}`);
let cmd = process.platform === 'win32' ? 'move' : 'mv';
let dest_path = path.join(this._wowdir, wallet_name);
console.log(dest_path);
console.log(`${cmd} "${this._create_wallet_tmp_path}" "${dest_path}"`);
let cmd_wcache = `${cmd} "${this._create_wallet_tmp_path}" "${dest_path}"`;
let cmd_wkeys = `${cmd} "${this._create_wallet_tmp_path}.keys" "${dest_path}.keys"`;
console.log(`[CMD] ${cmd_wcache}`);
console.log(`[CMD] ${cmd_wkeys}`);
let wow = childProcess.execSync(cmd_wcache);
let wow2 = childProcess.execSync(cmd_wkeys);
return 1;
}
_checkMemPool(){
if(this._state === 5){
this._sendCmd('show_transfers');
this._sendCmd('balance');
this._sendCmd('save');
setTimeout(this._checkMemPool.bind(this), this._checkMemPoolTimeout);
}
}
_sendPassword(){
this.log('sending pass');
this._cli_process.stdin.write(`${this._cli_wallet_password}\n`);
}
_sendCmd(cmd){
if(this._sending && cmd === 'show_transfers') {
// skip `show_transfers` command when making a transaction
return;
}
if(cmd === 'show_transfers') {
this.shown_transfers = true;
if(this._state !== 5){
return;
}
}
this.log(`Sending cmd "${cmd}"`);
try {
this._cli_process.stdin.write(`${cmd}\n`);
} catch(err) {
console.log(err);
return;
}
if(cmd.startsWith('set')){
this._sendPassword();
}
}
_parse_stdout(data){
if(platform === 'win'){ data = data.replace(/\r/g, ""); }
// detect incoming transaction
let re_incoming_tx = /Height \d+, txid \<([a-zA-Z0-9]+)\>, ([0-9]+.[0-9]+), idx/g;
if(data.match(re_incoming_tx)){
let matches;
while ((matches = re_incoming_tx.exec(data)) !== null) {
this._paymentReceived(matches[1], matches[2]);
}
}
// detect background mining prompt
let re_background_mining = /Do you want to do it now?/g;
if(data.match(re_background_mining)){
this._sendCmd("No\n");
}
if(this._state === 5 && data.match(/Error: invalid password/)){
this._sendCmd(this._cli_wallet_password);
}
// detect show_transfers output
let re_transfers_in = /([0-9]+|pool|pending)[ ]+(in|out)[ ]+(.+?(?= ))[ ]+([0-9]+.[0-9]+)[ ]+([a-zA-Z0-9]+)/g;
if(this._state === 5 && data.match(re_transfers_in)){
let matches;
let pushed = 0;
while ((matches = re_transfers_in.exec(data)) !== null) {
let blockheight = matches[1];
if(WowRpc.isNumeric(blockheight)){
blockheight = parseInt(blockheight);
}
let tx = {
blockheight: blockheight,
in: matches[2] === 'in' ? 'in' : 'out',
date: matches[3],
amount: matches[4],
id: matches[5]
};
if(this.getTxId(tx.id) === false){
this._cli_txs.push(tx);
pushed += 1;
}
}
if(pushed > 0){
this.log(`discovered ${pushed} tx's`);
this.wowCall(this.onWalletTxsChanged, this._cli_txs);
}
return;
}
// detect cli startup
if(data.match(/This is the command line wownero wallet./)){
this._setState(1);
}
// detect successful transfer
if(data.match(/Transaction successfully submitted/)){
this._sending = false;
this.onTransactionCompleted();
}
if(data.match(/Opened wallet: W/)){
this._cli_wallet_address = data.substring(15, data.indexOf('\n'));
this.log(`Wallet: ${this._cli_wallet_address}`);
this._setState(2);
}
// if(data === `Logging to ${this._cli_log_path}\n`){
// this._sendPassword();
// }
if(data.match(/Starting refresh.../)){
this._setState(3);
}
if(data.match(/Error: /)){
let re_error = /Error: (.*)/g;
let re_error_offline_daemon = /wallet failed to connect to daemon: (.*):(\d+)./g;
console.log(`||${data}||`);
let re_error_offline_daemon_matched = re_error_offline_daemon.exec(data);
if(re_error_offline_daemon_matched){
let msg = `Remote node '${re_error_offline_daemon_matched[1]}:${re_error_offline_daemon_matched[2]}' offline, please use another.`;
this.onError(msg);
return;
}
let re_error_matched = re_error.exec(data);
if(re_error_matched){
this.onError(re_error_matched[1]);
} else {
this.onError("Unknown error occurred");
}
}
if(data.match(/Refresh done, blocks received:/)){
this._setState(4);
this.onWalletOpened({
'wallet_path': this._wallet_path,
'address': this._cli_wallet_address
});
}
let currently_selected = data.match(/Currently selected account: \[([0-9])\]/);
if(currently_selected){
this._cli_wallet_selected_account = parseInt(currently_selected[1]);
}
let balance = data.match('Balance: ([0-9]+.[0-9]+)');
if(balance){
this._setBalance(balance[1]);
}
let balance_unlocked = data.match('unlocked balance: ([0-9]+.[0-9]+)');
if(balance_unlocked) {
this._setBalanceUnlocked(balance_unlocked[1]);
}
// detect output from command `show_transfers`
if(data.match(/\[wallet [a-zA-Z0-9]+\]:/)){
this._setState(5);
}
let re_height_refresh = /Height (\d+) \/ (\d+)/g;
let re_height_refresh_data = re_height_refresh.exec(data);
if(re_height_refresh_data){
let from = re_height_refresh_data[1];
let to = re_height_refresh_data[2];
this.wowCall(this.onHeightRefresh, {'from': from, 'to': to});
}
}
_parse_stdout_create_wallet(data){
if(platform === 'win'){ data = data.replace(/\r/g, ""); } // lol windows
this._buffer += data;
if(data === `Logging to ${this._cli_log_path}\n`){
this._cli_process.stdin.write(`wow\n`);
this._cli_process.stdin.write(`wow\n`); // ?? :D
}
// if(data.match(/List of available languages for your wallet's seed:/)){
// this._cli_process.stdin.write(`1\n`);
// }
//
// if(data.match(/Error: invalid language choice entered./)){
// this._cli_process.stdin.write(`1\n`);
// }
if(data.match(/Background refresh thread started/)){
let re_addy = /Generated new wallet: (W[o|W][a-zA-Z0-9]{95})/;
let re_addy_match = this._buffer.match(re_addy);
if(re_addy_match){
this._create_wallet['address'] = re_addy_match[1];
}
let re_view_key = /View key: ([0-9a-fA-F]+)\n/;
let re_view_key_match = this._buffer.match(re_view_key);
if(re_view_key_match){
this._create_wallet['view_key'] = re_view_key_match[1];
}
let re_seed_lol = /(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+ )(\w+)/g;
let re_seed_match = this._buffer.match(re_seed_lol);
if(re_seed_match){
let seed = re_seed_match[0].trim();
if(seed.split(' ').length !== 25){
this.onCreateWalletFinished("could not get seed; invalid num words");
}
this._create_wallet['seed'] = seed;
this.kill();
this.onCreateWalletFinished(this._create_wallet);
} else {
this.kill();
this.onCreateWalletFinished("could not get seed");
}
}
}
createWallet(wowdir, name, password){
try { fs.unlinkSync(`${this._create_wallet_tmp_path}`); }
catch(err) { }
try { fs.unlinkSync(`${this._create_wallet_tmp_path}.keys`); }
catch(err) { }
let wowdir_name = path.join(wowdir, name);
if(fs.existsSync(`${wowdir_name}`) || fs.existsSync(`${wowdir_name}.keys`)){
this.onCreateWalletFinished(`Wallet already exists: ${wowdir_name}`);
return;
}
let args = this._cli_args_default.concat(this._cli_args_create_wallet);
args.push('--password');
if(!password) {
args.push('');
} else {
args.push(password);
}
this._setState('cw');
console.log('FINAL ARGS:', args);
this._cli_process = childProcess.spawn(this._cli_path, args);
this._create_wallet['name'] = name;
this._create_wallet['password'] = password;
this._cli_process.stdout.on('data', function(data) {
data = new TextDecoder("utf-8").decode(data);
console.log("[cli] " + data);
this._parse_stdout_create_wallet(data);
}.bind(this));
this._cli_process.stdout.on('end', function(data) {
this._buffer = "";
console.log('PROCESS ENDED!')
});
this._cli_process.on('exit', function(code) {
this._buffer = "";
if (code !== 0) {
console.log('Failed: ' + code)
}
});
}
sendMonies(address, amount){
let cmd = `transfer ${address} ${amount}`;
console.log(cmd);
this._sending = true;
this._sendCmd(cmd)
}
connect(wallet_path, wallet_password){
this._wallet_path = wallet_path;
this._cli_wallet_password = wallet_password;
if(typeof this._cli_daemon_address == 'undefined'){
// this is some truly stupid hacky shit
this._cli_daemon_address = 'wow.candy.surf:34568';
}
let cli_args = ['--daemon-address', this._cli_daemon_address];
cli_args = cli_args.concat(this._cli_args_default);
cli_args.push('--wallet-file');
cli_args.push(wallet_path);
cli_args.push('--password');
cli_args.push(this._cli_wallet_password);
console.log('FINAL ARGS:', cli_args);
this._cli_process = childProcess.spawn(this._cli_path, cli_args);
this._cli_process.stdout.on('data', function(data) {
data = new TextDecoder("utf-8").decode(data);
console.log("[cli] " + data);
this._parse_stdout(data);
}.bind(this));
this._cli_process.stdout.on('end', function(data) {
this._buffer = "";
console.log('PROCESS ENDED!')
});
this._cli_process.on("error", function(e) { console.log(e); });
this._cli_process.on('exit', function(code) {
this._buffer = "";
if (code !== 0) {
console.log('Failed: ' + code)
}
});
};
exit(){
this._state = 0;
this._sendCmd('exit'); // seeya
this.log(`Wallet closed: ${this._cli_wallet_address}`);
}
kill(){
console.log('Wallet kill');
this._buffer = "";
this._state = 0;
if(this._cli_process) {
this._cli_process.stdout.pause();
this._cli_process.stdin.pause();
this._cli_process.kill('SIGKILL'); // F
this.log(`Wallet closed: ${this._cli_wallet_address}`);
}
}
_paymentReceived(tx_id, amount){
this.log(`Incoming tx: ${tx_id}, ${amount}`);
}
_setBalance(balance){
this.log(`New balance: ${balance}`);
this._cli_balance = parseFloat(balance);
this.wowCall(this.onWalletBalanceChanged, this._cli_balance);
}
_setBalanceUnlocked(balance){
this.log(`New balance unlocked: ${balance}`);
this._cli_balance_unlocked = parseFloat(balance);
this.wowCall(this.onWalletBalanceUnlockedChanged, this._cli_balance_unlocked);
}
_setState(state){
if(state === this._state) return;
this.log(`New state: ${this._states[state]}`);
this._state = state;
if(state === 5){
// connect check mempool loop
// this._sendCmd('set ask-password 0');
this._checkMemPool();
}
this.onStateChanged(state);
}
getTxId(txid){
let found = false;
this._cli_txs.forEach(function(obj){
if(obj.id === txid){
found = true;
}
});
return found;
}
log(msg){
console.log('\x1b[36m%s\x1b[0m', msg);
}
wowCall(fn, data){
// ¯\_(ツ)_/¯
if(fn != null){
fn(data);
}
}
getEmbeddedVersion(){
console.log('Retrieving embedded version.');
let args = ['--version'];
this._cli_process = childProcess.spawn(this._cli_path, args);
this._cli_process.stdout.on('data', (data) => {
data = new TextDecoder("utf-8").decode(data);
console.log("[cli] " + data);
let version = data.trim().split(" ").slice(1).join(" ").trim();
this._cli_process.kill();
this.onEmbeddedVersion(version);
});
}
onEmbeddedVersion(version){
// overloaded
}
onWalletOpened(data){
// overloaded
}
onCreateWalletFinished(){
// overloaded
}
onWalletAddressChanged(){
// overloaded
}
onWalletSelectedAccountChanged() {
// overloaded
}
onWalletBalanceUnlockedChanged() {
// overloaded
}
onWalletBalanceChanged() {
// overloaded
}
onWalletTxsChanged(data) {
// overloaded
}
onTransactionCompleted(){
// overloaded
}
onHeightRefresh(data){
// overloaded
}
onError(msg){
// overloaded
}
onStateChanged(state){
// overloaded
}
static isNumeric(value) {
return /^\d+$/.test(value);
}
}