axe.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. ;(function(){
  2. /* UNBUILD */
  3. var root;
  4. if(typeof window !== "undefined"){ root = window }
  5. if(typeof global !== "undefined"){ root = global }
  6. root = root || {};
  7. var console = root.console || {log: function(){}};
  8. function USE(arg, req){
  9. return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
  10. arg(mod = {exports: {}});
  11. USE[R(path)] = mod.exports;
  12. }
  13. function R(p){
  14. return p.split('/').slice(-1).toString().replace('.js','');
  15. }
  16. }
  17. if(typeof module !== "undefined"){ var common = module }
  18. /* UNBUILD */
  19. ;USE(function(module){
  20. if(typeof window !== "undefined"){ module.window = window }
  21. var tmp = module.window || module;
  22. var AXE = tmp.AXE || function(){};
  23. if(AXE.window = module.window){ AXE.window.AXE = AXE }
  24. try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
  25. module.exports = AXE;
  26. })(USE, './root');
  27. ;USE(function(module){
  28. var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
  29. (Gun.AXE = AXE).GUN = AXE.Gun = Gun;
  30. Gun.on('opt', function(at){
  31. start(at);
  32. this.to.next(at); // make sure to call the "next" middleware adapter.
  33. });
  34. function start(at){
  35. if(at.axe){ return }
  36. var opt = at.opt, peers = opt.peers;
  37. if(false === opt.axe){ return }
  38. if((typeof process !== "undefined") && 'false' === ''+(process.env||{}).AXE){ return }
  39. var axe = at.axe = {}, tmp;
  40. // 1. If any remembered peers or from last cache or extension
  41. // 2. Fallback to use hard coded peers from dApp
  42. // 3. Or any offered peers.
  43. //if(Gun.obj.empty(p)){
  44. // Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){
  45. // p[url] = {url: url, axe: {}};
  46. // });
  47. //}
  48. // Our current hypothesis is that it is most optimal
  49. // to take peers in a common network, and align
  50. // them in a line, where you only have left and right
  51. // peers, so messages propagate left and right in
  52. // a linear manner with reduced overlap, and
  53. // with one common superpeer (with ready failovers)
  54. // in case the p2p linear latency is high.
  55. // Or there could be plenty of other better options.
  56. /*
  57. AXE should have a couple of threshold items...
  58. let's pretend there is a variable max peers connected
  59. mob = 10000
  60. if we get more peers than that...
  61. we should start sending those peers a remote command
  62. that they should connect to this or that other peer
  63. and then once they (or before they do?) drop them from us.
  64. sake of the test... gonna set that peer number to 1.
  65. The mob threshold might be determined by other factors,
  66. like how much RAM or CPU stress we have.
  67. */
  68. opt.mob = opt.mob || Infinity;
  69. var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
  70. console.log("AXE enabled.");
  71. function verify(dht, msg) {
  72. var puts = Object.keys(msg.put);
  73. var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
  74. var subs = dht(soul);
  75. if (!subs) { return; }
  76. var tmp = [];
  77. Gun.obj.map(subs.split(','), function(pid) {
  78. if (pid in peers) {
  79. tmp.push(pid);
  80. mesh.say(msg, peers[pid]);
  81. }
  82. });
  83. /// Only connected peers in the tmp array.
  84. if (opt.super) {
  85. dht(soul, tmp.join(','));
  86. }
  87. }
  88. function route(get){ var tmp;
  89. if(!get){ return }
  90. if('string' != typeof (tmp = get['#'])){ return }
  91. return tmp;
  92. }
  93. var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
  94. at.opt.dht = Rad();
  95. at.on('in', function input(msg){
  96. var to = this.to, peer = (msg._||{}).via;
  97. var dht = opt.dht;
  98. var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
  99. var get = msg.get, hash, tmp;
  100. //if(get && opt.super && peer){
  101. if(get && opt.super && peer && (tmp = route(get))){
  102. hash = tmp; //Gun.obj.hash(get); // USE RAD INSTEAD!
  103. (routes[hash] || (routes[hash] = {}))[peer.id] = peer;
  104. (peer.routes || (peer.routes = {}))[hash] = routes[hash];
  105. /*if(soul = get['#']){ // SWITCH BACK TO USING DHT!
  106. if(key = get['.']){
  107. } else {
  108. }
  109. if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);}
  110. var pids = joindht(dht, soul, peer.id);
  111. if (pids) {
  112. var dht = {};
  113. dht[soul] = pids;
  114. mesh.say({dht:dht}, opt.peers[peer.id]);
  115. }
  116. }*/
  117. }
  118. if((tmp = msg['@']) && (tmp = at.dup.s[tmp]) && (tmp = tmp.it)){
  119. (tmp = (tmp._||ok)).ack = (tmp.ack || 0) + 1; // count remote ACKs to GET.
  120. }
  121. to.next(msg);
  122. if (opt.rtc && msg.dht) {
  123. Gun.obj.map(msg.dht, function(pids, soul) {
  124. dht(soul, pids);
  125. Gun.obj.map(pids.split(','), function(pid) {
  126. /// TODO: here we can put an algorithm of who must connect?
  127. if (!pid || pid in opt.peers || pid === opt.pid || opt.announce[pid]) { return; }
  128. opt.announce[pid] = true; /// To try only one connection to the same peer.
  129. opt.announce(pid);
  130. });
  131. });
  132. }
  133. });
  134. //try{console.log(req.connection.remoteAddress)}catch(e){};
  135. mesh.hear['opt'] = function(msg, peer){
  136. if(msg.ok){ return opt.log(msg) }
  137. var tmp = msg.opt;
  138. if(!tmp){ return }
  139. tmp = tmp.peers;
  140. if(!tmp || !Gun.text.is(tmp)){ return }
  141. if(axe.up[tmp] || 6 <= Object.keys(axe.up).length){ return }
  142. var o = tmp; //{peers: tmp};
  143. at.$.opt(o);
  144. o = peers[tmp];
  145. if(!o){ return }
  146. o.retry = 9;
  147. mesh.wire(o);
  148. if(peer){ mesh.say({dam: 'opt', ok: 1, '@': msg['#']}, peer) }
  149. }
  150. setInterval(function(tmp){
  151. if(!(tmp = at.stats && at.stats.stay)){ return }
  152. (tmp.axe = tmp.axe || {}).up = Object.keys(axe.up||{});
  153. },1000 * 60)
  154. setTimeout(function(tmp){
  155. if(!(tmp = at.stats && at.stats.stay)){ return }
  156. Gun.obj.map((tmp.axe||{}).up, function(url){ mesh.hear.opt({opt: {peers: url}}) })
  157. },1000);
  158. if(at.opt.super){
  159. var rotate = 0;
  160. mesh.way = function(msg) {
  161. if (msg.rtc) {
  162. if (msg.rtc.to) {
  163. /// Send announce to one peer only if the msg have 'to' attr
  164. var peer = (peers) ? peers[msg.rtc.to] : null;
  165. if (peer) { mesh.say(msg, peer); }
  166. return;
  167. }
  168. }
  169. if(msg.get){ mesh.say(msg, axe.up) } // always send gets up!
  170. if(msg.get && (tmp = route(msg.get))){
  171. var hash = tmp; //Gun.obj.hash(msg.get);
  172. var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
  173. var peers = routes[hash];
  174. function chat(peers, old){ // what about optimizing for directed peers?
  175. if(!peers){ return chat(opt.peers) }
  176. var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
  177. var meta = (msg._||yes);
  178. clearTimeout(meta.lack);
  179. var id, peer, c = 1; // opt. ?redundancy?
  180. while((id = ids[meta.turn || 0]) && c--){ // TODO: This hits peers in order, not necessarily best for load balancing. And what about optimizing for directed peers?
  181. peer = peers[id];
  182. meta.turn = (meta.turn || 0) + 1;
  183. if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
  184. }
  185. //console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
  186. if(0 < c){
  187. if(peers === opt.peers){ return } // prevent infinite lack loop.
  188. return meta.turn = 0, chat(opt.peers, peers)
  189. }
  190. var hash = msg['##'], ack = meta.ack;
  191. meta.lack = setTimeout(function(){
  192. if(ack && hash && hash === msg['##']){ return }
  193. if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
  194. //console.log(msg['#'], "CONTINUE:", ack, hash, msg['##']);
  195. chat(peers, old); // keep asking for data if there is mismatching hashes.
  196. }, 25);
  197. }
  198. return chat(peers);
  199. }
  200. // TODO: PUTs need to only go to subs!
  201. if(msg.put){
  202. var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
  203. var peers = {};
  204. Gun.obj.map(msg.put, function(node, soul){
  205. var hash = soul; //Gun.obj.hash({'#': soul});
  206. var to = routes[hash];
  207. if(!to){ return }
  208. Gun.obj.to(to, peers);
  209. });
  210. mesh.say(msg, peers);
  211. return;
  212. }
  213. mesh.say(msg, opt.peers); return; // TODO: DISABLE THIS!!! USE DHT!
  214. if (!msg.put) { mesh.say(msg); return; }
  215. //console.log('AXE HOOK!! ', msg);
  216. verify(opt.dht, msg);
  217. };
  218. } else {
  219. mesh.route = function(msg) {
  220. if (msg.rtc) {
  221. }
  222. if (!msg.put) { mesh.say(msg); return; }
  223. verify(opt.dht, msg);
  224. /// Always send to superpeers?
  225. Gun.obj.map(peers, function(peer) {
  226. if (peer.url) {
  227. mesh.say(msg, peer);
  228. }
  229. });
  230. };
  231. /*var connections = 0; // THIS HAS BEEN MOVED TO CORE NOW!
  232. at.on('hi', function(opt) {
  233. this.to.next(opt);
  234. //console.log('AXE PEER [HI]', new Date(), opt);
  235. connections++;
  236. /// The first connection don't need to resubscribe the nodes.
  237. if (connections === 1) { return; }
  238. /// Resubscribe all nodes.
  239. setTimeout(function() {
  240. var souls = Object.keys(at.graph);
  241. for (var i=0; i < souls.length; ++i) {
  242. //at.gun.get(souls[i]).off();
  243. at.next[souls[i]].ack = 0;
  244. at.gun.get(souls[i]).once(function(){});
  245. }
  246. //location.reload();
  247. }, 500);
  248. }, at);*/
  249. }
  250. axe.up = {};
  251. at.on('hi', function(peer){
  252. this.to.next(peer);
  253. if(!peer.url){ return }
  254. axe.up[peer.id] = peer;
  255. });
  256. at.on('bye', function(peer){ this.to.next(peer);
  257. if(peer.url){ delete axe.up[peer.id] }
  258. Gun.obj.map(peer.routes, function(route, hash){
  259. delete route[peer.id];
  260. if(Gun.obj.empty(route)){
  261. delete axe.routes[hash];
  262. }
  263. });
  264. });
  265. // handle rebalancing a mob of peers:
  266. at.on('hi', function(peer){
  267. this.to.next(peer);
  268. if(peer.url){ return } // I am assuming that if we are wanting to make an outbound connection to them, that we don't ever want to drop them unless our actual config settings change.
  269. var count = Object.keys(opt.peers).length;
  270. if(opt.mob >= count){ return } // TODO: Make dynamic based on RAM/CPU also. Or possibly even weird stuff like opt.mob / axe.up length?
  271. mesh.say({dam: 'mob', mob: count, peers: Object.keys(axe.up)}, peer);
  272. //setTimeout(function(){ mesh.bye(peer) }, 9); // something with better perf? // UNCOMMENT WHEN WE ACTIVATE THIS FEATURE
  273. });
  274. at.on('bye', function(peer){
  275. this.to.next(peer);
  276. });
  277. }
  278. function joindht(dht, soul, pids) {
  279. if (!pids || !soul || !dht) { return; }
  280. var subs = dht(soul);
  281. var tmp = subs ? subs.split(',') : [];
  282. Gun.obj.map(pids.split(','), function(pid) {
  283. if (pid && tmp.indexOf(pid) === -1) { tmp.push(pid); }
  284. });
  285. tmp = tmp.join(',');
  286. dht(soul, tmp);
  287. return tmp;
  288. }
  289. var empty = {}, yes = true, u;
  290. module.exports = AXE;
  291. })(USE, './axe');
  292. }());