|
|
|
// 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}
|
|
|
|
);
|
|
|
|
}
|