Merge pull request #756 from amark/master

master into debug
This commit is contained in:
Mark Nadal 2019-05-28 00:31:09 -07:00 committed by GitHub
commit 866593343d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2913 additions and 2502 deletions

180
axe.js
View File

@ -19,20 +19,13 @@
/* UNBUILD */
;USE(function(module){
if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module;
if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module;
var AXE = tmp.AXE || function(){};
if(AXE.window = module.window){ try{
AXE.window.AXE = AXE;
tmp = document.createEvent('CustomEvent');
tmp.initCustomEvent('extension', false, false, {type: "AXE"});
(window.dispatchEvent || window.fireEvent)(tmp);
window.postMessage({type: "AXE"}, '*');
} catch(e){} }
try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
module.exports = AXE;
if(AXE.window = module.window){ AXE.window.AXE = AXE }
try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
module.exports = AXE;
})(USE, './root');
;USE(function(module){
@ -41,8 +34,8 @@
(Gun.AXE = AXE).GUN = AXE.Gun = Gun;
Gun.on('opt', function(at){
if(!at.axe){
at.axe = {};
var peers = at.opt.peers, tmp;
var axe = at.axe = {}, tmp;
var opt = at.opt, peers = opt.peers;
// 1. If any remembered peers or from last cache or extension
// 2. Fallback to use hard coded peers from dApp
// 3. Or any offered peers.
@ -59,9 +52,10 @@
// with one common superpeer (with ready failovers)
// in case the p2p linear latency is high.
// Or there could be plenty of other better options.
console.log("axe");
var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
console.log("AXE enabled.");
function verify(dht, msg, send, at) {
function verify(dht, msg) {
var puts = Object.keys(msg.put);
var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
var subs = dht(soul);
@ -72,11 +66,11 @@
if (pid in peers) {
tmp.push(pid);
// console.log('[AXE] SEND TO >>>>> ', pid, msg.put.bob || msg.put);
send(msg, peers[pid]);
mesh.say(msg, peers[pid]);
}
});
/// Only connected peers in the tmp array.
if (at.on.opt.super) {
if (opt.super) {
dht(soul, tmp.join(','));
}
}
@ -88,38 +82,118 @@
// this.to.next(msg);
// console.log('[AXE] out:', msg, a);
// }, at);
function input(msg){
var to = this.to, peer = (msg._||{}).via;
var dht = opt.dht;
var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
var get = msg.get, hash, tmp;
if(get && opt.super && peer){
hash = Gun.obj.hash(get); // USE RAD INSTEAD!
(routes[hash] || (routes[hash] = {}))[peer.id] = peer;
(peer.routes || (peer.routes = {}))[hash] = routes[hash];
/*if(soul = get['#']){ // SWITCH BACK TO USING DHT!
if(key = get['.']){
} else {
}
if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);}
var pids = joindht(dht, soul, peer.id);
if (pids) {
var dht = {};
dht[soul] = pids;
mesh.say({dht:dht}, opt.peers[peer.id]);
}
}*/
}
if((tmp = msg['@']) && (tmp = at.dup.s[tmp]) && (tmp = tmp.it)){
(tmp = (tmp._||ok)).ack = (tmp.ack || 0) + 1;
}
to.next(msg);
if (opt.rtc && msg.dht) {
Gun.obj.map(msg.dht, function(pids, soul) {
dht(soul, pids);
Gun.obj.map(pids.split(','), function(pid) {
/// TODO: here we can put an algorithm of who must connect?
if (!pid || pid in opt.peers || pid === opt.pid || opt.announce[pid]) { return; }
opt.announce[pid] = true; /// To try only one connection to the same peer.
opt.announce(pid);
});
});
}
}
if(at.opt.super){
AXE.say = function(msg, send, at) {
var rotate = 0;
mesh.way = function(msg) {
if (msg.rtc) {
// console.log('[AXE] MSG WEBRTC: ', msg.rtc);
if (msg.rtc.to) {
/// Send announce to one peer only if the msg have 'to' attr
var peer = (at.on.opt.peers) ? at.on.opt.peers[msg.rtc.to] : null;
// if (peer) { at.on.opt.mesh.say(msg, peer); }
if (peer) { send(msg, peer); }
var peer = (peers) ? peers[msg.rtc.to] : null;
if (peer) { mesh.say(msg, peer); }
return;
}
}
if (!msg.put) { send(msg); return; }
if(msg.get){
var hash = Gun.obj.hash(msg.get);
var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
var peers = routes[hash];
function chat(peers, old){ // what about optimizing for directed peers?
if(!peers){ return chat(opt.peers) }
var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
var meta = (msg._||yes);
clearTimeout(meta.lack);
var id, peer, c = 1; // opt. ?redundancy?
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?
peer = peers[id];
meta.turn = (meta.turn || 0) + 1;
if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
}
//console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
if(0 < c){
if(peers === opt.peers){ return } // prevent infinite lack loop.
return meta.turn = 0, chat(opt.peers, peers)
}
var hash = msg['##'], ack = meta.ack;
meta.lack = setTimeout(function(){
if(ack && hash && hash === msg['##']){ return }
if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
//console.log(msg['#'], "CONTINUE:", ack, hash, msg['##']);
chat(peers, old); // keep asking for data if there is mismatching hashes.
}, 25);
}
return chat(peers);
}
// TODO: PUTs need to only go to subs!
mesh.say(msg, opt.peers); return; // TODO: DISABLE THIS!!! USE DHT!
if (!msg.put) { mesh.say(msg); return; }
//console.log('AXE HOOK!! ', msg);
verify(at.on.opt.dht, msg, send, at);
verify(opt.dht, msg);
};
} else {
AXE.say = function(msg, send, at) {
mesh.route = function(msg) {
if (msg.rtc) {
// console.log('[AXE] MSG WEBRTC: ', msg.rtc);
}
if (!msg.put) { send(msg); return; }
verify(at.on.opt.dht, msg, send, at);
if (!msg.put) { mesh.say(msg); return; }
verify(opt.dht, msg);
/// Always send to superpeers?
Gun.obj.map(at.on.opt.peers, function(peer) {
Gun.obj.map(peers, function(peer) {
if (peer.url) {
// console.log('SEND TO SUPERPEER', msg);
send(msg, peer);
mesh.say(msg, peer);
}
});
};
var connections = 0;
/*var connections = 0; // THIS HAS BEEN MOVED TO CORE NOW!
at.on('hi', function(opt) {
this.to.next(opt);
//console.log('AXE PEER [HI]', new Date(), opt);
@ -136,11 +210,20 @@
}
//location.reload();
}, 500);
}, at);
}, at);*/
}
at.on('bye', function(peer){ this.to.next(peer);
Gun.obj.map(peer.routes, function(route, hash){
delete route[peer.id];
if(Gun.obj.empty(route)){
delete axe.routes[hash];
}
});
});
}
this.to.next(at); // make sure to call the "next" middleware adapter.
});
function joindht(dht, soul, pids) {
if (!pids || !soul || !dht) { return; }
var subs = dht(soul);
@ -152,42 +235,9 @@
dht(soul, tmp);
return tmp;
}
function input(msg){
// console.log('[AXE] input: ', msg);
var at = this.as, to = this.to, peer = (msg._||{}).via;
var opt = at.opt;
var dht = opt.dht;
var get = msg.get, soul, key;
if(peer && get){
if(soul = get['#']){
if(key = get['.']){
} else {
var empty = {}, yes = true, u;
}
if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);}
var pids = joindht(dht, soul, peer.id);
if (pids) {
var dht = {};
dht[soul] = pids;
at.opt.mesh.say({dht:dht}, opt.peers[peer.id]);
}
}
}
to.next(msg);
if (opt.rtc && msg.dht) {
Gun.obj.map(msg.dht, function(pids, soul) {
dht(soul, pids);
Gun.obj.map(pids.split(','), function(pid) {
/// TODO: here we can put an algorithm of who must connect?
if (!pid || pid in opt.peers || pid === opt.pid || opt.announce[pid]) { return; }
opt.announce[pid] = true; /// To try only one connection to the same peer.
opt.announce(pid);
});
});
}
}
module.exports = AXE;
})(USE, './axe');
}());

