# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
This commit is contained in:
Mark Nadal 2022-06-06 18:14:10 -07:00
commit 19cb91ee4e
23 changed files with 431 additions and 74 deletions

View File

@ -1,6 +1,5 @@
{ {
"name": "gun-server", "name": "gun-server",
"stack": "heroku-18",
"website": "http://gun.eco/", "website": "http://gun.eco/",
"repository": "https://github.com/amark/gun", "repository": "https://github.com/amark/gun",
"logo": "https://avatars3.githubusercontent.com/u/8811914", "logo": "https://avatars3.githubusercontent.com/u/8811914",

81
lib/fork.js Normal file
View File

@ -0,0 +1,81 @@
/*
describe('API Chain Features', function(){
describe('Gun.chain.fork', function(){
var gun = Gun();
var fork;
it('create fork', function(done){
fork = gun.fork().wire();
done();
});
it('put data via fork', function(done){
fork.get("fork-test").get("fork").put("test123").once(()=>done());
});
it('get data via main', function(done){
gun.get("fork-test").get("fork").once((data)=>{
expect(data).to.be("test123");
done();
});
});
it('put data via main', function(done){
gun.get("fork-test").get("main").put("test321").once(()=>done());
});
it('get data via fork', function(done){
fork.get("fork-test").get("main").once((data)=>{
expect(data).to.be("test321");
done();
});
});
})
})
*/
(function (Gun, u) {
/**
*
* credits:
* github:bmatusiak
*
*/
Gun.chain.fork = function(g) {
var gun = this._;
var w = {},
mesh = () => {
var root = gun.root,
opt = root.opt;
return opt.mesh || Gun.Mesh(root);
}
w.link = function() {
if (this._l) return this._l;
this._l = {
send: (msg) => {
if (!this.l || !this.l.onmessage)
throw 'not attached';
this.l.onmessage(msg);
}
}
return this._l;
};
w.attach = function(l) {
if (this.l)
throw 'already attached';
var peer = { wire: l };
l.onmessage = function(msg) {
mesh().hear(msg.data || msg, peer);
};
mesh().hi(this.l = l && peer);
};
w.wire = function(opts) {
var f = new Gun(opts);
f.fork(w);
return f;
};
if (g) {
w.attach(g.link());
g.attach(w.link());
}
return w;
};
})((typeof window !== "undefined") ? window.Gun : require('../gun'))

93
lib/lex.js Normal file
View File

@ -0,0 +1,93 @@
(function (Gun, u) {
/**
*
* credits:
* github:bmatusiak
*
*/
var lex = (gun) => {
function Lex() {}
Lex.prototype = Object.create(Object.prototype, {
constructor: {
value: Lex
}
});
Lex.prototype.toString = function () {
return JSON.stringify(this);
}
Lex.prototype.more = function (m) {
this[">"] = m;
return this;
}
Lex.prototype.less = function (le) {
this["<"] = le;
return this;
}
Lex.prototype.in = function () {
var l = new Lex();
this["."] = l;
return l;
}
Lex.prototype.of = function () {
var l = new Lex();
this.hash(l)
return l;
}
Lex.prototype.hash = function (h) {
this["#"] = h;
return this;
}
Lex.prototype.prefix = function (p) {
this["*"] = p;
return this;
}
Lex.prototype.return = function (r) {
this["="] = r;
return this;
}
Lex.prototype.limit = function (l) {
this["%"] = l;
return this;
}
Lex.prototype.reverse = function (rv) {
this["-"] = rv || 1;
return this;
}
Lex.prototype.includes = function (i) {
this["+"] = i;
return this;
}
Lex.prototype.map = function (...args) {
return gun.map(this, ...args);
}
Lex.prototype.match = lex.match;
return new Lex();
};
lex.match = function(t,o){ var tmp, u;
o = o || this || {};
if('string' == typeof o){ o = {'=': o} }
if('string' !== typeof t){ return false }
tmp = (o['='] || o['*'] || o['>'] || o['<']);
if(t === tmp){ return true }
if(u !== o['=']){ return false }
tmp = (o['*'] || o['>']);
if(t.slice(0, (tmp||'').length) === tmp){ return true }
if(u !== o['*']){ return false }
if(u !== o['>'] && u !== o['<']){
return (t >= o['>'] && t <= o['<'])? true : false;
}
if(u !== o['>'] && t >= o['>']){ return true }
if(u !== o['<'] && t <= o['<']){ return true }
return false;
}
Gun.Lex = lex;
Gun.chain.lex = function () {
return lex(this);
}
})((typeof window !== "undefined") ? window.Gun : require('../gun'))

View File

@ -95,7 +95,7 @@ function Store(opt){
cb.end = true; cb(); cb.end = true; cb();
} }
// Gun.obj.map(cbs, cbe); // lets see if fixes heroku // Gun.obj.map(cbs, cbe); // lets see if fixes heroku
if(!IT){ Gun.obj.del(c.l, 1); return } if(!IT){ delete c.l[1]; return }
params.ContinuationToken = data.NextContinuationToken; params.ContinuationToken = data.NextContinuationToken;
store.list(cb, match, params, cbs); store.list(cb, match, params, cbs);
}); });

View File

@ -19,6 +19,13 @@ var mk = function(path){
fs.mkdirSync(path); fs.mkdirSync(path);
} }
var rn = function(path, newPath){
path = nodePath.join(dir, path)
newPath = nodePath.join(dir, newPath)
if(fs.existsSync(newPath)){ return }
fs.renameSync(path, newPath);
}
var between = function(text, start, end){ var between = function(text, start, end){
end = end || start; end = end || start;
var s = text.indexOf(start); var s = text.indexOf(start);
@ -65,15 +72,19 @@ var undent = function(code, n){
var arg = process.argv[2] || 'gun'; var arg = process.argv[2] || 'gun';
var g;
if('gun' === arg){ if('gun' === arg){
rm('./src'); g = 'gun';
rn('./src','./old_src');
mk('./src'); mk('./src');
mk('./src/polyfill'); mk('./src/polyfill');
mk('./src/adapters'); mk('./src/adapters');
} else { } else {
rm('./'+arg); g = arg;
rn('./'+arg,'./old_'+arg);
mk('./'+arg); mk('./'+arg);
} }
console.log("unbuild:", arg+'.js')
var f = read(arg+'.js'); var f = read(arg+'.js');
var code = next(f); var code = next(f);
@ -81,7 +92,7 @@ var undent = function(code, n){
code = next("/* UNBUILD */"); code = next("/* UNBUILD */");
if('gun' === arg){ if('gun' === g){
write('src/polyfill/unbuild.js', undent(code, 1)); write('src/polyfill/unbuild.js', undent(code, 1));
arg = ''; arg = '';
} }
@ -92,7 +103,20 @@ var undent = function(code, n){
var file = path(arg); var file = path(arg);
if(!file){ return } if(!file){ return }
code = code.replace(/\bUSE\(/g, 'require('); code = code.replace(/\bUSE\(/g, 'require(');
write(file, undent(code)); code = undent(code);
var rcode;
try{ rcode = read('old_'+file); } catch(e){}
// console.log(rcode);
if(rcode != code){
console.log("unbuild:","update",file);
}
write(file, code);
recurse(); recurse();
}()); }());
if('gun' === g){
rm('./old_src');
}else{
rm('./old_'+g);
}
}()); }());

