// twister_io.js
// 2013 Miguel Freitas
//
// low-level twister i/o.
// implements requests of dht resources. multiple pending requests to the same resource are joined.
// cache results (profile, avatar, etc) in memory.
// avatars are cached in localstored (expiration = 24 hours)

// main json rpc method. receives callbacks for success and error
function twisterRpc(method, params, resultFunc, resultArg, errorFunc, errorArg) {
    // removing hardcoded username from javascript: please use url http://user:pwd@localhost:28332 instead
    //var foo = new $.JsonRpcClient({ ajaxUrl: '/', username: 'user', password: 'pwd'});
    var foo = new $.JsonRpcClient({ajaxUrl: window.location.pathname.replace(/[^\/]*$/, '')});
    foo.call(method, params,
        function(ret) {resultFunc(resultArg, ret);},
        function(ret) {errorFunc(errorArg, ret);}  // FIXME why only if "(ret != null)"?
    );
}

// join multiple dhtgets to the same resources in this map
var _dhtgetPendingMap = {};

var _pubkeyMap = {};

// number of dhtgets in progress (requests to the daemon)
var _dhtgetsInProgress = 0;

// keep _maxDhtgets smaller than the number of daemon/browser sockets
// most browsers limit to 6 per domain (see http://www.browserscope.org/?category=network)
var _maxDhtgets = 5;

// requests not yet sent to the daemon due to _maxDhtgets limit
var _queuedDhtgets = [];

// private function to define a key in _dhtgetPendingMap
function _dhtgetLocator(peerAlias, resource, multi) {
    return peerAlias + ';' + resource + ';' + multi;
}

function _dhtgetAddPending(locator, cbFunc, cbReq) {
    if (!_dhtgetPendingMap[locator]) {
        _dhtgetPendingMap[locator] = [];
    }
    _dhtgetPendingMap[locator].push({cbFunc: cbFunc, cbReq: cbReq});
}

function _dhtgetProcessPending(locator, multi, ret) {
    if (_dhtgetPendingMap[locator]) {
        for (var i = 0; i < _dhtgetPendingMap[locator].length; i++) {
            var cbFunc = _dhtgetPendingMap[locator][i].cbFunc;
            var cbReq  = _dhtgetPendingMap[locator][i].cbReq;

            if (multi === 'url') {
                cbFunc(cbReq, ret);  // here is decodeshorturl case
            } else if (multi === 's') {
                if (typeof ret[0] !== 'undefined') {
                    cbFunc(cbReq, ret[0].p.v, ret);
                } else {
                    cbFunc(cbReq);
                }
            } else {
                var multiret = [];
                for (var j = 0; j < ret.length; j++) {
                    multiret.push(ret[j].p.v);
                }
                cbFunc(cbReq, multiret, ret);
            }
        }
        delete _dhtgetPendingMap[locator];
    } else {
        console.warn('_dhtgetProcessPending(): unknown locator ' + locator);
    }
}

function _dhtgetAbortPending(locator) {
    if (_dhtgetPendingMap[locator]) {
        for (var i = 0; i < _dhtgetPendingMap[locator].length; i++) {
            var cbFunc = _dhtgetPendingMap[locator][i].cbFunc;
            var cbReq  = _dhtgetPendingMap[locator][i].cbReq;
            cbFunc(cbReq);
        }
        delete _dhtgetPendingMap[locator];
    } else {
        console.warn('_dhtgetAbortPending(): unknown locator ' + locator);
    }
}

// get data from dht resource
// the value ["v"] is extracted from response and returned to callback
// null is passed to callback in case of an error
function dhtget(peerAlias, resource, multi, cbFunc, cbReq, timeoutArgs) {
    //console.log('dhtget ' + peerAlias + ' ' + resource + ' ' + multi);
    var locator = _dhtgetLocator(peerAlias, resource, multi);
    if (_dhtgetPendingMap[locator]) {
        _dhtgetAddPending(locator, cbFunc, cbReq);
    } else {
        _dhtgetAddPending(locator, cbFunc, cbReq);
        // limit the number of simultaneous dhtgets.
        // this should leave some sockets for other non-blocking daemon requests.
        if (_dhtgetsInProgress < _maxDhtgets) {
            _dhtgetInternal(peerAlias, resource, multi, timeoutArgs);
        } else {
            // just queue the locator. it will be unqueue when some dhtget completes.
            _queuedDhtgets.push(locator);
        }
    }
}