View File

@ -15,31 +15,32 @@
}
.ct-series-a .ct-line,
.ct-series-a .ct-point {
stroke: blue;
str-oke: blue !important;
}
.ct-series-b .ct-line,
.ct-series-b .ct-point {
stroke: green;
stroke: green !important;
}
.tall { height: 10em; }
</style>
<input id="url" class="center none" placeholder="enter peer stats source url">
<div class="center"><span class="shout" id="peers">0</span> peers <span class="shout" id="nodes">0</span> nodes <span class="shout" id="hours">0</span> hours</div>
<div class="center"><span class="shout" id="peers">0</span> peers <span class="shout" id="time">0</span> min <span class="shout" id="nodes">0</span> nodes <span class="shout" id="hours">0</span> hours</div>
<div class="leak" style="padding: 0 2em;">
<div class="leak ct-mem ct-perfect-fourth tall"></div>
<div class="leak ct-mem ct-chart ct-perfect-fourth tall"></div>
</div>
<div class="center leak" style="padding: 0 2em;">
<div class="unit col leak ct-damc tall" style="width: 49%;"></div>
<div class="unit col leak ct-damd tall" style="width: 49%;"></div>
<div class="unit col leak ct-damc ct-chart tall" style="width: 49%;"></div>
<div class="unit col leak ct-damd ct-chart tall" style="width: 49%;"></div>
</div>
<div class="center leak" style="padding: 0 2em;">
<div class="leak ct-cpu ct-perfect-fourth tall"></div>
<div class="leak ct-cpu ct-chart ct-perfect-fourth tall"></div>
</div>
<script src="./jquery.js"></script>
@ -52,6 +53,7 @@ setInterval(function(){
}, 1000 * 15);
stats.show = function(){ $.getJSON(url.value||(location.origin+'/gun/stats.radata'), function(data){ console.log(data);
$('#peers').text(data.peers.count);
$('#time').text((data.peers.time / 1000 / 60).toFixed(0));
$('#nodes').text(data.node.count);
$('#hours').text((data.up.time / 60 / 60).toFixed(0));
@ -76,8 +78,8 @@ stats.show = function(){ $.getJSON(url.value||(location.origin+'/gun/stats.radat
// A labels array that can contain any sort of values
labels: ['-1min', '-45s', '-30s', '-15s', '0'],
// Our series array that contains series objects or in this case series data arrays
series: [stats.din, stats.dout]
}, {fullWidth: true, low: 0, axisY: {
series: [stats.dout, stats.din]
}, {fullWidth: true, axisY: {
labelInterpolationFnc: function(v) { return v+'msgs' }
}});
@ -87,8 +89,8 @@ stats.show = function(){ $.getJSON(url.value||(location.origin+'/gun/stats.radat
// A labels array that can contain any sort of values
labels: ['-1min', '-45s', '-30s', '-15s', '0'],
// Our series array that contains series objects or in this case series data arrays
series: [stats.dind, stats.doutd]
}, {fullWidth: true, low: 0, axisY: {
series: [stats.doutd, stats.dind]
}, {fullWidth: true, axisY: {
labelInterpolationFnc: function(v) { return v+'MB' }
}});

4489
gun.js

File diff suppressed because it is too large Load Diff

2
gun.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -41,6 +41,7 @@ Gun.on('opt', function(root){
if(dam){
stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d}, 'out': {count: dam.say.c, done: dam.say.d}};
dam.hear.c = dam.hear.d = dam.say.c = dam.say.d = 0;
stats.peers.time = dam.bye.time || 0;
}
fs.writeFile(__dirname+'/../stats.'+root.opt.file, JSON.stringify(stats, null, 2), function(err){});

View File

@ -14,7 +14,7 @@
"e2e": "mocha e2e/distributed.js",
"docker": "hooks/build",
"minify": "uglifyjs gun.js -o gun.min.js -c -m",
"unbuild": "node lib/unbuild.js"
"unbuild": "node lib/unbuild.js & npm run minify"
},
"repository": {
"type": "git",

View File

@ -30,13 +30,14 @@ Gun.on('create', function(root){
});
});
setTimeout(function(){
// TODO: Holy Grail dangling by this thread! If gap / offline resync doesn't trigger, it doesn't work. Ouch, and this is a localStorage specific adapter. :(
root.on('out', {put: send, '#': root.ask(ack)});
},1);
}
root.on('out', function(msg){
if(msg.lS){ return }
if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){
if(msg.lS){ return } // TODO: for IndexedDB and others, shouldn't send to peers ACKs to our own GETs.
if(Gun.is(msg.$) && msg.put && !msg['@']){
id = msg['#'];
Gun.graph.is(msg.put, null, map);
if(!to){ to = setTimeout(flush, opt.wait || 1) }
@ -108,11 +109,9 @@ Gun.on('create', function(root){
if(data && has){
data = Gun.state.to(data, has);
}
if(!data && !Gun.obj.empty(opt.peers)){ // if data not found, don't ack if there are peers.
return; // Hmm, what if we have peers but we are disconnected?
}
//if(!data && !Gun.obj.empty(opt.peers)){ return } // if data not found, don't ack if there are peers. // Hmm, what if we have peers but we are disconnected?
//console.log("lS get", lex, data);
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$});
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$});// || root.$});
};
Gun.debug? setTimeout(to,1) : to();
});

View File

@ -1,67 +1,20 @@
var Gun = require('../index');
var Type = require('../type');
function Mesh(ctx){
function Mesh(root){
var mesh = function(){};
var opt = ctx.opt || {};
var opt = root.opt || {};
opt.log = opt.log || console.log;
opt.gap = opt.gap || opt.wait || 1;
opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
mesh.out = function(msg){ var tmp;
if(this.to){ this.to.next(msg) }
//if(mesh.last != msg['#']){ return mesh.last = msg['#'], this.to.next(msg) }
if((tmp = msg['@'])
&& (tmp = ctx.dup.s[tmp])
&& (tmp = tmp.it)
&& tmp._){
mesh.say(msg, (tmp._).via, 1);
tmp['##'] = msg['##'];
return;
}
// add hook for AXE?
if (Gun.AXE) { Gun.AXE.say(msg, mesh.say, this); return; }
mesh.say(msg);
}
ctx.on('create', function(root){
root.opt.pid = root.opt.pid || Type.text.random(9);
this.to.next(root);
ctx.on('out', mesh.out);
});
var dup = root.dup;
mesh.hear = function(raw, peer){
if(!raw){ return }
var dup = ctx.dup, id, hash, msg, tmp = raw[0];
if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) }
if('{' === tmp){
try{msg = JSON.parse(raw);}catch(e){opt.log('DAM JSON parse error', e)}
if(!msg){ return }
mesh.hear.d += raw.length; ++mesh.hear.c; // STATS!
if(dup.check(id = msg['#'])){ return }
dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed.
if((tmp = msg['@']) && msg.put){
hash = msg['##'] || (msg['##'] = mesh.hash(msg));
if((tmp = tmp + hash) != id){
if(dup.check(tmp)){ return }
(tmp = dup.s)[hash] = tmp[id];
}
}
(msg._ = function(){}).via = peer;
if((tmp = msg['><'])){
(msg._).to = Type.obj.map(tmp.split(','), tomap);
}
if(msg.dam){
if(tmp = mesh.hear[msg.dam]){
tmp(msg, peer, ctx);
}
return;
}
ctx.on('in', msg);
return;
} else
var msg, id, hash, tmp = raw[0];
if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) }
if('{' != raw[2]){ mesh.hear.d += raw.length||0; ++mesh.hear.c; } // STATS! // ugh, stupid double JSON encoding
if('[' === tmp){
try{msg = JSON.parse(raw);}catch(e){opt.log('DAM JSON parse error', e)}
if(!msg){ return }
@ -69,179 +22,222 @@ function Mesh(ctx){
while(m = msg[i++]){
mesh.hear(m, peer);
}
return;
}
if('{' === tmp || (Type.obj.is(raw) && (msg = raw))){
try{msg = msg || JSON.parse(raw);
}catch(e){return opt.log('DAM JSON parse error', e)}
if(!msg){ return }
if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) }
if(dup.check(id)){ return }
dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore?
if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) }
if(hash && (tmp = msg['@'] || (msg.get && id))){ // Reduces backward daisy in case varying hashes at different daisy depths are the same.
if(dup.check(tmp+hash)){ return }
dup.track(tmp+hash, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore?
}
(msg._ = function(){}).via = peer;
if(tmp = msg['><']){ (msg._).to = Type.obj.map(tmp.split(','), tomap) }
if(msg.dam){
if(tmp = mesh.hear[msg.dam]){
tmp(msg, peer, root);
}
return;
}
root.on('in', msg);
return;
}
}
mesh.hear.c = mesh.hear.d = 0;
var tomap = function(k,i,m){m(k,true)};
mesh.hear.c = mesh.hear.d = 0;
;(function(){
var message;
function each(peer){ mesh.say(message, peer) }
mesh.say = function(msg, peer, o){
/*
TODO: Plenty of performance optimizations
that can be made just based off of ordering,
and reducing function calls for cached writes.
*/
if(!peer){ message = msg;
Type.obj.map(opt.peers, each);
return;
}
var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen!
if(!wire){ return }
msh = (msg._) || empty;
if(peer === msh.via){ return }
if(!(raw = msh.raw)){ raw = mesh.raw(msg) }
if((tmp = msg['@'])
&& (tmp = ctx.dup.s[tmp])
&& (tmp = tmp.it)){
if(tmp.get && tmp['##'] && tmp['##'] === msg['##']){ // PERF: move this condition outside say?
return; // TODO: this still needs to be tested in the browser!
mesh.say = function(msg, peer){
if(this.to){ this.to.next(msg) } // compatible with middleware adapters.
if(!msg){ return false }
var id, hash, tmp, raw;
var meta = msg._||(msg._=function(){});
if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) }
if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) }
if(!(raw = meta.raw)){
raw = meta.raw = mesh.raw(msg);
if(hash && (tmp = msg['@'])){
dup.track(tmp+hash).it = msg;
if(tmp = (dup.s[tmp]||ok).it){
if(hash === tmp['##']){ return false }
tmp['##'] = hash;
}
}
}
if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id]) && !o){ return } // TODO: still needs to be tested
dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more!
if(!peer){ peer = (tmp = dup.s[msg['@']]) && (tmp = tmp.it) && (tmp = tmp._) && (tmp = tmp.via) }
if(!peer && mesh.way){ return mesh.way(msg) }
if(!peer || !peer.id){ message = msg;
if(!Type.obj.is(peer || opt.peers)){ return false }
Type.obj.map(peer || opt.peers, each); // in case peer is a peer list.
return;
}
if(!peer.wire && mesh.wire){ mesh.wire(peer) }
if(peer === meta.via){ return false }
if((tmp = meta.to) && (tmp[peer.url] || tmp[peer.pid] || tmp[peer.id]) /*&& !o*/){ return false }
if(peer.batch){
peer.tail = (peer.tail || 0) + raw.length;
peer.tail = (tmp = peer.tail || 0) + raw.length;
if(peer.tail <= opt.pack){
peer.batch.push(raw);
peer.batch.push(raw); // peer.batch += (tmp?'':',')+raw; // TODO: Prevent double JSON! // FOR v1.0 !?
return;
}
flush(peer);
}
peer.batch = [];
peer.batch = []; // peer.batch = '['; // TODO: Prevent double JSON!
setTimeout(function(){flush(peer)}, opt.gap);
send(raw, peer);
}
function flush(peer){
var tmp = peer.batch;
if(!tmp){ return }
var tmp = peer.batch; // var tmp = peer.batch + ']'; // TODO: Prevent double JSON!
peer.batch = peer.tail = null;
if(!tmp.length){ return }
try{send(JSON.stringify(tmp), peer);
}catch(e){opt.log('DAM JSON stringify error', e)}
}
function send(raw, peer){
var wire = peer.wire;
try{
if(peer.say){
peer.say(raw);
} else
if(wire.send){
wire.send(raw);
}
mesh.say.d += raw.length; ++mesh.say.c; // STATS!
}catch(e){
(peer.queue = peer.queue || []).push(raw);
}
if(!tmp){ return }
if(!tmp.length){ return } // if(3 > tmp.length){ return } // TODO: ^
try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp));
}catch(e){return opt.log('DAM JSON stringify error', e)}
if(!tmp){ return }
send(tmp, peer);
}
mesh.say.c = mesh.say.d = 0;
}());
// for now - find better place later.
function send(raw, peer){ try{
var wire = peer.wire;
if(peer.say){
peer.say(raw);
} else
if(wire.send){
wire.send(raw);
}
mesh.say.d += raw.length||0; ++mesh.say.c; // STATS!
}catch(e){
(peer.queue = peer.queue || []).push(raw);
}}
;(function(){
mesh.raw = function(msg){
mesh.raw = function(msg){ // TODO: Clean this up / delete it / move logic out!
if(!msg){ return '' }
var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp;
if(tmp = msh.raw){ return tmp }
var meta = (msg._) || {}, put, hash, tmp;
if(tmp = meta.raw){ return tmp }
if(typeof msg === 'string'){ return msg }
if(msg['@'] && (tmp = msg.put)){
if(!(hash = msg['##'])){
put = $(tmp, sort) || '';
hash = mesh.hash(msg, put);
msg['##'] = hash;
}
(tmp = dup.s)[hash = msg['@']+hash] = tmp[msg['#']];
msg['#'] = hash || msg['#'];
if(put){ (msg = Type.obj.to(msg)).put = _ }
if(!msg.dam){
var i = 0, to = []; Type.obj.map(opt.peers, function(p){
to.push(p.url || p.pid || p.id); if(++i > 9){ return true } // limit server, fast fix, improve later! // For "tower" peer, MUST include 6 surrounding ids.
}); if(i > 1){ msg['><'] = to.join() }
}
var i = 0, to = []; Type.obj.map(opt.peers, function(p){
to.push(p.url || p.id); if(++i > 9){ return true } // limit server, fast fix, improve later!
}); msg['><'] = to.join();
var raw = $(msg);
if(u !== put){
var raw = $(msg); // optimize by reusing put = the JSON.stringify from .hash?
/*if(u !== put){
tmp = raw.indexOf(_, raw.indexOf('put'));
raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1);
//raw = raw.replace('"'+ _ +'"', put); // https://github.com/amark/gun/wiki/@$$ Heisenbug
}
if(msh){
msh.raw = raw;
}
//raw = raw.replace('"'+ _ +'"', put); // NEVER USE THIS! ALSO NEVER DELETE IT TO NOT MAKE SAME MISTAKE! https://github.com/amark/gun/wiki/@$$ Heisenbug
}*/
if(meta){ meta.raw = raw }
return raw;
}
mesh.hash = function(msg, hash){
return Mesh.hash(hash || $(msg.put, sort) || '') || msg['#'] || Type.text.random(9);
}
function sort(k, v){ var tmp;
if(!(v instanceof Object)){ return v }
Type.obj.map(Object.keys(v).sort(), map, {to: tmp = {}, on: v});
return tmp;
}
function map(k){
this.to[k] = this.on[k];
}
var $ = JSON.stringify, _ = ':])([:';
}());
mesh.hi = function(peer){
var tmp = peer.wire || {};
if(peer.id || peer.url){
if(peer.id){
opt.peers[peer.url || peer.id] = peer;
} else {
tmp = peer.id = tmp.pid = peer.id || Type.text.random(9);
tmp = peer.id = peer.id || Type.text.random(9);
mesh.say({dam: '?'}, opt.peers[tmp] = peer);
}
if(!tmp.hied){ ctx.on(tmp.hied = 'hi', peer) }
peer.met = peer.met || +(new Date);
if(!tmp.hied){ root.on(tmp.hied = 'hi', peer) }
// @rogowski I need this here by default for now to fix go1dfish's bug
tmp = peer.queue; peer.queue = [];
Type.obj.map(tmp, function(msg){
mesh.say(msg, peer);
send(msg, peer);
});
}
mesh.bye = function(peer){
Type.obj.del(opt.peers, peer.id); // assume if peer.url then reconnect
ctx.on('bye', peer);
root.on('bye', peer);
var tmp = +(new Date); tmp = (tmp - (peer.met||tmp));
mesh.bye.time = ((mesh.bye.time || tmp) + tmp) / 2;
}
mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) }
mesh.hear['?'] = function(msg, peer){
if(!msg.pid){
mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer);
// @rogowski I want to re-enable this AXE logic with some fix/merge later.
// var tmp = peer.queue; peer.queue = [];
// Type.obj.map(tmp, function(msg){
// mesh.say(msg, peer);
// });
/* var tmp = peer.queue; peer.queue = [];
Type.obj.map(tmp, function(msg){
mesh.say(msg, peer);
}); */
// @rogowski 2: I think with my PID fix we can delete this and use the original.
return;
}
if(!peer.wire){ return }
if(!peer.wire.pid){ return } // only run code below if wire.pid exists
Type.obj.del(opt.peers, peer.wire.pid || peer.id);
delete peer.wire.pid;
peer.id = msg.pid;
mesh.hi(peer);
if(peer.pid){ return }
peer.pid = msg.pid;
}
root.on('create', function(root){
root.opt.pid = root.opt.pid || Type.text.random(9);
this.to.next(root);
root.on('out', mesh.say);
});
var gets = {};
root.on('bye', function(peer, tmp){ this.to.next(peer);
if(!(tmp = peer.url)){ return } gets[tmp] = true;
setTimeout(function(){ delete gets[tmp] },opt.lack || 9000);
});
root.on('hi', function(peer, tmp){ this.to.next(peer);
if(!(tmp = peer.url) || !gets[tmp]){ return } delete gets[tmp];
Type.obj.map(root.next, function(node, soul){
tmp = {}; tmp[soul] = root.graph[soul];
mesh.say({'##': Type.obj.hash(tmp), get: {'#': soul}}, peer);
})
});
return mesh;
}
Mesh.hash = function(s){ // via SO
if(typeof s !== 'string'){ return {err: 1} }
var c = 0;
if(!s.length){ return c }
for(var i=0,l=s.length,n; i<l; ++i){
n = s.charCodeAt(i);
c = ((c<<5)-c)+n;
c |= 0;
}
return c; // Math.abs(c);
}
;(function(){
Type.text.hash = function(s){ // via SO
if(typeof s !== 'string'){ return {err: 1} }
var c = 0;
if(!s.length){ return c }
for(var i=0,l=s.length,n; i<l; ++i){
n = s.charCodeAt(i);
c = ((c<<5)-c)+n;
c |= 0;
}
return c; // Math.abs(c);
}
var $ = JSON.stringify, u;
var empty = {}, u;
Type.obj.hash = function(obj, hash){
if(!hash && u === (obj = $(obj, sort))){ return }
return Type.text.hash(hash || obj || '');
}
function sort(k, v){ var tmp;
if(!(v instanceof Object)){ return v }
Type.obj.map(Object.keys(v).sort(), map, {to: tmp = {}, on: v});
return tmp;
}
Type.obj.hash.sort = sort;
function map(k){
this.to[k] = this.on[k];
}
}());
var empty = {}, ok = true, u;
Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) }
try{ module.exports = Mesh }catch(e){}