View File

@ -8,7 +8,6 @@
"ios": "browser.ios.js", "ios": "browser.ios.js",
"android": "browser.android.js", "android": "browser.android.js",
"scripts": { "scripts": {
"prepare": "npm run unbuildSea && npm run unbuild",
"start": "node --prof examples/http.js", "start": "node --prof examples/http.js",
"debug": "node --prof-process --preprocess -j isolate*.log > v8data.json && rm isolate*.log && echo 'drag & drop ./v8data.json into https://mapbox.github.io/flamebearer/'", "debug": "node --prof-process --preprocess -j isolate*.log > v8data.json && rm isolate*.log && echo 'drag & drop ./v8data.json into https://mapbox.github.io/flamebearer/'",
"https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start", "https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start",

View File

@ -66,7 +66,7 @@
at = user._ = root.get('~'+pair.pub)._; at = user._ = root.get('~'+pair.pub)._;
at.opt = upt; at.opt = upt;
// add our credentials in-memory only to our root user instance // add our credentials in-memory only to our root user instance
user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair}; user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair.pub};
at.sea = act.pair; at.sea = act.pair;
cat.ing = false; cat.ing = false;
try{if(pass && u == (obj_ify(cat.root.graph['~'+pair.pub].auth)||'')[':']){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle! try{if(pass && u == (obj_ify(cat.root.graph['~'+pair.pub].auth)||'')[':']){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle!
@ -74,7 +74,7 @@
if(SEA.window && ((gun.back('user')._).opt||opt).remember){ if(SEA.window && ((gun.back('user')._).opt||opt).remember){
// TODO: this needs to be modular. // TODO: this needs to be modular.
try{var sS = {}; try{var sS = {};
sS = window.sessionStorage; sS = window.sessionStorage; // TODO: FIX BUG putting on `.is`!
sS.recall = true; sS.recall = true;
sS.pair = JSON.stringify(pair); // auth using pair is more reliable than alias/pass sS.pair = JSON.stringify(pair); // auth using pair is more reliable than alias/pass
}catch(e){} }catch(e){}

View File

@ -2,7 +2,7 @@
var u; var u;
if(u+''== typeof btoa){ if(u+''== typeof btoa){
if(u+'' == typeof Buffer){ if(u+'' == typeof Buffer){
try{ global.Buffer = require("buffer", 1).Buffer }catch(e){ console.log("Please add `buffer` to your package.json!") } try{ global.Buffer = require("buffer", 1).Buffer }catch(e){ console.log("Please `npm install buffer` or add it to your package.json !") }
} }
global.btoa = function(data){ return Buffer.from(data, "binary").toString("base64") }; global.btoa = function(data){ return Buffer.from(data, "binary").toString("base64") };
global.atob = function(data){ return Buffer.from(data, "base64").toString("binary") }; global.atob = function(data){ return Buffer.from(data, "base64").toString("binary") };

View File

@ -3,23 +3,21 @@
// This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph // This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph
SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try { SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try {
/* /*
The Certify Protocol was made out of love by a Vietnamese code enthusiast. Vietnamese people around the world deserve respect!
IMPORTANT: A Certificate is like a Signature. No one knows who (authority) created/signed a cert until you put it into their graph. IMPORTANT: A Certificate is like a Signature. No one knows who (authority) created/signed a cert until you put it into their graph.
"certificants": '*' or a String (Bob.pub) || an Object that contains "pub" as a key || an array of [object || string]. These people will have the rights. "certificants": '*' or a String (Bob.pub) || an Object that contains "pub" as a key || an array of [object || string]. These people will have the rights.
"policy": A string ('inbox'), or a RAD/LEX object {'*': 'inbox'}, or an Array of RAD/LEX objects or strings. RAD/LEX object can contain key "?" with indexOf("*") > -1 to force key equals certificant pub. This rule is used to check against soul+'/'+key using Gun.text.match or String.match. "policy": A string ('inbox'), or a RAD/LEX object {'*': 'inbox'}, or an Array of RAD/LEX objects or strings. RAD/LEX object can contain key "?" with indexOf("*") > -1 to force key equals certificant pub. This rule is used to check against soul+'/'+key using Gun.text.match or String.match.
"authority": Key pair or priv of the certificate authority. "authority": Key pair or priv of the certificate authority.
"cb": A callback function after all things are done. "cb": A callback function after all things are done.
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.blacklist is set, SEA will look for blacklist before syncing. "opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.block is set, SEA will look for block before syncing.
*/ */
console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.') console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.')
certificants = (() => { certificants = (() => {
var data = [] var data = []
if (certificants) { if (certificants) {
if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') !== -1) return '*' if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') > -1) return '*'
if (typeof certificants === 'string') { if (typeof certificants === 'string') return certificants
return certificants
}
if (Array.isArray(certificants)) { if (Array.isArray(certificants)) {
if (certificants.length === 1 && certificants[0]) return typeof certificants[0] === 'object' && certificants[0].pub ? certificants[0].pub : typeof certificants[0] === 'string' ? certificants[0] : null if (certificants.length === 1 && certificants[0]) return typeof certificants[0] === 'object' && certificants[0].pub ? certificants[0].pub : typeof certificants[0] === 'string' ? certificants[0] : null
certificants.map(certificant => { certificants.map(certificant => {
@ -31,7 +29,7 @@
if (typeof certificants === 'object' && certificants.pub) return certificants.pub if (typeof certificants === 'object' && certificants.pub) return certificants.pub
return data.length > 0 ? data : null return data.length > 0 ? data : null
} }
return null return
})() })()
if (!certificants) return console.log("No certificant found.") if (!certificants) return console.log("No certificant found.")
@ -39,8 +37,11 @@
const expiry = opt.expiry && (typeof opt.expiry === 'number' || typeof opt.expiry === 'string') ? parseFloat(opt.expiry) : null const expiry = opt.expiry && (typeof opt.expiry === 'number' || typeof opt.expiry === 'string') ? parseFloat(opt.expiry) : null
const readPolicy = (policy || {}).read ? policy.read : null const readPolicy = (policy || {}).read ? policy.read : null
const writePolicy = (policy || {}).write ? policy.write : typeof policy === 'string' || Array.isArray(policy) || policy["+"] || policy["#"] || policy["."] || policy["="] || policy["*"] || policy[">"] || policy["<"] ? policy : null const writePolicy = (policy || {}).write ? policy.write : typeof policy === 'string' || Array.isArray(policy) || policy["+"] || policy["#"] || policy["."] || policy["="] || policy["*"] || policy[">"] || policy["<"] ? policy : null
const readBlacklist = ((opt || {}).blacklist || {}).read && (typeof opt.blacklist.read === 'string' || opt.blacklist.read['#']) ? opt.blacklist.read : null // The "blacklist" feature is now renamed to "block". Why ? BECAUSE BLACK LIVES MATTER!
const writeBlacklist = typeof (opt || {}).blacklist === 'string' || (((opt || {}).blacklist || {}).write || {})['#'] ? opt.blacklist : ((opt || {}).blacklist || {}).write && (typeof opt.blacklist.write === 'string' || opt.blacklist.write['#']) ? opt.blacklist.write : null // We can now use 3 keys: block, blacklist, ban
const block = (opt || {}).block || (opt || {}).blacklist || (opt || {}).ban || {}
const readBlock = block.read && (typeof block.read === 'string' || (block.read || {})['#']) ? block.read : null
const writeBlock = typeof block === 'string' ? block : block.write && (typeof block.write === 'string' || block.write['#']) ? block.write : null
if (!readPolicy && !writePolicy) return console.log("No policy found.") if (!readPolicy && !writePolicy) return console.log("No policy found.")
@ -50,8 +51,8 @@
...(expiry ? {e: expiry} : {}), // inject expiry if possible ...(expiry ? {e: expiry} : {}), // inject expiry if possible
...(readPolicy ? {r: readPolicy } : {}), // "r" stands for read, which means read permission. ...(readPolicy ? {r: readPolicy } : {}), // "r" stands for read, which means read permission.
...(writePolicy ? {w: writePolicy} : {}), // "w" stands for write, which means write permission. ...(writePolicy ? {w: writePolicy} : {}), // "w" stands for write, which means write permission.
...(readBlacklist ? {rb: readBlacklist} : {}), // inject READ blacklist if possible ...(readBlock ? {rb: readBlock} : {}), // inject READ block if possible
...(writeBlacklist ? {wb: writeBlacklist} : {}), // inject WRITE blacklist if possible ...(writeBlock ? {wb: writeBlock} : {}), // inject WRITE block if possible
}) })
const certificate = await SEA.sign(data, authority, null, {raw:1}) const certificate = await SEA.sign(data, authority, null, {raw:1})

View File

@ -80,7 +80,7 @@
if(!act.h.ok || !act.i.ok){ return } if(!act.h.ok || !act.i.ok){ return }
cat.ing = false; cat.ing = false;
cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
if(noop === cb){ pair? gun.auth(pair) : gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up. if(noop === cb){ pair ? gun.auth(pair) : gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
} }
root.get('~@'+alias).once(act.a); root.get('~@'+alias).once(act.a);
return gun; return gun;

View File

@ -100,12 +100,12 @@
if ((String.match(path, lex['#']) && String.match(key, lex['.'])) || (!lex['.'] && String.match(path, lex['#'])) || (!lex['#'] && String.match(key, lex['.'])) || String.match((path ? path + '/' + key : key), lex['#'] || lex)) { if ((String.match(path, lex['#']) && String.match(key, lex['.'])) || (!lex['.'] && String.match(path, lex['#'])) || (!lex['#'] && String.match(key, lex['.'])) || String.match((path ? path + '/' + key : key), lex['#'] || lex)) {
// is Certificant forced to present in Path // is Certificant forced to present in Path
if (lex['+'] && lex['+'].indexOf('*') > -1 && path && path.indexOf(certificant) == -1 && key.indexOf(certificant) == -1) return no(`Path "${path}" or key "${key}" must contain string "${certificant}".`) if (lex['+'] && lex['+'].indexOf('*') > -1 && path && path.indexOf(certificant) == -1 && key.indexOf(certificant) == -1) return no(`Path "${path}" or key "${key}" must contain string "${certificant}".`)
// path is allowed, but is there any WRITE blacklist? Check it out // path is allowed, but is there any WRITE block? Check it out
if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE blacklist if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE block
var root = at.$.back(-1) var root = eve.as.root.$.back(-1)
if (typeof data.wb === 'string' && '~' !== data.wb.slice(0, 1)) root = root.get('~' + pub) if (typeof data.wb === 'string' && '~' !== data.wb.slice(0, 1)) root = root.get('~' + pub)
return root.get(data.wb).get(certificant).once(value => { return root.get(data.wb).get(certificant).once(value => { // TODO: INTENT TO DEPRECATE.
if (value && (value === 1 || value === true)) return no("Certificant blacklisted.") if (value && (value === 1 || value === true)) return no(`Certificant ${certificant} blocked.`)
return cb(data) return cb(data)
}) })
} }

View File

@ -7,7 +7,7 @@
if(SEA.window){ if(SEA.window){
try{ try{
var sS = {}; var sS = {};
sS = window.sessionStorage; sS = window.sessionStorage; // TODO: FIX BUG putting on `.is`!
if(sS){ if(sS){
(root._).opt.remember = true; (root._).opt.remember = true;
((gun.back('user')._).opt||opt).remember = true; ((gun.back('user')._).opt||opt).remember = true;

View File

@ -40,7 +40,7 @@
api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
} }
catch(e){ catch(e){
console.log("Please add `@peculiar/webcrypto` to your package.json!"); console.log("Please `npm install @peculiar/webcrypto` or add it to your package.json !");
}} }}
module.exports = api module.exports = api

View File

@ -40,7 +40,7 @@
}}); }});
module.exports = SEA.verify; module.exports = SEA.verify;
// legacy & ossl leak mitigation: // legacy & ossl memory leak mitigation:
var knownKeys = {}; var knownKeys = {};
var keyForPair = SEA.opt.slow_leak = pair => { var keyForPair = SEA.opt.slow_leak = pair => {

View File

@ -7,13 +7,18 @@ if(!store){
Gun.log("Warning: No localStorage exists to persist data to!"); Gun.log("Warning: No localStorage exists to persist data to!");
store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}};
} }
var parse = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } }
var json = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } }
Gun.on('create', function lg(root){ Gun.on('create', function lg(root){
this.to.next(root); this.to.next(root);
var opt = root.opt, graph = root.graph, acks = [], disk, to; var opt = root.opt, graph = root.graph, acks = [], disk, to, size, stop;
if(false === opt.localStorage){ return } if(false === opt.localStorage){ return }
opt.prefix = opt.file || 'gun/'; opt.prefix = opt.file || 'gun/';
try{ disk = lg[opt.prefix] = lg[opt.prefix] || JSON.parse(store.getItem(opt.prefix)) || {}; // TODO: Perf! This will block, should we care, since limited to 5MB anyways? try{ disk = lg[opt.prefix] = lg[opt.prefix] || JSON.parse(size = store.getItem(opt.prefix)) || {}; // TODO: Perf! This will block, should we care, since limited to 5MB anyways?
}catch(e){ disk = lg[opt.prefix] = {}; } }catch(e){ disk = lg[opt.prefix] = {}; }
size = (size||'').length;
root.on('get', function(msg){ root.on('get', function(msg){
this.to.next(msg); this.to.next(msg);
@ -31,24 +36,30 @@ Gun.on('create', function lg(root){
root.on('put', function(msg){ root.on('put', function(msg){
this.to.next(msg); // remember to call next middleware adapter this.to.next(msg); // remember to call next middleware adapter
var put = msg.put, soul = put['#'], key = put['.'], tmp; // pull data off wire envelope var put = msg.put, soul = put['#'], key = put['.'], id = msg['#'], tmp; // pull data off wire envelope
disk[soul] = Gun.state.ify(disk[soul], key, put['>'], put[':'], soul); // merge into disk object disk[soul] = Gun.state.ify(disk[soul], key, put['>'], put[':'], soul); // merge into disk object
if(!msg['@']){ acks.push(msg['#']) } // then ack any non-ack write. // TODO: use batch id. if(stop && size > (4999880)){ root.on('in', {'@': id, err: "localStorage max!"}); return; }
if(!msg['@']){ acks.push(id) } // then ack any non-ack write. // TODO: use batch id.
if(to){ return } if(to){ return }
//flush();return; to = setTimeout(flush, 9+(size / 333)); // 0.1MB = 0.3s, 5MB = 15s
to = setTimeout(flush, opt.wait || 1); // that gets saved as a whole to disk every 1ms
}); });
function flush(){ function flush(){
if(!acks.length && ((setTimeout.turn||'').s||'').length){ setTimeout(flush,99); return; } // defer if "busy" && no saves.
var err, ack = acks; clearTimeout(to); to = false; acks = []; var err, ack = acks; clearTimeout(to); to = false; acks = [];
try{store.setItem(opt.prefix, JSON.stringify(disk)); json(disk, function(err, tmp){
}catch(e){ try{!err && store.setItem(opt.prefix, tmp);
Gun.log((err = (e || "localStorage failure")) + " Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"); }catch(e){ err = stop = e || "localStorage failure" }
root.on('localStorage:error', {err: err, get: opt.prefix, put: disk}); if(err){
} Gun.log(err + " Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install");
if(!err && !Object.empty(opt.peers)){ return } // only ack if there are no peers. // Switch this to probabilistic mode root.on('localStorage:error', {err: err, get: opt.prefix, put: disk});
setTimeout.each(ack, function(id){ }
root.on('in', {'@': id, err: err, ok: 0}); // localStorage isn't reliable, so make its `ok` code be a low number. size = tmp.length;
});
if(!err && !Object.empty(opt.peers)){ return } // only ack if there are no peers. // Switch this to probabilistic mode
setTimeout.each(ack, function(id){
root.on('in', {'@': id, err: err, ok: 0}); // localStorage isn't reliable, so make its `ok` code be a low number.
},0,99);
})
} }
}); });

View File

@ -1,6 +1,11 @@
require('./shim'); require('./shim');
var noop = function(){}
var parse = JSON.parseAsync || function(t,cb,r){ var u, d = +new Date; try{ cb(u, JSON.parse(t,r), json.sucks(+new Date - d)) }catch(e){ cb(e) } }
var json = JSON.stringifyAsync || function(v,cb,r,s){ var u, d = +new Date; try{ cb(u, JSON.stringify(v,r,s), json.sucks(+new Date - d)) }catch(e){ cb(e) } }
json.sucks = function(d){ if(d > 99){ console.log("Warning: JSON blocking CPU detected. Add `gun/lib/yson.js` to fix."); json.sucks = noop } }
function Mesh(root){ function Mesh(root){
var mesh = function(){}; var mesh = function(){};
var opt = root.opt || {}; var opt = root.opt || {};
@ -10,8 +15,6 @@ function Mesh(root){
opt.pack = opt.pack || (opt.max * 0.01 * 0.01); opt.pack = opt.pack || (opt.max * 0.01 * 0.01);
opt.puff = opt.puff || 9; // IDEA: do a start/end benchmark, divide ops/result. opt.puff = opt.puff || 9; // IDEA: do a start/end benchmark, divide ops/result.
var puff = setTimeout.turn || setTimeout; var puff = setTimeout.turn || setTimeout;
var parse = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } }
var json = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } }
var dup = root.dup, dup_check = dup.check, dup_track = dup.track; var dup = root.dup, dup_check = dup.check, dup_track = dup.track;
@ -38,7 +41,7 @@ function Mesh(root){
var P = opt.puff; var P = opt.puff;
(function go(){ (function go(){
var S = +new Date; var S = +new Date;
var i = 0, m; while(i < P && (m = msg[i++])){ hear(m, peer) } var i = 0, m; while(i < P && (m = msg[i++])){ mesh.hear(m, peer) }
msg = msg.slice(i); // slicing after is faster than shifting during. msg = msg.slice(i); // slicing after is faster than shifting during.
console.STAT && console.STAT(S, +new Date - S, 'hear loop'); console.STAT && console.STAT(S, +new Date - S, 'hear loop');
flush(peer); // force send all synchronously batched acks. flush(peer); // force send all synchronously batched acks.
@ -90,7 +93,6 @@ function Mesh(root){
mesh.leap = mesh.last = null; // warning! mesh.leap could be buggy. mesh.leap = mesh.last = null; // warning! mesh.leap could be buggy.
} }
var tomap = function(k,i,m){m(k,true)}; var tomap = function(k,i,m){m(k,true)};
var noop = function(){};
hear.c = hear.d = 0; hear.c = hear.d = 0;
;(function(){ ;(function(){
@ -105,7 +107,7 @@ function Mesh(root){
console.STAT && console.STAT(S, +new Date - S, 'say json+hash'); console.STAT && console.STAT(S, +new Date - S, 'say json+hash');
msg._.$put = t; msg._.$put = t;
msg['##'] = h; msg['##'] = h;
say(msg, peer); mesh.say(msg, peer);
delete msg._.$put; delete msg._.$put;
}, sort); }, sort);
} }
@ -147,7 +149,7 @@ function Mesh(root){
loop = 1; var wr = meta.raw; meta.raw = raw; // quick perf hack loop = 1; var wr = meta.raw; meta.raw = raw; // quick perf hack
var i = 0, p; while(i < 9 && (p = (pl||'')[i++])){ var i = 0, p; while(i < 9 && (p = (pl||'')[i++])){
if(!(p = ps[p])){ continue } if(!(p = ps[p])){ continue }
say(msg, p); mesh.say(msg, p);
} }
meta.raw = wr; loop = 0; meta.raw = wr; loop = 0;
pl = pl.slice(i); // slicing after is faster than shifting during. pl = pl.slice(i); // slicing after is faster than shifting during.
@ -221,7 +223,7 @@ function Mesh(root){
function res(err, raw){ function res(err, raw){
if(err){ return } // TODO: Handle!! if(err){ return } // TODO: Handle!!
meta.raw = raw; //if(meta && (raw||'').length < (999 * 99)){ meta.raw = raw } // HNPERF: If string too big, don't keep in memory. meta.raw = raw; //if(meta && (raw||'').length < (999 * 99)){ meta.raw = raw } // HNPERF: If string too big, don't keep in memory.
say(msg, peer); mesh.say(msg, peer);
} }
} }
}()); }());
@ -239,7 +241,6 @@ function Mesh(root){
} }
// for now - find better place later. // for now - find better place later.
function send(raw, peer){ try{ function send(raw, peer){ try{
//console.log('SAY:', peer.id, (raw||'').slice(0,250), ((raw||'').length / 1024 / 1024).toFixed(4));
var wire = peer.wire; var wire = peer.wire;
if(peer.say){ if(peer.say){
peer.say(raw); peer.say(raw);
@ -253,7 +254,8 @@ function Mesh(root){
}} }}
mesh.hi = function(peer){ mesh.hi = function(peer){
var tmp = peer.wire || {}; var wire = peer.wire, tmp;
if(!wire){ mesh.wire((peer.length && {url: peer, id: peer}) || peer); return }
if(peer.id){ if(peer.id){
opt.peers[peer.url || peer.id] = peer; opt.peers[peer.url || peer.id] = peer;
} else { } else {
@ -262,7 +264,7 @@ function Mesh(root){
delete dup.s[peer.last]; // IMPORTANT: see https://gun.eco/docs/DAM#self delete dup.s[peer.last]; // IMPORTANT: see https://gun.eco/docs/DAM#self
} }
peer.met = peer.met || +(new Date); peer.met = peer.met || +(new Date);
if(!tmp.hied){ root.on(tmp.hied = 'hi', peer) } if(!wire.hied){ root.on(wire.hied = 'hi', peer) }
// @rogowski I need this here by default for now to fix go1dfish's bug // @rogowski I need this here by default for now to fix go1dfish's bug
tmp = peer.queue; peer.queue = []; tmp = peer.queue; peer.queue = [];
setTimeout.each(tmp||[],function(msg){ setTimeout.each(tmp||[],function(msg){

View File

@ -63,13 +63,21 @@ Gun.chain.once = function(cb, opt){ opt = opt || {}; // avoid rewriting
if('string' == typeof tmp){ return } // TODO: BUG? Will this always load? if('string' == typeof tmp){ return } // TODO: BUG? Will this always load?
clearTimeout((cat.one||'')[id]); // clear "not found" since they only get set on cat. clearTimeout((cat.one||'')[id]); // clear "not found" since they only get set on cat.
clearTimeout(one[id]); one[id] = setTimeout(once, opt.wait||99); // TODO: Bug? This doesn't handle plural chains. clearTimeout(one[id]); one[id] = setTimeout(once, opt.wait||99); // TODO: Bug? This doesn't handle plural chains.
function once(){ function once(f){
if(!at.has && !at.soul){ at = {put: data, get: key} } // handles non-core messages. if(!at.has && !at.soul){ at = {put: data, get: key} } // handles non-core messages.
if(u === (tmp = at.put)){ tmp = ((msg.$$||'')._||'').put } if(u === (tmp = at.put)){ tmp = ((msg.$$||'')._||'').put }
if('string' == typeof Gun.valid(tmp)){ tmp = root.$.get(tmp)._.put; if(tmp === u){return} } if('string' == typeof Gun.valid(tmp)){
tmp = root.$.get(tmp)._.put;
if(tmp === u && !f){
one[id] = setTimeout(function(){ once(1) }, opt.wait||99); // TODO: Quick fix. Maybe use ack count for more predictable control?
return
}
}
//console.log("AND VANISHED", data);
if(eve.stun){ return } if('' === one[id]){ return } one[id] = ''; if(eve.stun){ return } if('' === one[id]){ return } one[id] = '';
if(cat.soul || cat.has){ eve.off() } // TODO: Plural chains? // else { ?.off() } // better than one check? if(cat.soul || cat.has){ eve.off() } // TODO: Plural chains? // else { ?.off() } // better than one check?
cb.call($, tmp, at.get); cb.call($, tmp, at.get);
clearTimeout(one[id]); // clear "not found" since they only get set on cat. // TODO: This was hackily added, is it necessary or important? Probably not, in future try removing this. Was added just as a safety for the `&& !f` check.
}; };
}, {on: 1}); }, {on: 1});
return gun; return gun;

View File

@ -28,12 +28,13 @@ Gun.chain.put = function(data, cb, as){ // I rewrote it :)
} }
k && (to.path || (to.path = [])).push(k); k && (to.path || (to.path = [])).push(k);
if(!(v = valid(d)) && !(g = Gun.is(d))){ if(!(v = valid(d)) && !(g = Gun.is(d))){
if(!Object.plain(d)){ (as.ack||noop).call(as, as.out = {err: as.err = Gun.log("Invalid data: " + ((d && (tmp = d.constructor) && tmp.name) || typeof d) + " at " + (as.via.back(function(at){at.get && tmp.push(at.get)}, tmp = []) || tmp.join('.'))+'.'+(to.path||[]).join('.'))}); as.ran(as); return } if(!Object.plain(d)){ ran.err(as, "Invalid data: "+ check(d) +" at " + (as.via.back(function(at){at.get && tmp.push(at.get)}, tmp = []) || tmp.join('.'))+'.'+(to.path||[]).join('.')); return }
var seen = as.seen || (as.seen = []), i = seen.length; var seen = as.seen || (as.seen = []), i = seen.length;
while(i--){ if(d === (tmp = seen[i]).it){ v = d = tmp.link; break } } while(i--){ if(d === (tmp = seen[i]).it){ v = d = tmp.link; break } }
} }
if(k && v){ at.node = state_ify(at.node, k, s, d) } // handle soul later. if(k && v){ at.node = state_ify(at.node, k, s, d) } // handle soul later.
else { else {
if(!as.seen){ ran.err(as, "Data at root of graph must be a node (an object)."); return }
as.seen.push(cat = {it: d, link: {}, todo: g? [] : Object.keys(d).sort().reverse(), path: (to.path||[]).slice(), up: at}); // Any perf reasons to CPU schedule this .keys( ? as.seen.push(cat = {it: d, link: {}, todo: g? [] : Object.keys(d).sort().reverse(), path: (to.path||[]).slice(), up: at}); // Any perf reasons to CPU schedule this .keys( ?
at.node = state_ify(at.node, k, s, cat.link); at.node = state_ify(at.node, k, s, cat.link);
!g && cat.todo.length && to.push(cat); !g && cat.todo.length && to.push(cat);
@ -113,11 +114,14 @@ function ran(as){
setTimeout.each(Object.keys(stun = stun.add||''), function(cb){ if(cb = stun[cb]){cb()} }); // resume the stunned reads // Any perf reasons to CPU schedule this .keys( ? setTimeout.each(Object.keys(stun = stun.add||''), function(cb){ if(cb = stun[cb]){cb()} }); // resume the stunned reads // Any perf reasons to CPU schedule this .keys( ?
}).hatch = tmp; // this is not official yet ^ }).hatch = tmp; // this is not official yet ^
//console.log(1, "PUT", as.run, as.graph); //console.log(1, "PUT", as.run, as.graph);
(as.via._).on('out', {put: as.out = as.graph, opt: as.opt, '#': ask, _: tmp}); (as.via._).on('out', {put: as.out = as.graph, ok: as.ok || as.opt, opt: as.opt, '#': ask, _: tmp});
}; ran.end = function(stun,root){ }; ran.end = function(stun,root){
stun.end = noop; // like with the earlier id, cheaper to make this flag a function so below callbacks do not have to do an extra type check. stun.end = noop; // like with the earlier id, cheaper to make this flag a function so below callbacks do not have to do an extra type check.
if(stun.the.to === stun && stun === stun.the.last){ delete root.stun } if(stun.the.to === stun && stun === stun.the.last){ delete root.stun }
stun.off(); stun.off();
}; ran.err = function(as, err){
(as.ack||noop).call(as, as.out = { err: as.err = Gun.log(err) });
as.ran(as);
} }
function get(as){ function get(as){
@ -141,6 +145,7 @@ function get(as){
return; return;
} }
} }
function check(d, tmp){ return ((d && (tmp = d.constructor) && tmp.name) || typeof d) }
var u, empty = {}, noop = function(){}, turn = setTimeout.turn, valid = Gun.valid, state_ify = Gun.state.ify; var u, empty = {}, noop = function(){}, turn = setTimeout.turn, valid = Gun.valid, state_ify = Gun.state.ify;
var iife = function(fn,as){fn.call(as||empty)} var iife = function(fn,as){fn.call(as||empty)}

View File

@ -39,6 +39,7 @@ Gun.ask = require('./ask');
return gun; return gun;
} }
function universe(msg){ function universe(msg){
// TODO: BUG! msg.out = null being set!
//if(!F){ var eve = this; setTimeout(function(){ universe.call(eve, msg,1) },Math.random() * 100);return; } // ADD F TO PARAMS! //if(!F){ var eve = this; setTimeout(function(){ universe.call(eve, msg,1) },Math.random() * 100);return; } // ADD F TO PARAMS!
if(!msg){ return } if(!msg){ return }
if(msg.out === universe){ this.to.next(msg); return } if(msg.out === universe){ this.to.next(msg); return }
@ -70,7 +71,7 @@ Gun.ask = require('./ask');
} }
ctx.latch = root.hatch; ctx.match = root.hatch = []; ctx.latch = root.hatch; ctx.match = root.hatch = [];
var put = msg.put; var put = msg.put;
var DBG = ctx.DBG = msg.DBG, S = +new Date; var DBG = ctx.DBG = msg.DBG, S = +new Date; CT = CT || S;
if(put['#'] && put['.']){ /*root && root.on('put', msg);*/ return } // TODO: BUG! This needs to call HAM instead. if(put['#'] && put['.']){ /*root && root.on('put', msg);*/ return } // TODO: BUG! This needs to call HAM instead.
DBG && (DBG.p = S); DBG && (DBG.p = S);
ctx['#'] = msg['#']; ctx['#'] = msg['#'];
@ -107,13 +108,14 @@ Gun.ask = require('./ask');
if(!valid(val)){ err = ERR+cut(key)+"on"+cut(soul)+"bad "+(typeof val)+cut(val); break } if(!valid(val)){ err = ERR+cut(key)+"on"+cut(soul)+"bad "+(typeof val)+cut(val); break }
//ctx.all++; //ctx.ack[soul+key] = ''; //ctx.all++; //ctx.ack[soul+key] = '';
ham(val, key, soul, state, msg); ham(val, key, soul, state, msg);
++C; // courtesy count;
} }
if((kl = kl.slice(i)).length){ turn(pop); return } if((kl = kl.slice(i)).length){ turn(pop); return }
++ni; kl = null; pop(o); ++ni; kl = null; pop(o);
}()); }());
} Gun.on.put = put; } Gun.on.put = put;
// TODO: MARK!!! clock below, reconnect sync, SEA certify wire merge, User.auth taking multiple times, // msg put, put, say ack, hear loop... // TODO: MARK!!! clock below, reconnect sync, SEA certify wire merge, User.auth taking multiple times, // msg put, put, say ack, hear loop...
// WASIS BUG! first .once( undef 2nd good. .off othe rpeople: .open // WASIS BUG! local peer not ack. .off other people: .open
function ham(val, key, soul, state, msg){ function ham(val, key, soul, state, msg){
var ctx = msg._||'', root = ctx.root, graph = root.graph, lot, tmp; var ctx = msg._||'', root = ctx.root, graph = root.graph, lot, tmp;
var vertex = graph[soul] || empty, was = state_is(vertex, key, 1), known = vertex[key]; var vertex = graph[soul] || empty, was = state_is(vertex, key, 1), known = vertex[key];
@ -132,6 +134,7 @@ Gun.ask = require('./ask');
} }
ctx.stun++; // TODO: 'forget' feature in SEA tied to this, bad approach, but hacked in for now. Any changes here must update there. ctx.stun++; // TODO: 'forget' feature in SEA tied to this, bad approach, but hacked in for now. Any changes here must update there.
var aid = msg['#']+ctx.all++, id = {toString: function(){ return aid }, _: ctx}; id.toJSON = id.toString; // this *trick* makes it compatible between old & new versions. var aid = msg['#']+ctx.all++, id = {toString: function(){ return aid }, _: ctx}; id.toJSON = id.toString; // this *trick* makes it compatible between old & new versions.
root.dup.track(id)['#'] = msg['#']; // fixes new OK acks for RPC like RTC.
DBG && (DBG.ph = DBG.ph || +new Date); DBG && (DBG.ph = DBG.ph || +new Date);
root.on('put', {'#': id, '@': msg['@'], put: {'#': soul, '.': key, ':': val, '>': state}, _: ctx}); root.on('put', {'#': id, '@': msg['@'], put: {'#': soul, '.': key, ':': val, '>': state}, _: ctx});
} }
@ -156,16 +159,23 @@ Gun.ask = require('./ask');
if(!(msg = ctx.msg) || ctx.err || msg.err){ return } if(!(msg = ctx.msg) || ctx.err || msg.err){ return }
msg.out = universe; msg.out = universe;
ctx.root.on('out', msg); ctx.root.on('out', msg);
CF(); // courtesy check;
} }
function ack(msg){ // aggregate ACKs. function ack(msg){ // aggregate ACKs.
var id = msg['@'] || '', ctx; var id = msg['@'] || '', ctx;
if(!(ctx = id._)){ return } if(!(ctx = id._)){
var dup = (dup = msg.$) && (dup = dup._) && (dup = dup.root) && (dup = dup.dup);
if(!(dup = dup.check(id))){ return }
msg['@'] = dup['#'] || msg['@'];
return;
}
ctx.acks = (ctx.acks||0) + 1; ctx.acks = (ctx.acks||0) + 1;
if(ctx.err = msg.err){ if(ctx.err = msg.err){
msg['@'] = ctx['#']; msg['@'] = ctx['#'];
fire(ctx); // TODO: BUG? How it skips/stops propagation of msg if any 1 item is error, this would assume a whole batch/resync has same malicious intent. fire(ctx); // TODO: BUG? How it skips/stops propagation of msg if any 1 item is error, this would assume a whole batch/resync has same malicious intent.
} }
if(!ctx.stop && !ctx.crack){ ctx.crack = ctx.match && ctx.match.push(function(){back(ctx)}) } // handle synchronous acks if(!ctx.stop && !ctx.crack){ ctx.crack = ctx.match && ctx.match.push(function(){back(ctx)}) } // handle synchronous acks. NOTE: If a storage peer ACKs synchronously then the PUT loop has not even counted up how many items need to be processed, so ctx.STOP flags this and adds only 1 callback to the end of the PUT loop.
back(ctx); back(ctx);
} }
function back(ctx){ function back(ctx){
@ -177,6 +187,7 @@ Gun.ask = require('./ask');
var ERR = "Error: Invalid graph!"; var ERR = "Error: Invalid graph!";
var cut = function(s){ return " '"+(''+s).slice(0,9)+"...' " } var cut = function(s){ return " '"+(''+s).slice(0,9)+"...' " }
var L = JSON.stringify, MD = 2147483647, State = Gun.state; var L = JSON.stringify, MD = 2147483647, State = Gun.state;
var C = 0, CT, CF = function(){if(C>999 && (C/-(CT - (CT = +new Date))>1)){Gun.window && console.log("Warning: You're syncing 1K+ records a second, faster than DOM can update - consider limiting query.");CF=function(){C=0}}};
}()); }());
@ -250,19 +261,20 @@ Gun.ask = require('./ask');
if(!Object.plain(opt)){ opt = {} } if(!Object.plain(opt)){ opt = {} }
if(!Object.plain(at.opt)){ at.opt = opt } if(!Object.plain(at.opt)){ at.opt = opt }
if('string' == typeof tmp){ tmp = [tmp] } if('string' == typeof tmp){ tmp = [tmp] }
if(!Object.plain(at.opt.peers)){ at.opt.peers = {}}
if(tmp instanceof Array){ if(tmp instanceof Array){
if(!Object.plain(at.opt.peers)){ at.opt.peers = {}} opt.peers = {};
tmp.forEach(function(url){ tmp.forEach(function(url){
var p = {}; p.id = p.url = url; var p = {}; p.id = p.url = url;
at.opt.peers[url] = at.opt.peers[url] || p; opt.peers[url] = at.opt.peers[url] = at.opt.peers[url] || p;
}) })
} }
at.opt.peers = at.opt.peers || {};
obj_each(opt, function each(k){ var v = this[k]; obj_each(opt, function each(k){ var v = this[k];
if((this && this.hasOwnProperty(k)) || 'string' == typeof v || Object.empty(v)){ this[k] = v; return } if((this && this.hasOwnProperty(k)) || 'string' == typeof v || Object.empty(v)){ this[k] = v; return }
if(v && v.constructor !== Object && !(v instanceof Array)){ return } if(v && v.constructor !== Object && !(v instanceof Array)){ return }
obj_each(v, each); obj_each(v, each);
}); });
at.opt.from = opt;
Gun.on('opt', at); Gun.on('opt', at);
at.opt.uuid = at.opt.uuid || function uuid(l){ return Gun.state().toString(36).replace('.','') + String.random(l||12) } at.opt.uuid = at.opt.uuid || function uuid(l){ return Gun.state().toString(36).replace('.','') + String.random(l||12) }
return gun; return gun;