// decode shortened url
// the expanded url is returned to callback
// null is passed to callback in case of an error
function decodeShortURI(locator, cbFunc, cbReq, timeoutArgs) {
    if (!locator) return;
    if (parseInt(twisterVersion) < 93500) {
        console.warn('can\'t fetch URI "' + req + '" — '
            + polyglot.t('daemon_is_obsolete', {versionReq: '0.9.35'}));
        return;
    }

    if (_dhtgetPendingMap[locator]) {
        _dhtgetAddPending(locator, cbFunc, cbReq);
    } else {
        _dhtgetAddPending(locator, cbFunc, cbReq);
        // limit the number of simultaneous decodeshorturl's and dhtgets.
        // this should leave some sockets for other non-blocking daemon requests.
        if (_dhtgetsInProgress < _maxDhtgets) {
            _decodeshorturlInternal(locator, timeoutArgs);
        } else {
            // just queue the locator. it will be unqueue when some dhtget completes.
            _queuedDhtgets.push(locator);
        }
    }
}

function _dhtgetInternal(peerAlias, resource, multi, timeoutArgs) {
    var locator = _dhtgetLocator(peerAlias, resource, multi);
    _dhtgetsInProgress++;
    argsList = [peerAlias, resource, multi];
    if (typeof timeoutArgs !== 'undefined') {
        argsList = argsList.concat(timeoutArgs);
    }
    twisterRpc('dhtget', argsList,
        function(req, ret) {
            _dhtgetsInProgress--;
            _dhtgetProcessPending(req.locator, req.multi, ret);
            _dhtgetDequeue();
        }, {locator: locator, multi: multi},
        function(req, ret) {
            console.warn('RPC "dhtget" error: ' + (ret && ret.message ? ret.message : ret));
            _dhtgetsInProgress--;
            _dhtgetAbortPending(req);
            _dhtgetDequeue();
        }, locator
    );
}

function _decodeshorturlInternal(locator, timeoutArgs) {
    _dhtgetsInProgress++;
    argsList = [locator];
    if (typeof timeoutArgs !== 'undefined') {
        argsList = argsList.concat(timeoutArgs);
    }
    twisterRpc('decodeshorturl', argsList,
        function(req, ret) {
            _dhtgetsInProgress--;
            _dhtgetProcessPending(req, 'url', ret);
            _dhtgetDequeue();
        }, locator,
        function(req, ret) {
            console.warn('RPC "decodeshorturl" error: ' + (ret && ret.message ? ret.message : ret));
            _dhtgetsInProgress--;
            _dhtgetAbortPending(req);
            _dhtgetDequeue();
        }, locator
    );
}

function _dhtgetDequeue() {
    if (_queuedDhtgets.length) {
        var locator = _queuedDhtgets.pop();
        var locatorSplit = locator.split(';');
        if (locatorSplit.length === 3) {
            _dhtgetInternal(locatorSplit[0], locatorSplit[1], locatorSplit[2]);
        } else {
            _decodeshorturlInternal( locator )
        }
    }
}

// removes queued dhtgets (requests that have not been made to the daemon)
// this is used by user search dropdown to discard old users we are not interested anymore
function removeUserFromDhtgetQueue(peerAlias) {
    var resources = ['profile', 'avatar']
    for (var i = 0; i < resources.length; i++) {
        var locator = _dhtgetLocator(peerAlias, resources[i], 's');
        var locatorIndex = _queuedDhtgets.indexOf(locator);
        if (locatorIndex > -1) {
            _queuedDhtgets.splice(locatorIndex, 1);
            delete _dhtgetPendingMap[locator];
        }
    }
}

function removeUsersFromDhtgetQueue(users) {
    for (var i = 0; i < users.length; i++) {
        removeUserFromDhtgetQueue(users[i]);
    }
}

// store value at the dht resource
function dhtput(peerAlias, resource, multi, value, sig_user, seq, cbFunc, cbReq) {
    twisterRpc('dhtput', [peerAlias, resource, multi, value, sig_user, seq],
        function(req, ret) {
            if (req.cbFunc)
                req.cbFunc(req.cbReq, true);
        }, {cbFunc: cbFunc, cbReq: cbReq},
        function(req, ret) {
            console.warn('RPC "dhtput" error: ' + (ret && ret.message ? ret.message : ret));
            if (req.cbFunc)
                req.cbFunc(req.cbReq, false);
        }, {cbFunc: cbFunc, cbReq: cbReq}
    );
}

