diff --git a/README.md b/README.md index 5bb40adc..433ca715 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Currently, [Internet Archive](https://news.ycombinator.com/item?id=17685682) and HackerNoon run GUN in production. -Decentralized alternatives to [Reddit](https://notabug.io/t/whatever/comments/36588a16b9008da4e3f15663c2225e949eca4a15/gpu-bot-test), [YouTube](https://d.tube/), [Wikipedia](https://news.ycombinator.com/item?id=17685682), etc. are already pushing terabytes of daily P2P traffic on GUN. We are a [friendly community](https://gitter.im/amark/gun) creating a free fun future for freedom: +Decentralized alternatives to [Reddit](https://notabug.io/t/whatever/comments/36588a16b9008da4e3f15663c2225e949eca4a15/gpu-bot-test), [YouTube](https://d.tube/), [Wikipedia](https://news.ycombinator.com/item?id=17685682), etc. have already pushed terabytes of daily P2P traffic on GUN. We are a [friendly community](https://gitter.im/amark/gun) creating a free fun future for freedom: @@ -117,7 +117,8 @@ Thanks to:
John Williamson, Robin Bron, Elie Makhoul, -Mike Staub +Mike Staub, +Bradley Matusiak

- Join others in sponsoring code: https://www.patreon.com/gunDB ! @@ -185,28 +186,24 @@ rm -rf *data* ### Additional Cryptography Libraries -To install with npm, first install `npm install gun -S`. -For just the networking layer, import Gun: + > These are only needed for NodeJS, they shim the native Browser WebCrypto API. + +If you want to use [SEA](https://gun.eco/docs/SEA) for `User` auth and security, you will need to install: + +`npm install text-encoding isomorphic-webcrypto --save` + +Then you can require [SEA](https://gun.eco/docs/SEA) without an error: ```javascript -var Gun = require('gun/gun'); +var GUN = require('gun/gun'); +var SEA = require('gun/sea'); ``` -If you also need to install SEA for user auth and crypto, also install some of its dependencies like this: - -`npm install text-encoding @peculiar/webcrypto --save` - -You will need to require it too (it will be automatically added to the Gun object): - -```javascript -var Gun = require('gun/gun'); -var Sea = require('gun/sea'); -``` - - ## Deploy -To quickly spin up a Gun test server for your development team, utilize either [Heroku](http://heroku.com) or [Docker](http://docker.com) or any variant thereof [Dokku](http://dokku.viewdocs.io/dokku/), [Flynn.io](http://flynn.io), [now.sh](https://zeit.co/now), etc. ! + > Note: The default examples that get auto-deployed on `npm start` CDN-ify all GUN files, modules, & storage. + +To quickly spin up a GUN relay peer for your development team, utilize either [Heroku](http://heroku.com), [Docker](http://docker.com), any variant thereof [Dokku](http://dokku.viewdocs.io/dokku/), [Flynn.io](http://flynn.io), [now.sh](https://zeit.co/now), etc. ! Or use all of them so your relays are decentralized too! ### [Heroku](https://www.heroku.com/) @@ -243,6 +240,8 @@ Then visit the deployed app by following the 'view app' button, in your browser. ### [Docker](https://www.docker.com/) + > Warning: Docker image is community contributed and may be old with missing security updates, please check version numbers to compare. + [![Docker Automated buil](https://img.shields.io/docker/automated/gundb/gun.svg)](https://hub.docker.com/r/gundb/gun/) [![](https://images.microbadger.com/badges/image/gundb/gun.svg)](https://microbadger.com/images/gundb/gun "Get your own image badge on microbadger.com") [![Docker Pulls](https://img.shields.io/docker/pulls/gundb/gun.svg)](https://hub.docker.com/r/gundb/gun/) [![Docker Stars](https://img.shields.io/docker/stars/gundb/gun.svg)](https://hub.docker.com/r/gundb/gun/) Pull from the [Docker Hub](https://hub.docker.com/r/gundb/gun/) [![](https://images.microbadger.com/badges/commit/gundb/gun.svg)](https://microbadger.com/images/gundb/gun). Or: diff --git a/axe.js b/axe.js index 825e5e90..8e3b6d4c 100644 --- a/axe.js +++ b/axe.js @@ -129,7 +129,7 @@ }*/ } if((tmp = msg['@']) && (tmp = at.dup.s[tmp]) && (tmp = tmp.it)){ - (tmp = (tmp._||ok)).ack = (tmp.ack || 0) + 1; // count remote ACKs to GET. + (tmp = (tmp._||{})).ack = (tmp.ack || 0) + 1; // count remote ACKs to GET. } to.next(msg); diff --git a/examples/http-external-ws.js b/examples/http-external-ws.js deleted file mode 100644 index c0d909d2..00000000 --- a/examples/http-external-ws.js +++ /dev/null @@ -1,66 +0,0 @@ -var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8765; - -var Gun = require('../'); - -// have to do this before instancing gun(?) -Gun.on('out', function(msg){ - this.to.next( msg ); - msg = JSON.stringify(msg); - gunPeers.forEach( function(peer){ peer.send( msg ) }) -}) - -var gun = Gun({ - file: 'data.json' -}); - -var server = require('http').createServer(function(req, res){ - var insert = ""; - if( req.url.endsWith( "gun.js" ) ) - insert = "../"; - - require('fs').createReadStream(require('path').join(__dirname, insert, req.url)).on('error',function(){ // static files! - res.writeHead(200, {'Content-Type': 'text/html'}); - res.end(require('fs').readFileSync(require('path').join(__dirname, 'index.html'))); // or default to index - }).pipe(res); // stream -}); - -// do not do this to attach server... instead pull websocket provider and use that. -// gun.wsp(server); - -var ws = require( 'ws' ); // default websocket provider gun used... -var WebSocketServer = ws.Server; - -var wss = new WebSocketServer( { - server: server, // 'ws' npm - autoAcceptConnections : false // want to handle the request (websocket npm?) - }); - -wss.on('connection',acceptConnection ) - -var gunPeers = []; // used as a list of connected clients. - -function acceptConnection( connection ) { - // connection.upgradeReq.headers['sec-websocket-protocol'] === (if present) protocol requested by client - // connection.upgradeReq.url === url request - console.log( "connect?", connection.upgradeReq.headers, connection.upgradeReq.url ) - gunPeers.push( connection ); - connection.on( 'error',function(error){console.log( "WebSocket Error:", error) } ); - - connection.on('message', function (msg) { - msg = JSON.parse(msg) - if ("forEach" in msg) msg.forEach(m => gun.on('in', JSON.parse(m))); - else gun.on('in', msg) - }) - - connection.on( 'close', function(reason,desc){ - // gunpeers gone. - var i = gunPeers.findIndex( function(p){return p===connection} ); - if( i >= 0 ) - gunPeers.splice( i, 1 ); - - }) -} - -server.listen(port); - -console.log('Server started on port ' + port + ' with '); diff --git a/examples/install.sh b/examples/install.sh index 8e984fe5..1af60ff7 100644 --- a/examples/install.sh +++ b/examples/install.sh @@ -6,6 +6,7 @@ # Copy paste and run each line into your terminal. # If you are on Windows, http://nodejs.org/download/ has # an installer that will automatically do it for you. +# curl -o- https://raw.githubusercontent.com/amark/gun/master/examples/install.sh | bash #debian/ubuntu su - diff --git a/gun.js b/gun.js index 868e042b..63452fe8 100644 --- a/gun.js +++ b/gun.js @@ -619,7 +619,7 @@ var Type = USE('./type'); function Dup(opt){ var dup = {s:{}}; - opt = opt || {max: 1000, age: 1000 * 9};//1000 * 60 * 2}; + opt = opt || {max: 1000, age: /*1000 * 9};//*/ 1000 * 9 * 3}; dup.check = function(id){ var tmp; if(!(tmp = dup.s[id])){ return false } if(tmp.pass){ return tmp.pass = false } @@ -629,18 +629,17 @@ var it = dup.s[id] || (dup.s[id] = {}); it.was = time_is(); if(pass){ it.pass = true } - if(!dup.to){ - dup.to = setTimeout(function(){ - var now = time_is(); - Type.obj.map(dup.s, function(it, id){ - if(it && opt.age > (now - it.was)){ return } - Type.obj.del(dup.s, id); - }); - dup.to = null; - }, opt.age + 9); - } + if(!dup.to){ dup.to = setTimeout(dup.drop, opt.age + 9) } return it; } + dup.drop = function(age){ + var now = time_is(); + Type.obj.map(dup.s, function(it, id){ + if(it && (age || opt.age) > (now - it.was)){ return } + Type.obj.del(dup.s, id); + }); + dup.to = null; + } return dup; } var time_is = Type.time.is; @@ -702,7 +701,8 @@ return; } dup.track(tmp); - if(!at.ask(msg['@'], msg)){ + if(tmp = msg['@']){ dup.track(tmp) } // HNPERF: Bump original request's liveliness. + if(!at.ask(tmp, msg)){ if(msg.get){ Gun.on.get(msg, gun); //at.on('get', get(msg)); } @@ -721,18 +721,21 @@ ;(function(){ Gun.on.put = function(msg, gun){ var at = gun._, ctx = {$: gun, graph: at.graph, put: {}, map: {}, souls: {}, machine: Gun.state(), ack: msg['@'], cat: at, stop: {}}; + if(!Gun.obj.map(msg.put, perf, ctx)){ return } // HNPERF: performance test, not core code, do not port. if(!Gun.graph.is(msg.put, null, verify, ctx)){ ctx.err = "Error: Invalid graph!" } if(ctx.err){ return at.on('in', {'@': msg['#'], err: Gun.log(ctx.err) }) } obj_map(ctx.put, merge, ctx); if(!ctx.async){ obj_map(ctx.map, map, ctx) } if(u !== ctx.defer){ + var to = ctx.defer - ctx.machine; setTimeout(function(){ Gun.on.put(msg, gun); - }, ctx.defer - ctx.machine); + }, to > MD? MD : to ); // setTimeout Max Defer 32bit :( } if(!ctx.diff){ return } at.on('put', obj_to(msg, {put: ctx.diff})); }; + var MD = 2147483647; function verify(val, key, node, soul){ var ctx = this; var state = Gun.state.is(node, key), tmp; if(!state){ return ctx.err = "Error: No state on '"+key+"' in node '"+soul+"'!" } @@ -798,6 +801,7 @@ (msg.$._).on('in', msg); this.cat.stop = null; // temporary fix till a better solution? } + function perf(node, soul){ if(node !== this.graph[soul]){ return true } } // HNPERF: do not port! Gun.on.get = function(msg, gun){ var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; @@ -811,15 +815,17 @@ // Maybe... in case the in-memory key we have is a local write // we still need to trigger a pull/merge from peers. } else { - node = Gun.obj.copy(node); + node = Gun.window? Gun.obj.copy(node) : node; // HNPERF: If !browser bump Performance? Is this too dangerous to reference root graph? Copy / shallow copy too expensive for big nodes. Gun.obj.to(node); // 1 layer deep copy // Gun.obj.copy(node); // too slow on big nodes } node = Gun.graph.node(node); tmp = (at||empty).ack; + var faith = function(){}; faith.faith = true; // HNPERF: We're testing performance improvement by skipping going through security again, but this should be audited. root.on('in', { '@': msg['#'], how: 'mem', put: node, - $: gun + $: gun, + _: faith }); //if(0 < tmp){ return } root.on('get', msg); @@ -1962,6 +1968,7 @@ var dup = root.dup; + // TODO: somewhere in here caused a out-of-memory crash! How? It is inbound! mesh.hear = function(raw, peer){ if(!raw){ return } var msg, id, hash, tmp = raw[0]; @@ -1990,11 +1997,11 @@ if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } if(msg.DBG_s){ opt.log(+new Date - msg.DBG_s, 'to hear', id) } 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? + dup.track(id, true).it = 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? + dup.track(tmp+hash, true).it = 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) } @@ -2004,9 +2011,9 @@ } return; } - var S; LOG && (S = +new Date); + var S, ST; LOG && (S = +new Date); root.on('in', msg); - LOG && !msg.nts && opt.log(S, +new Date - S, 'msg', msg['#']); + LOG && !msg.nts && (ST = +new Date - S) > 9 && opt.log(S, ST, 'msg', msg['#']); return; } } @@ -2014,6 +2021,7 @@ mesh.hear.c = mesh.hear.d = 0; ;(function(){ + var SMIA = 0; var message; function each(peer){ mesh.say(message, peer) } mesh.say = function(msg, peer){ @@ -2025,24 +2033,28 @@ 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); + raw = mesh.raw(msg); if(hash && (tmp = msg['@'])){ - dup.track(tmp+hash).it = msg; + dup.track(tmp+hash).it = it(msg); if(tmp = (dup.s[tmp]||ok).it){ if(hash === tmp['##']){ return false } tmp['##'] = hash; } } } - LOG && opt.log(S, +new Date - S, 'say prep'); - dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more! + //LOG && opt.log(S, +new Date - S, 'say prep'); + dup.track(id).it = 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 && msg['@']){ + LOG && opt.log(+new Date, ++SMIA, 'total no peer to ack to'); + return false; + } // TODO: Temporary? If ack via trace has been lost, acks will go to all peers, which trashes browser bandwidth. Not relaying the ack will force sender to ask for ack again. Note, this is technically wrong for mesh behavior. if(!peer && mesh.way){ return mesh.way(msg) } if(!peer || !peer.id){ message = msg; if(!Type.obj.is(peer || opt.peers)){ return false } - var S; LOG && (S = +new Date); + //var S; LOG && (S = +new Date); Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. - LOG && opt.log(S, +new Date - S, 'say loop'); + //LOG && opt.log(S, +new Date - S, 'say loop'); return; } if(!peer.wire && mesh.wire){ mesh.wire(peer) } @@ -2079,20 +2091,21 @@ // for now - find better place later. function send(raw, peer){ try{ var wire = peer.wire; - var S; LOG && (S = +new Date); + var S, ST; LOG && (S = +new Date); if(peer.say){ peer.say(raw); } else if(wire.send){ wire.send(raw); } - LOG && opt.log(S, +new Date - S, 'wire send', raw.length); + LOG && (ST = +new Date - S) > 9 && opt.log(S, ST, 'wire send', raw.length); mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! }catch(e){ (peer.queue = peer.queue || []).push(raw); }} ;(function(){ + // TODO: this caused a out-of-memory crash! mesh.raw = function(msg){ // TODO: Clean this up / delete it / move logic out! if(!msg){ return '' } var meta = (msg._) || {}, put, hash, tmp; @@ -2100,7 +2113,7 @@ if(typeof msg === 'string'){ return msg } 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. + to.push(p.url || p.pid || p.id); if(++i > 3){ return true } // limit server, fast fix, improve later! // For "tower" peer, MUST include 6 surrounding ids. // REDUCED THIS TO 3 for temporary relay peer performance, towers still should list neighbors. }); if(i > 1){ msg['><'] = to.join() } } var raw = $(msg); // optimize by reusing put = the JSON.stringify from .hash? @@ -2109,7 +2122,7 @@ raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); //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 } + if(meta && (raw||'').length < (1000 * 100)){ meta.raw = raw } // HNPERF: If string too big, don't keep in memory. return raw; } var $ = JSON.stringify, _ = ':])([:'; @@ -2211,6 +2224,8 @@ } }()); + function it(msg){ return msg || {_: msg._, '##': msg['##']} } // HNPERF: Only need some meta data, not full reference (took up too much memory). // HNPERF: Garrrgh! We add meta data to msg over time, copying the object happens to early. + var empty = {}, ok = true, u; var LOG = console.LOG; diff --git a/gun.min.js b/gun.min.js index 41456ee7..6165d35d 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1 +1 @@ -!function(){var t;"undefined"!=typeof window&&(t=window),"undefined"!=typeof global&&(t=global);var e=(t=t||{}).console||{log:function(){}};function _(e,t){return t?require(e):e.slice?_[o(e)]:function(t,n){e(t={exports:{}}),_[o(n)]=t.exports};function o(t){return t.split("/").slice(-1).toString().replace(".js","")}}if("undefined"!=typeof module)var m=module;_(function(t){var p={fn:{is:function(t){return!!t&&"function"==typeof t}}};p.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},p.num={is:function(t){return!d(t)&&(0<=t-parseFloat(t)+1||1/0===t||-1/0===t)}},p.text={is:function(t){return"string"==typeof t}},p.text.ify=function(t){return p.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},p.text.random=function(t,n){var e="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"]||n["<"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},p.list={is:function(t){return t instanceof Array}},p.list.slit=Array.prototype.slice,p.list.sort=function(e){return function(t,n){return t&&n?(t=t[e])<(n=n[e])?-1:n",s.drift=0,s.is=function(t,n,e){var o=n&&t&&t[m]&&t[m][s._]||e;if(o)return g(o=o[n])?o:-1/0},s.lex=function(){return s().toString(36).replace(".","")},s.ify=function(t,n,e,o,i){if(!t||!t[m]){if(!i)return;t=a.soul.ify(t,i)}var r=c(t[m],s._);return void 0!==n&&n!==m&&(g(e)&&(r[n]=e),void 0!==o&&(t[n]=o)),t},s.to=function(t,n,e){var o=(t||{})[n];return p(o)&&(o=d(o)),s.ify(e,n,s.is(t,n),o,a.soul(t))},function(){function u(t,n){m!==n&&s.ify(this.o,n,this.s)}s.map=function(i,r,a){var t=p(t=i||r)?t:null;return i=v(i=i||r)?i:null,t&&!i?(r=g(r)?r:s(),t[m]=t[m]||{},h(t,u,{o:t,s:r}),t):(a=a||p(r)?r:void 0,r=g(r)?r:s(),function(t,n,e,o){if(!i)return u.call({o:e,s:r},t,n),t;i.call(a||this||{},t,n,e,o),l(e,n)&&void 0===e[n]||u.call({o:e,s:r},t,n)})}}();var f=n.obj,c=f.as,l=f.has,p=f.is,h=f.map,d=f.copy,g=n.num.is,v=n.fn.is,m=a._;t.exports=s})(_,"./state"),_(function(t){var a=_("./type"),f=_("./val"),c=_("./node"),r={};!function(){function i(t,n){if(!t||n!==c.soul(t)||!c.is(t,this.fn,this.as))return!0;this.cb&&(e.n=t,e.as=this.as,this.cb.call(e.as,t,n,e))}function e(t){t&&c.is(e.n,t,e.as)}r.is=function(t,n,e,o){return!(!t||!l(t)||u(t))&&!s(t,i,{cb:n,fn:e,as:o})}}(),function(){function u(t,n){var e;return(e=function(t,n){var e,o=t.seen,i=o.length;for(;i--;)if(e=o[i],n.obj===e.obj)return e;o.push(n)}(t,n))?e:(n.env=t,n.soul=i,c.ify(n.obj,o,n)&&(n.link=n.link||f.link.ify(c.soul(n.node)),n.obj!==t.shell&&(t.graph[f.link.is(n.link)]=n.node)),n)}function o(t,n,e){var o,i,r=this,a=r.env;if(c._===n&&h(t,f.link._))return e._;if(o=s(t,n,e,r,a)){if(n||(r.node=r.node||e||{},h(t,c._)&&c.soul(t)&&(r.node._=d(t._)),r.node=c.soul.ify(r.node,f.link.is(r.link)),r.link=r.link||f.link.ify(c.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,e,r),h(e,n))){if(void 0===(t=e[n]))return void p(e,n);if(!(o=s(t,n,e,r,a)))return}if(!n)return r.node;if(!0===o)return t;if((i=u(a,{obj:t,path:r.path.concat(n)})).node)return i.link}}function i(t){var n=this,e=f.link.is(n.link),o=n.env.graph;n.link=n.link||f.link.ify(t),n.link[f.link._]=t,n.node&&n.node[c._]&&(n.node[c._][f.link._]=t),h(o,e)&&(o[t]=o[e],p(o,e))}function s(t,n,e,o,i){var r;return!!f.is(t)||(l(t)?1:(r=i.invalid)?s(t=r.call(i.as||{},t,n,e),n,e,o,i):(i.err="Invalid value at '"+o.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}r.ify=function(t,n,e){var o={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(o.link=f.link.ify(n.soul)),n.shell=(e||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||e,u(n,o),n.root=o.node,n.graph}}(),r.node=function(t){var n=c.soul(t);if(n)return e({},n,t)},function(){function i(t,n){var e,o;if(c._!==n)(e=f.link.is(t))?(o=this.opt.seen[e])?this.obj[n]=o:this.obj[n]=this.opt.seen[e]=r.to(this.graph,e,this.opt):this.obj[n]=t;else{if(u(t,f.link._))return;this.obj[n]=d(t)}}r.to=function(t,n,e){if(t){var o={};return e=e||{seen:{}},s(t[n],i,{obj:o,graph:t,opt:e}),o}}}();a.fn.is;var n=a.obj,l=n.is,p=n.del,h=n.has,u=n.empty,e=n.put,s=n.map,d=n.copy;t.exports=r})(_,"./graph"),_(function(t){_("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var e=t["#"]||t,o=(this.tag||empty)[e];if(!o)return;return o=this.on(e,n),clearTimeout(o.err),!0}e=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return e;var i=this.on(e,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),e}}})(_,"./ask"),_(function(t){var r=_("./type");var a=r.time.is;t.exports=function(o){var i={s:{}};return o=o||{max:1e3,age:9e3},i.check=function(t){var n;return!!(n=i.s[t])&&(n.pass?n.pass=!1:i.track(t))},i.track=function(t,n){var e=i.s[t]||(i.s[t]={});return e.was=a(),n&&(e.pass=!0),i.to||(i.to=setTimeout(function(){var e=a();r.obj.map(i.s,function(t,n){t&&o.age>e-t.was||r.obj.del(i.s,n)}),i.to=null},o.age+9)),e},i}})(_,"./dup"),_(function(t){function c(t){return t instanceof c?(this._={gun:this,$:this}).$:this instanceof c?c.create(this._={gun:this,$:this,opt:t}):new c(t)}c.is=function(t){return t instanceof c||t&&t._&&t===t._.$||!1},c.version=.9,(c.chain=c.prototype).toJSON=function(){};var n=_("./type");n.obj.to(n,c),c.HAM=_("./HAM"),c.val=_("./val"),c.node=_("./node"),c.state=_("./state"),c.graph=_("./graph"),c.on=_("./onto"),c.ask=_("./ask"),c.dup=_("./dup"),function(){function a(t){var n,e,o=this.as,i=o.at||o,r=i.$;(e=t["#"])||(e=t["#"]=u(9)),(n=i.dup).check(e)?o.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(e),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),o.out||(t.out=a,i.on("out",t)))}c.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||c.on,t.ask=t.ask||c.ask,t.dup=t.dup||c.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),c.on("create",t),t.on("create",t)),t.once=1,n}}(),function(){function i(t,n,e,o){var i=this,r=c.state.is(e,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+o+"'!";var a=i.graph[o]||v,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[o]=c.state.to(e,n,i.put[o]),(i.diff||(i.diff={}))[o]=c.state.to(e,n,i.diff[o]),i.souls[o]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var e=this,o=e.$._,i=(o.next||v)[n];if(!i){if(!(o.opt||v).super)return void(e.souls[n]=!1);i=e.$.get(n)._}var r=e.map[n]={put:t,get:n,$:i.$},a={ctx:e,msg:r};e.async=!!o.tag.node,e.ack&&(r["@"]=e.ack),h(t,u,a),e.async&&(e.and||o.on("node",function(t){this.to.next(t),t===e.map[t.get]&&(e.souls[t.get]=!1,h(t.put,s,t),h(e.souls,function(t){if(t)return t})||e.c||(e.c=1,this.off(),h(e.map,f,e)))}),e.and=!0,o.on("node",r))}function u(t,n){var e=this.ctx,o=e.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;o[r]=c.state.to(a,n,o[r]),e.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var e=this.put,o=this.$._;o.put=c.state.to(e,n,o.put)}function f(t,n){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}c.on.put=function(t,n){var e=n._,o={$:n,graph:e.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:e,stop:{}};if(c.graph.is(t.put,null,i,o)||(o.err="Error: Invalid graph!"),o.err)return e.on("in",{"@":t["#"],err:c.log(o.err)});h(o.put,r,o),o.async||h(o.map,f,o),void 0!==o.defer&&setTimeout(function(){c.on.put(t,n)},o.defer-o.machine),o.diff&&e.on("put",p(t,{put:o.diff}))},c.on.get=function(t,n){var e=n._,o=t.get,i=o[d],r=e.graph[i],a=o[g],u=(e.next||(e.next={}))[i];if(!r)return e.on("get",t);if(a){if("string"!=typeof a||!l(r,a))return e.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||v).ack,e.on("in",{"@":t["#"],how:"mem",put:r,$:n}),e.on("get",t)}}(),c.chain.opt=function(t){t=t||{};var n=this._,e=t.peers||t;return s(t)||(t={}),s(n.opt)||(n.opt=t),r(e)&&(e=[e]),o(e)&&(e=h(e,function(t,n,e){(n={}).id=n.url=t,e(t,n)}),s(n.opt.peers)||(n.opt.peers={}),n.opt.peers=p(e,n.opt.peers)),n.opt.peers=n.opt.peers||{},h(t,function t(n,e){!l(this,e)||i.is(n)||a.empty(n)?this[e]=n:n&&n.constructor!==Object&&!o(n)||h(n,t,this[e])},n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return f()+u(12)},this};var o=c.list.is,i=c.text,r=i.is,u=i.random,a=c.obj,s=a.is,l=a.has,p=a.to,h=a.map,f=(a.copy,c.state.lex),d=c.val.link._,g=".",v=(c.node._,c.val.link.is,{});e.only=function(t,n){return e.only.i&&t===e.only.i&&e.only.i++&&(e.log.apply(e,arguments)||n)},(c.log=function(){return!c.log.off&&e.log.apply(e,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,e){return(e=c.log.once)[t]=e[t]||0,e[t]++||c.log(n)},c.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&((window.GUN=window.Gun=c).window=window);try{void 0!==m&&(m.exports=c)}catch(t){}t.exports=c})(_,"./root"),_(function(t){var u=_("./root");u.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var e=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var o=0,i=t.length,r=e;o(r.acks||0)&&this.off(),r.ack&&r.ack(t,this)},r.opt),e=0,o=n.root.now;h.del(n.root,"now");var i=n.root.mum;n.root.mum={},r.ref._.on("out",{$:r.ref,put:r.out=r.env.graph,opt:r.opt,"#":t}),n.root.mum=i?h.to(i,n.root.mum):i,n.root.now=o},r),r.res&&r.res())}function n(t,n){if(t)return!0}function c(r,t,n,a){var u=this,s=f.is(r);!t&&a.path.length&&(u.res||o)(function(){for(var t=a.path,n=u.ref,e=(u.opt,0),o=t.length;e .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var e=this,o=e._,i=o.put;if(0=(o.batch||1e3))return f();i||(i=setTimeout(f,o.wait||1))}),r.on("get",function(n){this.to.next(n);var e,o,i=n.get;function t(){if(i&&(e=i["#"])){var t=i["."];(o=s[e]||void 0)&&t&&(o=Gun.state.to(o,t)),r.on("in",{"@":n["#"],put:Gun.graph.node(o),how:"lS",lS:n.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,e,o){s[o]=Gun.state.to(e,n,s[o])},f=function(t){var e;u=0,clearTimeout(i),i=!1;var n=a;a={},t&&(s=t);try{p.setItem(o.prefix,JSON.stringify(s))}catch(t){Gun.log(e=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"),r.on("localStorage:error",{err:e,file:o.prefix,flush:s,retry:f})}(e||Gun.obj.empty(o.peers))&&Gun.obj.map(n,function(t,n){r.on("in",{"@":n,err:e,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var g=_("../type"),f="undefined"!=typeof setImmediate?setImmediate:setTimeout;!function(){g.text.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var e=0,o=t.length;e<"])&&(r._.to=g.obj.map(o.split(","),s)),r.dam?void((o=l.hear[r.dam])&&o(r,i,u)):(v&&(a=+new Date),u.on("in",r),void(v&&!r.nts&&p.log(a,+new Date-a,"msg",r["#"])));var a}}};var s=function(t,n,e){e(t,!0)};function d(n,e){try{var t,o=e.wire;v&&(t=+new Date),e.say?e.say(n):o.send&&o.send(n),v&&p.log(t,+new Date-t,"wire send",n.length),l.say.d+=n.length||0,++l.say.c}catch(t){(e.queue=e.queue||[]).push(n)}}l.hear.c=l.hear.d=0,function(){var s;function f(t){l.say(s,t)}function c(t){var n=t.batch;if(t.batch=t.tail=null,n&&n.length){var e;v&&(e=+new Date);try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return p.log("DAM JSON stringify error",t)}v&&p.log(e,+new Date-e,"say stringify",n.length),n&&d(n,t)}}l.say=function(t,n){if(this.to&&this.to.next(t),!t)return!1;var e,o,i,r;v&&(a=+new Date);var a,u=t._||(t._=function(){});if((e=t["#"])||(e=t["#"]=g.text.random(9)),(o=t["##"])||void 0===t.put||(o=t["##"]=g.obj.hash(t.put)),!(r=u.raw)&&(r=u.raw=l.raw(t),o&&(i=t["@"])&&(h.track(i+o).it=t,i=(h.s[i]||!0).it))){if(o===i["##"])return!1;i["##"]=o}if(v&&p.log(a,+new Date-a,"say prep"),h.track(e).it=t,n||(n=(i=h.s[t["@"]])&&(i=i.it)&&(i=i._)&&(i=i.via)),!n&&l.way)return l.way(t);if(!n||!n.id)return s=t,!!g.obj.is(n||p.peers)&&(v&&(a=+new Date),g.obj.map(n||p.peers,f),void(v&&p.log(a,+new Date-a,"say loop")));if(!n.wire&&l.wire&&l.wire(n),e!==n.last){if(n.last=e,n===u.via)return!1;if((i=u.to)&&(i[n.url]||i[n.pid]||i[n.id]))return!1;if(n.batch){if(n.tail=(i=n.tail||0)+r.length,n.tail<=p.pack)return void n.batch.push(r);c(n)}n.batch=[],setTimeout(function(){c(n)},p.gap),d(r,n)}},l.say.c=l.say.d=0}(),function(){l.raw=function(t){if(!t)return"";var n,e=t._||{};if(n=e.raw)return n;if("string"==typeof t)return t;if(!t.dam){var o=0,i=[];g.obj.map(p.peers,function(t){if(i.push(t.url||t.pid||t.id),9<++o)return!0}),1<"]=i.join())}var r=a(t);return e&&(e.raw=r),r};var a=JSON.stringify}(),l.hi=function(n){var t=n.wire||{};n.id?p.peers[n.url||n.id]=n:(t=n.id=n.id||g.text.random(9),l.say({dam:"?",pid:u.opt.pid},p.peers[t]=n),delete h.s[n.last]),n.met=n.met||+new Date,t.hied||u.on(t.hied="hi",n),t=n.queue,n.queue=[],g.obj.map(t,function(t){d(t,n)})},l.bye=function(t){u.on("bye",t);var n=+new Date;n-=t.met||n,l.bye.time=((l.bye.time||n)+n)/2,v=e.LOG},l.hear["!"]=function(t,n){p.log("Error:",t.err)},l.hear["?"]=function(t,n){t.pid&&(n.pid||(n.pid=t.pid),t["@"])||(l.say({dam:"?",pid:p.pid,"@":t["#"]},n),delete h.s[n.last])},u.on("create",function(t){t.opt.pid=t.opt.pid||g.text.random(9),this.to.next(t),t.on("out",l.say)}),u.on("bye",function(t,n){t=p.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),g.obj.del(p.peers,t.id),t.wire=null});var i={};return u.on("bye",function(t,n){this.to.next(t),(n=t.url)&&(i[n]=!0,setTimeout(function(){delete i[n]},p.lack||9e3))}),u.on("hi",function(e,o){this.to.next(e),(o=e.url)&&i[o]&&(delete i[o],g.obj.map(u.next,function(t,n){(o={})[n]=u.graph[n],l.say({"##":g.obj.hash(o),get:{"#":n}},e)}))}),l}}catch(t){}})(_,"./adapters/mesh"),_(function(t){var f=_("../index");f.Mesh=_("./mesh"),f.on("opt",function(t){this.to.next(t);var o=t.opt;if(!t.once&&!1!==o.WebSocket){var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var e=o.WebSocket||n.WebSocket||n.webkitWebSocket||n.mozWebSocket;if(e){o.WebSocket=e;var i=o.mesh=o.mesh||f.Mesh(t);i.wire||o.wire;i.wire=o.wire=u,setTimeout(function(){t.on("out",{dam:"hi"})},1);var r=2e3,a="undefined"!=typeof document&&document}}function u(n){try{if(!n||!n.url)return e&&e(n);var t=n.url.replace("http","ws"),e=n.wire=new o.WebSocket(t);return e.onclose=function(){o.mesh.bye(n),s(n)},e.onerror=function(t){s(n)},e.onopen=function(){o.mesh.hi(n)},e.onmessage=function(t){t&&o.mesh.hear(t.data||t,n)},e}catch(t){}}function s(n){clearTimeout(n.defer),a&&n.retry<=0||(n.retry=(n.retry||o.retry||60)-1,n.defer=setTimeout(function t(){if(a&&a.hidden)return setTimeout(t,r);u(n)},r))}})})(_,"./adapters/websocket")}(); \ No newline at end of file +!function(){var t;"undefined"!=typeof window&&(t=window),"undefined"!=typeof global&&(t=global);var e=(t=t||{}).console||{log:function(){}};function _(e,t){return t?require(e):e.slice?_[o(e)]:function(t,n){e(t={exports:{}}),_[o(n)]=t.exports};function o(t){return t.split("/").slice(-1).toString().replace(".js","")}}if("undefined"!=typeof module)var l=module;_(function(t){var n,c,l={};function e(t,n){v(this,n)&&void 0!==this[n]||(this[n]=t)}function o(t,n){var e=this.n;if(!e||!(n===e||g(e)&&v(e,n)))return!!n||void 0}function p(t,n){2!==arguments.length?(p.r=p.r||[]).push(t):(p.r=p.r||{})[t]=n}l.fn={is:function(t){return!!t&&"function"==typeof t}},l.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},l.num={is:function(t){return!d(t)&&(0<=t-parseFloat(t)+1||1/0===t||-1/0===t)}},l.text={is:function(t){return"string"==typeof t}},l.text.ify=function(t){return l.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},l.text.random=function(t,n){var e="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"]||n["<"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},l.list={is:function(t){return t instanceof Array}},l.list.slit=Array.prototype.slice,l.list.sort=function(e){return function(t,n){return t&&n?(t=t[e])<(n=n[e])?-1:n",u.drift=0,u.is=function(t,n,e){var o=n&&t&&t[k]&&t[k][u._]||e;if(o)return v(o=o[n])?o:-1/0},u.lex=function(){return u().toString(36).replace(".","")},u.ify=function(t,n,e,o,i){if(!t||!t[k]){if(!i)return;t=a.soul.ify(t,i)}var r=l(t[k],u._);return void 0!==n&&n!==k&&(v(e)&&(r[n]=e),void 0!==o&&(t[n]=o)),t},u.to=function(t,n,e){var o=(t||{})[n];return h(o)&&(o=g(o)),u.ify(e,n,u.is(t,n),o,a.soul(t))},u.map=function(i,r,a){var t=h(t=i||r)?t:null;return i=m(i=i||r)?i:null,t&&!i?(r=v(r)?r:u(),t[k]=t[k]||{},d(t,f,{o:t,s:r}),t):(a=a||h(r)?r:void 0,r=v(r)?r:u(),function(t,n,e,o){if(!i)return f.call({o:e,s:r},t,n),t;i.call(a||this||{},t,n,e,o),p(e,n)&&void 0===e[n]||f.call({o:e,s:r},t,n)})};var c=n.obj,l=c.as,p=c.has,h=c.is,d=c.map,g=c.copy,v=n.num.is,m=n.fn.is,k=a._;t.exports=u})(_,"./state"),_(function(t){var a=_("./type"),u=_("./val"),s=_("./node"),i={};function r(t,n){if(!t||n!==s.soul(t)||!s.is(t,this.fn,this.as))return!0;this.cb&&(e.n=t,e.as=this.as,this.cb.call(e.as,t,n,e))}function e(t){t&&s.is(e.n,t,e.as)}function f(t,n){var e;return(e=function(t,n){var e,o=t.seen,i=o.length;for(;i--;)if(e=o[i],n.obj===e.obj)return e;o.push(n)}(t,n))?e:(n.env=t,n.soul=c,s.ify(n.obj,o,n)&&(n.link=n.link||u.link.ify(s.soul(n.node)),n.obj!==t.shell&&(t.graph[u.link.is(n.link)]=n.node)),n)}function o(t,n,e){var o,i,r=this,a=r.env;if(s._===n&&g(t,u.link._))return e._;if(o=l(t,n,e,r,a)){if(n||(r.node=r.node||e||{},g(t,s._)&&s.soul(t)&&(r.node._=b(t._)),r.node=s.soul.ify(r.node,u.link.is(r.link)),r.link=r.link||u.link.ify(s.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,e,r),g(e,n))){if(void 0===(t=e[n]))return void d(e,n);if(!(o=l(t,n,e,r,a)))return}if(!n)return r.node;if(!0===o)return t;if((i=f(a,{obj:t,path:r.path.concat(n)})).node)return i.link}}function c(t){var n=this,e=u.link.is(n.link),o=n.env.graph;n.link=n.link||u.link.ify(t),n.link[u.link._]=t,n.node&&n.node[s._]&&(n.node[s._][u.link._]=t),g(o,e)&&(o[t]=o[e],d(o,e))}function l(t,n,e,o,i){var r;return!!u.is(t)||(h(t)?1:(r=i.invalid)?l(t=r.call(i.as||{},t,n,e),n,e,o,i):(i.err="Invalid value at '"+o.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}function p(t,n){var e,o;if(s._!==n)(e=u.link.is(t))?(o=this.opt.seen[e])?this.obj[n]=o:this.obj[n]=this.opt.seen[e]=i.to(this.graph,e,this.opt):this.obj[n]=t;else{if(v(t,u.link._))return;this.obj[n]=b(t)}}i.is=function(t,n,e,o){return!(!t||!h(t)||v(t))&&!k(t,r,{cb:n,fn:e,as:o})},i.ify=function(t,n,e){var o={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(o.link=u.link.ify(n.soul)),n.shell=(e||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||e,f(n,o),n.root=o.node,n.graph},i.node=function(t){var n=s.soul(t);if(n)return m({},n,t)},i.to=function(t,n,e){if(t){var o={};return e=e||{seen:{}},k(t[n],p,{obj:o,graph:t,opt:e}),o}};a.fn.is;var n=a.obj,h=n.is,d=n.del,g=n.has,v=n.empty,m=n.put,k=n.map,b=n.copy;t.exports=i})(_,"./graph"),_(function(t){_("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var e=t["#"]||t,o=(this.tag||empty)[e];if(!o)return;return o=this.on(e,n),clearTimeout(o.err),!0}e=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return e;var i=this.on(e,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),e}}})(_,"./ask"),_(function(t){var a=_("./type");var u=a.time.is;t.exports=function(i){var r={s:{}};return i=i||{max:1e3,age:27e3},r.check=function(t){var n;return!!(n=r.s[t])&&(n.pass?n.pass=!1:r.track(t))},r.track=function(t,n){var e=r.s[t]||(r.s[t]={});return e.was=u(),n&&(e.pass=!0),r.to||(r.to=setTimeout(r.drop,i.age+9)),e},r.drop=function(e){var o=u();a.obj.map(r.s,function(t,n){t&&(e||i.age)>o-t.was||a.obj.del(r.s,n)}),r.to=null},r}})(_,"./dup"),_(function(t){function p(t){return t instanceof p?(this._={gun:this,$:this}).$:this instanceof p?p.create(this._={gun:this,$:this,opt:t}):new p(t)}p.is=function(t){return t instanceof p||t&&t._&&t===t._.$||!1},p.version=.9,(p.chain=p.prototype).toJSON=function(){};var n=_("./type");function a(t){var n,e,o=this.as,i=o.at||o,r=i.$;(e=t["#"])||(e=t["#"]=u(9)),(n=i.dup).check(e)?o.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(e),(e=t["@"])&&n.track(e),i.ask(e,t)||(t.get&&p.on.get(t,r),t.put&&p.on.put(t,r)),this.to.next(t),o.out||(t.out=a,i.on("out",t)))}n.obj.to(n,p),p.HAM=_("./HAM"),p.val=_("./val"),p.node=_("./node"),p.state=_("./state"),p.graph=_("./graph"),p.on=_("./onto"),p.ask=_("./ask"),p.dup=_("./dup"),p.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||p.on,t.ask=t.ask||p.ask,t.dup=t.dup||p.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),p.on("create",t),t.on("create",t)),t.once=1,n},function(){p.on.put=function(t,n){var e=n._,o={$:n,graph:e.graph,put:{},map:{},souls:{},machine:p.state(),ack:t["@"],cat:e,stop:{}};if(p.obj.map(t.put,l,o)){if(p.graph.is(t.put,null,a,o)||(o.err="Error: Invalid graph!"),o.err)return e.on("in",{"@":t["#"],err:p.log(o.err)});if(g(o.put,u,o),o.async||g(o.map,c,o),void 0!==o.defer){var i=o.defer-o.machine;setTimeout(function(){p.on.put(t,n)},r(r.acks||0)&&this.off(),r.ack&&r.ack(t,this)},r.opt),e=0,o=n.root.now;h.del(n.root,"now");var i=n.root.mum;n.root.mum={},r.ref._.on("out",{$:r.ref,put:r.out=r.env.graph,opt:r.opt,"#":t}),n.root.mum=i?h.to(i,n.root.mum):i,n.root.now=o},r),r.res&&r.res())}function n(t,n){if(t)return!0}function c(r,t,n,a){var u=this,s=f.is(r);!t&&a.path.length&&(u.res||o)(function(){for(var t=a.path,n=u.ref,e=(u.opt,0),o=t.length;e .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var e=this,o=e._,i=o.put;if(0=(o.batch||1e3))return f();i=i||setTimeout(f,o.wait||1)}),a.on("get",function(n){this.to.next(n);var e,o,i,r=n.get;function t(){if(r&&(e=r["#"])){var t=r["."];(o=s[e]||i)&&t&&(o=Gun.state.to(o,t)),a.on("in",{"@":n["#"],put:Gun.graph.node(o),how:"lS",lS:n.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,e,o){s[o]=Gun.state.to(e,n,s[o])},f=function(t){var e;u=0,clearTimeout(i),i=!1;var n=r;r={},t&&(s=t);try{p.setItem(o.prefix,JSON.stringify(s))}catch(t){Gun.log(e=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"),a.on("localStorage:error",{err:e,file:o.prefix,flush:s,retry:f})}(e||Gun.obj.empty(o.peers))&&Gun.obj.map(n,function(t,n){a.on("in",{"@":n,err:e,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var m=_("../type"),k="undefined"!=typeof setImmediate?setImmediate:setTimeout;function b(t){return t||{_:t._,"##":t["##"]}}!function(){m.text.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var e=0,o=t.length;e<"])&&(r._.to=m.obj.map(o.split(","),h)),r.dam?void((o=f.hear[r.dam])&&o(r,i,s)):(y&&(a=+new Date),s.on("in",r),void(y&&!r.nts&&9<(u=new Date-a)&&c.log(a,u,"msg",r["#"])));var a,u}}};var u,p,h=function(t,n,e){e(t,!0)};function d(t){f.say(u,t)}function g(t){var n=t.batch;if(t.batch=t.tail=null,n&&n.length){var e;y&&(e=+new Date);try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return c.log("DAM JSON stringify error",t)}y&&c.log(e,new Date-e,"say stringify",n.length),n&&v(n,t)}}function v(n,e){try{var t,o,i=e.wire;y&&(t=+new Date),e.say?e.say(n):i.send&&i.send(n),y&&9<(o=new Date-t)&&c.log(t,o,"wire send",n.length),f.say.d+=n.length||0,++f.say.c}catch(t){(e.queue=e.queue||[]).push(n)}}f.hear.c=f.hear.d=0,p=0,f.say=function(t,n){if(this.to&&this.to.next(t),!t)return!1;var e,o,i,r;y&&new Date;var a=t._||(t._=function(){});if((e=t["#"])||(e=t["#"]=m.text.random(9)),(o=t["##"])||void 0===t.put||(o=t["##"]=m.obj.hash(t.put)),!(r=a.raw)&&(r=f.raw(t),o&&(i=t["@"])&&(l.track(i+o).it=b(t),i=(l.s[i]||!0).it))){if(o===i["##"])return!1;i["##"]=o}if(l.track(e).it=b(t),!(n=n||(i=l.s[t["@"]])&&(i=i.it)&&(i=i._)&&(i=i.via))&&t["@"])return y&&c.log(+new Date,++p,"total no peer to ack to"),!1;if(!n&&f.way)return f.way(t);if(!n||!n.id)return u=t,!!m.obj.is(n||c.peers)&&void m.obj.map(n||c.peers,d);if(!n.wire&&f.wire&&f.wire(n),e!==n.last){if(n.last=e,n===a.via)return!1;if((i=a.to)&&(i[n.url]||i[n.pid]||i[n.id]))return!1;if(n.batch){if(n.tail=(i=n.tail||0)+r.length,n.tail<=c.pack)return void n.batch.push(r);g(n)}n.batch=[],setTimeout(function(){g(n)},c.gap),v(r,n)}},f.say.c=f.say.d=0,function(){f.raw=function(t){if(!t)return"";var n,e=t._||{};if(n=e.raw)return n;if("string"==typeof t)return t;if(!t.dam){var o=0,i=[];m.obj.map(c.peers,function(t){if(i.push(t.url||t.pid||t.id),3<++o)return!0}),1<"]=i.join())}var r=a(t);return e&&(r||"").length<1e5&&(e.raw=r),r};var a=JSON.stringify}(),f.hi=function(n){var t=n.wire||{};n.id?c.peers[n.url||n.id]=n:(t=n.id=n.id||m.text.random(9),f.say({dam:"?",pid:s.opt.pid},c.peers[t]=n),delete l.s[n.last]),n.met=n.met||+new Date,t.hied||s.on(t.hied="hi",n),t=n.queue,n.queue=[],m.obj.map(t,function(t){v(t,n)})},f.bye=function(t){s.on("bye",t);var n=+new Date;n-=t.met||n,f.bye.time=((f.bye.time||n)+n)/2,y=e.LOG},f.hear["!"]=function(t,n){c.log("Error:",t.err)},f.hear["?"]=function(t,n){t.pid&&(n.pid||(n.pid=t.pid),t["@"])||(f.say({dam:"?",pid:c.pid,"@":t["#"]},n),delete l.s[n.last])},s.on("create",function(t){t.opt.pid=t.opt.pid||m.text.random(9),this.to.next(t),t.on("out",f.say)}),s.on("bye",function(t,n){t=c.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),m.obj.del(c.peers,t.id),t.wire=null});var i={};return s.on("bye",function(t,n){this.to.next(t),(n=t.url)&&(i[n]=!0,setTimeout(function(){delete i[n]},c.lack||9e3))}),s.on("hi",function(e,o){this.to.next(e),(o=e.url)&&i[o]&&(delete i[o],m.obj.map(s.next,function(t,n){(o={})[n]=s.graph[n],f.say({"##":m.obj.hash(o),get:{"#":n}},e)}))}),f}}catch(t){}})(_,"./adapters/mesh"),_(function(t){var f=_("../index");f.Mesh=_("./mesh"),f.on("opt",function(t){this.to.next(t);var o=t.opt;if(!t.once&&!1!==o.WebSocket){var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var e=o.WebSocket||n.WebSocket||n.webkitWebSocket||n.mozWebSocket;if(e){o.WebSocket=e;var i=o.mesh=o.mesh||f.Mesh(t);i.wire||o.wire;i.wire=o.wire=u,setTimeout(function(){t.on("out",{dam:"hi"})},1);var r=2e3,a="undefined"!=typeof document&&document}}function u(n){try{if(!n||!n.url)return e&&e(n);var t=n.url.replace("http","ws"),e=n.wire=new o.WebSocket(t);return e.onclose=function(){o.mesh.bye(n),s(n)},e.onerror=function(t){s(n)},e.onopen=function(){o.mesh.hi(n)},e.onmessage=function(t){t&&o.mesh.hear(t.data||t,n)},e}catch(t){}}function s(n){clearTimeout(n.defer),a&&n.retry<=0||(n.retry=(n.retry||o.retry||60)-1,n.defer=setTimeout(function t(){if(a&&a.hidden)return setTimeout(t,r);u(n)},r))}})})(_,"./adapters/websocket")}(); \ No newline at end of file diff --git a/lib/doll.js b/lib/doll.js index 2a1b7f69..c8358550 100644 --- a/lib/doll.js +++ b/lib/doll.js @@ -41,4 +41,15 @@ }) })} $.fn.appendTo = function(html){ return this.append(html, 0, 1) } + $.fn.parents = function(q, c){ + var I = $(), l = I.tags, p = 'parentElement'; + this.each(function(i, tag){ + if(c){ (c = {})[p] = tag ; tag = c } + while(tag){ if((tag = tag[p]) && $(tag).is(q)){ + l.push(tag); if(c){ return } + }} + }); + return I; + } + $.fn.closest = function(q, c){ return this.parents(q, 1) } }()); \ No newline at end of file diff --git a/lib/evict.js b/lib/evict.js index 2470fbdf..8e0023bf 100644 --- a/lib/evict.js +++ b/lib/evict.js @@ -5,16 +5,21 @@ this.to.next(root); if(root.once){ return } if(typeof process == 'undefined'){ return } - var util = process.memoryUsage; + var util = process.memoryUsage, heap; if(!util){ return } - - ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 1399) * 0.8; // max_old_space_size defaults to 1400 MB. Note: old space !== memory space though. + try{ heap = require('v8').getHeapStatistics }catch(e){} + if(!heap){ return } + + ev.max = parseFloat(root.opt.memory || (heap().heap_size_limit / 1024 / 1024) || process.env.WEB_MEMORY || 1399) * 0.8; // max_old_space_size defaults to 1400 MB. Note: old space !== memory space though. setInterval(check, 1000); function check(){ - var used = ev.used = util().rss / 1024 / 1024; + var used = util().rss / 1024 / 1024; + var hused = heap().used_heap_size / 1024 / 1024; + //if(hused < ev.max && used < ev.max){ return } if(used < ev.max){ return } - setTimeout(GC, 1); + console.LOG && Gun.log('evict memory:', hused.toFixed(), used.toFixed(), ev.max.toFixed()); + GC();//setTimeout(GC, 1); } function GC(){ var souls = Object.keys(root.graph||empty); @@ -24,7 +29,7 @@ if(--toss < 0){ return } root.gun.get(soul).off(); }); - //console.log(+new Date - S, 'gc'); + root.dup.drop(1000 * 9); // clean up message tracker } /* root.on('in', function(msg){ diff --git a/lib/multicast.js b/lib/multicast.js index e4c5e4ad..d4023b22 100644 --- a/lib/multicast.js +++ b/lib/multicast.js @@ -22,11 +22,10 @@ Gun.on('create', function(root){ socket.bind({port: udp.port, exclusive: true}, function(){ socket.setBroadcast(true); socket.setMulticastTTL(128); - try{ socket.addMembership(udp.address); }catch(e){} }); socket.on("listening", function(){ - try { socket.addMembership(udp.address) }catch(e){ return } + try { socket.addMembership(udp.address) }catch(e){ console.error(e); return; } udp.peer = {id: udp.address + ':' + udp.port, wire: socket}; udp.peer.say = function(raw){ @@ -60,7 +59,7 @@ Gun.on('create', function(root){ var url = 'http://' + info.address + ':' + (port || (opt.web && opt.web.address()||{}).port) + '/gun'; if(root.opt.peers[url]){ return } - + //console.log('discovered', url, message, info); root.$.opt(url); diff --git a/lib/radisk.js b/lib/radisk.js index 03bdcc6a..8c873546 100644 --- a/lib/radisk.js +++ b/lib/radisk.js @@ -108,42 +108,84 @@ 6. Merge and write all of those to the in-memory file and back to disk. 7. If file too large, split. More details needed here. */ + /* NEW APPROACH: + 1. For each item in radix memory + 2. Add it to a radix bucket corresponding to directory of files + 3. Iterate over each bucket + 4. Resume old approach. + */ r.save = function(rad, cb){ - var s = function Span(){}; - s.find = function(tree, key){ - if(key < s.start){ return } - s.start = key; - r.list(s.lex); - return true; + if(r.save.ing){ + r.save.ing.push({rad: rad, ack: cb}); + return; } - s.lex = function(file){ - file = (u === file)? u : decodeURIComponent(file); - if(!file || file > s.start){ - s.mix(s.file || opt.code.from, s.start, s.end = file); + //console.only(99); var ID = Gun.text.random(2), S = (+new Date); console.log("[[[[[[[[", ID); + r.save.ing = []; + var ack = cb; + var s = function Span(err, ok){ + var tmp = r.save.ing; + //console.only(99); var TMP; console.log("]]]]]]]]", ID, (TMP = +new Date) - S, 'more?', !!tmp); + r.save.ing = null; + map(tmp, function(q){ // if many, not the most efficient to requeue, but works for now. + if(!q || !q.rad || !q.ack){ return } + r.save(q.rad, q.ack); + }) + ack(err, ok); + }; + cb = s; + s.files = {}; + s.i = 0; // TODO: revise? Using counter for critical path not my favorite. + s.place = function(tree, key){ + var go = function(file, last){ + file = decodeURIComponent(file || last || opt.code.from); + (s.files[file] || (s.files[file] = Radix()))(key, tree); + if(!(--s.i)){ s.go() } // TODO: See above, revise? return true; } - s.file = file; + go.reverse = 1; + go.end = key; + r.list(go); + ++s.i; // TODO: See above, revise? } - s.mix = function(file, start, end){ - s.start = s.end = s.file = u; + s.go = function(){ + if(s.gone){ return } s.gone = true; + s.seq = []; + map(s.files, function(mem, file){ s.seq.push({file: file, mem: mem}) }); + s.files = null; + s.c = 0; + s.merge(s.c); + } + s.merge = function(i){ + i = i || 0; + //var at = s.seq[i]; + var at = s.seq.shift(); + if(!at){ + if(s.ok){ return cb(null, s.ok) } + return cb("No file to save data to."); + } + var file = at.file, mem = at.mem; r.parse(file, function(err, disk){ if(err){ return cb(err) } + if(!disk && file !== opt.code.from){ // corrupt file? + r.list.bad(file); // remove from dir list + r.save(rad, cb); // try again + return; + } disk = disk || Radix(); - Radix.map(rad, function(val, key){ - if(key < start){ return } - if(end && end < key){ return s.start = key } + Radix.map(mem, function(val, key){ // PLUGIN: consider adding HAM as an extra layer of protection disk(key, val); // merge batch[key] -> disk[key] }); - r.write(file, disk, s.next); - }); + r.write(file, disk, s.pop); + }) } - s.next = function(err, ok){ - if(s.err = err){ return cb(err) } - if(s.start){ return Radix.map(rad, s.find) } - cb(err, ok); + s.pop = function(err, ok){ + if(s.err = err || s.err){ return cb(err) } + s.ok = ok || s.ok || 1; + s.merge(++s.c); } - Radix.map(rad, s.find); + Radix.map(rad, s.place); + if(!s.i){ s.go() }; // TODO: See above, revise? } /* @@ -168,7 +210,8 @@ f.limit = Math.ceil(f.count/2); f.count = 0; f.sub = Radix(); - Radix.map(rad, f.slice); + // IMPORTANT: DO THIS IN REVERSE, SO LAST HALF OF DATA MOVED TO NEW FILE BEFORE DROPPING FROM CURRENT FILE. + Radix.map(rad, f.slice, {reverse: true}); return true; } f.text += enc; @@ -176,29 +219,28 @@ f.write = function(){ var tmp = ename(file); var S; LOG && (S = +new Date); - opt.store.put(tmp, f.text, function(err){ + r.list.add(tmp, function(err){ LOG && opt.log(S, +new Date - S, "wrote disk", tmp); if(err){ return cb(err) } - r.list.add(tmp, cb); + opt.store.put(tmp, f.text, cb); }); } f.slice = function(val, key){ - if(key < f.file){ return } - if(f.limit < (++f.count)){ - var name = f.file; - f.file = key; - f.count = 0; - r.write(name, f.sub, f.next, o); + f.sub(f.end = key, val); + if(f.limit <= (++f.count)){ + r.write(key, f.sub, f.swap, o); return true; } - f.sub(key, val); } - f.next = function(err){ + f.swap = function(err){ if(err){ return cb(err) } f.sub = Radix(); - if(!Radix.map(rad, f.slice)){ - r.write(f.file, f.sub, cb, o); - } + Radix.map(rad, f.stop); + r.write(f.file, f.sub, cb, o); + } + f.stop = function(val, key){ + if(key >= f.end){ return true } + f.sub(key, val); } if(opt.jsonify){ return r.write.jsonify(f, file, rad, cb, o) } // temporary testing idea if(!Radix.map(rad, f.each, true)){ f.write() } @@ -244,7 +286,7 @@ } o.span = (u !== o.start) || (u !== o.end); // is there a start or end? var g = function Get(){}; - g.lex = function(file){ var tmp; + g.lex = function(file){ var tmp; // // TODO: this had a out-of-memory crash! file = (u === file)? u : decodeURIComponent(file); tmp = o.next || key || (o.reverse? o.end || '\uffff' : o.start || ''); if(!file || (o.reverse? file < tmp : file > tmp)){ @@ -259,19 +301,25 @@ g.it(null, u, {}); return true; } - r.parse(g.file, g.it); + r.parse(g.file, g.check); return true; } g.file = file; } g.it = function(err, disk, info){ if(g.err = err){ opt.log('err', err) } + if(!disk && g.file){ // corrupt file? + r.list.bad(g.file); // remove from dir list + r.read(key, cb, o); // look again + return; + } g.info = info; if(disk){ RAD = g.disk = disk } disk = Q[g.file]; delete Q[g.file]; LOG && opt.log(S, +new Date - S, 'rad read in, ack', disk.length); S = +new Date; + var STMP = disk.length; // TMP STATS! DELETE! map(disk, g.ack); - LOG && opt.log(S, +new Date - S, 'rad read acked'); + LOG && opt.log(S, +new Date - S, 'rad read acked', STMP, JSON.stringify(g.file)); } g.ack = function(as){ if(!as.ack){ return } @@ -288,12 +336,57 @@ return } if(u !== data){ + var S = +new Date; as.ack(g.err, data, o); // more might be coming! + LOG && opt.log(S, +new Date - S, 'rad range ack.'); // 1.4s if(o.parsed >= o.limit){ return } // even if more, we've hit our limit, asking peer will need to make a new ask with a new starting point. } o.next = as.file; r.read(key, as.ack, o); } + g.check = function(err, disk, info){ + g.it(err, disk, info); + var good = true; + Radix.map(disk, function(val, key){ + // assume in memory for now, since both write/read already call r.list which will init it. + var go = function(file){ + if(info.file !== file){ + good = false + } + return true; + } + go.reverse = 1; + go.end = key; + r.list(go); + }); + if(good){ return } + var id = Gun.text.random(3); console.log("MISLOCATED DATA", id); + r.save(disk, function ack(err, ok){ + if(err){ return r.save(disk, ack) } // ad infinitum??? + console.log("MISLOCATED CORRECTED", id); + }); + } + /*g.check2 = function(err, disk, info){ + if(err || !disk){ return g.it(err, disk, info) } + var good = true; + Radix.map(disk, function(val, key){ + // assume in memory for now, since both write/read already call r.list which will init it. + var go = function(file){ + if(info.file !== file){ good = false } + return true; + } + go.reverse = 1; + go.end = key; + r.list(go); + }); + if(good){ return g.it(err, disk, info) } + var id = Gun.text.random(3); console.log("MISLOCATED DATA", id); + r.save(disk, function ack(err, ok){ + if(err){ return r.save(disk, ack) } // ad infinitum??? + console.log("MISLOCATED CORRECTED", id); + r.read(key, cb, o); + }); + }*/ if(o.reverse){ g.lex.reverse = true } LOG && (S = +new Date); r.list(g.lex); @@ -313,7 +406,7 @@ var Q = {}, s = String.fromCharCode(31); r.parse = function(file, cb, raw){ var q; if(q = Q[file]){ return q.push(cb) } q = Q[file] = [cb]; - var p = function Parse(){}, info = {}; + var p = function Parse(){}, info = {file: ename(file)}; p.disk = Radix(); p.read = function(err, data){ var tmp; LOG && opt.log(S, +new Date - S, 'read disk', ename(file)); @@ -336,7 +429,7 @@ LOG && (S = +new Date); if(opt.jsonify || '{' === data[0]){ // temporary testing idea try{ - var json = JSON.parse(data); + var json = JSON.parse(data); // TODO: this caused a out-of-memory crash! p.disk.$ = json; LOG && opt.log(S, +new Date - S, 'rad parsed JSON'); map(q, p.ack); @@ -400,10 +493,11 @@ var dir, q, f = String.fromCharCode(28), ef = ename(f); r.list = function(cb){ if(dir){ - var tmp = {reverse: (cb.reverse)? 1 : 0}; + var last, tmp = {reverse: (cb.reverse)? 1 : 0, start: cb.start, end: cb.end}; Radix.map(dir, function(val, key){ - return cb(key); - }, tmp) || cb(); + if(!val){ return } + return cb(last = key); + }, tmp) || cb(u, last); return; } if(q){ return q.push(cb) } q = [cb]; @@ -414,7 +508,7 @@ if(has || file === ef){ return cb(u, 1); } - dir(file, true); + dir(file, 1); cb.listed = (cb.listed || 0) + 1; r.write(f, dir, function(err, ok){ if(err){ return cb(err) } @@ -423,6 +517,10 @@ cb(u, 1); }, true); } + r.list.bad = function(file, cb){ + dir(ename(file), 0); + r.write(f, dir, cb||noop); + } r.list.init = function(err, disk){ if(err){ opt.log('list', err); diff --git a/lib/radix.js b/lib/radix.js index e4533f32..441f9540 100644 --- a/lib/radix.js +++ b/lib/radix.js @@ -58,21 +58,26 @@ //var keys = Object.keys(t).sort(); opt = (true === opt)? {branch: true} : (opt || {}); if(rev = opt.reverse){ keys = keys.slice().reverse() } - var start = opt.start, end = opt.end; + var start = opt.start, end = opt.end, END = '\uffff'; var i = 0, l = keys.length; for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt; if(!tree || '' === key || _ === key){ continue } p = pre.slice(); p.push(key); pt = p.join(''); if(u !== start && pt < (start||'').slice(0,pt.length)){ continue } - if(u !== end && (end || '\uffff') < pt){ continue } + if(u !== end && (end || END) < pt){ continue } if(rev){ // children must be checked first when going in reverse. tmp = map(tree, cb, opt, p); if(u !== tmp){ return tmp } } if(u !== (tmp = tree[''])){ - tmp = cb(tmp, pt, key, pre); - if(u !== tmp){ return tmp } + var yes = 1; + if(u !== start && pt < (start||'')){ yes = 0 } + if(u !== end && pt > (end || END)){ yes = 0 } + if(yes){ + tmp = cb(tmp, pt, key, pre); + if(u !== tmp){ return tmp } + } } else if(opt.branch){ tmp = cb(u, pt, key, pre); diff --git a/lib/rfs.js b/lib/rfs.js index 6ed7d131..8a2e805b 100644 --- a/lib/rfs.js +++ b/lib/rfs.js @@ -56,6 +56,14 @@ function Store(opt){ } }); }; + + store.list = function(cb, match, params, cbs){ + var dir = fs.readdirSync(opt.file); + dir.forEach(function(file){ + cb(file); + }) + cb(); + }; return store; } diff --git a/lib/server.js b/lib/server.js index 71ab9fc8..7b8be474 100644 --- a/lib/server.js +++ b/lib/server.js @@ -2,11 +2,10 @@ var Gun = require('../gun'), u; Gun.serve = require('./serve'); //process.env.GUN_ENV = process.env.GUN_ENV || 'debug'; - //console.LOG = true; // only temporarily, REVERT THIS IN FUTURE! + console.LOG = true; // only do this for dev. Gun.on('opt', function(root){ - if(u === root.opt.super){ - root.opt.super = true; - } + if(u === root.opt.super){ root.opt.super = true } + if(u === root.opt.faith){ root.opt.faith = true } // HNPERF: This should probably be off, but we're testing performance improvements, please audit. root.opt.log = root.opt.log || Gun.log; this.to.next(root); }) diff --git a/lib/store.js b/lib/store.js index 820753ea..734ea585 100644 --- a/lib/store.js +++ b/lib/store.js @@ -31,7 +31,7 @@ Gun.on('create', function(root){ val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc); rad(soul+esc+key, val, (track? ack : u)); }); - LOG && Gun.log(S, +new Date - S, 'put loop'); + //LOG && Gun.log(S, +new Date - S, 'put loop'); function ack(err, ok){ acks--; if(ack.err){ return } @@ -79,22 +79,27 @@ Gun.on('create', function(root){ } if(has['-'] || (soul||{})['-']){ o.reverse = true } if((tmp = (root.next||empty)[soul]) && tmp.put){ + var SPUT = tmp.put; if(o.atom){ tmp = (tmp.next||empty)[o.atom] ; - if(tmp && tmp.rad){ return } + if(tmp && tmp.rad){ + LOG && Gun.log("still cached atom", JSON.stringify(get), Object.keys(SPUT||{}).length); + return; + } } else if(tmp && tmp.rad){ + LOG && Gun.log("still cached", JSON.stringify(get), Object.keys(SPUT||{}).length); return; } } - var S = (+new Date); // STATS! + var S = (+new Date), C = 0, CC = 0; // STATS! rad(key||'', function(err, data, o){ try{opt.store.stats.get.time[statg % 50] = (+new Date) - S; ++statg; opt.store.stats.get.count++; if(err){ opt.store.stats.get.err = err } }catch(e){} // STATS! //if(u === data && o.chunks > 1){ return } // if we already sent a chunk, ignore ending empty responses. // this causes tests to fail. - LOG && Gun.log(S, +new Date - S, 'got'); S = +new Date; // MARK RETURN HERE!!!! Gun.log will always log unless off :/ switch to something like LOG && whatever? + LOG && Gun.log(S, +new Date - S, 'got'); S = +new Date; if(data){ if(typeof data !== 'string'){ if(o.atom){ @@ -105,11 +110,15 @@ Gun.on('create', function(root){ } if(!graph && data){ each(data, '') } } - LOG && Gun.log(S, +new Date - S, 'got prep'); - root.on('in', {'@': id, put: graph, '%': o.more? 1 : u, err: err? err : u, _: each}); + LOG && Gun.log(S, +new Date - S, 'got prep count', CC, C); S = +new Date; CC = 0; + var faith = function(){}; faith.faith = true; faith.rad = get; // HNPERF: We're testing performance improvement by skipping going through security again, but this should be audited. + root.on('in', {'@': id, put: graph, '%': o.more? 1 : u, err: err? err : u, _: faith}); + LOG && Gun.log(S, +new Date - S, 'got sent nodes in graph', Object.keys(graph||{}).length); + graph = u; // each is outside our scope, we have to reset graph to nothing! }, o); LOG && Gun.log(S, +new Date - S, 'get call'); - function each(val, has, a,b){ + function each(val, has, a,b){ // TODO: THIS CODE NEEDS TO BE FASTER!!!! + C++; ++CC; if(!val){ return } has = (key+has).split(esc); var soul = has.slice(0,1)[0]; @@ -121,7 +130,6 @@ Gun.on('create', function(root){ (graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul); if(o.limit && o.limit <= o.count){ return true } } - each.rad = get; LOG = console.LOG; }); opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS! diff --git a/lib/webrtc.js b/lib/webrtc.js index cc61ec60..b3539fca 100644 --- a/lib/webrtc.js +++ b/lib/webrtc.js @@ -21,13 +21,16 @@ opt.RTCSessionDescription = rtcsd; opt.RTCIceCandidate = rtcic; opt.rtc = opt.rtc || {'iceServers': [ - {url: 'stun:stun.l.google.com:19302'}, - {url: "stun:stun.sipgate.net:3478"}, - {url: "stun:stun.stunprotocol.org"}, - {url: "stun:stun.sipgate.net:10000"}, - {url: "stun:217.10.68.152:10000"}, - {url: 'stun:stun.services.mozilla.com'} + {urls: 'stun:stun.l.google.com:19302'}, + {urls: "stun:stun.sipgate.net:3478"}/*, + {urls: "stun:stun.stunprotocol.org"}, + {urls: "stun:stun.sipgate.net:10000"}, + {urls: "stun:217.10.68.152:10000"}, + {urls: 'stun:stun.services.mozilla.com'}*/ ]}; + // TODO: Select the most appropriate stuns. + // FIXME: Find the wire throwing ICE Failed + // The above change corrects at least firefox RTC Peer handler where it **throws** on over 6 ice servers, and updates url: to urls: removing deprecation warning opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2}; opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}}; opt.announce = function(to){ diff --git a/package.json b/package.json index 3a0755f7..e31efe13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gun", - "version": "0.2019.1228", + "version": "0.2020.116", "description": "A realtime, decentralized, offline-first, graph data synchronization engine.", "main": "index.js", "browser": "browser.js", diff --git a/sea.js b/sea.js index 02f4ef8e..dcb1b232 100644 --- a/sea.js +++ b/sea.js @@ -325,7 +325,7 @@ var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... - var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) + var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) @@ -345,7 +345,7 @@ // Next: ECDH keys for encryption/decryption... try{ - var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) + var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! var key = {}; @@ -404,8 +404,8 @@ var priv = pair.priv; var jwk = S.jwk(pub, priv); var hash = await sha(json); - var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) - .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign']) + .then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } @@ -439,12 +439,12 @@ opt = opt || {}; // SEA.I // verify is free! Requires no user permission. var pub = pair.pub || pair; - var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']); + var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']); var hash = await sha(json.m); var buf, sig, check, tmp; try{ buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! sig = new Uint8Array(buf); - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); + check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)); if(!check){ throw "Signature did not match." } }catch(e){ if(SEA.opt.fallback){ @@ -470,7 +470,7 @@ var keyForPair = SEA.opt.slow_leak = pair => { if (knownKeys[pair]) return knownKeys[pair]; var jwk = S.jwk(pair); - knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]); return knownKeys[pair]; }; @@ -482,12 +482,12 @@ var buf; var sig; var check; try{ buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) if(!check){ throw "Signature did not match." } }catch(e){ buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) if(!check){ throw "Signature did not match." } } var r = check? S.parse(json.m) : u; @@ -612,7 +612,7 @@ var epriv = pair.epriv; var ecdhSubtle = shim.ossl || shim.subtle; var pubKeyData = keysToEcdhJwk(pub); - var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy ! + var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },{name: 'ECDH', namedCurve: 'P-256'}); // Thanks to @sirpy ! var privKeyData = keysToEcdhJwk(epub, epriv); var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveBits']).then(async (privKey) => { // privateKey scope doesn't leak out from here! @@ -643,7 +643,7 @@ jwk, { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true } ), // ??? refactor - S.ecdh + {name: 'ECDH', namedCurve: 'P-256'} ] } @@ -659,7 +659,7 @@ SEA.verify = USE('./verify'); SEA.encrypt = USE('./encrypt'); SEA.decrypt = USE('./decrypt'); - SEA.aeskey = USE('./aeskey'); + SEA.opt.aeskey = USE('./aeskey'); // not official! SEA.random = SEA.random || shim.random; diff --git a/sea/pair.js b/sea/pair.js index 3a59e177..b01aad31 100644 --- a/sea/pair.js +++ b/sea/pair.js @@ -19,7 +19,7 @@ var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... - var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) + var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) @@ -39,7 +39,7 @@ // Next: ECDH keys for encryption/decryption... try{ - var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) + var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! var key = {}; diff --git a/sea/secret.js b/sea/secret.js index b53830b6..1e3e3272 100644 --- a/sea/secret.js +++ b/sea/secret.js @@ -13,7 +13,7 @@ var epriv = pair.epriv; var ecdhSubtle = shim.ossl || shim.subtle; var pubKeyData = keysToEcdhJwk(pub); - var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy ! + var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },{name: 'ECDH', namedCurve: 'P-256'}); // Thanks to @sirpy ! var privKeyData = keysToEcdhJwk(epub, epriv); var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveBits']).then(async (privKey) => { // privateKey scope doesn't leak out from here! @@ -44,7 +44,7 @@ jwk, { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true } ), // ??? refactor - S.ecdh + {name: 'ECDH', namedCurve: 'P-256'} ] } diff --git a/sea/sign.js b/sea/sign.js index 4efd2e5f..65d57ba0 100644 --- a/sea/sign.js +++ b/sea/sign.js @@ -24,8 +24,8 @@ var priv = pair.priv; var jwk = S.jwk(pub, priv); var hash = await sha(json); - var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) - .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign']) + .then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } diff --git a/sea/verify.js b/sea/verify.js index 06286e8c..3735f3f7 100644 --- a/sea/verify.js +++ b/sea/verify.js @@ -15,12 +15,12 @@ opt = opt || {}; // SEA.I // verify is free! Requires no user permission. var pub = pair.pub || pair; - var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']); + var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']); var hash = await sha(json.m); var buf, sig, check, tmp; try{ buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! sig = new Uint8Array(buf); - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); + check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)); if(!check){ throw "Signature did not match." } }catch(e){ if(SEA.opt.fallback){ @@ -46,7 +46,7 @@ var keyForPair = SEA.opt.slow_leak = pair => { if (knownKeys[pair]) return knownKeys[pair]; var jwk = S.jwk(pair); - knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]); return knownKeys[pair]; }; @@ -58,12 +58,12 @@ var buf; var sig; var check; try{ buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) if(!check){ throw "Signature did not match." } }catch(e){ buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) if(!check){ throw "Signature did not match." } } var r = check? S.parse(json.m) : u; diff --git a/src/adapters/mesh.js b/src/adapters/mesh.js index 9e52f427..7a557c06 100644 --- a/src/adapters/mesh.js +++ b/src/adapters/mesh.js @@ -11,6 +11,7 @@ function Mesh(root){ var dup = root.dup; + // TODO: somewhere in here caused a out-of-memory crash! How? It is inbound! mesh.hear = function(raw, peer){ if(!raw){ return } var msg, id, hash, tmp = raw[0]; @@ -39,11 +40,11 @@ function Mesh(root){ if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } if(msg.DBG_s){ opt.log(+new Date - msg.DBG_s, 'to hear', id) } 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? + dup.track(id, true).it = 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? + dup.track(tmp+hash, true).it = 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) } @@ -53,9 +54,9 @@ function Mesh(root){ } return; } - var S; LOG && (S = +new Date); + var S, ST; LOG && (S = +new Date); root.on('in', msg); - LOG && !msg.nts && opt.log(S, +new Date - S, 'msg', msg['#']); + LOG && !msg.nts && (ST = +new Date - S) > 9 && opt.log(S, ST, 'msg', msg['#']); return; } } @@ -63,6 +64,7 @@ function Mesh(root){ mesh.hear.c = mesh.hear.d = 0; ;(function(){ + var SMIA = 0; var message; function each(peer){ mesh.say(message, peer) } mesh.say = function(msg, peer){ @@ -74,24 +76,28 @@ function Mesh(root){ 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); + raw = mesh.raw(msg); if(hash && (tmp = msg['@'])){ - dup.track(tmp+hash).it = msg; + dup.track(tmp+hash).it = it(msg); if(tmp = (dup.s[tmp]||ok).it){ if(hash === tmp['##']){ return false } tmp['##'] = hash; } } } - LOG && opt.log(S, +new Date - S, 'say prep'); - dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more! + //LOG && opt.log(S, +new Date - S, 'say prep'); + dup.track(id).it = 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 && msg['@']){ + LOG && opt.log(+new Date, ++SMIA, 'total no peer to ack to'); + return false; + } // TODO: Temporary? If ack via trace has been lost, acks will go to all peers, which trashes browser bandwidth. Not relaying the ack will force sender to ask for ack again. Note, this is technically wrong for mesh behavior. if(!peer && mesh.way){ return mesh.way(msg) } if(!peer || !peer.id){ message = msg; if(!Type.obj.is(peer || opt.peers)){ return false } - var S; LOG && (S = +new Date); + //var S; LOG && (S = +new Date); Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. - LOG && opt.log(S, +new Date - S, 'say loop'); + //LOG && opt.log(S, +new Date - S, 'say loop'); return; } if(!peer.wire && mesh.wire){ mesh.wire(peer) } @@ -128,20 +134,21 @@ function Mesh(root){ // for now - find better place later. function send(raw, peer){ try{ var wire = peer.wire; - var S; LOG && (S = +new Date); + var S, ST; LOG && (S = +new Date); if(peer.say){ peer.say(raw); } else if(wire.send){ wire.send(raw); } - LOG && opt.log(S, +new Date - S, 'wire send', raw.length); + LOG && (ST = +new Date - S) > 9 && opt.log(S, ST, 'wire send', raw.length); mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! }catch(e){ (peer.queue = peer.queue || []).push(raw); }} ;(function(){ + // TODO: this caused a out-of-memory crash! mesh.raw = function(msg){ // TODO: Clean this up / delete it / move logic out! if(!msg){ return '' } var meta = (msg._) || {}, put, hash, tmp; @@ -149,7 +156,7 @@ function Mesh(root){ if(typeof msg === 'string'){ return msg } 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. + to.push(p.url || p.pid || p.id); if(++i > 3){ return true } // limit server, fast fix, improve later! // For "tower" peer, MUST include 6 surrounding ids. // REDUCED THIS TO 3 for temporary relay peer performance, towers still should list neighbors. }); if(i > 1){ msg['><'] = to.join() } } var raw = $(msg); // optimize by reusing put = the JSON.stringify from .hash? @@ -158,7 +165,7 @@ function Mesh(root){ raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); //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 } + if(meta && (raw||'').length < (1000 * 100)){ meta.raw = raw } // HNPERF: If string too big, don't keep in memory. return raw; } var $ = JSON.stringify, _ = ':])([:'; @@ -260,6 +267,8 @@ function Mesh(root){ } }()); +function it(msg){ return msg || {_: msg._, '##': msg['##']} } // HNPERF: Only need some meta data, not full reference (took up too much memory). // HNPERF: Garrrgh! We add meta data to msg over time, copying the object happens to early. + var empty = {}, ok = true, u; var LOG = console.LOG; diff --git a/src/dup.js b/src/dup.js index aae15467..2a61b0b3 100644 --- a/src/dup.js +++ b/src/dup.js @@ -2,7 +2,7 @@ var Type = require('./type'); function Dup(opt){ var dup = {s:{}}; - opt = opt || {max: 1000, age: 1000 * 9};//1000 * 60 * 2}; + opt = opt || {max: 1000, age: /*1000 * 9};//*/ 1000 * 9 * 3}; dup.check = function(id){ var tmp; if(!(tmp = dup.s[id])){ return false } if(tmp.pass){ return tmp.pass = false } @@ -12,18 +12,17 @@ function Dup(opt){ var it = dup.s[id] || (dup.s[id] = {}); it.was = time_is(); if(pass){ it.pass = true } - if(!dup.to){ - dup.to = setTimeout(function(){ - var now = time_is(); - Type.obj.map(dup.s, function(it, id){ - if(it && opt.age > (now - it.was)){ return } - Type.obj.del(dup.s, id); - }); - dup.to = null; - }, opt.age + 9); - } + if(!dup.to){ dup.to = setTimeout(dup.drop, opt.age + 9) } return it; } + dup.drop = function(age){ + var now = time_is(); + Type.obj.map(dup.s, function(it, id){ + if(it && (age || opt.age) > (now - it.was)){ return } + Type.obj.del(dup.s, id); + }); + dup.to = null; + } return dup; } var time_is = Type.time.is; diff --git a/src/root.js b/src/root.js index 187a3bdc..2abbc22e 100644 --- a/src/root.js +++ b/src/root.js @@ -53,7 +53,8 @@ Gun.dup = require('./dup'); return; } dup.track(tmp); - if(!at.ask(msg['@'], msg)){ + if(tmp = msg['@']){ dup.track(tmp) } // HNPERF: Bump original request's liveliness. + if(!at.ask(tmp, msg)){ if(msg.get){ Gun.on.get(msg, gun); //at.on('get', get(msg)); } @@ -72,18 +73,21 @@ Gun.dup = require('./dup'); ;(function(){ Gun.on.put = function(msg, gun){ var at = gun._, ctx = {$: gun, graph: at.graph, put: {}, map: {}, souls: {}, machine: Gun.state(), ack: msg['@'], cat: at, stop: {}}; + if(!Gun.obj.map(msg.put, perf, ctx)){ return } // HNPERF: performance test, not core code, do not port. if(!Gun.graph.is(msg.put, null, verify, ctx)){ ctx.err = "Error: Invalid graph!" } if(ctx.err){ return at.on('in', {'@': msg['#'], err: Gun.log(ctx.err) }) } obj_map(ctx.put, merge, ctx); if(!ctx.async){ obj_map(ctx.map, map, ctx) } if(u !== ctx.defer){ + var to = ctx.defer - ctx.machine; setTimeout(function(){ Gun.on.put(msg, gun); - }, ctx.defer - ctx.machine); + }, to > MD? MD : to ); // setTimeout Max Defer 32bit :( } if(!ctx.diff){ return } at.on('put', obj_to(msg, {put: ctx.diff})); }; + var MD = 2147483647; function verify(val, key, node, soul){ var ctx = this; var state = Gun.state.is(node, key), tmp; if(!state){ return ctx.err = "Error: No state on '"+key+"' in node '"+soul+"'!" } @@ -149,6 +153,7 @@ Gun.dup = require('./dup'); (msg.$._).on('in', msg); this.cat.stop = null; // temporary fix till a better solution? } + function perf(node, soul){ if(node !== this.graph[soul]){ return true } } // HNPERF: do not port! Gun.on.get = function(msg, gun){ var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; @@ -162,15 +167,17 @@ Gun.dup = require('./dup'); // Maybe... in case the in-memory key we have is a local write // we still need to trigger a pull/merge from peers. } else { - node = Gun.obj.copy(node); + node = Gun.window? Gun.obj.copy(node) : node; // HNPERF: If !browser bump Performance? Is this too dangerous to reference root graph? Copy / shallow copy too expensive for big nodes. Gun.obj.to(node); // 1 layer deep copy // Gun.obj.copy(node); // too slow on big nodes } node = Gun.graph.node(node); tmp = (at||empty).ack; + var faith = function(){}; faith.faith = true; // HNPERF: We're testing performance improvement by skipping going through security again, but this should be audited. root.on('in', { '@': msg['#'], how: 'mem', put: node, - $: gun + $: gun, + _: faith }); //if(0 < tmp){ return } root.on('get', msg); diff --git a/test/rad/crash.js b/test/rad/crash.js new file mode 100644 index 00000000..6f79ff0f --- /dev/null +++ b/test/rad/crash.js @@ -0,0 +1,92 @@ +var root; +var Gun; +(function(){ + var env; + if(typeof global !== 'undefined'){ env = global } + if(typeof window !== 'undefined'){ env = window } + root = env.window? env.window : global; + try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){} + try{ indexedDB.deleteDatabase('radatatest') }catch(e){} + if(root.Gun){ + root.Gun = root.Gun; + root.Gun.TESTING = true; + } else { + try{ require('fs').unlinkSync('data.json') }catch(e){} + try{ require('../../lib/fsrm')('radatatest') }catch(e){} + root.Gun = require('../../gun'); + root.Gun.TESTING = true; + //require('../lib/file'); + require('../../lib/store'); + require('../../lib/rfs'); + } + + try{ var expect = global.expect = require("../expect") }catch(e){} + +}(this)); + +;(function(){ +Gun = root.Gun + +if(Gun.window && !Gun.window.RindexedDB){ return } + +var opt = {}; +opt.file = 'radatatest'; +var Radisk = (Gun.window && Gun.window.Radisk) || require('../../lib/radisk'); +opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))(opt); +opt.chunk = 170; +var Radix = Radisk.Radix; +var rad = Radisk(opt), esc = String.fromCharCode(27); + +describe('RAD Crashes', function(){ + + describe('If Some of Split Fails, Keep Original Data', function(){ + var gun = Gun({chunk: opt.chunk}); + + it('write initial', function(done){ + var all = {}, to, start, tmp; + var names = ['al', 'alex', 'alexander', 'alice']; + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put(i, function(ack){ + expect(ack.err).to.not.be.ok(); + delete all[i]; + if(!Gun.obj.empty(all)){ return } + done(); + }) + }); + }); + + it('write alan', function(done){ + var all = {}, to, start, tmp; + var names = ['alan']; + console.log("DID YOU ADD `Gun.CRASH` to Radisk f.swap?"); + Gun.CRASH = true; // add check for this in f.swap! + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put(i); + }); + setTimeout(function(){ + Gun.CRASH = false; + done(); + }, 1000); + }); + + it('read names', function(done){ + console.log("Better to .skip 1st run, .only 2nd run & prevent clearing radatatest."); + var g = Gun(); + var all = {al: 1, alex: 2, alexander: 3, alice: 4}; + g.get('names').map().on(function(v,k){ + //console.log("DATA:", k, v); + if(all[k] === v){ delete all[k] } + if(!Gun.obj.empty(all)){ return } + done(); + }); + }); + + }); + +}); + +}()); \ No newline at end of file diff --git a/test/rad/rad.js b/test/rad/rad.js index d1a638d2..bc2f5f5f 100644 --- a/test/rad/rad.js +++ b/test/rad/rad.js @@ -19,16 +19,16 @@ var Gun; require('../../lib/store'); require('../../lib/rfs'); } - + try{ var expect = global.expect = require("../expect") }catch(e){} - + }(this)); - + ;(function(){ Gun = root.Gun if(Gun.window && !Gun.window.RindexedDB){ return } - + var opt = {}; opt.file = 'radatatest'; var Radisk = (Gun.window && Gun.window.Radisk) || require('../../lib/radisk'); @@ -36,11 +36,11 @@ opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))( opt.chunk = 1000; var Radix = Radisk.Radix; var rad = Radisk(opt), esc = String.fromCharCode(27); - + describe('RAD', function(){ - + var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammamaria","Andy","Anselme","Ardeen","Armand","Ashelman","Aube","Averyl","Baker","Barger","Baten","Bee","Benia","Bernat","Bevers","Bittner","Bobbe","Bonny","Boyce","Breech","Brittaney","Bryn","Burkitt","Cadmann","Campagna","Carlee","Carver","Cavallaro","Chainey","Chaunce","Ching","Cianca","Claudina","Clyve","Colon","Cooke","Corrina","Crawley","Cullie","Dacy","Daniela","Daryn","Deedee","Denie","Devland","Dimitri","Dolphin","Dorinda","Dream","Dunham","Eachelle","Edina","Eisenstark","Elish","Elvis","Eng","Erland","Ethan","Evelyn","Fairman","Faus","Fenner","Fillander","Flip","Foskett","Fredette","Fullerton","Gamali","Gaspar","Gemina","Germana","Gilberto","Giuditta","Goer","Gotcher","Greenstein","Grosvenor","Guthrey","Haldane","Hankins","Harriette","Hayman","Heise","Hepsiba","Hewie","Hiroshi","Holtorf","Howlond","Hurless","Ieso","Ingold","Isidora","Jacoba","Janelle","Jaye","Jennee","Jillana","Johnson","Josy","Justinian","Kannan","Kast","Keeley","Kennett","Kho","Kiran","Knowles","Koser","Kroll","LaMori","Lanctot","Lasky","Laverna","Leff","Leonanie","Lewert","Lilybel","Lissak","Longerich","Lou","Ludeman","Lyman","Madai","Maia","Malvina","Marcy","Maris","Martens","Mathilda","Maye","McLain","Melamie","Meras","Micco","Millburn","Mittel","Montfort","Moth","Mutz","Nananne","Nazler","Nesta","Nicolina","Noellyn","Nuli","Ody","Olympie","Orlena","Other","Pain","Parry","Paynter","Pentheas","Pettifer","Phyllida","Plath","Posehn","Proulx","Quinlan","Raimes","Ras","Redmer","Renelle","Ricard","Rior","Rocky","Ron","Rosetta","Rubia","Ruttger","Salbu","Sandy","Saw","Scholz","Secor","September","Shanleigh","Shenan","Sholes","Sig","Sisely","Soble","Spanos","Stanwinn","Stevie","Stu","Suzanne","Tacy","Tanney","Tekla","Thackeray","Thomasin","Tilla","Tomas","Tracay","Tristis","Ty","Urana","Valdis","Vasta","Vezza","Vitoria","Wait","Warring","Weissmann","Whetstone","Williamson","Wittenburg","Wymore","Yoho","Zamir","Zimmermann"]; - + //console.log("HYPER TEST");var z = 10000; while(--z){ names.push(Gun.text.random(7)) }this.timeout(9000); describe('Radix', function(){ @@ -59,7 +59,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam expect(Gun.obj.empty(all)).to.be.ok(); done(); }); - + it('radix write read again', function(done){ var all = {}; names.forEach(function(v,i){ @@ -74,7 +74,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam expect(Gun.obj.empty(all)).to.be.ok(); done(); }); - + it('radix read start end', function(done){ var all = {}, start = 'Warring'.toLowerCase(), end = 'Zamir'.toLowerCase(); names.forEach(function(v,i){ @@ -92,7 +92,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam expect(Gun.obj.empty(all)).to.be.ok(); done(); }); - + it('radix read start- end+', function(done){ var all = {}, start = 'Warrinf'.toLowerCase(), end = 'Zamis'.toLowerCase(); names.forEach(function(v,i){ @@ -134,7 +134,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam }); describe('Radisk', function(){ - + /*it('parse', function(done){ this.timeout(60000); if(Gun.window){ return done() } @@ -144,8 +144,8 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam }, raw); return; });*/ - - + + it('write contacts', function(done){ var all = {}, to, start; names.forEach(function(v,i){ @@ -178,7 +178,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam }, opt); }); console.log("UNDO THIS RETURN!!!");return;*/ - + it('read contacts start end', function(done){ var opt = {}; opt.start = 'Warring'.toLowerCase(); @@ -200,7 +200,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam done(); }, opt); }); - + it('read contacts', function(done){ var all = {}, find = 'a'; names.forEach(function(v){ @@ -217,7 +217,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam done(); }); }); - + it('read again', function(done){ var all = {}, find = 'm'; names.forEach(function(v){ @@ -252,12 +252,12 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam }); }); - + var ntmp = names; describe('RAD + GUN', function(){ var ochunk = 1000; var gun = Gun({chunk: ochunk}); - + it('write same', function(done){ var all = {}, to, start, tmp; var names = [], c = 285; @@ -273,7 +273,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam }) }); }); - + it('write contacts', function(done){ var all = {}, to, start, tmp; names.forEach(function(v,i){ @@ -342,9 +342,46 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam },100); }); }); - + + it.skip('read contacts smaller than cursor', function(done){ // TODO!!! + var all = {}, cursor = 'm', to; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v < cursor){ all[v] = true } + }); + gun.get('names').get({'.': {'<': cursor}, '%': 1000 * 100}).once().map().once(function(data, key){ + expect(data.name).to.be.ok(); + expect(data.age).to.be.ok(); + //if(!all.hasOwnProperty(key)){console.error(key);} + expect(all.hasOwnProperty(key)).to.be.ok(); + delete all[key]; + clearTimeout(to); + to = setTimeout(function(){ + expect(Gun.obj.empty(all)).to.be.ok(); + done(); + },100); + }); + }); + + it.skip('read contacts in descending order', function(done){ // TODO!!! + var all = {}, cursor = 'm', to; + names.forEach(function(v){ + all[v] = true; + }); + gun.get('names').get({'.': {'-': true}, '%': 1000 * 100}).once().map().once(function(data, key){ + expect(data.name).to.be.ok(); + expect(data.age).to.be.ok(); + delete all[key]; + clearTimeout(to); + to = setTimeout(function(){ + expect(Gun.obj.empty(all)).to.be.ok(); + done(); + },100); + }); + }); + }); - + }); - -}()); \ No newline at end of file + +}()); diff --git a/test/rad/recover.js b/test/rad/recover.js new file mode 100644 index 00000000..65980805 --- /dev/null +++ b/test/rad/recover.js @@ -0,0 +1,100 @@ +var root; +var Gun; +(function(){ + var env; + if(typeof global !== 'undefined'){ env = global } + if(typeof window !== 'undefined'){ env = window } + root = env.window? env.window : global; + try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){} + try{ indexedDB.deleteDatabase('radatatest') }catch(e){} + if(root.Gun){ + root.Gun = root.Gun; + root.Gun.TESTING = true; + } else { + try{ require('fs').unlinkSync('data.json') }catch(e){} + try{ require('../../lib/fsrm')('radatatest') }catch(e){} + root.Gun = require('../../gun'); + root.Gun.TESTING = true; + //require('../lib/file'); + require('../../lib/store'); + require('../../lib/rfs'); + } + + try{ var expect = global.expect = require("../expect") }catch(e){} + +}(this)); + +;(function(){ +Gun = root.Gun + +if(Gun.window && !Gun.window.RindexedDB){ return } + +var opt = {}; +opt.file = 'radatatest'; +var Radisk = (Gun.window && Gun.window.Radisk) || require('../../lib/radisk'); +opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))(opt); +opt.chunk = 170; +var Radix = Radisk.Radix; +var rad = Radisk(opt), esc = String.fromCharCode(27); + +describe('RAD Crashes', function(){ + + describe('If No File Added to Index, Recover', function(){ + var gun = Gun({chunk: opt.chunk}); + + it('write initial', function(done){ + var all = {}, to, start, tmp; + var names = ['al', 'alex', 'alexander', 'alice']; + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put(i, function(ack){ + expect(ack.err).to.not.be.ok(); + delete all[i]; + if(!Gun.obj.empty(all)){ return } + done(); + }) + }); + }); + + it('write alan', function(done){ + var all = {}, to, start, tmp; + var names = ['alan']; + console.log("DID YOU ADD `Gun.CRASH` != 1%C to Radisk f.write list.add callback?"); + Gun.CRASH = true; // add check for this in f.swap! + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put(i); + }); + setTimeout(function(){ + Gun.CRASH = false; + done(); + }, 1000); + }); + + it('write zach', function(done){ + gun.get('names').get('zach').put(9, function(ack){ + expect(ack.err).to.not.be.ok(); + done() ; + }); + }); + + it('read names', function(done){ + console.log("Better to .skip 1st run, .only 2nd run & prevent clearing radatatest."); + var g = Gun(); + var all = {al: 1, alex: 2, alexander: 3, alice: 4, zach: 9}; + //g.get('names').get('zach').put(9, function(ack){ done() }); return; + g.get('names').map().on(function(v,k){ + //console.log("DATA:", k, v); + if(all[k] === v){ delete all[k] } + if(!Gun.obj.empty(all)){ return } + done(); + }); + }); + + }); + +}); + +}()); \ No newline at end of file diff --git a/test/sea/sea.js b/test/sea/sea.js index 78530618..36256456 100644 --- a/test/sea/sea.js +++ b/test/sea/sea.js @@ -38,7 +38,9 @@ describe('SEA', function(){ var pub; describe('Utility', function(){ it('generates aeskey from jwk', function(done) { - SEA.aeskey('x','x').then(k => { + console.log("WARNING: THIS DOES NOT WORK IN BROWSER!!!! NEEDS FIX"); + SEA.opt.aeskey('x','x').then(k => { + //console.log("DATA", k.data); expect(k.data.toString('base64')).to.be('Xd6JaIf2dUybFb/jpEGuSAbfL96UABMR4IvxEGIuC74=') done() }) @@ -139,6 +141,16 @@ describe('SEA', function(){ });});}); }) + /*it('DOESNT DECRYPT SCIENTIFIC NOTATION', function(done){ + var pair, s, v; + SEA.pair(function(pair){ + SEA.encrypt('4e2', pair, function(s){ + SEA.decrypt(s, pair, function(v){ + expect(400).to.be(v); + done(); + });});}); + })*/ + it('legacy', function(done){ (async function(){ var pw = 'test123'; // https://cdn.jsdelivr.net/npm/gun@0.9.99999/sea.js !