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.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
wow-lite-wallet/src/main/wowrpc.js

516 lines
16 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 { 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(), 'wowlight-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_wallet_selected_account = null;
this._cli_balance_unlocked = null;
this._cli_balance = null;
this._cli_txs = [];
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
]
this._cli_args_connect = [
'--daemon-address',
'node.wowne.ro:34568'
];
// '--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');
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){
// 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]);
}
}
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){
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 = /NOTE: the following 25 words can be used to recover access to your wallet. Write them down and store them somewhere safe and secure. Please do not store them in your email or on file storage services outside of your immediate control.\n\n(.*)\n(.*)\n(.*)\n\*\*\*\*/;
let re_seed_match = this._buffer.match(re_seed);
if(re_seed_match){
let seed = `${re_seed_match[1]} ${re_seed_match[2]} ${re_seed_match[3]}`;
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;
let cli_args = [];
cli_args = cli_args.concat(this._cli_args_connect);
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);
}
}
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);
}
}