// get something from profile and store it in elem.text or do callback
function getProfileResource(peerAlias, resource, elem, cbFunc, cbReq) {
    var profile;
    if (twister.profiles[peerAlias]) {
        profile = twister.profiles[peerAlias];
    } else {
        profile = _getResourceFromStorage('profile:' + peerAlias);
        if (profile)
            twister.profiles[peerAlias] = profile;
    }
    if (profile) {
        if (elem)
            elem.text(profile[resource]);
        if (cbFunc)
            cbFunc(cbReq, profile[resource]);
    } else {
        loadProfile(peerAlias,
            function (peerAlias, req, res) {
                if (req.elem)
                    req.elem.text(res[req.resource]);
                if (typeof req.cbFunc === 'function')
                    req.cbFunc(req.cbReq, res[req.resource]);
            },
            {elem: elem, resource: resource, cbFunc: cbFunc, cbReq: cbReq}
        );
    }
}

// get fullname and store it in elem.text
function getFullname(peerAlias, elem) {
    elem.text(peerAlias);  // fallback: set the peerAlias first in case the profile has no fullname
    getProfileResource(peerAlias, 'fullname', undefined,
        function(req, name) {
            if (name && (name = name.trim()))
                req.elem.text(name);

            if (typeof twisterFollowingO !== 'undefined' &&  // FIXME delete this check when you fix client init sequence
                ($.Options.isFollowingMe.val === 'everywhere' || req.elem.hasClass('profile-name'))) {
                // here we try to detect if peer follows us and then display it
                if (twisterFollowingO.knownFollowers.indexOf(req.peerAlias) > -1) {
                    req.elem.addClass('isFollowing');
                    req.elem.attr('title', polyglot.t('follows you'));
                } else if (twisterFollowingO.notFollowers.indexOf(req.peerAlias) === -1) {
                    if (twisterFollowingO.followingsFollowings[req.peerAlias] &&
                        twisterFollowingO.followingsFollowings[req.peerAlias].following) {
                        if (twisterFollowingO.followingsFollowings[req.peerAlias].following.indexOf(defaultScreenName) > -1) {
                            if (twisterFollowingO.knownFollowers.indexOf(req.peerAlias) === -1) {
                                twisterFollowingO.knownFollowers.push(req.peerAlias);
                                twisterFollowingO.save();
                                addPeerToFollowersList(getElem('.followers-modal .followers-list'), req.peerAlias, true);
                                $('.module.mini-profile .open-followers')
                                    .attr('title', twisterFollowingO.knownFollowers.length.toString());
                            }
                            req.elem.addClass('isFollowing');
                            req.elem.attr('title', polyglot.t('follows you'));
                        }
                    } else {
                        loadFollowingFromDht(req.peerAlias, 1, [], 0,
                            function (req, following, seqNum) {
                                if (following.indexOf(defaultScreenName) > -1) {
                                    if (twisterFollowingO.knownFollowers.indexOf(req.peerAlias) === -1) {
                                        twisterFollowingO.knownFollowers.push(req.peerAlias);
                                        addPeerToFollowersList(getElem('.followers-modal .followers-list'), req.peerAlias, true);
                                        $('.module.mini-profile .open-followers')
                                            .attr('title', twisterFollowingO.knownFollowers.length.toString());
                                    }
                                    req.elem.addClass('isFollowing');
                                    req.elem.attr('title', polyglot.t('follows you'));
                                } else if (twisterFollowingO.notFollowers.indexOf(req.peerAlias) === -1)
                                    twisterFollowingO.notFollowers.push(req.peerAlias);

                                twisterFollowingO.save();
                            }, {elem: req.elem, peerAlias: req.peerAlias}
                        );
                    }
                }
            }
        }, {elem: elem, peerAlias: peerAlias}
    );
}

// get bio, format it as post message and store result to elem
function getBioToElem(peerAlias, elem) {
    getProfileResource(peerAlias, 'bio', undefined, fillElemWithTxt, elem);
}

// get tox address and store it in elem.text
function getTox(peerAlias, elem) {
    getProfileResource(peerAlias, 'tox', false,
        function(elem, val) {
            if (val) {
                elem.attr('href', 'tox:' + val);
                elem.next().attr('data', val).attr('title', 'Copy to clipboard');
                elem.parent().css('display', 'inline-block').parent().show();
            }
        }, elem
    );
}