View File

@ -19,8 +19,8 @@ Gun.on('opt', function(root){
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root);
var wire = opt.wire;
opt.wire = open;
var wire = mesh.wire || opt.wire;
mesh.wire = opt.wire = open;
function open(peer){ try{
if(!peer || !peer.url){ return wire && wire(peer) }
var url = peer.url.replace('http', 'ws');

View File

@ -247,7 +247,7 @@ function not(at, msg){
if(u === tmp && u !== at.put){ return true }
neat.put = u;
if(neat.ack){
neat.ack = -1;
neat.ack = -1; // TODO: BUG? Should this be 0?
}
neat.on('in', {
get: key,

View File

@ -101,6 +101,7 @@ Gun.chain.off = function(){
var gun = this, at = gun._, tmp;
var cat = at.back;
if(!cat){ return }
at.ack = 0; // so can resubscribe.
if(tmp = cat.next){
if(tmp[at.get]){
obj_del(tmp, at.get);

View File

@ -107,11 +107,11 @@ function batch(){ var as = this;
var cat = (as.$.back(-1)._), ask = cat.ask(function(ack){
cat.root.on('ack', ack);
if(ack.err){ Gun.log(ack) }
if(!ack.lack){ this.off() } // One response is good enough for us currently. Later we may want to adjust this.
if(++acks > (as.acks || 0)){ this.off() } // Adjustable ACKs! Only 1 by default.
if(!as.ack){ return }
as.ack(ack, this);
//--C;
}, as.opt);
}, as.opt), acks = 0;
//C++;
// NOW is a hack to get synchronous replies to correctly call.
// and STOP is a hack to get async behavior to correctly call.

View File

@ -185,7 +185,7 @@ Gun.dup = require('./dup');
if(text_is(tmp)){ tmp = [tmp] }
if(list_is(tmp)){
tmp = obj_map(tmp, function(url, i, map){
map(url, {url: url});
i = {}; i.id = i.url = url; map(url, i);
});
if(!obj_is(at.opt.peers)){ at.opt.peers = {}}
at.opt.peers = obj_to(tmp, at.opt.peers);

View File

@ -169,7 +169,7 @@ describe("The Holy Grail AXE Test!", function(){
})
});
it("Jhon Read what Bob say to Alice: Hi Alice!", function(){
it("John Read what Bob say to Alice: Hi Alice!", function(){
return john.run(function(test){
test.async();
console.log("I AM JOHN");

198
test/panic/1putackget.js Normal file
View File

@ -0,0 +1,198 @@
/*
This is the first in a series of basic networking correctness tests.
Each test itself might be dumb and simple, but built up together,
they prove desired end goals for behavior at scale.
1. (this file) Is a browser write is confirmed as save by multiple peers even if by daisy chain.
2.
*/
var config = {
IP: require('ip').address(),
port: 8765,
servers: 2,
browsers: 2,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/jquery.js': __dirname + '/../../examples/jquery.js'
}
}
var panic = require('panic-server');
panic.server().on('request', function(req, res){
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port);
var clients = panic.clients;
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){
return {
type: 'node',
port: config.port + (i + 1)
}
}),
panic: 'http://' + config.IP + ':' + config.port
});
var servers = clients.filter('Node.js');
var bob = servers.pluck(1);
var carl = servers.excluding(bob).pluck(1);
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var dave = browsers.excluding(alice).pluck(1);
describe("Put ACK", function(){
//this.timeout(5 * 60 * 1000);
this.timeout(10 * 60 * 1000);
it("Servers have joined!", function(){
return servers.atLeast(config.servers);
});
it("GUN started!", function(){
var tests = [], i = 0;
servers.each(function(client){
tests.push(client.run(function(test){
var env = test.props;
test.async();
try{ require('fs').unlinkSync(env.i+'data') }catch(e){}
try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){}
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
var port = env.config.port + env.i;
var Gun = require('gun');
var peers = [], i = env.config.servers;
while(i--){
var tmp = (env.config.port + (i + 1));
if(port != tmp){ // ignore ourselves
peers.push('http://'+ env.config.IP + ':' + tmp + '/gun');
}
}
console.log(port, " connect to ", peers);
var gun = Gun({file: env.i+'data', peers: peers, web: server});
server.listen(port, function(){
test.done();
});
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it(config.browsers +" browser(s) have joined!", function(){
console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
try{ localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radata') }catch(e){}
var env = test.props;
var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun');
window.ref = gun.get('a');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Put", function(){
return alice.run(function(test){
console.log("I AM ALICE");
test.async();
var c = test.props.acks, acks = {};
c = c < 2? 2 : c;
ref.put({hello: 'world'}, function(ack){
//console.log("acks:", ack, c);
acks[ack['#']] = 1;
if(Object.keys(acks).length == c){
wire();
return test.done();
}
}, {acks: c});
function wire(){
var hear = ref._.root.opt.mesh.hear;
ref._.root.opt.mesh.hear = function(raw, peer){
var msg = Gun.obj.ify(raw);
console.log('hear:', msg);
hear(raw, peer);
(ref.hear || (ref.hear = [])).push(msg);
}
var say = ref._.root.opt.mesh.say;
ref._.root.opt.mesh.say = function(raw, peer){
var yes = say(raw, peer);
if(yes === false){ return }
console.log("say:", msg, yes);
(ref.say || (ref.say = [])).push(Gun.obj.ify(msg));
}
}
}, {acks: config.servers});
});
it("Get", function(){
/*
Here is the recursive rule for GET, keep replying while hashes mismatch.
1. Receive a GET message.
2. If it has a hash, and if you have a thing matching the GET, then see if the hashes are the same, if they are then don't ACK, don't relay, end.
3. If you would have the thing but do not, then ACK that YOU have nothing.
4. If you have a thing matching the GET or an ACK for the GET's message, add the hash to the GET message, and ACK with the thing or ideally the remaining difference.
5. Pick ?3? OTHER peers preferably by priority that they have got the thing, send them the GET, plus all "up" peers.
6. If no ACKs you are done, end.
7. If you get ACKs back to the GET with things and different hashes, optionally merge into the thing you have GOT and update the hash.
8. Go to 4.
*/
return dave.run(function(test){
console.log("I AM DAVE");
test.async();
var c = 0, to;
var hear = ref._.root.opt.mesh.hear;
ref._.root.opt.mesh.hear = function(raw, peer){
var msg = Gun.obj.ify(raw);
console.log('hear:', msg);
hear(raw, peer);
(ref.hear || (ref.hear = [])).push(msg);
if(msg.put){ ++c }
}
ref.get(function(ack){
if(!ack.put || ack.put.hello !== 'world'){ return }
if(c > 1){ too_many_acks }
clearTimeout(to);
to = setTimeout(test.done, 1000);
});
}, {acks: config.servers});
});
it("DAM", function(){
return alice.run(function(test){
test.async();
if(ref.say){ said_too_much }
if(ref.hear.length > 1){ heard_to_much }
test.done()
}, {acks: config.servers});
});
it("All finished!", function(done){
console.log("Done! Cleaning things up...");
setTimeout(function(){
done();
},1000);
});
after("Everything shut down.", function(){
browsers.run(function(){
//location.reload();
//setTimeout(function(){
//}, 15 * 1000);
});
return servers.run(function(){
process.exit();
});
});
});

165
test/panic/2getget.js Normal file
View File

@ -0,0 +1,165 @@
/*
This is the first in a series of basic networking correctness tests.
Each test itself might be dumb and simple, but built up together,
they prove desired end goals for behavior at scale.
1. (this file) Is a browser write is confirmed as save by multiple peers even if by daisy chain.
2.
*/
var config = {
IP: require('ip').address(),
port: 8765,
servers: 1,
browsers: 3,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/jquery.js': __dirname + '/../../examples/jquery.js'
}
}
var panic = require('panic-server');
panic.server().on('request', function(req, res){
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port);
var clients = panic.clients;
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){
return {
type: 'node',
port: config.port + (i + 1)
}
}),
panic: 'http://' + config.IP + ':' + config.port
});
var servers = clients.filter('Node.js');
var bob = servers.pluck(1);
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var carl = browsers.excluding(alice).pluck(1);
var dave = browsers.excluding([alice, carl]).pluck(1);
var cd = new panic.ClientList([carl, dave]);
describe("Put ACK", function(){
//this.timeout(5 * 60 * 1000);
this.timeout(10 * 60 * 1000);
it("Servers have joined!", function(){
return servers.atLeast(config.servers);
});
it("GUN started!", function(){
var tests = [], i = 0;
servers.each(function(client){
tests.push(client.run(function(test){
var env = test.props;
test.async();
try{ require('fs').unlinkSync(env.i+'data') }catch(e){}
try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){}
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
var port = env.config.port + env.i;
var Gun = require('gun');
var peers = [], i = env.config.servers;
while(i--){
var tmp = (env.config.port + (i + 1));
if(port != tmp){ // ignore ourselves
peers.push('http://'+ env.config.IP + ':' + tmp + '/gun');
}
}
console.log(port, " connect to ", peers);
var gun = Gun({file: env.i+'data', peers: peers, web: server});
server.listen(port, function(){
test.done();
});
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it(config.browsers +" browser(s) have joined!", function(){
console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
try{ localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radata') }catch(e){}
var env = test.props;
var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun');
window.gun = gun;
window.ref = gun.get('a');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("connect", function(){
return alice.run(function(test){
console.log("I AM ALICE");
test.async();
gun.get('random').get(function(ack){
setTimeout(function(){
test.done();
}, 1000);
})
});
});
it("Put", function(){
return alice.run(function(test){
test.async();
var say = ref._.root.opt.mesh.say;
ref._.root.opt.mesh.say = function(){}; // prevent from syncing
var c = 0;
ref.put({hello: 'world'}, function(ack){ ++c });
setTimeout(function(){
ref._.root.opt.mesh.say = say;
if(c){ should_not_have_ack }
test.done();
}, 1000);
});
});
it("Get", function(){
return cd.run(function(test){
test.async();
console.log("I am Carl or Dave");
ref.get(function(ack){
console.log('ack', ack);
if(ack.put){
test.done();
}
});
});
});
it("All finished!", function(done){
console.log("Done! Cleaning things up...");
setTimeout(function(){
done();
},1000);
});
after("Everything shut down.", function(){
browsers.run(function(){
//location.reload();
//setTimeout(function(){
//}, 15 * 1000);
});
return servers.run(function(){
process.exit();
});
});
});

View File

@ -50,6 +50,8 @@ describe("The Holy Grail Test!", function(){
test.async();
try{ require('fs').unlinkSync(env.i+'data') }catch(e){}
try{ require('fs').unlinkSync((env.i+1)+'data') }catch(e){}
try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){}
try{ require('gun/lib/fsrm')((env.i+1)+'data') }catch(e){}
var port = env.config.port + env.i;
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
@ -104,7 +106,8 @@ describe("The Holy Grail Test!", function(){
return server.run(function(test){
console.log(3);
var env = test.props;
try{ require('fs').unlinkSync(env.i+'data'); }catch(e){}
try{ require('fs').unlinkSync(env.i+'data') }catch(e){}
try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){}
process.exit(0);
}, {i: 1, config: config})
});
@ -176,6 +179,7 @@ describe("The Holy Grail Test!", function(){
var env = test.props;
test.async();
try{ require('fs').unlinkSync(env.i+'data') }catch(e){}
try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){}
var port = env.config.port + env.i;
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");