View File

@ -48,8 +48,9 @@ Object.keys = Object.keys || function(o){
} }
;(function(){ // max ~1ms or before stack overflow ;(function(){ // max ~1ms or before stack overflow
var u, sT = setTimeout, l = 0, c = 0, sI = (typeof setImmediate !== ''+u && setImmediate) || sT; // queueMicrotask faster but blocks UI var u, sT = setTimeout, l = 0, c = 0, sI = (typeof setImmediate !== ''+u && setImmediate) || sT; // queueMicrotask faster but blocks UI
sT.hold = sT.hold || 9;
sT.poll = sT.poll || function(f){ //f(); return; // for testing sT.poll = sT.poll || function(f){ //f(); return; // for testing
if((1 >= (+new Date - l)) && c++ < 3333){ f(); return } if((sT.hold >= (+new Date - l)) && c++ < 3333){ f(); return }
sI(function(){ l = +new Date; f() },c=0) sI(function(){ l = +new Date; f() },c=0)
} }
}()); }());

View File

@ -1,3 +1,4 @@
// Valid values are a subset of JSON: null, binary, number (!Infinity), text, // Valid values are a subset of JSON: null, binary, number (!Infinity), text,
// or a soul relation. Arrays need special algorithms to handle concurrency, // or a soul relation. Arrays need special algorithms to handle concurrency,
// so they are not supported directly. Use an extension that supports them if // so they are not supported directly. Use an extension that supports them if
@ -5,10 +6,11 @@
module.exports = function (v) { module.exports = function (v) {
// "deletes", nulling out keys. // "deletes", nulling out keys.
return v === null || return v === null ||
"string" === typeof v || "string" === typeof v ||
"boolean" === typeof v || "boolean" === typeof v ||
// we want +/- Infinity to be, but JSON does not support it, sad face. // we want +/- Infinity to be, but JSON does not support it, sad face.
// can you guess what v === v checks for? ;) // can you guess what v === v checks for? ;)
("number" === typeof v && v != Infinity && v != -Infinity && v === v) || ("number" === typeof v && v != Infinity && v != -Infinity && v === v) ||
(!!v && "string" == typeof v["#"] && Object.keys(v).length === 1 && v["#"]); (!!v && "string" == typeof v["#"] && Object.keys(v).length === 1 && v["#"]);
} }