// get bitmessage address and store it in elem.text
function getBitmessage(peerAlias, elem) {
    getProfileResource(peerAlias, 'bitmessage', false,
        function(elem, val) {
            if (val) {
                elem.attr('href', 'bitmsg:' + val + '?action=add&label=' + peerAlias);
                elem.next().attr('data', val).attr('title', 'Copy to clipboard');
                elem.parent().css('display', 'inline-block').parent().show();
            }
        }, elem
    );
}

// get location and store it in elem.text
function getLocation(peerAlias, elem) {
    getProfileResource(peerAlias, 'location', elem);
}

// get location and store it in elem.text
function getWebpage(peerAlias, elem) {
    getProfileResource(peerAlias, 'url', elem,
        function(elem, val) {
            if (typeof(val) !== 'undefined') {
                if (val.indexOf('://') < 0) {
                    val = 'http://' + val;
                }
                elem.attr('href', val);
            }
        }, elem
    );
}

function getGroupChatName(groupAlias, elem) {
    twisterRpc('getgroupinfo', [groupAlias],
        function(elem, ret) {
            elem.text(ret.description);
        }, elem,
        function(req, ret) {
            console.warn('RPC "getgroupinfo" error: ' + (ret && ret.message ? ret.message : ret));
            req.elem.text(req.groupAlias);
        }, {elem: elem, groupAlias: groupAlias}
    );
}

// we must cache avatar results to disk to lower bandwidth on
// other peers. dht server limits udp rate so requesting too much
// data will only cause new requests to fail.
function _getResourceFromStorage(locator) {
    var storage = $.localStorage;
    if (storage.isSet(locator)) {
        var storedResource = storage.get(locator);
        var curTime = new Date().getTime() / 1000;
        // avatar is downloaded once per day    FIXME why once per day? what about profiles?
        // FIXME need to check what type of data is requested and what time is allowed for it
        if (storedResource.time + 86400 > curTime) {  // 3600 * 24
            return storedResource.data;
        }
    }
    return null;
}

function _putResourceIntoStorage(locator, data) {
    $.localStorage.set(locator, {
        time: Math.trunc(new Date().getTime() / 1000),
        data: data
    });
}

function cleanupStorage() {
    var curTime = new Date().getTime() / 1000;
    var storage = $.localStorage, keys = storage.keys(), item = '';
    var delAvatars = delProfiles = 0;

    for (var i = 0; i < keys.length; i++) {
        item = keys[i];
        // FIXME need to decide what time for type of data is allowed
        if (item.substr(0, 7) === 'avatar:') {
            if (storage.get(item).time + 86400 < curTime) {  // 3600 * 24 hours
                storage.remove(item);
                delAvatars++;
                //console.log('local storage item \'' + item + '\' was too old, deleted');
            }
        } else if (item.substr(0, 8) === 'profile:') {
            if (storage.get(item).time + 86400 < curTime) {  // 3600 * 24 hours
                storage.remove(item);
                delProfiles++;
                //console.log('local storage item \'' + item + '\' was too old, deleted');
            }
        }
    }

    console.log('cleaning of storage is completed for ' + (new Date().getTime() / 1000 - curTime) + 's');
    if (delAvatars) console.log('  ' + delAvatars + ' cached avatars was too old, deleted');
    if (delProfiles) console.log('  ' + delProfiles + ' cached profiles was too old, deleted');
    console.log('  ' + 'there was ' + i + ' items in total, now ' + (i - delAvatars - delProfiles));
}

// get avatar and set it in img.attr("src")
// TODO rename to getAvatarImgToELem(), move nin theme related stuff to nin's theme_option.js
function getAvatar(peerAlias, img) {
    if (!img.length)
        return;

    if (peerAlias === 'nobody') {

        var avatar = 'img/tornado_avatar.png';

        switch ($.Options.theme.val) {
          case 'nin':
            avatar = 'theme_nin/img/tornado_avatar.png';
          break;
          case 'nin_night':
            avatar = 'theme_nin_night/img/tornado_avatar.png';
          break;
          case 'nin_original':
            avatar = 'theme_nin_original/img/tornado_avatar.png';
          break;
        }

        img.attr('src', avatar);

        return;
    }

    if (twister.avatars[peerAlias]) {
        img.attr('src', twister.avatars[peerAlias].src);
    } else {
        var data = _getResourceFromStorage('avatar:' + peerAlias);

        if (data) {
            if (typeof data !== 'object')
                data = {src: data, version: 0};

            switch (data.src.substr(0, 4)) {
                case 'jpg/':
                    data.src = 'data:image/jpeg;base64,/9j/' + window.btoa(data.src.slice(4));
                    break;
                case 'png/':
                    data.src = 'data:image/png;base64,' + window.btoa(data.src.slice(4));
                    break;
                case 'gif/':
                    data.src = 'data:image/gif;base64,' + window.btoa(data.src.slice(4));
                    break;
            }
            twister.avatars[peerAlias] = data;
            img.attr('src', data.src);
        } else {
            loadAvatar(peerAlias,
                function (peerAlias, req, res) {
                    req.attr('src', res);
                },
                img
            );
        }
    }
}

