axe.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. ;(function(){
  2. /* UNBUILD */
  3. function USE(arg, req){
  4. return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
  5. arg(mod = {exports: {}});
  6. USE[R(path)] = mod.exports;
  7. }
  8. function R(p){
  9. return p.split('/').slice(-1).toString().replace('.js','');
  10. }
  11. }
  12. if(typeof module !== "undefined"){ var MODULE = module }
  13. /* UNBUILD */
  14. ;USE(function(module){
  15. if(typeof window !== "undefined"){ module.window = window }
  16. var tmp = module.window || module;
  17. var AXE = tmp.AXE || function(){};
  18. if(AXE.window = module.window){ AXE.window.AXE = AXE }
  19. try{ if(typeof MODULE !== "undefined"){ MODULE.exports = AXE } }catch(e){}
  20. module.exports = AXE;
  21. })(USE, './root');
  22. ;USE(function(module){
  23. var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
  24. (Gun.AXE = AXE).GUN = AXE.Gun = Gun;
  25. var ST = 0;
  26. Gun.on('opt', function(at){
  27. start(at);
  28. this.to.next(at); // make sure to call the "next" middleware adapter.
  29. });
  30. function start(at){
  31. if(at.axe){ return }
  32. var opt = at.opt, peers = opt.peers;
  33. if(false === opt.axe){ return }
  34. if((typeof process !== "undefined") && 'false' === ''+(process.env||{}).AXE){ return }
  35. var axe = at.axe = {}, tmp;
  36. // 1. If any remembered peers or from last cache or extension
  37. // 2. Fallback to use hard coded peers from dApp
  38. // 3. Or any offered peers.
  39. //if(Gun.obj.empty(p)){
  40. // Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){
  41. // p[url] = {url: url, axe: {}};
  42. // });
  43. //}
  44. // Our current hypothesis is that it is most optimal
  45. // to take peers in a common network, and align
  46. // them in a line, where you only have left and right
  47. // peers, so messages propagate left and right in
  48. // a linear manner with reduced overlap, and
  49. // with one common superpeer (with ready failovers)
  50. // in case the p2p linear latency is high.
  51. // Or there could be plenty of other better options.
  52. /*
  53. AXE should have a couple of threshold items...
  54. let's pretend there is a variable max peers connected
  55. mob = 10000
  56. if we get more peers than that...
  57. we should start sending those peers a remote command
  58. that they should connect to this or that other peer
  59. and then once they (or before they do?) drop them from us.
  60. sake of the test... gonna set that peer number to 1.
  61. The mob threshold might be determined by other factors,
  62. like how much RAM or CPU stress we have.
  63. */
  64. opt.mob = opt.mob || 9876 || Infinity;
  65. var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
  66. console.log("AXE enabled.");
  67. function verify(dht, msg) {
  68. var S = (+new Date);
  69. var puts = Object.keys(msg.put);
  70. var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
  71. var subs = dht(soul);
  72. if (!subs) { return; }
  73. var tmp = [];
  74. Gun.obj.map(subs.split(','), function(pid) {
  75. if (pid in peers) {
  76. tmp.push(pid);
  77. mesh.say(msg, peers[pid]);
  78. }
  79. });
  80. /// Only connected peers in the tmp array.
  81. if (opt.super) {
  82. dht(soul, tmp.join(','));
  83. }
  84. console.STAT && console.STAT(S, +new Date - S, 'axe verify');
  85. }
  86. function route(get){ var tmp;
  87. if(!get){ return }
  88. if('string' != typeof (tmp = get['#'])){ return }
  89. return tmp;
  90. }
  91. // TODO: AXE NEEDS TO BE CHECKED FOR NEW CODE SYSTEM!!!!!!!!!!
  92. var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
  93. at.opt.dht = Rad();
  94. at.on('in', input);
  95. function input(msg){
  96. var to = this.to, peer = (msg._||'').via; // warning! mesh.leap could be buggy!
  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])){
  119. tmp.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 S = (+new Date); // STATS!
  177. var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
  178. var meta = (msg._||yes);
  179. clearTimeout(meta.lack);
  180. var id, peer, c = 1; // opt. ?redundancy?
  181. 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?
  182. peer = peers[id];
  183. meta.turn = (meta.turn || 0) + 1;
  184. if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
  185. }
  186. console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe chat');
  187. //console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
  188. if(0 < c){
  189. if(peers === opt.peers){ return } // prevent infinite lack loop.
  190. return meta.turn = 0, chat(opt.peers, peers)
  191. }
  192. var hash = msg['##'], ack = meta.ack || at.dup.s[msg['#']];
  193. meta.lack = setTimeout(function(){
  194. if(ack && hash && hash === msg['##']){ return }
  195. if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
  196. //console.log(msg['#'], "CONTINUE:", ack, hash, msg['##']);
  197. chat(peers, old); // keep asking for data if there is mismatching hashes.
  198. }, 25);
  199. }
  200. return chat(peers);
  201. }
  202. // TODO: PUTs need to only go to subs!
  203. if(msg.put){
  204. var S = (+new Date); // STATS!
  205. var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
  206. var peers = {};
  207. Gun.obj.map(msg.put, function(node, soul){
  208. var hash = soul; //Gun.obj.hash({'#': soul});
  209. var to = routes[hash];
  210. if(!to){ return }
  211. Gun.obj.to(to, peers);
  212. });
  213. console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe put');
  214. mesh.say(msg, peers);
  215. return;
  216. }
  217. mesh.say(msg, opt.peers); return; // TODO: DISABLE THIS!!! USE DHT!
  218. if (!msg.put) { mesh.say(msg); return; }
  219. //console.log('AXE HOOK!! ', msg);
  220. verify(opt.dht, msg);
  221. };
  222. } else {
  223. mesh.route = function(msg) {
  224. if (msg.rtc) {
  225. }
  226. if (!msg.put) { mesh.say(msg); return; }
  227. verify(opt.dht, msg);
  228. /// Always send to superpeers?
  229. Gun.obj.map(peers, function(peer) {
  230. if (peer.url) {
  231. mesh.say(msg, peer);
  232. }
  233. });
  234. };
  235. }
  236. axe.up = {};
  237. at.on('hi', function(peer){
  238. this.to.next(peer);
  239. if(!peer.url){ return }
  240. axe.up[peer.id] = peer;
  241. });
  242. at.on('bye', function(peer){ this.to.next(peer);
  243. if(peer.url){ delete axe.up[peer.id] }
  244. var S = +new Date;
  245. Gun.obj.map(peer.routes, function(route, hash){
  246. delete route[peer.id];
  247. if(Gun.obj.empty(route)){
  248. delete axe.routes[hash];
  249. }
  250. });
  251. console.STAT && console.STAT(S, +new Date - S, 'axe bye');
  252. });
  253. // handle rebalancing a mob of peers:
  254. at.on('hi', function(peer){
  255. this.to.next(peer);
  256. 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.
  257. var count = Object.keys(opt.peers).length;
  258. if(opt.mob >= count){ return } // TODO: Make dynamic based on RAM/CPU also. Or possibly even weird stuff like opt.mob / axe.up length?
  259. var peers = Object.keys(axe.up);
  260. if(!peers.length){ return }
  261. mesh.say({dam: 'mob', mob: count, peers: peers}, peer);
  262. //setTimeout(function(){ mesh.bye(peer) }, 9); // something with better perf? // UNCOMMENT WHEN WE ACTIVATE THIS FEATURE
  263. });
  264. at.on('bye', function(peer){
  265. this.to.next(peer);
  266. });
  267. at.on('hi', function(peer){
  268. this.to.next(peer);
  269. // this code handles disconnecting from self & duplicates
  270. setTimeout(function(){ // must wait
  271. if(peer.pid !== opt.pid){
  272. // this extra logic checks for duplicate connections between 2 peers.
  273. if(!Gun.obj.map(axe.up, function(p){
  274. if(peer.pid === p.pid && peer !== p){
  275. return yes = true;
  276. }
  277. })){ return }
  278. }
  279. mesh.say({dam: '-'}, peer);
  280. delete at.dup.s[peer.last];
  281. }, Math.random() * 100);
  282. });
  283. mesh.hear['-'] = function(msg, peer){
  284. mesh.bye(peer);
  285. peer.url = '';
  286. }
  287. }
  288. function joindht(dht, soul, pids) {
  289. if (!pids || !soul || !dht) { return; }
  290. var subs = dht(soul);
  291. var tmp = subs ? subs.split(',') : [];
  292. Gun.obj.map(pids.split(','), function(pid) {
  293. if (pid && tmp.indexOf(pid) === -1) { tmp.push(pid); }
  294. });
  295. tmp = tmp.join(',');
  296. dht(soul, tmp);
  297. return tmp;
  298. }
  299. var empty = {}, yes = true, u;
  300. module.exports = AXE;
  301. })(USE, './axe');
  302. }());