View File

@ -46,6 +46,7 @@ Gun.on('opt', function(root){
var wait = 2 * 999; var wait = 2 * 999;
function reconnect(peer){ function reconnect(peer){
clearTimeout(peer.defer); clearTimeout(peer.defer);
if(!opt.peers[peer.url]){ return }
if(doc && peer.retry <= 0){ return } if(doc && peer.retry <= 0){ return }
peer.retry = (peer.retry || opt.retry+1 || 60) - ((-peer.tried + (peer.tried = +new Date) < wait*4)?1:0); peer.retry = (peer.retry || opt.retry+1 || 60) - ((-peer.tried + (peer.tried = +new Date) < wait*4)?1:0);
peer.defer = setTimeout(function to(){ peer.defer = setTimeout(function to(){

118
test/bug/1243.js Normal file
View File

@ -0,0 +1,118 @@
/**
* Heroku CLI REQUIRED!
*
* NOTE: Does not work with npm installed heroku-cli
* - Uninstall with: npm uninstall heroku -g
* - Install Heroku with: curl https://cli-assets.heroku.com/install.sh | sh
*
* 1. Login Heroku with: heroku login
* 2. After login you can run test
* like: $ mocha test/bug/1243.js
*
*/
const expect = require('../expect');
const path = require('path');
const http = require("https");
const outputData = false;
function request(hostname, done){
http.get('https://'+hostname+'/',{
timeout: 1000 * 60 * 5
}, (res) => {
done(res.statusCode);
});
}
function spawn(cmd, done) {
const spawn = require('child_process').spawn;
let args = cmd.split(" ");
cmd = args.shift();
const s = spawn(cmd, args);
let stderrOUT = "";
s.stdout.on('data', (data) => {
saveData(data.toString());
});
s.stderr.on('data', (data) => {
saveData(data.toString());
});
function saveData(out){
stderrOUT += out.toString();
if(outputData) console.log(out.toString());
}
s.on('exit', (code) => {
done(stderrOUT);
});
}
function makeid(length) {
let result = '';
let characters = 'abcdefghijklmnopqrstuvwxyz';
let charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
}
return result;
}
describe('Heroku deploy', function() {
const heroku_name = 'test-gun-' + makeid(5);
const dir_cwd = path.join(__dirname, "../..");
it('create herokuapp '+ heroku_name, function(done) {
this.timeout(15 * 1000);
let c = 'heroku create ' + heroku_name;
spawn(c, (stdout) => {
if (stdout.indexOf("done") > -1) {
done();
}
})
})
it('add git remote', function(done) {
this.timeout(15 * 1000);
let c = 'heroku git:remote -a '+ heroku_name;
spawn(c, (stdout) => {
if (stdout.indexOf("set git") > -1) {
done();
}
})
})
it('git push heroku', function(done) {
this.timeout(1000 * 60 * 2); // 2 min
let c = 'git push heroku master --force';
spawn(c, (stdout) => {
if (stdout.indexOf("deployed to Heroku") > -1) {
done();
}
})
})
it('fetch heroku app https', function(done) {
this.timeout(1000 * 60 * 5); // 2 min
request(heroku_name+".herokuapp.com",( statusCode )=>{
expect(statusCode).to.be(200);
done();
})
})
it('destroy herokuapp ' + heroku_name, function(done) {
this.timeout(15 * 1000);
let c = 'heroku apps:destroy ' + heroku_name + ' --confirm=' + heroku_name;
spawn(c, (stdout) => {
if (stdout.indexOf("done") > -1) {
done();
}
})
})
})