function loadProfile(peerAlias, cbFunc, cbReq) {
    dhtget(peerAlias, 'profile', 's',
        function(req, res, resRaw) {
            if (!resRaw || typeof res !== 'object')
                return;

            res.version = parseInt(resRaw[0].p.seq);

            if (!twister.profiles[req.peerAlias] || !twister.profiles[req.peerAlias].version
                || res.version > twister.profiles[req.peerAlias].version) {
                console.log('got ' + req.peerAlias + '\'s profile version ' + res.version + ' — going to cache and redraw globally');
                cacheProfile(req.peerAlias, res);
                redrawProfile(req.peerAlias, res);
            }

            if (typeof req.cbFunc === 'function')
                req.cbFunc(req.peerAlias, req.cbReq, res);
        },
        {peerAlias: peerAlias, cbFunc: cbFunc, cbReq: cbReq}
    );
}

function loadAvatar(peerAlias, cbFunc, cbReq) {
    dhtget(peerAlias, 'avatar', 's',
        function(req, res, resRaw) {
            if (!resRaw)
                return;

            if (!res)
                res = 'img/genericPerson.png';

            var version = parseInt(resRaw[0].p.seq);

            if (!twister.avatars[req.peerAlias] || !twister.avatars[req.peerAlias].version
                || version > twister.avatars[req.peerAlias].version) {
                console.log('got ' + req.peerAlias + '\'s avatar version ' + version + ' — going to cache and redraw globally');
                cacheAvatar(req.peerAlias, res, version);
                redrawAvatar(req.peerAlias, res);
            }

            if (typeof req.cbFunc === 'function')
                req.cbFunc(req.peerAlias, req.cbReq, res);
        },
        {peerAlias: peerAlias, cbFunc: cbFunc, cbReq: cbReq}
    );
}

function cacheProfile(peerAlias, req) {
    var dat = {};
    for (var i in req)
        if (req[i])
            dat[i] = req[i];

    twister.profiles[peerAlias] = dat;

    _putResourceIntoStorage('profile:' + peerAlias, dat);
}

function cacheAvatar(peerAlias, req, version) {
    twister.avatars[peerAlias] = {src: req, version: version};

    if (req === 'img/genericPerson.png')
        return;

    if (req.substr(0, 27) === 'data:image/jpeg;base64,/9j/') {
        req = 'jpg/' + window.atob(req.slice(27));
    } else {
        var s = req.substr(0, 22);
        if (s === 'data:image/png;base64,' || s === 'data:image/gif;base64,')
            req = req.substr(11, 3) + '/' + window.atob(req.slice(22));
    }
    _putResourceIntoStorage('avatar:' + peerAlias, {src: req, version: version});
}

function saveProfile(peerAlias, req, cbFunc, cbReq, cbErrFunc, cbErrReq) {
    if (twister.profiles[peerAlias] && twister.profiles[peerAlias].version)
        req.version = ++twister.profiles[peerAlias].version;
    else
        req.version = 1;

    cacheProfile(peerAlias, req);

    var dat = {};
    for (var i in req)
        if (i !== 'version' && req[i])
            dat[i] = req[i];

    dhtput(peerAlias, 'profile', 's', dat,
        peerAlias, req.version,
        function (req, res) {
            if (!res) {
                if (typeof req.cbErrFunc === 'function')
                    req.cbErrFunc(req.cbErrReq);

                return;
            }

            if (typeof req.cbFunc === 'function')
                req.cbFunc(req.cbReq);
        },
        {cbFunc: cbFunc, cbReq: cbReq, cbErrFunc: cbErrFunc, cbErrReq: cbErrReq}
    );
}

function saveAvatar(peerAlias, req, cbFunc, cbReq, cbErrFunc, cbErrReq) {
    if (twister.avatars[peerAlias] && twister.avatars[peerAlias].version)
        var version = ++twister.avatars[peerAlias].version;
    else
        var version = 1;

    cacheAvatar(peerAlias, req, version);

    dhtput(peerAlias, 'avatar', 's', req,
        peerAlias, version,
        function (req, res) {
            if (!res) {
                if (typeof req.cbErrFunc === 'function')
                    req.cbErrFunc(req.cbErrReq);

                return;
            }

            if (typeof req.cbFunc === 'function')
                req.cbFunc(req.cbReq);
        },
        {cbFunc: cbFunc, cbReq: cbReq, cbErrFunc: cbErrFunc, cbErrReq: cbErrReq}
    );
}

// get estimative for number of followers (use known peers of torrent tracker)
function getFollowers(peerAlias, elem) {
    dhtget(peerAlias, 'tracker', 'm',
        function(elem, ret) {
            if (ret && ret.length && ret[0].followers) {
                elem.text(ret[0].followers)
            }
        }, elem
    );
}

function getPostsCount(peerAlias, elem) {
    dhtget(peerAlias, 'status', 's',
        function(req, v) {
            var count = 0;
            if (v && v.userpost) {
                count = v.userpost.k + 1;
            }
            var oldCount = parseInt(req.elem.text());
            if (!oldCount || count > oldCount) {
                req.elem.text(count);
            }
            if (peerAlias === defaultScreenName && count) {
                incLastPostId(v.userpost.k);
            }
        }, {peerAlias: peerAlias, elem: elem}
    );
}

function getStatusTime(peerAlias, elem) {
    dhtget(peerAlias, 'status', 's',
        function (req, ret) {
            if (!ret || !ret.userpost)
                return;

            req.elem.text(timeGmtToText(ret.userpost.time))
                .closest('.latest-activity')
                    .attr('data-screen-name', req.peerAlias)
                    .attr('data-id', ret.userpost.k)
                    .attr('data-time', ret.userpost.time)
            ;
        }, {peerAlias: peerAlias, elem: elem}
    );
}

function getPostMaxAvailability(peerAlias, k, cbFunc, cbReq) {
    twisterRpc('getpiecemaxseen', [peerAlias, k],
        function(req, ret) {
            req.cbFunc(req.cbReq, ret);
        }, {cbFunc: cbFunc, cbReq: cbReq},
        function(req, ret) {
            console.warn('RPC "getpiecemaxseen" error: ' + (ret && ret.message ? ret.message : ret));
        }
    );
}

function checkPubkeyExists(peerAlias, cbFunc, cbReq) {
    // pubkey is checked in block chain db.
    // so only accepted registrations are reported (local wallet users are not)
    twisterRpc('dumppubkey', [peerAlias],
        function(req, ret) {
            req.cbFunc(req.cbReq, ret.length > 0);
        }, {cbFunc: cbFunc, cbReq: cbReq},
        function(req, ret) {
            console.warn('RPC "dumppubkey" error: ' + (ret && ret.message ? ret.message : ret));
            alert(polyglot.t('error_connecting_to_daemon'));
        }
    );
}

// pubkey is obtained from block chain db.
// so only accepted registrations are reported (local wallet users are not)
// cbFunc is called as cbFunc(cbReq, pubkey)
// if user doesn't exist then pubkey.length == 0
function dumpPubkey(peerAlias, cbFunc, cbReq) {
    if (_pubkeyMap[peerAlias]) {
        if (cbFunc)
            cbFunc(cbReq, _pubkeyMap[peerAlias]);
    } else {
        twisterRpc('dumppubkey', [peerAlias],
            function (req, ret) {
                if (ret.length > 0) {
                    _pubkeyMap[peerAlias] = ret;
                }
                if (req.cbFunc) {
                    req.cbFunc(req.cbReq, ret);
                }
            }, {cbFunc: cbFunc, cbReq: cbReq},
            function (req, ret) {
                console.warn('RPC "dumppubkey" error: ' + (ret && ret.message ? ret.message : ret));
                alert(polyglot.t('error_connecting_to_daemon'));
            }
        );
    }
}

// privkey is obtained from wallet db
// so privkey is returned even for unsent transactions
function dumpPrivkey(peerAlias, cbFunc, cbReq) {
    twisterRpc('dumpprivkey', [peerAlias],
        function(req, ret) {
            req.cbFunc(req.cbReq, ret);
        }, {cbFunc: cbFunc, cbReq: cbReq},
        function(req, ret) {
            req.cbFunc(req.cbReq, '');
            console.warn('user unknown — RPC "dumppubkey" error: ' + (ret && ret.message ? ret.message : ret));
        }, {cbFunc: cbFunc, cbReq: cbReq}
    );
}