diff --git a/.travis.yml b/.travis.yml index 4cf6f163..9ae50c30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,7 @@ branches: except: - debug node_js: - - 8 - 10 cache: directories: - - node_modules \ No newline at end of file + - node_modules diff --git a/README.md b/README.md index c2438c08..c584a776 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@

-[![npm](https://img.shields.io/npm/dm/gun.svg)](https://www.npmjs.com/package/gun) +[![](https://data.jsdelivr.com/v1/package/npm/gun/badge?style=rounded)](https://www.jsdelivr.com/package/npm/gun) [![Travis](https://img.shields.io/travis/amark/gun/master.svg)](https://travis-ci.org/amark/gun) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Famark%2Fgun.svg?size=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Famark%2Fgun?ref=badge_shield) [![Gitter](https://img.shields.io/gitter/room/amark/gun.js.svg)](https://gitter.im/amark/gun) -[![](https://data.jsdelivr.com/v1/package/npm/gun/badge?style=rounded)](https://www.jsdelivr.com/package/npm/gun) **GUN** is an _ecosystem_ of tools that let you build tomorrow's dApps, today. @@ -12,7 +11,7 @@ Decentralized alternatives to [Reddit](https://notabug.io/t/whatever/comments/36 - + @@ -20,8 +19,9 @@ Decentralized alternatives to [Reddit](https://notabug.io/t/whatever/comments/36
+
-The ecosystem is one nice stack of technologies that looks like this: +The ecosystem is one nice stack of technologies that looks like this: (names -> use case)
@@ -167,7 +167,7 @@ Technically, **GUN is a graph synchronization protocol** with a *lightweight emb This would not be possible without **community contributors**, big shout out to: -**[ajmeyghani](https://github.com/ajmeyghani) ([Learn GUN Basics with Diagrams](https://medium.com/@ajmeyghani/gundb-a-graph-database-in-javascript-3860a08d873c))**; **[anywhichway](https://github.com/anywhichway) ([Block Storage](https://github.com/anywhichway/gun-block))**; **[beebase](https://github.com/beebase) ([Quasar](https://github.com/beebase/gun-vuex-quasar))**; **[BrockAtkinson](https://github.com/BrockAtkinson) ([brunch config](https://github.com/BrockAtkinson/brunch-gun))**; **[Brysgo](https://github.com/brysgo) ([GraphQL](https://github.com/brysgo/graphql-gun))**; **[d3x0r](https://github.com/d3x0r) ([SQLite](https://github.com/d3x0r/gun-db))**; **[forrestjt](https://github.com/forrestjt) ([file.js](https://github.com/amark/gun/blob/master/lib/file.js))**; **[hillct](https://github.com/hillct) (Docker)**; **[JosePedroDias](https://github.com/josepedrodias) ([graph visualizer](http://acor.sl.pt:9966))**; **[JuniperChicago](https://github.com/JuniperChicago) ([cycle.js bindings](https://github.com/JuniperChicago/cycle-gun))**; **[jveres](https://github.com/jveres) ([todoMVC](https://github.com/jveres/todomvc))**; **[kristianmandrup](https://github.com/kristianmandrup) ([edge](https://github.com/kristianmandrup/gun-edge))**; **[Lightnet](https://github.com/Lightnet)** ([Awesome Vue User Examples](https://glitch.com/edit/#!/jsvuegunui?path=README.md:1:0) & [User Kitchen Sink Playground](https://gdb-auth-vue-node.glitch.me/)); **[lmangani](https://github.com/lmangani) ([Cytoscape Visualizer](https://github.com/lmangani/gun-scape), [Cassandra](https://github.com/lmangani/gun-cassandra), [Fastify](https://github.com/lmangani/fastify-gundb), [LetsEncrypt](https://github.com/lmangani/polyGun-letsencrypt))**; **[mhelander](https://github.com/mhelander) ([SEA](https://github.com/amark/gun/blob/master/sea.js))**; [omarzion](https://github.com/omarzion) ([Sticky Note App](https://github.com/omarzion/stickies)); [PsychoLlama](https://github.com/PsychoLlama) ([LevelDB](https://github.com/PsychoLlama/gun-level)); **[RangerMauve](https://github.com/RangerMauve) ([schema](https://github.com/gundb/gun-schema))**; **[robertheessels](https://github.com/swifty) ([gun-p2p-auth](https://github.com/swifty/gun-p2p-auth))**; **[rogowski](https://github.com/rogowski) (AXE)**; [sbeleidy](https://github.com/sbeleidy); **[sbiaudet](https://github.com/sbiaudet) ([C# Port](https://github.com/sbiaudet/cs-gun))**; **[Sean Matheson](https://github.com/ctrlplusb) ([Observable/RxJS/Most.js bindings](https://github.com/ctrlplusb/gun-most))**; **[Shadyzpop](https://github.com/Shadyzpop) ([React Native example](https://github.com/amark/gun/tree/master/examples/react-native))**; **[sjones6](https://github.com/sjones6) ([Flint](https://github.com/sjones6/gun-flint))**; **[Stefdv](https://github.com/stefdv) (Polymer/web components)**; **[zrrrzzt](https://github.com/zrrrzzt) ([JWT Auth](https://gist.github.com/zrrrzzt/6f88dc3cedee4ee18588236756d2cfce))**; **[xmonader](https://github.com/xmonader) ([Python Port](https://github.com/xmonader/pygundb))**; **[88dev](https://github.com/88dev) ([Database Viewer](https://github.com/88dev/gun-show))**; +**[ajmeyghani](https://github.com/ajmeyghani) ([Learn GUN Basics with Diagrams](https://medium.com/@ajmeyghani/gundb-a-graph-database-in-javascript-3860a08d873c))**; **[anywhichway](https://github.com/anywhichway) ([Block Storage](https://github.com/anywhichway/gun-block))**; **[beebase](https://github.com/beebase) ([Quasar](https://github.com/beebase/gun-vuex-quasar))**; **[BrockAtkinson](https://github.com/BrockAtkinson) ([brunch config](https://github.com/BrockAtkinson/brunch-gun))**; **[Brysgo](https://github.com/brysgo) ([GraphQL](https://github.com/brysgo/graphql-gun))**; **[d3x0r](https://github.com/d3x0r) ([SQLite](https://github.com/d3x0r/gun-db))**; **[forrestjt](https://github.com/forrestjt) ([file.js](https://github.com/amark/gun/blob/master/lib/file.js))**; **[hillct](https://github.com/hillct) (Docker)**; **[JosePedroDias](https://github.com/josepedrodias) ([graph visualizer](http://acor.sl.pt:9966))**; **[JuniperChicago](https://github.com/JuniperChicago) ([cycle.js bindings](https://github.com/JuniperChicago/cycle-gun))**; **[jveres](https://github.com/jveres) ([todoMVC](https://github.com/jveres/todomvc))**; **[kristianmandrup](https://github.com/kristianmandrup) ([edge](https://github.com/kristianmandrup/gun-edge))**; **[Lightnet](https://github.com/Lightnet)** ([Awesome Vue User Examples](https://glitch.com/edit/#!/jsvuegunui?path=README.md:1:0) & [User Kitchen Sink Playground](https://gdb-auth-vue-node.glitch.me/)); **[lmangani](https://github.com/lmangani) ([Cytoscape Visualizer](https://github.com/lmangani/gun-scape), [Cassandra](https://github.com/lmangani/gun-cassandra), [Fastify](https://github.com/lmangani/fastify-gundb), [LetsEncrypt](https://github.com/lmangani/polyGun-letsencrypt))**; **[mhelander](https://github.com/mhelander) ([SEA](https://github.com/amark/gun/blob/master/sea.js))**; [omarzion](https://github.com/omarzion) ([Sticky Note App](https://github.com/omarzion/stickies)); [PsychoLlama](https://github.com/PsychoLlama) ([LevelDB](https://github.com/PsychoLlama/gun-level)); **[RangerMauve](https://github.com/RangerMauve) ([schema](https://github.com/gundb/gun-schema))**; **[robertheessels](https://github.com/swifty) ([gun-p2p-auth](https://github.com/swifty/gun-p2p-auth))**; **[rogowski](https://github.com/rogowski) (AXE)**; [sbeleidy](https://github.com/sbeleidy); **[sbiaudet](https://github.com/sbiaudet) ([C# Port](https://github.com/sbiaudet/cs-gun))**; **[Sean Matheson](https://github.com/ctrlplusb) ([Observable/RxJS/Most.js bindings](https://github.com/ctrlplusb/gun-most))**; **[Shadyzpop](https://github.com/Shadyzpop) ([React Native example](https://github.com/amark/gun/tree/master/examples/react-native))**; **[sjones6](https://github.com/sjones6) ([Flint](https://github.com/sjones6/gun-flint))**; **[Stefdv](https://github.com/stefdv) (Polymer/web components)**; **[zrrrzzt](https://github.com/zrrrzzt) ([JWT Auth](https://gist.github.com/zrrrzzt/6f88dc3cedee4ee18588236756d2cfce))**; **[xmonader](https://github.com/xmonader) ([Python Port](https://github.com/xmonader/pygundb))**; I am missing many others, apologies, will be adding them soon! @@ -190,7 +190,7 @@ var Gun = require('gun/gun'); If you also need to install SEA for user auth and crypto, also install some of its dependencies like this: -`npm install @trust/crypto text-encoding node-webcrypto-ossl --save` +`npm install text-encoding node-webcrypto-ossl --save` You will need to require it too (it will be automatically added to the Gun object): @@ -208,6 +208,8 @@ To quickly spin up a Gun test server for your development team, utilize either [ [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/amark/gun) + > Heroku deletes your data every 15 minutes, one way to fix this is by adding [cheap storage](https://gun.eco/docs/Using-Amazon-S3-for-Storage). + Or: ```bash diff --git a/app.json b/app.json index 005f6f0e..441f5c9a 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,7 @@ { "name": "gun-server", - "website": "http://gun.js.org", + "stack": "heroku-18", + "website": "http://gun.eco/", "repository": "https://github.com/amark/gun", "logo": "https://avatars3.githubusercontent.com/u/8811914", "keywords": ["node", "gun", "gunDB", "database","graph","offline-first"], diff --git a/as.js b/as.js index f6ac4f37..9be7c337 100644 --- a/as.js +++ b/as.js @@ -1,7 +1,73 @@ ;(function(){ - function as(el, gun, cb){ + function as(el, gun, cb, opt){ el = $(el); if(gun === as.gui && as.el && as.el.is(el)){ return } + + opt = opt || {}; + opt.match = opt.match || '{{ '; + opt.end = opt.end || ' }}'; + ;(function(){ // experimental + function nest(t, s,e, r, i,tmp,u){ + if(r && !r.length){ return t||'' } + if(!t){ return [] } + e = e || s; + i = t.indexOf(s, i||0); + if(0 > i){ return [] } + tmp = t.indexOf(e, i+1); + if(!r){ return [t.slice(i+s.length, tmp)].concat(nest(t, s,e, r, tmp,tmp,u)) } + return t.slice(0,i)+r[0]+nest(t.slice(tmp+e.length), s,e, r.slice(1), 0,tmp,u); + } + + /* experimental */ + function template(tag, attr){ + var html = (tag = $(tag))[0].outerHTML, sub, tmp; + if(html && (0 > html.indexOf(opt.match))){ return } + if(!attr){ + $.each(tag[0].attributes, function(i,v){ + if(!v){ return } + if(!nest(v.value, opt.match, opt.end).length){ return } + template(tag, v.name) + }); + if((sub = tag.children()).length){ + return sub.each(function(){ template(this) }); + } + } + var data = [], plate = attr? tag.attr(attr) : tag.html(); + tmp = nest(plate, opt.match, opt.end); + if(!tmp.length){ return } + $.each(tmp, function(pos, match){ + var expr = match.split(' '); + var path = (expr[0]).split('.'); + if(expr = expr.slice(1).join(' ')){ + expr = new Function("_", "b", "return (_)" + expr); + } + var val = (expr && expr('')) || ''; + data.push(val); + if(!attr){ tag.text(val) } + + var ref = gun, sup = [], tmp; + if(tmp = tag.attr('name')){ sup.push(tmp) } + tag.parents("[name]").each(function(){ + sup.push($(this).attr('name')); + }); + $.each(path = sup.reverse().concat(path), function(i,v){ + ref = ref.get(v); + }); + ref.on(function(v){ + v = data[pos] = expr? expr(v) : v; + var tmp = nest(plate, opt.match, opt.end, data); + if(attr){ + tag.attr(attr, tmp); + } else { + tag.text(tmp); + } + }); + }); + } + template(el); + + }()); + as.gui = gun; as.el = el; if(el.data('as')){ @@ -59,10 +125,12 @@ if(many && ui.is('.sort')){ var up = ui.closest("[name='#']"); var tmp = as.sort(data, up.parent().children().last()); - up.insertAfter(tmp); + tmp? up.insertAfter(tmp) : up.prependTo(up.parent()); } if(as.lock === gui){ return } - (ui[0] && u === ui[0].value)? ui.text(data) : ui.val(data); + if(!(data && data instanceof Object)){ + (ui[0] && u === ui[0].value)? ui.text(data) : ui.val(data); + } ui.data('was', data); if(cb){ cb(data, key, ui); @@ -80,12 +148,7 @@ }, wait || 200); } } - as.sort = function sort(id, li){ - var num = parseFloat(id); - var id = $(li).find('.sort').text() || -Infinity; - var at = num >= parseFloat(id); - return at ? li : sort(id, li.prev()); - } + as.sort = function sort(num, li){ return parseFloat(num) >= parseFloat($(li).find('.sort').text() || -Infinity)? li : sort(num, li.prev()) } $(document).on('keyup', 'input, textarea, [contenteditable]', as.wait(function(){ var el = $(this); var data = (el[0] && u === el[0].value)? el.text() : el.val(); @@ -94,7 +157,7 @@ as.lock = g; g.put(data); }, 99)); - $(document).on('submit', 'form', function(e){ e.preventDefault() }); + //$(document).on('submit', 'form', function(e){ e.preventDefault() }); var u; window.as = as; $.as = as; @@ -146,4 +209,31 @@ ;$(function(){ $('.page').not(':first').hide(); $.as.route(location.hash.slice(1)); + $(JOY.start = JOY.start || function(){ $.as(document, gun, null, JOY.opt) }); + + if($('body').attr('peers')){ (console.warn || console.log)('Warning: Please upgrade to https://github.com/eraeco/joydb#peers !') } + }); +;(function(){ // need to isolate into separate module! + var joy = window.JOY = function(){}; + joy.auth = function(a,b,cb,o){ + if(!o){ o = cb ; cb = 0 } + if(o === true){ + gun.user().create(a, b); + return; + } + gun.user().auth(a,b, cb,o); + } + + var opt = joy.opt = window.CONFIG || {}, peers; + $('link[type=peer]').each(function(){ (peers || (peers = [])).push($(this).attr('href')) }); + !window.gun && (opt.peers = opt.peers || peers || (function(){ + (console.warn || console.log)('Warning: No peer provided, defaulting to DEMO peer. Do not run in production, or your data will be regularly wiped, reset, or deleted. For more info, check https://github.com/eraeco/joydb#peers !'); + return ['https://gunjs.herokuapp.com/gun']; + }())); + window.gun = window.gun || Gun(opt); + + gun.on('auth', function(ack){ + console.log("Your namespace is publicly available at", ack.soul); + }); +}()); \ No newline at end of file diff --git a/axe.js b/axe.js index a950e363..bb7a2008 100644 --- a/axe.js +++ b/axe.js @@ -1,99 +1,287 @@ ;(function(){ - /* UNBUILD */ - var root; - if(typeof window !== "undefined"){ root = window } - if(typeof global !== "undefined"){ root = global } - root = root || {}; - var console = root.console || {log: function(){}}; - function USE(arg, req){ - return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } - } - if(typeof module !== "undefined"){ var common = module } - /* UNBUILD */ + /* UNBUILD */ + var root; + if(typeof window !== "undefined"){ root = window } + if(typeof global !== "undefined"){ root = global } + root = root || {}; + var console = root.console || {log: function(){}}; + function USE(arg, req){ + return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ + arg(mod = {exports: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var common = module } + /* UNBUILD */ - ;USE(function(module){ + ;USE(function(module){ if(typeof window !== "undefined"){ module.window = window } var tmp = module.window || module; - var AXE = tmp.AXE || function(){}; - - if(AXE.window = module.window){ try{ - AXE.window.AXE = AXE; - tmp = document.createEvent('CustomEvent'); - tmp.initCustomEvent('extension', false, false, {type: "AXE"}); - (window.dispatchEvent || window.fireEvent)(tmp); - window.postMessage({type: "AXE"}, '*'); - } catch(e){} } + var AXE = tmp.AXE || function(){}; + if(AXE.window = module.window){ AXE.window.AXE = AXE } try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){} module.exports = AXE; - })(USE, './root'); + })(USE, './root'); - ;USE(function(module){ + ;USE(function(module){ - var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1); - (Gun.AXE = AXE).GUN = AXE.Gun = Gun; + var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1); + (Gun.AXE = AXE).GUN = AXE.Gun = Gun; - Gun.on('opt', function(at){ - if(!at.axe){ - at.axe = {}; - var p = at.opt.peers, tmp; - // 1. If any remembered peers or from last cache or extension - // 2. Fallback to use hard coded peers from dApp - // 3. Or any offered peers. - //if(Gun.obj.empty(p)){ - // Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){ - // p[url] = {url: url, axe: {}}; - // }); - //} - // Our current hypothesis is that it is most optimal - // to take peers in a common network, and align - // them in a line, where you only have left and right - // peers, so messages propagate left and right in - // a linear manner with reduced overlap, and - // with one common superpeer (with ready failovers) - // in case the p2p linear latency is high. - // Or there could be plenty of other better options. - console.log("axe", at.opt); - if(at.opt.super){ - function verify(msg, send, at) { - var peers = Object.keys(p), puts = Object.keys(msg.put), i, j, peer; - var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls? - for (i=0; i < peers.length; ++i) { - peer = p[peers[i]]; - //if (peer.url) {console.log('AXE do not reject superpeers'); send(msg, peer); continue;} /// always send to superpeers? - if (!peer.id) {console.log('AXE peer without id: ', peer); continue;} - if (!Gun.subscribe[soul] || !Gun.subscribe[soul][peer.id]) { console.log('AXE SAY reject msg to peer: %s, soul: %s', peer.id, soul); continue; } - send(msg, peer); - } - } - AXE.say = function(msg, send, at) { - if (!msg.put) { send(msg); return; } - console.log('AXE HOOK!! ', msg); - verify(msg, send, at); - }; - /// TODO: remove peer from all Gun.subscribe. On `mesh.bye` event? - } - if(at.opt.super){ - at.on('in', USE('./lib/super', 1), at); - } else { - //at.on('in', input, at); - } - } - this.to.next(at); // make sure to call the "next" middleware adapter. - }); + Gun.on('opt', function(at){ + start(at); + this.to.next(at); // make sure to call the "next" middleware adapter. + }); - function input(msg){ - var at = this.as, to = this.to; - } + function start(at){ + if(at.axe){ return } + var opt = at.opt, peers = opt.peers; + if(false === opt.axe){ return } + if((typeof process !== "undefined") && 'false' === ''+(process.env||{}).AXE){ return } + var axe = at.axe = {}, tmp; + // 1. If any remembered peers or from last cache or extension + // 2. Fallback to use hard coded peers from dApp + // 3. Or any offered peers. + //if(Gun.obj.empty(p)){ + // Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){ + // p[url] = {url: url, axe: {}}; + // }); + //} + // Our current hypothesis is that it is most optimal + // to take peers in a common network, and align + // them in a line, where you only have left and right + // peers, so messages propagate left and right in + // a linear manner with reduced overlap, and + // with one common superpeer (with ready failovers) + // in case the p2p linear latency is high. + // Or there could be plenty of other better options. + var mesh = opt.mesh = opt.mesh || Gun.Mesh(at); + console.log("AXE enabled."); - module.exports = AXE; - })(USE, './axe'); + function verify(dht, msg) { + var puts = Object.keys(msg.put); + var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls? + var subs = dht(soul); + if (!subs) { return; } + var tmp = []; + Gun.obj.map(subs.split(','), function(pid) { + if (pid in peers) { + tmp.push(pid); + mesh.say(msg, peers[pid]); + } + }); + /// Only connected peers in the tmp array. + if (opt.super) { + dht(soul, tmp.join(',')); + } + } + function route(get){ var tmp; + if(!get){ return } + if('string' != typeof (tmp = get['#'])){ return } + return tmp; + } + var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1); + at.opt.dht = Rad(); + at.on('in', function input(msg){ + var to = this.to, peer = (msg._||{}).via; + var dht = opt.dht; + var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING! + var get = msg.get, hash, tmp; + //if(get && opt.super && peer){ + if(get && opt.super && peer && (tmp = route(get))){ + hash = tmp; //Gun.obj.hash(get); // USE RAD INSTEAD! + (routes[hash] || (routes[hash] = {}))[peer.id] = peer; + (peer.routes || (peer.routes = {}))[hash] = routes[hash]; + + /*if(soul = get['#']){ // SWITCH BACK TO USING DHT! + if(key = get['.']){ + + } else { + + } + if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);} + var pids = joindht(dht, soul, peer.id); + if (pids) { + var dht = {}; + dht[soul] = pids; + mesh.say({dht:dht}, opt.peers[peer.id]); + } + }*/ + } + if((tmp = msg['@']) && (tmp = at.dup.s[tmp]) && (tmp = tmp.it)){ + (tmp = (tmp._||ok)).ack = (tmp.ack || 0) + 1; // count remote ACKs to GET. + } + to.next(msg); + + if (opt.rtc && msg.dht) { + Gun.obj.map(msg.dht, function(pids, soul) { + dht(soul, pids); + Gun.obj.map(pids.split(','), function(pid) { + /// TODO: here we can put an algorithm of who must connect? + if (!pid || pid in opt.peers || pid === opt.pid || opt.announce[pid]) { return; } + opt.announce[pid] = true; /// To try only one connection to the same peer. + opt.announce(pid); + }); + }); + } + }); + + //try{console.log(req.connection.remoteAddress)}catch(e){}; + mesh.hear['opt'] = function(msg, peer){ + if(msg.ok){ return opt.log(msg) } + var tmp = msg.opt; + if(!tmp){ return } + tmp = tmp.peers; + if(!tmp || !Gun.text.is(tmp)){ return } + if(axe.up[tmp] || 6 <= Object.keys(axe.up).length){ return } + var o = tmp; //{peers: tmp}; + at.$.opt(o); + o = peers[tmp]; + if(!o){ return } + o.retry = 9; + mesh.wire(o); + if(peer){ mesh.say({dam: 'opt', ok: 1, '@': msg['#']}, peer) } + } + setInterval(function(tmp){ + if(!(tmp = at.stats && at.stats.stay)){ return } + (tmp.axe = tmp.axe || {}).up = Object.keys(axe.up||{}); + },1000 * 60) + setTimeout(function(tmp){ + if(!(tmp = at.stats && at.stats.stay)){ return } + Gun.obj.map((tmp.axe||{}).up, function(url){ mesh.hear.opt({opt: {peers: url}}) }) + },1000); + + if(at.opt.super){ + var rotate = 0; + mesh.way = function(msg) { + if (msg.rtc) { + if (msg.rtc.to) { + /// Send announce to one peer only if the msg have 'to' attr + var peer = (peers) ? peers[msg.rtc.to] : null; + if (peer) { mesh.say(msg, peer); } + return; + } + } + if(msg.get){ mesh.say(msg, axe.up) } // always send gets up! + if(msg.get && (tmp = route(msg.get))){ + var hash = tmp; //Gun.obj.hash(msg.get); + var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING! + var peers = routes[hash]; + function chat(peers, old){ // what about optimizing for directed peers? + if(!peers){ return chat(opt.peers) } + var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!! + var meta = (msg._||yes); + clearTimeout(meta.lack); + var id, peer, c = 1; // opt. ?redundancy? + while((id = ids[meta.turn || 0]) && c--){ // TODO: This hits peers in order, not necessarily best for load balancing. And what about optimizing for directed peers? + peer = peers[id]; + meta.turn = (meta.turn || 0) + 1; + if((old && old[id]) || false === mesh.say(msg, peer)){ ++c } + } + //console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers); + if(0 < c){ + if(peers === opt.peers){ return } // prevent infinite lack loop. + return meta.turn = 0, chat(opt.peers, peers) + } + var hash = msg['##'], ack = meta.ack; + meta.lack = setTimeout(function(){ + if(ack && hash && hash === msg['##']){ return } + if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit. + //console.log(msg['#'], "CONTINUE:", ack, hash, msg['##']); + chat(peers, old); // keep asking for data if there is mismatching hashes. + }, 25); + } + return chat(peers); + } + // TODO: PUTs need to only go to subs! + if(msg.put){ + var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING! + var peers = {}; + Gun.obj.map(msg.put, function(node, soul){ + var hash = soul; //Gun.obj.hash({'#': soul}); + var to = routes[hash]; + if(!to){ return } + Gun.obj.to(to, peers); + }); + mesh.say(msg, peers); + return; + } + mesh.say(msg, opt.peers); return; // TODO: DISABLE THIS!!! USE DHT! + + + if (!msg.put) { mesh.say(msg); return; } + //console.log('AXE HOOK!! ', msg); + verify(opt.dht, msg); + }; + } else { + mesh.route = function(msg) { + if (msg.rtc) { + } + if (!msg.put) { mesh.say(msg); return; } + verify(opt.dht, msg); + /// Always send to superpeers? + Gun.obj.map(peers, function(peer) { + if (peer.url) { + mesh.say(msg, peer); + } + }); + }; + /*var connections = 0; // THIS HAS BEEN MOVED TO CORE NOW! + at.on('hi', function(opt) { + this.to.next(opt); + //console.log('AXE PEER [HI]', new Date(), opt); + connections++; + /// The first connection don't need to resubscribe the nodes. + if (connections === 1) { return; } + /// Resubscribe all nodes. + setTimeout(function() { + var souls = Object.keys(at.graph); + for (var i=0; i < souls.length; ++i) { + //at.gun.get(souls[i]).off(); + at.next[souls[i]].ack = 0; + at.gun.get(souls[i]).once(function(){}); + } + //location.reload(); + }, 500); + }, at);*/ + } + axe.up = {}; + at.on('hi', function(peer){ + this.to.next(peer); + if(!peer.url){ return } + axe.up[peer.id] = peer; + }) + at.on('bye', function(peer){ this.to.next(peer); + if(peer.url){ delete axe.up[peer.id] } + Gun.obj.map(peer.routes, function(route, hash){ + delete route[peer.id]; + if(Gun.obj.empty(route)){ + delete axe.routes[hash]; + } + }); + }); + } + + function joindht(dht, soul, pids) { + if (!pids || !soul || !dht) { return; } + var subs = dht(soul); + var tmp = subs ? subs.split(',') : []; + Gun.obj.map(pids.split(','), function(pid) { + if (pid && tmp.indexOf(pid) === -1) { tmp.push(pid); } + }); + tmp = tmp.join(','); + dht(soul, tmp); + return tmp; + } + + var empty = {}, yes = true, u; + + module.exports = AXE; + })(USE, './axe'); }()); diff --git a/examples/axe.html b/examples/axe.html index 40dade3b..5c2caa09 100644 --- a/examples/axe.html +++ b/examples/axe.html @@ -8,8 +8,11 @@ +

- + + + : + + + + +
+ + + + + + +
+
  • + + + + 0 + +
  • +
    + + + + + + + + + \ No newline at end of file diff --git a/examples/chat/index.html b/examples/chat/index.html index c154186b..c15f4229 100644 --- a/examples/chat/index.html +++ b/examples/chat/index.html @@ -1,161 +1,213 @@ - - Converse - - - - - - -
    -
    -
    Have a Conversation...
    -
    -
      -
    • -
    + .chat__name-input { + flex: 1; + padding: 10px; + } -
    -
    -
    say
    - -

    -
    -
    + .chat__message-input { + flex: 5; + padding: 10px; + } -
    -
  • - -

    - 0 -
    -
  • -
    -
    -
    -
    + .chat__submit { + padding: 10px; + color: white; + border-radius: 5px; + } - - - - - - \ No newline at end of file +
    +
  • + +

    + 0 +
    +
  • +
    + + + + + + + + + diff --git a/examples/game/nts.html b/examples/game/nts.html index c8397f26..c52427e4 100644 --- a/examples/game/nts.html +++ b/examples/game/nts.html @@ -3,7 +3,7 @@ -

    +

    @@ -16,4 +16,4 @@ when.innerHTML = print; }); - \ No newline at end of file + diff --git a/examples/game/space.html b/examples/game/space.html index cd93483d..b2f3637a 100644 --- a/examples/game/space.html +++ b/examples/game/space.html @@ -221,7 +221,7 @@ }); function send(raw){ if(!raw){ return } - if(raw.indexOf('webrtc') >= 0){ + if(raw.indexOf('rtc') >= 0){ if(!this._send){ return } return this._send(raw); } diff --git a/examples/http.js b/examples/http.js index ceacfd1b..3ae161e1 100644 --- a/examples/http.js +++ b/examples/http.js @@ -1,21 +1,23 @@ -;(function(){ - var cluster = require('cluster'); - if(cluster.isMaster){ - return cluster.fork() && cluster.on('exit', function(){ cluster.fork() }); - } - - var fs = require('fs'); - var config = { port: process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8765 }; - var Gun = require('../'); // require('gun') - - if(process.env.HTTPS_KEY){ - config.key = fs.readFileSync(process.env.HTTPS_KEY); - config.cert = fs.readFileSync(process.env.HTTPS_CERT); - config.server = require('https').createServer(config, Gun.serve(__dirname)); - } else { - config.server = require('http').createServer(Gun.serve(__dirname)); - } - - var gun = Gun({web: config.server.listen(config.port) }); - console.log('Relay peer started on port ' + config.port + ' with /gun'); +;(function(){ + var cluster = require('cluster'); + if(cluster.isMaster){ + return cluster.fork() && cluster.on('exit', function(){ cluster.fork() }); + } + + var fs = require('fs'); + var config = { port: process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8765 }; + var Gun = require('../'); // require('gun') + + if(process.env.HTTPS_KEY){ + config.key = fs.readFileSync(process.env.HTTPS_KEY); + config.cert = fs.readFileSync(process.env.HTTPS_CERT); + config.server = require('https').createServer(config, Gun.serve(__dirname)); + } else { + config.server = require('http').createServer(Gun.serve(__dirname)); + } + + var gun = Gun({web: config.server.listen(config.port)}); + console.log('Relay peer started on port ' + config.port + ' with /gun'); + + module.exports = gun; }()); \ No newline at end of file diff --git a/examples/install.sh b/examples/install.sh index f88f0661..8e984fe5 100644 --- a/examples/install.sh +++ b/examples/install.sh @@ -8,6 +8,8 @@ # an installer that will automatically do it for you. #debian/ubuntu +su - +apt-get install sudo -y sudo apt-get update -y sudo apt-get install curl git git-core -y #fedora/openSUSE diff --git a/examples/party.html b/examples/party.html index e466da70..40783099 100644 --- a/examples/party.html +++ b/examples/party.html @@ -4,7 +4,7 @@ Party by Neon ERA - + -
    Party by NEON ERA.
    +
    Join the Private Party!
    -
    - - +
    + + + +
    +

    Your friend has invited you to add a privacy extension to your browser:

    +

    - Decrypts your friends' messages across any site!

    +

    - Stops tech monopolies from selling your private data to advertisers.

    +

    - Gives you ownership and control over all your data online.

    +

    - Creates a searchable history of your posts, friends, and more!

    +
    +

    Express your thoughts & connect with the world around you!

    - Discover new relationships.

    @@ -143,12 +168,10 @@

    - Watch fun videos and photos from people who share.

    - But this time, you own it: fully decentralized.

    -
    -
    -

    Welcome, you are currently connected to 2 peers. Why not try to sign up or log in?

    +

    Welcome,

    - Your identity is created here, by you. Not on a server.

    -

    - It uses secure cryptographic methods to protect you.

    +

    - It uses secure cryptographic methods to protect you.

    - Only you have access to it, meaning even we cannot reset your password!

    - For added security, you can freely download and run it on your own computer.

    @@ -718,7 +741,7 @@ if(e.err){ return } var m = $($("#d"+e.id)[0] || $('#d0').clone(true,true).attr('id', 'd'+e.id).css('backgroundImage', '').appendTo('#draft')).addClass('pulse'); if(up){ return up.shrink(e, resize, 1000) } - console.log(e.id, e.base64); + //console.log(e.id, e.base64); m.removeClass('pulse').css({ backgroundImage: 'url(' + e.base64 + ')', backgroundRepeat: 'no-repeat', @@ -727,7 +750,7 @@ }); }); - + diff --git a/examples/start.js.html b/examples/start.js.html new file mode 100644 index 00000000..f2acf679 --- /dev/null +++ b/examples/start.js.html @@ -0,0 +1,22 @@ +/* + + + + \ No newline at end of file diff --git a/examples/stats.html b/examples/stats.html new file mode 100644 index 00000000..e8ee7944 --- /dev/null +++ b/examples/stats.html @@ -0,0 +1,146 @@ + + + + + + + + + + + +
    0 peers 0 min 0 nodes 0 hours
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + + + \ No newline at end of file diff --git a/examples/style.css b/examples/style.css index a81e9b72..e7dd1ee4 100644 --- a/examples/style.css +++ b/examples/style.css @@ -1,38 +1,26 @@ -@import url(https://fonts.googleapis.com/css?family=Oxygen); - html, body { margin: 0; padding: 0; - font-family: 'Oxygen', 'Trebuchet MS', arial; position: relative; - background: black; - color: white; + line-height: 1.5; + font-size: 18pt; } -body { - font-size: 18pt; -} - -div, ul, ol, li, p, span, form, button, input, textarea { +div, ul, ol, li, p, span, form, button, input, textarea, img { margin: 0; padding: 0; position: relative; - overflow: hidden; - font-size: 1em; - line-height: 1.5em; + vertical-align: inherit; -webkit-transition: all 0.3s; transition: all 0.3s; box-sizing: border-box; } -button, input, textarea { - background: white; - border: none; - color: black; -} - -a { - color: white; +a, button, input, textarea { + background: inherit; + border: inherit; + color: inherit; + text-decoration: inherit; } input, textarea { @@ -43,6 +31,10 @@ ul, li { list-style: none; } +[contenteditable=true]:empty:before { + content: attr(placeholder); +} + .model, .none { display: none } .hide { opacity: 0; @@ -50,48 +42,75 @@ ul, li { transition: all 2s; } -.page { +.full, .page { width: 100%; min-height: 100vh; } +.max { + max-width: 48em; +} +.min { + min-width: 12em; +} .pad { - margin: 5% auto; - min-width: 250px; width: 95%; - max-width: 50em; + margin: 5% auto; + max-width: 48em; + min-width: 12em; } .right { float: right; + text-align: right; } .left { float: left; + text-align: left; } .center { text-align: center; + vertical-align: middle; + margin-left: auto; + margin-right: auto; } .mid { margin-left: auto; margin-right: auto; } -.flush { - line-height: 0em; -} - -.rim { - margin: 2%; -} -.gap { - padding: 3%; -} -.gully { - margin-bottom: 1%; +.top { + vertical-align: top; +} +.low { + vertical-align: bottom; } +.rim { margin: 2%; } +.gap { padding: 3%; } +.stack { line-height: 0; } +.crack { margin-bottom: 1%; } .sit { margin-bottom: 0; } + .row { width: 100%; } -.col { max-width: 33em; } +.col { + max-width: 24em; + min-width: 12em; +} + +.focus { + margin-left: auto; + margin-right: auto; + float: none; + clear: both; +} +.unit, .symbol { + display: inline-block; + vertical-align: inherit; +} + +.leak { overflow: visible; } +.hold { overflow: hidden; } + .act { display: block; font-weight: normal; @@ -100,102 +119,68 @@ ul, li { transition: all 0.3s; cursor: pointer; } -.symbol { - display: inline-block; -} + +.sap { border-radius: 0.1em; } +.jot { border-bottom: 1px dashed #95B2CA; } .loud { font-size: 150%; } -.jot { - border-bottom: 1px dashed #95B2CA; -} -.sap { - border-radius: 0.1em; +.shout { + font-size: 36pt; + font-size: 6.5vmax; } -.red { - background: #ea3224; -} -.green { - background: #33cc33; -} -.blue { - background: #4D79D8; -} -.yellow { - background: #f2b919; -} -.black { - background: black; -} -.white { - background: white; -} +.red { background: #ea3224; } +.green { background: #33cc33; } +.blue { background: #4D79D8; } +.yellow { background: #d3a438; } +.black { background: black; } +.white { background: white; } -.shade { - background: rgba(0%, 0%, 0%, 0.1); -} -.tint { - background: rgba(100%, 100%, 100%, 0.1); -} +.shade { background: rgba(0%, 0%, 0%, 0.1); } +.tint { background: rgba(100%, 100%, 100%, 0.1); } -.redt { - color: #ea3224; -} -.greent { - color: #33cc33; -} -.bluet { - color: #4D79D8; -} -.yellowt { - color: #f2b919; -} -.blackt { - color: black; -} -.whitet { - color: white; -} +.redt { color: #ea3224; } +.greent { color: #33cc33; } +.bluet { color: #4D79D8; } +.yellowt { color: #d3a438; } +.blackt { color: black; } +.whitet { color: white; } .hue { background: #4D79D8; -webkit-animation: hue 900s infinite; animation: hue 900s infinite; -} - -@keyframes hue { +} @keyframes hue { 0% {background-color: #4D79D8;} 25% {background-color: #33cc33;} - 50% {background-color: #f2b919;} + 50% {background-color: #d3a438;} 75% {background-color: #ea3224;} 100% {background-color: #4D79D8;} } @-webkit-keyframes hue { 0% {background-color: #4D79D8;} 25% {background-color: #33cc33;} - 50% {background-color: #f2b919;} + 50% {background-color: #d3a438;} 75% {background-color: #ea3224;} 100% {background-color: #4D79D8;} } .huet { - color: #4D79D8; + color: #4D79D8; -webkit-animation: huet 900s infinite; animation: huet 900s infinite; -} - -@keyframes huet { +} @keyframes huet { 0% {color: #4D79D8;} 25% {color: #33cc33;} - 50% {color: #f2b919;} + 50% {color: #d3a438;} 75% {color: #ea3224;} 100% {color: #4D79D8;} } @-webkit-keyframes huet { 0% {color: #4D79D8;} 25% {color: #33cc33;} - 50% {color: #f2b919;} + 50% {color: #d3a438;} 75% {color: #ea3224;} 100% {color: #4D79D8;} } @@ -204,19 +189,17 @@ ul, li { background: #ea3224; -webkit-animation: hue2 900s infinite; animation: hue2 900s infinite; -} - -@keyframes hue2 { +} @keyframes hue2 { 0% {background-color: #ea3224;} 25% {background-color: #4D79D8;} 50% {background-color: #33cc33;} - 75% {background-color: #f2b919;} + 75% {background-color: #d3a438;} 100% {background-color: #ea3224;} } @-webkit-keyframes hue2 { 0% {background-color: #ea3224;} 25% {background-color: #4D79D8;} 50% {background-color: #33cc33;} - 75% {background-color: #f2b919;} + 75% {background-color: #d3a438;} 100% {background-color: #ea3224;} } @@ -224,19 +207,17 @@ ul, li { color: #ea3224; -webkit-animation: huet2 900s infinite; animation: huet2 900s infinite; -} - -@keyframes huet2 { +} @keyframes huet2 { 0% {color: #ea3224;} 25% {color: #4D79D8;} 50% {color: #33cc33;} - 75% {color: #f2b919;} + 75% {color: #d3a438;} 100% {color: #ea3224;} } @-webkit-keyframes huet2 { 0% {color: #ea3224;} 25% {color: #4D79D8;} 50% {color: #33cc33;} - 75% {color: #f2b919;} + 75% {color: #d3a438;} 100% {color: #ea3224;} } @@ -244,17 +225,15 @@ ul, li { background: #33cc33; -webkit-animation: hue3 900s infinite; animation: hue3 900s infinite; -} - -@keyframes hue3 { +} @keyframes hue3 { 0% {background-color: #33cc33;} - 25% {background-color: #f2b919;} + 25% {background-color: #d3a438;} 50% {background-color: #ea3224;} 75% {background-color: #4D79D8;} 100% {background-color: #33cc33;} } @-webkit-keyframes hue3 { 0% {background-color: #33cc33;} - 25% {background-color: #f2b919;} + 25% {background-color: #d3a438;} 50% {background-color: #ea3224;} 75% {background-color: #4D79D8;} 100% {background-color: #33cc33;} @@ -264,74 +243,64 @@ ul, li { color: #33cc33; -webkit-animation: huet3 900s infinite; animation: huet3 900s infinite; -} - -@keyframes huet3 { +} @keyframes huet3 { 0% {color: #33cc33;} - 25% {color: #f2b919;} + 25% {color: #d3a438;} 50% {color: #ea3224;} 75% {color: #4D79D8;} 100% {color: #33cc33;} } @-webkit-keyframes huet3 { 0% {color: #33cc33;} - 25% {color: #f2b919;} + 25% {color: #d3a438;} 50% {color: #ea3224;} 75% {color: #4D79D8;} 100% {color: #33cc33;} } .hue4 { - background: #f2b919; + background: #d3a438; -webkit-animation: hue4 900s infinite; animation: hue4 900s infinite; -} - -@keyframes hue4 { - 0% {background-color: #f2b919;} +} @keyframes hue4 { + 0% {background-color: #d3a438;} 25% {background-color: #ea3224;} 50% {background-color: #4D79D8;} 75% {background-color: #33cc33;} - 100% {background-color: #f2b919;} + 100% {background-color: #d3a438;} } @-webkit-keyframes hue4 { - 0% {background-color: #f2b919;} + 0% {background-color: #d3a438;} 25% {background-color: #ea3224;} 50% {background-color: #4D79D8;} 75% {background-color: #33cc33;} - 100% {background-color: #f2b919;} + 100% {background-color: #d3a438;} } .huet4 { - color: #f2b919; + color: #d3a438; -webkit-animation: huet4 900s infinite; animation: huet4 900s infinite; -} - -@keyframes huet4 { - 0% {color: #f2b919;} +} @keyframes huet4 { + 0% {color: #d3a438;} 25% {color: #ea3224;} 50% {color: #4D79D8;} 75% {color: #33cc33;} - 100% {color: #f2b919;} + 100% {color: #d3a438;} } @-webkit-keyframes huet4 { - 0% {color: #f2b919;} + 0% {color: #d3a438;} 25% {color: #ea3224;} 50% {color: #4D79D8;} 75% {color: #33cc33;} - 100% {color: #f2b919;} + 100% {color: #d3a438;} } .pulse { animation: pulse 2s infinite; -} - -@keyframes pulse -{ +} @keyframes pulse { 0% {opacity: 1;} 50% {opacity: 0.5;} 100% {opacity: 1;} } - .joy { width: 100px; height: 100px; @@ -341,12 +310,18 @@ ul, li { pointer-events: none; z-index: 999999999; animation: joy 1s steps(28); +} @keyframes joy { + 0% {background-position: 0 0;} + 100% {background-position: -2800px 0;} +} + +.visually-hidden { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } -@keyframes joy { - 0% { - background-position: 0 0; - } - 100% { - background-position: -2800px 0; - } -} \ No newline at end of file diff --git a/examples/todo/index.html b/examples/todo/index.html index a3d4e630..6f941d38 100644 --- a/examples/todo/index.html +++ b/examples/todo/index.html @@ -1,83 +1,151 @@ - - Think - - - - -
    - - -
    -
    -
    Add a Thought...
    - + -
    -
      -
    -
    - - - - - -
    -
    - \ No newline at end of file + + + Think + + + + + + + +
    +
    +

    Add a thought...

    + +
    +
      +
    +
    + + + + + +
    + diff --git a/gun.js b/gun.js index e01b93ce..8c2b71f5 100644 --- a/gun.js +++ b/gun.js @@ -1,2218 +1,2266 @@ -;(function(){ - - /* UNBUILD */ - var root; - if(typeof window !== "undefined"){ root = window } - if(typeof global !== "undefined"){ root = global } - root = root || {}; - var console = root.console || {log: function(){}}; - function USE(arg, req){ - return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } - } - if(typeof module !== "undefined"){ var common = module } - /* UNBUILD */ - - ;USE(function(module){ - // Generic javascript utilities. - var Type = {}; - //Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }} - Type.fn = {is: function(fn){ return (!!fn && 'function' == typeof fn) }} - Type.bi = {is: function(b){ return (b instanceof Boolean || typeof b == 'boolean') }} - Type.num = {is: function(n){ return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }} - Type.text = {is: function(t){ return (typeof t == 'string') }} - Type.text.ify = function(t){ - if(Type.text.is(t)){ return t } - if(typeof JSON !== "undefined"){ return JSON.stringify(t) } - return (t && t.toString)? t.toString() : t; - } - Type.text.random = function(l, c){ - var s = ''; - l = l || 24; // you are not going to make a 0 length random number, so no need to check type - c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; - while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } - return s; - } - Type.text.match = function(t, o){ var r = false; - t = t || ''; - o = Type.text.is(o)? {'=': o} : o || {}; // {'~', '=', '*', '<', '>', '+', '-', '?', '!'} // ignore case, exactly equal, anything after, lexically larger, lexically lesser, added in, subtacted from, questionable fuzzy match, and ends with. - if(Type.obj.has(o,'~')){ t = t.toLowerCase(); o['='] = (o['='] || o['~']).toLowerCase() } - if(Type.obj.has(o,'=')){ return t === o['='] } - if(Type.obj.has(o,'*')){ if(t.slice(0, o['*'].length) === o['*']){ r = true; t = t.slice(o['*'].length) } else { return false }} - if(Type.obj.has(o,'!')){ if(t.slice(-o['!'].length) === o['!']){ r = true } else { return false }} - if(Type.obj.has(o,'+')){ - if(Type.list.map(Type.list.is(o['+'])? o['+'] : [o['+']], function(m){ - if(t.indexOf(m) >= 0){ r = true } else { return true } - })){ return false } - } - if(Type.obj.has(o,'-')){ - if(Type.list.map(Type.list.is(o['-'])? o['-'] : [o['-']], function(m){ - if(t.indexOf(m) < 0){ r = true } else { return true } - })){ return false } - } - if(Type.obj.has(o,'>')){ if(t > o['>']){ r = true } else { return false }} - if(Type.obj.has(o,'<')){ if(t < o['<']){ r = true } else { return false }} - function fuzzy(t,f){ var n = -1, i = 0, c; for(;c = f[i++];){ if(!~(n = t.indexOf(c, n+1))){ return false }} return true } // via http://stackoverflow.com/questions/9206013/javascript-fuzzy-search - if(Type.obj.has(o,'?')){ if(fuzzy(t, o['?'])){ r = true } else { return false }} // change name! - return r; - } - Type.list = {is: function(l){ return (l instanceof Array) }} - Type.list.slit = Array.prototype.slice; - Type.list.sort = function(k){ // creates a new sort function based off some key - return function(A,B){ - if(!A || !B){ return 0 } A = A[k]; B = B[k]; - if(A < B){ return -1 }else if(A > B){ return 1 } - else { return 0 } - } - } - Type.list.map = function(l, c, _){ return obj_map(l, c, _) } - Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation - Type.obj = {is: function(o){ return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }} - Type.obj.put = function(o, k, v){ return (o||{})[k] = v, o } - Type.obj.has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) } - Type.obj.del = function(o, k){ - if(!o){ return } - o[k] = null; - delete o[k]; - return o; - } - Type.obj.as = function(o, k, v, u){ return o[k] = o[k] || (u === v? {} : v) } - Type.obj.ify = function(o){ - if(obj_is(o)){ return o } - try{o = JSON.parse(o); - }catch(e){o={}}; - return o; - } - ;(function(){ var u; - function map(v,k){ - if(obj_has(this,k) && u !== this[k]){ return } - this[k] = v; - } - Type.obj.to = function(from, to){ - to = to || {}; - obj_map(from, map, to); - return to; - } - }()); - Type.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2 - return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways! - } - ;(function(){ - function empty(v,i){ var n = this.n; - if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return } - if(i){ return true } - } - Type.obj.empty = function(o, n){ - if(!o){ return true } - return obj_map(o,empty,{n:n})? false : true; - } - }()); - ;(function(){ - function t(k,v){ - if(2 === arguments.length){ - t.r = t.r || {}; - t.r[k] = v; - return; - } t.r = t.r || []; - t.r.push(k); - }; - var keys = Object.keys; - Type.obj.map = function(l, c, _){ - var u, i = 0, x, r, ll, lle, f = fn_is(c); - t.r = null; - if(keys && obj_is(l)){ - ll = keys(l); lle = true; - } - if(list_is(l) || ll){ - x = (ll || l).length; - for(;i < x; i++){ - var ii = (i + Type.list.index); - if(f){ - r = lle? c.call(_ || this, l[ll[i]], ll[i], t) : c.call(_ || this, l[i], ii, t); - if(r !== u){ return r } - } else { - //if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing! - if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now - } - } - } else { - for(i in l){ - if(f){ - if(obj_has(l,i)){ - r = _? c.call(_, l[i], i, t) : c(l[i], i, t); - if(r !== u){ return r } - } - } else { - //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing! - if(c === l[i]){ return i } // use this for now - } - } - } - return f? t.r : Type.list.index? 0 : -1; - } - }()); - Type.time = {}; - Type.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } - - var fn_is = Type.fn.is; - var list_is = Type.list.is; - var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map; - module.exports = Type; - })(USE, './type'); - - ;USE(function(module){ - // On event emitter generic javascript utility. - module.exports = function onto(tag, arg, as){ - if(!tag){ return {to: onto} } - var u, tag = (this.tag || (this.tag = {}))[tag] || - (this.tag[tag] = {tag: tag, to: onto._ = { - next: function(arg){ var tmp; - if((tmp = this.to)){ - tmp.next(arg); - }} - }}); - if(arg instanceof Function){ - var be = { - off: onto.off || - (onto.off = function(){ - if(this.next === onto._.next){ return !0 } - if(this === this.the.last){ - this.the.last = this.back; - } - this.to.back = this.back; - this.next = onto._.next; - this.back.to = this.to; - if(this.the.last === this.the){ - delete this.on.tag[this.the.tag]; - } - }), - to: onto._, - next: arg, - the: tag, - on: this, - as: as, - }; - (be.back = tag.last || tag).to = be; - return tag.last = be; - } - if((tag = tag.to) && u !== arg){ tag.next(arg) } - return tag; - }; - })(USE, './onto'); - - ;USE(function(module){ - /* Based on the Hypothetical Amnesia Machine thought experiment */ - function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ - if(machineState < incomingState){ - return {defer: true}; // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. - } - if(incomingState < currentState){ - return {historical: true}; // the incoming value is within the boundary of the machine's state, but not within the range. - - } - if(currentState < incomingState){ - return {converge: true, incoming: true}; // the incoming value is within both the boundary and the range of the machine's state. - - } - if(incomingState === currentState){ - incomingValue = Lexical(incomingValue) || ""; - currentValue = Lexical(currentValue) || ""; - if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different - return {state: true}; - } - /* - The following is a naive implementation, but will always work. - Never change it unless you have specific needs that absolutely require it. - If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same. - As a result, it is highly discouraged to modify despite the fact that it is naive, - because convergence (data integrity) is generally more important. - Any difference in this algorithm must be given a new and different name. - */ - if(incomingValue < currentValue){ // Lexical only works on simple value types! - return {converge: true, current: true}; - } - if(currentValue < incomingValue){ // Lexical only works on simple value types! - return {converge: true, incoming: true}; - } - } - return {err: "Invalid CRDT Data: "+ incomingValue +" to "+ currentValue +" at "+ incomingState +" to "+ currentState +"!"}; - } - if(typeof JSON === 'undefined'){ - throw new Error( - 'JSON is not included in this browser. Please load it first: ' + - 'ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js' - ); - } - var Lexical = JSON.stringify, undefined; - module.exports = HAM; - })(USE, './HAM'); - - ;USE(function(module){ - var Type = USE('./type'); - var Val = {}; - Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first. - if(v === u){ return false } - if(v === null){ return true } // "deletes", nulling out keys. - if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. - if(text_is(v) // by "text" we mean strings. - || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. - return true; // simple values are valid. - } - return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. - } - Val.link = Val.rel = {_: '#'}; - ;(function(){ - Val.link.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} - if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. - var o = {}; - obj_map(v, map, o); - if(o.id){ // a valid id was found. - return o.id; // yay! Return it. - } - } - return false; // the value was not a valid soul relation. - } - function map(s, k){ var o = this; // map over the object... - if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. - if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value. - o.id = s; // we found the soul! - } else { - return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. - } - } - }()); - Val.link.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. - Type.obj.has._ = '.'; - var rel_ = Val.link._, u; - var bi_is = Type.bi.is; - var num_is = Type.num.is; - var text_is = Type.text.is; - var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; - module.exports = Val; - })(USE, './val'); - - ;USE(function(module){ - var Type = USE('./type'); - var Val = USE('./val'); - var Node = {_: '_'}; - Node.soul = function(n, o){ return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it. - Node.soul.ify = function(n, o){ // put a soul on an object. - o = (typeof o === 'string')? {soul: o} : o || {}; - n = n || {}; // make sure it exists. - n._ = n._ || {}; // make sure meta exists. - n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it. - return n; - } - Node.soul._ = Val.link._; - ;(function(){ - Node.is = function(n, cb, as){ var s; // checks to see if an object is a valid node. - if(!obj_is(n)){ return false } // must be an object. - if(s = Node.soul(n)){ // must have a soul on it. - return !obj_map(n, map, {as:as,cb:cb,s:s,n:n}); - } - return false; // nope! This was not a valid node. - } - function map(v, k){ // we invert this because the way we check for this is via a negation. - if(k === Node._){ return } // skip over the metadata. - if(!Val.is(v)){ return true } // it is true that this is an invalid node. - if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value. - } - }()); - ;(function(){ - Node.ify = function(obj, o, as){ // returns a node from a shallow object. - if(!o){ o = {} } - else if(typeof o === 'string'){ o = {soul: o} } - else if(o instanceof Function){ o = {map: o} } - if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) } - if(o.node = Node.soul.ify(o.node || {}, o)){ - obj_map(obj, map, {o:o,as:as}); - } - return o.node; // This will only be a valid node if the object wasn't already deep! - } - function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value. - if(o.map){ - tmp = o.map.call(this.as, v, ''+k, o.node); - if(u === tmp){ - obj_del(o.node, k); - } else - if(o.node){ o.node[k] = tmp } - return; - } - if(Val.is(v)){ - o.node[k] = v; - } - } - }()); - var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map; - var text = Type.text, text_random = text.random; - var soul_ = Node.soul._; - var u; - module.exports = Node; - })(USE, './node'); - - ;USE(function(module){ - var Type = USE('./type'); - var Node = USE('./node'); - function State(){ - var t; - /*if(perf){ - t = start + perf.now(); // Danger: Accuracy decays significantly over time, even if precise. - } else {*/ - t = time(); - //} - if(last < t){ - return N = 0, last = t + State.drift; - } - return last = t + ((N += 1) / D) + State.drift; - } - var time = Type.time.is, last = -Infinity, N = 0, D = 1000; // WARNING! In the future, on machines that are D times faster than 2016AD machines, you will want to increase D by another several orders of magnitude so the processing speed never out paces the decimal resolution (increasing an integer effects the state accuracy). - var perf = (typeof performance !== 'undefined')? (performance.timing && performance) : false, start = (perf && perf.timing && perf.timing.navigationStart) || (perf = false); - State._ = '>'; - State.drift = 0; - State.is = function(n, k, o){ // convenience function to get the state on a key on a node and return it. - var tmp = (k && n && n[N_] && n[N_][State._]) || o; - if(!tmp){ return } - return num_is(tmp = tmp[k])? tmp : -Infinity; - } - State.lex = function(){ return State().toString(36).replace('.','') } - State.ify = function(n, k, s, v, soul){ // put a key's state on a node. - if(!n || !n[N_]){ // reject if it is not node-like. - if(!soul){ // unless they passed a soul - return; - } - n = Node.soul.ify(n, soul); // then make it so! - } - var tmp = obj_as(n[N_], State._); // grab the states data. - if(u !== k && k !== N_){ - if(num_is(s)){ - tmp[k] = s; // add the valid state. - } - if(u !== v){ // Note: Not its job to check for valid values! - n[k] = v; - } - } - return n; - } - State.to = function(from, k, to){ - var val = (from||{})[k]; - if(obj_is(val)){ - val = obj_copy(val); - } - return State.ify(to, k, State.is(from, k), val, Node.soul(from)); - } - ;(function(){ - State.map = function(cb, s, as){ var u; // for use with Node.ify - var o = obj_is(o = cb || s)? o : null; - cb = fn_is(cb = cb || s)? cb : null; - if(o && !cb){ - s = num_is(s)? s : State(); - o[N_] = o[N_] || {}; - obj_map(o, map, {o:o,s:s}); - return o; - } - as = as || obj_is(s)? s : u; - s = num_is(s)? s : State(); - return function(v, k, o, opt){ - if(!cb){ - map.call({o: o, s: s}, v,k); - return v; - } - cb.call(as || this || {}, v, k, o, opt); - if(obj_has(o,k) && u === o[k]){ return } - map.call({o: o, s: s}, v,k); - } - } - function map(v,k){ - if(N_ === k){ return } - State.ify(this.o, k, this.s) ; - } - }()); - var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy; - var num = Type.num, num_is = num.is; - var fn = Type.fn, fn_is = fn.is; - var N_ = Node._, u; - module.exports = State; - })(USE, './state'); - - ;USE(function(module){ - var Type = USE('./type'); - var Val = USE('./val'); - var Node = USE('./node'); - var Graph = {}; - ;(function(){ - Graph.is = function(g, cb, fn, as){ // checks to see if an object is a valid graph. - if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object. - return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object. - } - function map(n, s){ // we invert this because the way'? we check for this is via a negation. - if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph. - if(!this.cb){ return } - nf.n = n; nf.as = this.as; // sequential race conditions aren't races. - this.cb.call(nf.as, n, s, nf); - } - function nf(fn){ // optional callback for each node. - if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value. - } - }()); - ;(function(){ - Graph.ify = function(obj, env, as){ - var at = {path: [], obj: obj}; - if(!env){ - env = {}; - } else - if(typeof env === 'string'){ - env = {soul: env}; - } else - if(env instanceof Function){ - env.map = env; - } - if(env.soul){ - at.link = Val.link.ify(env.soul); - } - env.shell = (as||{}).shell; - env.graph = env.graph || {}; - env.seen = env.seen || []; - env.as = env.as || as; - node(env, at); - env.root = at.node; - return env.graph; - } - function node(env, at){ var tmp; - if(tmp = seen(env, at)){ return tmp } - at.env = env; - at.soul = soul; - if(Node.ify(at.obj, map, at)){ - at.link = at.link || Val.link.ify(Node.soul(at.node)); - if(at.obj !== env.shell){ - env.graph[Val.link.is(at.link)] = at.node; - } - } - return at; - } - function map(v,k,n){ - var at = this, env = at.env, is, tmp; - if(Node._ === k && obj_has(v,Val.link._)){ - return n._; // TODO: Bug? - } - if(!(is = valid(v,k,n, at,env))){ return } - if(!k){ - at.node = at.node || n || {}; - if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? - at.node._ = obj_copy(v._); - } - at.node = Node.soul.ify(at.node, Val.link.is(at.link)); - at.link = at.link || Val.link.ify(Node.soul(at.node)); - } - if(tmp = env.map){ - tmp.call(env.as || {}, v,k,n, at); - if(obj_has(n,k)){ - v = n[k]; - if(u === v){ - obj_del(n, k); - return; - } - if(!(is = valid(v,k,n, at,env))){ return } - } - } - if(!k){ return at.node } - if(true === is){ - return v; - } - tmp = node(env, {obj: v, path: at.path.concat(k)}); - if(!tmp.node){ return } - return tmp.link; //{'#': Node.soul(tmp.node)}; - } - function soul(id){ var at = this; - var prev = Val.link.is(at.link), graph = at.env.graph; - at.link = at.link || Val.link.ify(id); - at.link[Val.link._] = id; - if(at.node && at.node[Node._]){ - at.node[Node._][Val.link._] = id; - } - if(obj_has(graph, prev)){ - graph[id] = graph[prev]; - obj_del(graph, prev); - } - } - function valid(v,k,n, at,env){ var tmp; - if(Val.is(v)){ return true } - if(obj_is(v)){ return 1 } - if(tmp = env.invalid){ - v = tmp.call(env.as || {}, v,k,n); - return valid(v,k,n, at,env); - } - env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!"; - if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." } - } - function seen(env, at){ - var arr = env.seen, i = arr.length, has; - while(i--){ has = arr[i]; - if(at.obj === has.obj){ return has } - } - arr.push(at); - } - }()); - Graph.node = function(node){ - var soul = Node.soul(node); - if(!soul){ return } - return obj_put({}, soul, node); - } - ;(function(){ - Graph.to = function(graph, root, opt){ - if(!graph){ return } - var obj = {}; - opt = opt || {seen: {}}; - obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt}); - return obj; - } - function map(v,k){ var tmp, obj; - if(Node._ === k){ - if(obj_empty(v, Val.link._)){ - return; - } - this.obj[k] = obj_copy(v); - return; - } - if(!(tmp = Val.link.is(v))){ - this.obj[k] = v; - return; - } - if(obj = this.opt.seen[tmp]){ - this.obj[k] = obj; - return; - } - this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt); - } - }()); - var fn_is = Type.fn.is; - var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; - var u; - module.exports = Graph; - })(USE, './graph'); - - ;USE(function(module){ - // request / response module, for asking and acking messages. - USE('./onto'); // depends upon onto! - module.exports = function ask(cb, as){ - if(!this.on){ return } - if(!(cb instanceof Function)){ - if(!cb || !as){ return } - var id = cb['#'] || cb, tmp = (this.tag||empty)[id]; - if(!tmp){ return } - tmp = this.on(id, as); - clearTimeout(tmp.err); - return true; - } - var id = (as && as['#']) || Math.random().toString(36).slice(2); - if(!cb){ return id } - var to = this.on(id, cb, as); - to.err = to.err || setTimeout(function(){ - to.next({err: "Error: No ACK received yet.", lack: true}); - to.off(); - }, (this.opt||{}).lack || 9000); - return id; - } - })(USE, './ask'); - - ;USE(function(module){ - var Type = USE('./type'); - function Dup(opt){ - var dup = {s:{}}; - opt = opt || {max: 1000, age: 1000 * 9};//1000 * 60 * 2}; - dup.check = function(id){ var tmp; - if(!(tmp = dup.s[id])){ return false } - if(tmp.pass){ return tmp.pass = false } - return dup.track(id); - } - dup.track = function(id, pass){ - 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); - } - return it; - } - return dup; - } - var time_is = Type.time.is; - module.exports = Dup; - })(USE, './dup'); - - ;USE(function(module){ - - function Gun(o){ - if(o instanceof Gun){ return (this._ = {gun: this, $: this}).$ } - if(!(this instanceof Gun)){ return new Gun(o) } - return Gun.create(this._ = {gun: this, $: this, opt: o}); - } - - Gun.is = function($){ return ($ instanceof Gun) || ($ && $._ && ($ === $._.$)) || false } - - Gun.version = 0.9; - - Gun.chain = Gun.prototype; - Gun.chain.toJSON = function(){}; - - var Type = USE('./type'); - Type.obj.to(Type, Gun); - Gun.HAM = USE('./HAM'); - Gun.val = USE('./val'); - Gun.node = USE('./node'); - Gun.state = USE('./state'); - Gun.graph = USE('./graph'); - Gun.on = USE('./onto'); - Gun.ask = USE('./ask'); - Gun.dup = USE('./dup'); - - ;(function(){ - Gun.create = function(at){ - at.root = at.root || at; - at.graph = at.graph || {}; - at.on = at.on || Gun.on; - at.ask = at.ask || Gun.ask; - at.dup = at.dup || Gun.dup(); - var gun = at.$.opt(at.opt); - if(!at.once){ - at.on('in', root, at); - at.on('out', root, {at: at, out: root}); - Gun.on('create', at); - at.on('create', at); - } - at.once = 1; - return gun; - } - function root(msg){ - //add to.next(at); // TODO: MISSING FEATURE!!! - var ev = this, as = ev.as, at = as.at || as, gun = at.$, dup, tmp; - if(!(tmp = msg['#'])){ tmp = msg['#'] = text_rand(9) } - if((dup = at.dup).check(tmp)){ - if(as.out === msg.out){ - msg.out = u; - ev.to.next(msg); - } - return; - } - dup.track(tmp); - if(!at.ask(msg['@'], msg)){ - if(msg.get){ - Gun.on.get(msg, gun); //at.on('get', get(msg)); - } - if(msg.put){ - Gun.on.put(msg, gun); //at.on('put', put(msg)); - } - } - ev.to.next(msg); - if(!as.out){ - msg.out = root; - at.on('out', msg); - } - } - }()); - - ;(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.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){ - setTimeout(function(){ - Gun.on.put(msg, gun); - }, ctx.defer - ctx.machine); - } - if(!ctx.diff){ return } - at.on('put', obj_to(msg, {put: ctx.diff})); - }; - 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+"'!" } - var vertex = ctx.graph[soul] || empty, was = Gun.state.is(vertex, key, true), known = vertex[key]; - var HAM = Gun.HAM(ctx.machine, state, was, val, known); - if(!HAM.incoming){ - if(HAM.defer){ // pick the lowest - ctx.defer = (state < (ctx.defer || Infinity))? state : ctx.defer; - } - return; - } - ctx.put[soul] = Gun.state.to(node, key, ctx.put[soul]); - (ctx.diff || (ctx.diff = {}))[soul] = Gun.state.to(node, key, ctx.diff[soul]); - ctx.souls[soul] = true; - } - function merge(node, soul){ - var ctx = this, cat = ctx.$._, at = (cat.next || empty)[soul]; - if(!at){ - if(!(cat.opt||empty).super){ - ctx.souls[soul] = false; - return; - } - at = (ctx.$.get(soul)._); - } - var msg = ctx.map[soul] = { - put: node, - get: soul, - $: at.$ - }, as = {ctx: ctx, msg: msg}; - ctx.async = !!cat.tag.node; - if(ctx.ack){ msg['@'] = ctx.ack } - obj_map(node, each, as); - if(!ctx.async){ return } - if(!ctx.and){ - // If it is async, we only need to setup one listener per context (ctx) - cat.on('node', function(m){ - this.to.next(m); // make sure to call other context's listeners. - if(m !== ctx.map[m.get]){ return } // filter out events not from this context! - ctx.souls[m.get] = false; // set our many-async flag - obj_map(m.put, patch, m); // merge into view - if(obj_map(ctx.souls, function(v){ if(v){ return v } })){ return } // if flag still outstanding, keep waiting. - if(ctx.c){ return } ctx.c = 1; // failsafe for only being called once per context. - this.off(); - obj_map(ctx.map, map, ctx); // all done, trigger chains. - }); - } - ctx.and = true; - cat.on('node', msg); // each node on the current context's graph needs to be emitted though. - } - function each(val, key){ - var ctx = this.ctx, graph = ctx.graph, msg = this.msg, soul = msg.get, node = msg.put, at = (msg.$._), tmp; - graph[soul] = Gun.state.to(node, key, graph[soul]); - if(ctx.async){ return } - at.put = Gun.state.to(node, key, at.put); - } - function patch(val, key){ - var msg = this, node = msg.put, at = (msg.$._); - at.put = Gun.state.to(node, key, at.put); - } - function map(msg, soul){ - if(!msg.$){ return } - this.cat.stop = this.stop; // temporary fix till a better solution? - (msg.$._).on('in', msg); - this.cat.stop = null; // temporary fix till a better solution? - } - - Gun.on.get = function(msg, gun){ - var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; - var next = root.next || (root.next = {}), at = next[soul]; - if(obj_has(soul, '*')){ // TEMPORARY HACK FOR MARTTI, TESTING - var graph = {}; - Gun.obj.map(root.graph, function(node, s){ - if(Gun.text.match(s, soul)){ - graph[s] = Gun.obj.copy(node); - } - }); - if(!Gun.obj.empty(graph)){ - root.on('in', { - '@': msg['#'], - how: '*', - put: graph, - $: gun - }); - } - } // TEMPORARY HACK FOR MARTTI, TESTING - if(!node){ return root.on('get', msg) } - if(has){ - if(!obj_has(node, has)){ return root.on('get', msg) } - node = Gun.state.to(node, has); - // If we have a key in-memory, do we really need to fetch? - // 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.graph.node(node); - tmp = (at||empty).ack; - root.on('in', { - '@': msg['#'], - how: 'mem', - put: node, - $: gun - }); - //if(0 < tmp){ return } - root.on('get', msg); - } - }()); - - ;(function(){ - Gun.chain.opt = function(opt){ - opt = opt || {}; - var gun = this, at = gun._, tmp = opt.peers || opt; - if(!obj_is(opt)){ opt = {} } - if(!obj_is(at.opt)){ at.opt = opt } - if(text_is(tmp)){ tmp = [tmp] } - if(list_is(tmp)){ - tmp = obj_map(tmp, function(url, i, map){ - map(url, {url: url}); - }); - if(!obj_is(at.opt.peers)){ at.opt.peers = {}} - at.opt.peers = obj_to(tmp, at.opt.peers); - } - at.opt.peers = at.opt.peers || {}; - obj_to(opt, at.opt); // copies options on to `at.opt` only if not already taken. - Gun.on('opt', at); - at.opt.uuid = at.opt.uuid || function(){ return state_lex() + text_rand(12) } - return gun; - } - }()); - - var list_is = Gun.list.is; - var text = Gun.text, text_is = text.is, text_rand = text.random; - var obj = Gun.obj, obj_is = obj.is, obj_has = obj.has, obj_to = obj.to, obj_map = obj.map, obj_copy = obj.copy; - var state_lex = Gun.state.lex, _soul = Gun.val.link._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is; - var empty = {}, u; - - console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) }; - - Gun.log = function(){ return (!Gun.log.off && console.log.apply(console, arguments)), [].slice.call(arguments).join(' ') } - Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) } - - ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; - Gun.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'!"); - ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; - - if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } - try{ if(typeof common !== "undefined"){ common.exports = Gun } }catch(e){} - module.exports = Gun; - - /*Gun.on('opt', function(ctx){ // FOR TESTING PURPOSES - this.to.next(ctx); - if(ctx.once){ return } - ctx.on('node', function(msg){ - var to = this.to; - //Gun.node.is(msg.put, function(v,k){ msg.put[k] = v + v }); - setTimeout(function(){ - to.next(msg); - },1); - }); - });*/ - })(USE, './root'); - - ;USE(function(module){ - var Gun = USE('./root'); - Gun.chain.back = function(n, opt){ var tmp; - n = n || 1; - if(-1 === n || Infinity === n){ - return this._.root.$; - } else - if(1 === n){ - return (this._.back || this._).$; - } - var gun = this, at = gun._; - if(typeof n === 'string'){ - n = n.split('.'); - } - if(n instanceof Array){ - var i = 0, l = n.length, tmp = at; - for(i; i < l; i++){ - tmp = (tmp||empty)[n[i]]; - } - if(u !== tmp){ - return opt? gun : tmp; - } else - if((tmp = at.back)){ - return tmp.$.back(n, opt); - } - return; - } - if(n instanceof Function){ - var yes, tmp = {back: at}; - while((tmp = tmp.back) - && u === (yes = n(tmp, opt))){} - return yes; - } - if(Gun.num.is(n)){ - return (at.back || at).$.back(n - 1); - } - return this; - } - var empty = {}, u; - })(USE, './back'); - - ;USE(function(module){ - // WARNING: GUN is very simple, but the JavaScript chaining API around GUN - // is complicated and was extremely hard to build. If you port GUN to another - // language, consider implementing an easier API to build. - var Gun = USE('./root'); - Gun.chain.chain = function(sub){ - var gun = this, at = gun._, chain = new (sub || gun).constructor(gun), cat = chain._, root; - cat.root = root = at.root; - cat.id = ++root.once; - cat.back = gun._; - cat.on = Gun.on; - cat.on('in', input, cat); // For 'in' if I add my own listeners to each then I MUST do it before in gets called. If I listen globally for all incoming data instead though, regardless of individual listeners, I can transform the data there and then as well. - cat.on('out', output, cat); // However for output, there isn't really the global option. I must listen by adding my own listener individually BEFORE this one is ever called. - return chain; - } - - function output(msg){ - var put, get, at = this.as, back = at.back, root = at.root, tmp; - if(!msg.$){ msg.$ = at.$ } - this.to.next(msg); - if(get = msg.get){ - /*if(u !== at.put){ - at.on('in', at); - return; - }*/ - if(get['#'] || at.soul){ - get['#'] = get['#'] || at.soul; - msg['#'] || (msg['#'] = text_rand(9)); - back = (root.$.get(get['#'])._); - if(!(get = get['.'])){ - tmp = back.ack; - if(!tmp){ back.ack = -1 } - if(obj_has(back, 'put')){ - back.on('in', back); - } - if(tmp){ return } - msg.$ = back.$; - } else - if(obj_has(back.put, get)){ - put = (back.$.get(get)._); - if(!(tmp = put.ack)){ put.ack = -1 } - back.on('in', { - $: back.$, - put: Gun.state.to(back.put, get), - get: back.get - }); - if(tmp){ return } - } - root.ask(ack, msg); - return root.on('in', msg); - } - if(root.now){ root.now[at.id] = root.now[at.id] || true; at.pass = {} } - if(get['.']){ - if(at.get){ - msg = {get: {'.': at.get}, $: at.$}; - //if(back.ask || (back.ask = {})[at.get]){ return } - (back.ask || (back.ask = {})); - back.ask[at.get] = msg.$._; // TODO: PERFORMANCE? More elegant way? - return back.on('out', msg); - } - msg = {get: {}, $: at.$}; - return back.on('out', msg); - } - at.ack = at.ack || -1; - if(at.get){ - msg.$ = at.$; - get['.'] = at.get; - (back.ask || (back.ask = {}))[at.get] = msg.$._; // TODO: PERFORMANCE? More elegant way? - return back.on('out', msg); - } - } - return back.on('out', msg); - } - - function input(msg){ - var eve = this, cat = eve.as, root = cat.root, gun = msg.$, at = (gun||empty)._ || empty, change = msg.put, rel, tmp; - if(cat.get && msg.get !== cat.get){ - msg = obj_to(msg, {get: cat.get}); - } - if(cat.has && at !== cat){ - msg = obj_to(msg, {$: cat.$}); - if(at.ack){ - cat.ack = at.ack; - //cat.ack = cat.ack || at.ack; - } - } - if(u === change){ - tmp = at.put; - eve.to.next(msg); - if(cat.soul){ return } // TODO: BUG, I believee the fresh input refactor caught an edge case that a `gun.get('soul').get('key')` that points to a soul that doesn't exist will not trigger val/get etc. - if(u === tmp && u !== at.put){ return } - echo(cat, msg, eve); - if(cat.has){ - not(cat, msg); - } - obj_del(at.echo, cat.id); - obj_del(cat.map, at.id); - return; - } - if(cat.soul){ - eve.to.next(msg); - echo(cat, msg, eve); - if(cat.next){ obj_map(change, map, {msg: msg, cat: cat}) } - return; - } - if(!(rel = Gun.val.link.is(change))){ - if(Gun.val.is(change)){ - if(cat.has || cat.soul){ - not(cat, msg); - } else - if(at.has || at.soul){ - (at.echo || (at.echo = {}))[cat.id] = at.echo[at.id] || cat; - (cat.map || (cat.map = {}))[at.id] = cat.map[at.id] || {at: at}; - //if(u === at.put){ return } // Not necessary but improves performance. If we have it but at does not, that means we got things out of order and at will get it. Once at gets it, it will tell us again. - } - eve.to.next(msg); - echo(cat, msg, eve); - return; - } - if(cat.has && at !== cat && obj_has(at, 'put')){ - cat.put = at.put; - }; - if((rel = Gun.node.soul(change)) && at.has){ - at.put = (cat.root.$.get(rel)._).put; - } - tmp = (root.stop || {})[at.id]; - //if(tmp && tmp[cat.id]){ } else { - eve.to.next(msg); - //} - relate(cat, msg, at, rel); - echo(cat, msg, eve); - if(cat.next){ obj_map(change, map, {msg: msg, cat: cat}) } - return; - } - var was = root.stop; - tmp = root.stop || {}; - tmp = tmp[at.id] || (tmp[at.id] = {}); - //if(tmp[cat.id]){ return } - tmp.is = tmp.is || at.put; - tmp[cat.id] = at.put || true; - //if(root.stop){ - eve.to.next(msg) - //} - relate(cat, msg, at, rel); - echo(cat, msg, eve); - } - var C = 0; - - function relate(at, msg, from, rel){ - if(!rel || node_ === at.get){ return } - var tmp = (at.root.$.get(rel)._); - if(at.has){ - from = tmp; - } else - if(from.has){ - relate(from, msg, from, rel); - } - if(from === at){ return } - if(!from.$){ from = {} } - (from.echo || (from.echo = {}))[at.id] = from.echo[at.id] || at; - if(at.has && !(at.map||empty)[from.id]){ // if we haven't seen this before. - not(at, msg); - } - tmp = from.id? ((at.map || (at.map = {}))[from.id] = at.map[from.id] || {at: from}) : {}; - if(rel === tmp.link){ - if(!(tmp.pass || at.pass)){ - return; - } - } - if(at.pass){ - Gun.obj.map(at.map, function(tmp){ tmp.pass = true }) - obj_del(at, 'pass'); - } - if(tmp.pass){ obj_del(tmp, 'pass') } - if(at.has){ at.link = rel } - ask(at, tmp.link = rel); - } - function echo(at, msg, ev){ - if(!at.echo){ return } // || node_ === at.get ? - //if(at.has){ msg = obj_to(msg, {event: ev}) } - obj_map(at.echo, reverb, msg); - } - function reverb(to){ - if(!to || !to.on){ return } - to.on('in', this); - } - function map(data, key){ // Map over only the changes on every update. - var cat = this.cat, next = cat.next || empty, via = this.msg, chain, at, tmp; - if(node_ === key && !next[key]){ return } - if(!(at = next[key])){ - return; - } - //if(data && data[_soul] && (tmp = Gun.val.link.is(data)) && (tmp = (cat.root.$.get(tmp)._)) && obj_has(tmp, 'put')){ - // data = tmp.put; - //} - if(at.has){ - //if(!(data && data[_soul] && Gun.val.link.is(data) === Gun.node.soul(at.put))){ - if(u === at.put || !Gun.val.link.is(data)){ - at.put = data; - } - chain = at.$; - } else - if(tmp = via.$){ - tmp = (chain = via.$.get(key))._; - if(u === tmp.put || !Gun.val.link.is(data)){ - tmp.put = data; - } - } - at.on('in', { - put: data, - get: key, - $: chain, - via: via - }); - } - function not(at, msg){ - if(!(at.has || at.soul)){ return } - var tmp = at.map, root = at.root; - at.map = null; - if(at.has){ - if(at.dub && at.root.stop){ at.dub = null } - at.link = null; - } - //if(!root.now || !root.now[at.id]){ - if(!at.pass){ - if((!msg['@']) && null === tmp){ return } - //obj_del(at, 'pass'); - } - if(u === tmp && Gun.val.link.is(at.put)){ return } // This prevents the very first call of a thing from triggering a "clean up" call. // TODO: link.is(at.put) || !val.is(at.put) ? - obj_map(tmp, function(proxy){ - if(!(proxy = proxy.at)){ return } - obj_del(proxy.echo, at.id); - }); - tmp = at.put; - obj_map(at.next, function(neat, key){ - if(u === tmp && u !== at.put){ return true } - neat.put = u; - if(neat.ack){ - neat.ack = -1; - } - neat.on('in', { - get: key, - $: neat.$, - put: u - }); - }); - } - function ask(at, soul){ - var tmp = (at.root.$.get(soul)._); - if(at.ack){ - tmp.on('out', {get: {'#': soul}}); - if(!at.ask){ return } // TODO: PERFORMANCE? More elegant way? - } - tmp = at.ask; Gun.obj.del(at, 'ask'); - obj_map(tmp || at.next, function(neat, key){ - neat.on('out', {get: {'#': soul, '.': key}}); - }); - Gun.obj.del(at, 'ask'); // TODO: PERFORMANCE? More elegant way? - } - function ack(msg, ev){ - var as = this.as, get = as.get || empty, at = as.$._, tmp = (msg.put||empty)[get['#']]; - if(at.ack){ at.ack = (at.ack + 1) || 1; } - if(!msg.put || (get['.'] && !obj_has(tmp, at.get))){ - if(at.put !== u){ return } - at.on('in', { - get: at.get, - put: at.put = u, - $: at.$, - '@': msg['@'] - }); - return; - } - if(node_ == get['.']){ // is this a security concern? - at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']}); - return; - } - Gun.on.put(msg, at.root.$); - } - var empty = {}, u; - var obj = Gun.obj, obj_has = obj.has, obj_put = obj.put, obj_del = obj.del, obj_to = obj.to, obj_map = obj.map; - var text_rand = Gun.text.random; - var _soul = Gun.val.link._, node_ = Gun.node._; - })(USE, './chain'); - - ;USE(function(module){ - var Gun = USE('./root'); - Gun.chain.get = function(key, cb, as){ - var gun, tmp; - if(typeof key === 'string'){ - var back = this, cat = back._; - var next = cat.next || empty; - if(!(gun = next[key])){ - gun = cache(key, back); - } - gun = gun.$; - } else - if(key instanceof Function){ - if(true === cb){ return soul(this, key, cb, as) } - gun = this; - var at = gun._, root = at.root, tmp = root.now, ev; - as = cb || {}; - as.at = at; - as.use = key; - as.out = as.out || {}; - as.out.get = as.out.get || {}; - (ev = at.on('in', use, as)).rid = rid; - (root.now = {$:1})[as.now = at.id] = ev; - var mum = root.mum; root.mum = {}; - at.on('out', as.out); - root.mum = mum; - root.now = tmp; - return gun; - } else - if(num_is(key)){ - return this.get(''+key, cb, as); - } else - if(tmp = rel.is(key)){ - return this.get(tmp, cb, as); - } else { - (as = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP - if(cb){ cb.call(as, as._.err) } - return as; - } - if(tmp = cat.stun){ // TODO: Refactor? - gun._.stun = gun._.stun || tmp; - } - if(cb && cb instanceof Function){ - gun.get(cb, as); - } - return gun; - } - function cache(key, back){ - var cat = back._, next = cat.next, gun = back.chain(), at = gun._; - if(!next){ next = cat.next = {} } - next[at.get = key] = at; - if(back === cat.root.$){ - at.soul = key; - } else - if(cat.soul || cat.has){ - at.has = key; - //if(obj_has(cat.put, key)){ - //at.put = cat.put[key]; - //} - } - return at; - } - function soul(gun, cb, opt, as){ - var cat = gun._, acks = 0, tmp; - if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat), gun } - gun.get(function(msg, ev){ - if(u === msg.put && (tmp = (obj_map(cat.root.opt.peers, function(v,k,t){t(k)})||[]).length) && ++acks < tmp){ - return; - } - ev.rid(msg); - var at = ((at = msg.$) && at._) || {}; - tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub; - cb(tmp, as, msg, ev); - }, {out: {get: {'.':true}}}); - return gun; - } - function use(msg){ - var eve = this, as = eve.as, cat = as.at, root = cat.root, gun = msg.$, at = (gun||{})._ || {}, data = msg.put || at.put, tmp; - if((tmp = root.now) && eve !== tmp[as.now]){ return eve.to.next(msg) } - //console.log("USE:", cat.id, cat.soul, cat.has, cat.get, msg, root.mum); - //if(at.async && msg.root){ return } - //if(at.async === 1 && cat.async !== true){ return } - //if(root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - //if(!at.async && !cat.async && at.put && msg.put === at.put){ return } - //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - - - //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); - //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); - if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } - //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? - if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ - tmp = ((msg.$$ = at.root.gun.get(tmp))._); - if(u !== tmp.put){ - msg = obj_to(msg, {put: data = tmp.put}); - } - } - if((tmp = root.mum) && at.id){ // TODO: can we delete mum entirely now? - var id = at.id + (eve.id || (eve.id = Gun.text.random(9))); - if(tmp[id]){ return } - if(u !== data && !rel.is(data)){ tmp[id] = true; } - } - as.use(msg, eve); - if(eve.stun){ - eve.stun = null; - return; - } - eve.to.next(msg); - } - function rid(at){ - var cat = this.on; - if(!at || cat.soul || cat.has){ return this.off() } - if(!(at = (at = (at = at.$ || at)._ || at).id)){ return } - var map = cat.map, tmp, seen; - //if(!map || !(tmp = map[at]) || !(tmp = tmp.at)){ return } - if(tmp = (seen = this.seen || (this.seen = {}))[at]){ return true } - seen[at] = true; - return; - //tmp.echo[cat.id] = {}; // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. - //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. - return; - } - var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_to = Gun.obj.to; - var num_is = Gun.num.is; - var rel = Gun.val.link, node_soul = Gun.node.soul, node_ = Gun.node._; - var empty = {}, u; - })(USE, './get'); - - ;USE(function(module){ - var Gun = USE('./root'); - Gun.chain.put = function(data, cb, as){ - // #soul.has=value>state - // ~who#where.where=what>when@was - // TODO: BUG! Put probably cannot handle plural chains! - var gun = this, at = (gun._), root = at.root.$, tmp; - as = as || {}; - as.data = data; - as.via = as.$ = as.via || as.$ || gun; - if(typeof cb === 'string'){ - as.soul = cb; - } else { - as.ack = as.ack || cb; - } - if(at.soul){ - as.soul = at.soul; - } - if(as.soul || root === gun){ - if(!obj_is(as.data)){ - (as.ack||noop).call(as, as.out = {err: Gun.log("Data saved to the root level of the graph must be a node (an object), not a", (typeof as.data), 'of "' + as.data + '"!')}); - if(as.res){ as.res() } - return gun; - } - as.soul = as.soul || (as.not = Gun.node.soul(as.data) || (as.via.back('opt.uuid') || Gun.text.random)()); - if(!as.soul){ // polyfill async uuid for SEA - as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback - if(err){ return Gun.log(err) } // TODO: Handle error! - (as.ref||as.$).put(as.data, as.soul = soul, as); - }); - return gun; - } - as.$ = root.get(as.soul); - as.ref = as.$; - ify(as); - return gun; - } - if(Gun.is(data)){ - data.get(function(soul, o, msg){ - if(!soul && Gun.val.is(msg.put)){ - return Gun.log("The reference you are saving is a", typeof msg.put, '"'+ msg.put +'", not a node (object)!'); - } - gun.put(Gun.val.link.ify(soul), cb, as); - }, true); - return gun; - } - if(at.has && (tmp = Gun.val.link.is(data))){ at.dub = tmp } - as.ref = as.ref || (root._ === (tmp = at.back))? gun : tmp.$; - if(as.ref._.soul && Gun.val.is(as.data) && at.get){ - as.data = obj_put({}, at.get, as.data); - as.ref.put(as.data, as.soul, as); - return gun; - } - as.ref.get(any, true, {as: as}); - if(!as.out){ - // TODO: Perf idea! Make a global lock, that blocks everything while it is on, but if it is on the lock it does the expensive lookup to see if it is a dependent write or not and if not then it proceeds full speed. Meh? For write heavy async apps that would be terrible. - as.res = as.res || stun; // Gun.on.stun(as.ref); // TODO: BUG! Deal with locking? - as.$._.stun = as.ref._.stun; - } - return gun; - }; - - function ify(as){ - as.batch = batch; - var opt = as.opt||{}, env = as.env = Gun.state.map(map, opt.state); - env.soul = as.soul; - as.graph = Gun.graph.ify(as.data, env, as); - if(env.err){ - (as.ack||noop).call(as, as.out = {err: Gun.log(env.err)}); - if(as.res){ as.res() } - return; - } - as.batch(); - } - - function stun(cb){ - if(cb){ cb() } - return; - var as = this; - if(!as.ref){ return } - if(cb){ - as.after = as.ref._.tag; - as.now = as.ref._.tag = {}; - cb(); - return; - } - if(as.after){ - as.ref._.tag = as.after; - } - } - - function batch(){ var as = this; - if(!as.graph || obj_map(as.stun, no)){ return } - as.res = as.res || function(cb){ if(cb){ cb() } }; - as.res(function(){ - var cat = (as.$.back(-1)._), ask = cat.ask(function(ack){ - cat.root.on('ack', ack); - if(ack.err){ Gun.log(ack) } - if(!ack.lack){ this.off() } // One response is good enough for us currently. Later we may want to adjust this. - if(!as.ack){ return } - as.ack(ack, this); - }, as.opt); - // NOW is a hack to get synchronous replies to correctly call. - // and STOP is a hack to get async behavior to correctly call. - // neither of these are ideal, need to be fixed without hacks, - // but for now, this works for current tests. :/ - var tmp = cat.root.now; obj.del(cat.root, 'now'); - var mum = cat.root.mum; cat.root.mum = {}; - (as.ref._).on('out', { - $: as.ref, put: as.out = as.env.graph, opt: as.opt, '#': ask - }); - cat.root.mum = mum? obj.to(mum, cat.root.mum) : mum; - cat.root.now = tmp; - }, as); - if(as.res){ as.res() } - } function no(v,k){ if(v){ return true } } - - function map(v,k,n, at){ var as = this; - var is = Gun.is(v); - if(k || !at.path.length){ return } - (as.res||iife)(function(){ - var path = at.path, ref = as.ref, opt = as.opt; - var i = 0, l = path.length; - for(i; i < l; i++){ - ref = ref.get(path[i]); - } - if(is){ ref = v } - var id = (ref._).dub; - if(id || (id = Gun.node.soul(at.obj))){ - ref.back(-1).get(id); - at.soul(id); - return; - } - (as.stun = as.stun || {})[path] = true; - ref.get(soul, true, {as: {at: at, as: as, p:path}}); - }, {as: as, at: at}); - //if(is){ return {} } - } - - function soul(id, as, msg, eve){ - var as = as.as, cat = as.at; as = as.as; - var at = ((msg || {}).$ || {})._ || {}; - id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.link.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous? - if(eve){ eve.stun = true } - if(!id){ // polyfill async uuid for SEA - at.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback - if(err){ return Gun.log(err) } // TODO: Handle error. - solve(at, at.dub = at.dub || id, cat, as); - }); - return; - } - solve(at, at.dub = id, cat, as); - } - - function solve(at, id, cat, as){ - at.$.back(-1).get(id); - cat.soul(id); - as.stun[cat.path] = false; - as.batch(); - } - - function any(soul, as, msg, eve){ - as = as.as; - if(!msg.$ || !msg.$._){ return } // TODO: Handle - if(msg.err){ // TODO: Handle - console.log("Please report this as an issue! Put.any.err"); - return; - } - var at = (msg.$._), data = at.put, opt = as.opt||{}, root, tmp; - if((tmp = as.ref) && tmp._.now){ return } - if(eve){ eve.stun = true } - if(as.ref !== as.$){ - tmp = (as.$._).get || at.get; - if(!tmp){ // TODO: Handle - console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? - return; - } - as.data = obj_put({}, tmp, as.data); - tmp = null; - } - if(u === data){ - if(!at.get){ return } // TODO: Handle - if(!soul){ - tmp = at.$.back(function(at){ - if(at.link || at.soul){ return at.link || at.soul } - as.data = obj_put({}, at.get, as.data); - }); - } - tmp = tmp || at.soul || at.link || at.dub;// || at.get; - at = tmp? (at.root.$.get(tmp)._) : at; - as.soul = tmp; - data = as.data; - } - if(!as.not && !(as.soul = as.soul || soul)){ - if(as.path && obj_is(as.data)){ - as.soul = (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); - } else { - //as.data = obj_put({}, as.$._.get, as.data); - if(node_ == at.get){ - as.soul = (at.put||empty)['#'] || at.dub; - } - as.soul = as.soul || at.soul || at.link || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); - } - if(!as.soul){ // polyfill async uuid for SEA - as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback - if(err){ return Gun.log(err) } // Handle error. - as.ref.put(as.data, as.soul = soul, as); - }); - return; - } - } - as.ref.put(as.data, as.soul, as); - } - var obj = Gun.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; - var u, empty = {}, noop = function(){}, iife = function(fn,as){fn.call(as||empty)}; - var node_ = Gun.node._; - })(USE, './put'); - - ;USE(function(module){ - var Gun = USE('./root'); - USE('./chain'); - USE('./back'); - USE('./put'); - USE('./get'); - module.exports = Gun; - })(USE, './index'); - - ;USE(function(module){ - var Gun = USE('./index'); - Gun.chain.on = function(tag, arg, eas, as){ - var gun = this, at = gun._, tmp, act, off; - if(typeof tag === 'string'){ - if(!arg){ return at.on(tag) } - act = at.on(tag, arg, eas || at, as); - if(eas && eas.$){ - (eas.subs || (eas.subs = [])).push(act); - } - return gun; - } - var opt = arg; - opt = (true === opt)? {change: true} : opt || {}; - opt.at = at; - opt.ok = tag; - //opt.last = {}; - gun.get(ok, opt); // TODO: PERF! Event listener leak!!!? - return gun; - } - - function ok(msg, ev){ var opt = this; - var gun = msg.$, at = (gun||{})._ || {}, data = at.put || msg.put, cat = opt.at, tmp; - if(u === data){ - return; - } - if(tmp = msg.$$){ - tmp = (msg.$$._); - if(u === tmp.put){ - return; - } - data = tmp.put; - } - if(opt.change){ // TODO: BUG? Move above the undef checks? - data = msg.put; - } - // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE - //if(tmp.put === data && tmp.get === id && !Gun.node.soul(data)){ return } - //tmp.put = data; - //tmp.get = id; - // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE - //at.last = data; - if(opt.as){ - opt.ok.call(opt.as, msg, ev); - } else { - opt.ok.call(gun, data, msg.get, msg, ev); - } - } - - Gun.chain.val = function(cb, opt){ - Gun.log.once("onceval", "Future Breaking API Change: .val -> .once, apologies unexpected."); - return this.once(cb, opt); - } - Gun.chain.once = function(cb, opt){ - var gun = this, at = gun._, data = at.put; - if(0 < at.ack && u !== data){ - (cb || noop).call(gun, data, at.get); - return gun; - } - if(cb){ - (opt = opt || {}).ok = cb; - opt.at = at; - opt.out = {'#': Gun.text.random(9)}; - gun.get(val, {as: opt}); - opt.async = true; //opt.async = at.stun? 1 : true; - } else { - Gun.log.once("valonce", "Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); - var chain = gun.chain(); - chain._.nix = gun.once(function(){ - chain._.on('in', gun._); - }); - return chain; - } - return gun; - } - - function val(msg, eve, to){ - var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; - if(tmp = msg.$$){ - link = tmp = (msg.$$._); - if(u !== link.put){ - data = link.put; - } - } - if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } - if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) - || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (!to && (link||at).ack <= tmp))){ - tmp = (eve.wait = {})[at.id] = setTimeout(function(){ - val.call({as:opt}, msg, eve, tmp || 1); - }, opt.wait || 99); - return; - } - if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) } - eve.rid(msg); - opt.ok.call(gun || opt.$, data, msg.get); - } - - Gun.chain.off = function(){ - // make off more aggressive. Warning, it might backfire! - var gun = this, at = gun._, tmp; - var cat = at.back; - if(!cat){ return } - if(tmp = cat.next){ - if(tmp[at.get]){ - obj_del(tmp, at.get); - } else { - - } - } - if(tmp = cat.ask){ - obj_del(tmp, at.get); - } - if(tmp = cat.put){ - obj_del(tmp, at.get); - } - if(tmp = at.soul){ - obj_del(cat.root.graph, tmp); - } - if(tmp = at.map){ - obj_map(tmp, function(at){ - if(at.link){ - cat.root.$.get(at.link).off(); - } - }); - } - if(tmp = at.next){ - obj_map(tmp, function(neat){ - neat.$.off(); - }); - } - at.on('off', {}); - return gun; - } - var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to; - var rel = Gun.val.link; - var empty = {}, noop = function(){}, u; - })(USE, './on'); - - ;USE(function(module){ - var Gun = USE('./index'); - Gun.chain.map = function(cb, opt, t){ - var gun = this, cat = gun._, chain; - if(!cb){ - if(chain = cat.each){ return chain } - cat.each = chain = gun.chain(); - chain._.nix = gun.back('nix'); - gun.on('in', map, chain._); - return chain; - } - Gun.log.once("mapfn", "Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); - chain = gun.chain(); - gun.map().on(function(data, key, at, ev){ - var next = (cb||noop).call(this, data, key, at, ev); - if(u === next){ return } - if(data === next){ return chain._.on('in', at) } - if(Gun.is(next)){ return chain._.on('in', next._) } - chain._.on('in', {get: key, put: next}); - }); - return chain; - } - function map(msg){ - if(!msg.put || Gun.val.is(msg.put)){ return this.to.next(msg) } - if(this.as.nix){ this.off() } // TODO: Ugly hack! - obj_map(msg.put, each, {at: this.as, msg: msg}); - this.to.next(msg); - } - function each(v,k){ - if(n_ === k){ return } - var msg = this.msg, gun = msg.$, at = this.at, tmp = (gun.get(k)._); - (tmp.echo || (tmp.echo = {}))[at.id] = tmp.echo[at.id] || at; - } - var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u; - })(USE, './map'); - - ;USE(function(module){ - var Gun = USE('./index'); - Gun.chain.set = function(item, cb, opt){ - var gun = this, soul; - cb = cb || function(){}; - opt = opt || {}; opt.item = opt.item || item; - if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } - if(!Gun.is(item)){ - if(Gun.obj.is(item)){; - item = gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).put(item); - } - return gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); - } - item.get(function(soul, o, msg){ - if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } - gun.put(Gun.obj.put({}, soul, Gun.val.link.ify(soul)), cb, opt); - },true); - return item; - } - })(USE, './set'); - - ;USE(function(module){ - if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? - - var root, noop = function(){}, store, u; - try{store = (Gun.window||noop).localStorage}catch(e){} - if(!store){ - console.log("Warning: No localStorage exists to persist data to!"); - store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; - } - /* - NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! - If you update anything here, consider updating the other adapters as well. - */ - - Gun.on('create', function(root){ - // This code is used to queue offline writes for resync. - // See the next 'opt' code below for actual saving of data. - var ev = this.to, opt = root.opt; - if(root.once){ return ev.next(root) } - //if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! - opt.prefix = opt.file || 'gun/'; - var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {}; - var empty = Gun.obj.empty, id, to, go; - // add re-sync command. - if(!empty(gap)){ - var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {}; - Gun.obj.map(gap, function(node, soul){ - Gun.obj.map(node, function(val, key){ - send[soul] = Gun.state.to(disk[soul], key, send[soul]); - }); - }); - setTimeout(function(){ - root.on('out', {put: send, '#': root.ask(ack)}); - },1); - } - - root.on('out', function(msg){ - if(msg.lS){ return } - if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){ - id = msg['#']; - Gun.graph.is(msg.put, null, map); - if(!to){ to = setTimeout(flush, opt.wait || 1) } - } - this.to.next(msg); - }); - root.on('ack', ack); - - function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook. - if(ack.err || !ack.ok){ return } - var id = ack['@']; - setTimeout(function(){ - Gun.obj.map(gap, function(node, soul){ - Gun.obj.map(node, function(val, key){ - if(id !== val){ return } - delete node[key]; - }); - if(empty(node)){ - delete gap[soul]; - } - }); - flush(); - }, opt.wait || 1); - }; - ev.next(root); - - var map = function(val, key, node, soul){ - (gap[soul] || (gap[soul] = {}))[key] = id; - } - var flush = function(){ - clearTimeout(to); - to = false; - try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap)); - }catch(e){ Gun.log(err = e || "localStorage failure") } - } - }); - - Gun.on('create', function(root){ - this.to.next(root); - var opt = root.opt; - if(root.once){ return } - if(false === opt.localStorage){ return } - opt.prefix = opt.file || 'gun/'; - var graph = root.graph, acks = {}, count = 0, to; - var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; - var lS = function(){}, u; - root.on('localStorage', disk); // NON-STANDARD EVENT! - - root.on('put', function(at){ - this.to.next(at); - Gun.graph.is(at.put, null, map); - if(!at['@']){ acks[at['#']] = true; } // only ack non-acks. - count += 1; - if(count >= (opt.batch || 1000)){ - return flush(); - } - if(to){ return } - to = setTimeout(flush, opt.wait || 1); - }); - - root.on('get', function(msg){ - this.to.next(msg); - var lex = msg.get, soul, data, u; - function to(){ - if(!lex || !(soul = lex['#'])){ return } - //if(0 >= msg.cap){ return } - var has = lex['.']; - data = disk[soul] || u; - if(data && has){ - data = Gun.state.to(data, has); - } - if(!data && !Gun.obj.empty(opt.peers)){ // if data not found, don't ack if there are peers. - return; // Hmm, what if we have peers but we are disconnected? - } - //console.log("lS get", lex, data); - root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$}); - }; - Gun.debug? setTimeout(to,1) : to(); - }); - - var map = function(val, key, node, soul){ - disk[soul] = Gun.state.to(node, key, disk[soul]); - } - - var flush = function(data){ - var err; - count = 0; - clearTimeout(to); - to = false; - var ack = acks; - acks = {}; - if(data){ disk = data } - try{store.setItem(opt.prefix, JSON.stringify(disk)); - }catch(e){ - Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."); - root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); - } - if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers. - Gun.obj.map(ack, function(yes, id){ - root.on('in', { - '@': id, - err: err, - ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number. - }); - }); - } - }); - })(USE, './adapters/localStorage'); - - ;USE(function(module){ - var Type = USE('../type'); - - function Mesh(ctx){ - var mesh = function(){}; - var opt = ctx.opt || {}; - opt.log = opt.log || console.log; - opt.gap = opt.gap || opt.wait || 1; - opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. - - mesh.out = function(msg){ var tmp; - if(this.to){ this.to.next(msg) } - //if(mesh.last != msg['#']){ return mesh.last = msg['#'], this.to.next(msg) } - if((tmp = msg['@']) - && (tmp = ctx.dup.s[tmp]) - && (tmp = tmp.it) - && tmp._){ - mesh.say(msg, (tmp._).via, 1); - tmp['##'] = msg['##']; - return; - } - // add hook for AXE? - //if (Gun.AXE && opt && opt.super) { Gun.AXE.say(msg, mesh.say, this); return; } // rogowski - mesh.say(msg); - } - - ctx.on('create', function(root){ - root.opt.pid = root.opt.pid || Type.text.random(9); - this.to.next(root); - ctx.on('out', mesh.out); - }); - - mesh.hear = function(raw, peer){ - if(!raw){ return } - var dup = ctx.dup, id, hash, msg, tmp = raw[0]; - if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) } - try{msg = JSON.parse(raw); - }catch(e){opt.log('DAM JSON parse error', e)} - if('{' === tmp){ - if(!msg){ return } - if(dup.check(id = msg['#'])){ return } - dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. - if((tmp = msg['@']) && msg.put){ - hash = msg['##'] || (msg['##'] = mesh.hash(msg)); - if((tmp = tmp + hash) != id){ - if(dup.check(tmp)){ return } - (tmp = dup.s)[hash] = tmp[id]; - } - } - (msg._ = function(){}).via = peer; - if((tmp = msg['><'])){ - (msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); - } - if(msg.dam){ - if(tmp = mesh.hear[msg.dam]){ - tmp(msg, peer, ctx); - } - return; - } - ctx.on('in', msg); - - return; - } else - if('[' === tmp){ - if(!msg){ return } - var i = 0, m; - while(m = msg[i++]){ - mesh.hear(m, peer); - } - - return; - } - } - - ;(function(){ - mesh.say = function(msg, peer, o){ - /* - TODO: Plenty of performance optimizations - that can be made just based off of ordering, - and reducing function calls for cached writes. - */ - if(!peer){ - Type.obj.map(opt.peers, function(peer){ - mesh.say(msg, peer); - }); - return; - } - var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! - if(!wire){ return } - msh = (msg._) || empty; - if(peer === msh.via){ return } - if(!(raw = msh.raw)){ raw = mesh.raw(msg) } - if((tmp = msg['@']) - && (tmp = ctx.dup.s[tmp]) - && (tmp = tmp.it)){ - if(tmp.get && tmp['##'] && tmp['##'] === msg['##']){ // PERF: move this condition outside say? - return; // TODO: this still needs to be tested in the browser! - } - } - if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id]) && !o){ return } // TODO: still needs to be tested - if(peer.batch){ - peer.tail = (peer.tail || 0) + raw.length; - if(peer.tail <= opt.pack){ - peer.batch.push(raw); - return; - } - flush(peer); - } - peer.batch = []; - setTimeout(function(){flush(peer)}, opt.gap); - send(raw, peer); - } - function flush(peer){ - var tmp = peer.batch; - if(!tmp){ return } - peer.batch = peer.tail = null; - if(!tmp.length){ return } - try{send(JSON.stringify(tmp), peer); - }catch(e){opt.log('DAM JSON stringify error', e)} - } - function send(raw, peer){ - var wire = peer.wire; - try{ - if(wire.send){ - wire.send(raw); - } else - if(peer.say){ - peer.say(raw); - } - }catch(e){ - (peer.queue = peer.queue || []).push(raw); - } - } - - }()); - - ;(function(){ - - mesh.raw = function(msg){ - if(!msg){ return '' } - var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp; - if(tmp = msh.raw){ return tmp } - if(typeof msg === 'string'){ return msg } - if(msg['@'] && (tmp = msg.put)){ - if(!(hash = msg['##'])){ - put = $(tmp, sort) || ''; - hash = mesh.hash(msg, put); - msg['##'] = hash; - } - (tmp = dup.s)[hash = msg['@']+hash] = tmp[msg['#']]; - msg['#'] = hash || msg['#']; - if(put){ (msg = Type.obj.to(msg)).put = _ } - } - var i = 0, to = []; Type.obj.map(opt.peers, function(p){ - to.push(p.url || p.id); if(++i > 9){ return true } // limit server, fast fix, improve later! - }); msg['><'] = to.join(); - var raw = $(msg); - if(u !== put){ - tmp = raw.indexOf(_, raw.indexOf('put')); - raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); - //raw = raw.replace('"'+ _ +'"', put); // https://github.com/amark/gun/wiki/@$$ Heisenbug - } - if(msh){ - msh.raw = raw; - } - return raw; - } - - mesh.hash = function(msg, hash){ - return Mesh.hash(hash || $(msg.put, sort) || '') || msg['#'] || Type.text.random(9); - } - - function sort(k, v){ var tmp; - if(!(v instanceof Object)){ return v } - Type.obj.map(Object.keys(v).sort(), map, {to: tmp = {}, on: v}); - return tmp; - } - - function map(k){ - this.to[k] = this.on[k]; - } - var $ = JSON.stringify, _ = ':])([:'; - - }()); - - mesh.hi = function(peer){ - var tmp = peer.wire || {}; - if(peer.id || peer.url){ - opt.peers[peer.url || peer.id] = peer; - Type.obj.del(opt.peers, tmp.id); - } else { - tmp = tmp.id = tmp.id || Type.text.random(9); - mesh.say({dam: '?'}, opt.peers[tmp] = peer); - } - if(!tmp.hied){ ctx.on(tmp.hied = 'hi', peer) } - tmp = peer.queue; peer.queue = []; - Type.obj.map(tmp, function(msg){ - mesh.say(msg, peer); - }); - } - mesh.bye = function(peer){ - Type.obj.del(opt.peers, peer.id); // assume if peer.url then reconnect - ctx.on('bye', peer); - } - - mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } - mesh.hear['?'] = function(msg, peer){ - if(!msg.pid){ return mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer) } - peer.id = peer.id || msg.pid; - mesh.hi(peer); - } - - return mesh; - } - - Mesh.hash = function(s){ // via SO - if(typeof s !== 'string'){ return {err: 1} } - var c = 0; - if(!s.length){ return c } - for(var i=0,l=s.length,n; i= 0 || Infinity === n || -Infinity === n) }} + Type.text = {is: function(t){ return (typeof t == 'string') }} + Type.text.ify = function(t){ + if(Type.text.is(t)){ return t } + if(typeof JSON !== "undefined"){ return JSON.stringify(t) } + return (t && t.toString)? t.toString() : t; + } + Type.text.random = function(l, c){ + var s = ''; + l = l || 24; // you are not going to make a 0 length random number, so no need to check type + c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; + while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } + return s; + } + Type.text.match = function(t, o){ var tmp, u; + if('string' !== typeof t){ return false } + if('string' == typeof o){ o = {'=': o} } + o = o || {}; + tmp = (o['='] || o['*'] || o['>'] || o['<']); + if(t === tmp){ return true } + if(u !== o['=']){ return false } + tmp = (o['*'] || o['>'] || o['<']); + if(t.slice(0, (tmp||'').length) === tmp){ return true } + if(u !== o['*']){ return false } + if(u !== o['>'] && u !== o['<']){ + return (t >= o['>'] && t <= o['<'])? true : false; + } + if(u !== o['>'] && t >= o['>']){ return true } + if(u !== o['<'] && t <= o['<']){ return true } + return false; + } + Type.list = {is: function(l){ return (l instanceof Array) }} + Type.list.slit = Array.prototype.slice; + Type.list.sort = function(k){ // creates a new sort function based off some key + return function(A,B){ + if(!A || !B){ return 0 } A = A[k]; B = B[k]; + if(A < B){ return -1 }else if(A > B){ return 1 } + else { return 0 } + } + } + Type.list.map = function(l, c, _){ return obj_map(l, c, _) } + Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation + Type.obj = {is: function(o){ return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }} + Type.obj.put = function(o, k, v){ return (o||{})[k] = v, o } + Type.obj.has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) } + Type.obj.del = function(o, k){ + if(!o){ return } + o[k] = null; + delete o[k]; + return o; + } + Type.obj.as = function(o, k, v, u){ return o[k] = o[k] || (u === v? {} : v) } + Type.obj.ify = function(o){ + if(obj_is(o)){ return o } + try{o = JSON.parse(o); + }catch(e){o={}}; + return o; + } + ;(function(){ var u; + function map(v,k){ + if(obj_has(this,k) && u !== this[k]){ return } + this[k] = v; + } + Type.obj.to = function(from, to){ + to = to || {}; + obj_map(from, map, to); + return to; + } + }()); + Type.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2 + return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways! + } + ;(function(){ + function empty(v,i){ var n = this.n; + if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return } + if(i){ return true } + } + Type.obj.empty = function(o, n){ + if(!o){ return true } + return obj_map(o,empty,{n:n})? false : true; + } + }()); + ;(function(){ + function t(k,v){ + if(2 === arguments.length){ + t.r = t.r || {}; + t.r[k] = v; + return; + } t.r = t.r || []; + t.r.push(k); + }; + var keys = Object.keys, map; + Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) } + Type.obj.map = map = function(l, c, _){ + var u, i = 0, x, r, ll, lle, f = fn_is(c); + t.r = null; + if(keys && obj_is(l)){ + ll = keys(l); lle = true; + } + if(list_is(l) || ll){ + x = (ll || l).length; + for(;i < x; i++){ + var ii = (i + Type.list.index); + if(f){ + r = lle? c.call(_ || this, l[ll[i]], ll[i], t) : c.call(_ || this, l[i], ii, t); + if(r !== u){ return r } + } else { + //if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing! + if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now + } + } + } else { + for(i in l){ + if(f){ + if(obj_has(l,i)){ + r = _? c.call(_, l[i], i, t) : c(l[i], i, t); + if(r !== u){ return r } + } + } else { + //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing! + if(c === l[i]){ return i } // use this for now + } + } + } + return f? t.r : Type.list.index? 0 : -1; + } + }()); + Type.time = {}; + Type.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } + + var fn_is = Type.fn.is; + var list_is = Type.list.is; + var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map; + module.exports = Type; + })(USE, './type'); + + ;USE(function(module){ + // On event emitter generic javascript utility. + module.exports = function onto(tag, arg, as){ + if(!tag){ return {to: onto} } + var u, tag = (this.tag || (this.tag = {}))[tag] || + (this.tag[tag] = {tag: tag, to: onto._ = { + next: function(arg){ var tmp; + if((tmp = this.to)){ + tmp.next(arg); + }} + }}); + if(arg instanceof Function){ + var be = { + off: onto.off || + (onto.off = function(){ + if(this.next === onto._.next){ return !0 } + if(this === this.the.last){ + this.the.last = this.back; + } + this.to.back = this.back; + this.next = onto._.next; + this.back.to = this.to; + if(this.the.last === this.the){ + delete this.on.tag[this.the.tag]; + } + }), + to: onto._, + next: arg, + the: tag, + on: this, + as: as, + }; + (be.back = tag.last || tag).to = be; + return tag.last = be; + } + if((tag = tag.to) && u !== arg){ tag.next(arg) } + return tag; + }; + })(USE, './onto'); + + ;USE(function(module){ + /* Based on the Hypothetical Amnesia Machine thought experiment */ + function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ + if(machineState < incomingState){ + return {defer: true}; // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. + } + if(incomingState < currentState){ + return {historical: true}; // the incoming value is within the boundary of the machine's state, but not within the range. + + } + if(currentState < incomingState){ + return {converge: true, incoming: true}; // the incoming value is within both the boundary and the range of the machine's state. + + } + if(incomingState === currentState){ + incomingValue = Lexical(incomingValue) || ""; + currentValue = Lexical(currentValue) || ""; + if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different + return {state: true}; + } + /* + The following is a naive implementation, but will always work. + Never change it unless you have specific needs that absolutely require it. + If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same. + As a result, it is highly discouraged to modify despite the fact that it is naive, + because convergence (data integrity) is generally more important. + Any difference in this algorithm must be given a new and different name. + */ + if(incomingValue < currentValue){ // Lexical only works on simple value types! + return {converge: true, current: true}; + } + if(currentValue < incomingValue){ // Lexical only works on simple value types! + return {converge: true, incoming: true}; + } + } + return {err: "Invalid CRDT Data: "+ incomingValue +" to "+ currentValue +" at "+ incomingState +" to "+ currentState +"!"}; + } + if(typeof JSON === 'undefined'){ + throw new Error( + 'JSON is not included in this browser. Please load it first: ' + + 'ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js' + ); + } + var Lexical = JSON.stringify, undefined; + module.exports = HAM; + })(USE, './HAM'); + + ;USE(function(module){ + var Type = USE('./type'); + var Val = {}; + Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first. + if(v === u){ return false } + if(v === null){ return true } // "deletes", nulling out keys. + if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. + if(text_is(v) // by "text" we mean strings. + || bi_is(v) // by "binary" we mean boolean. + || num_is(v)){ // by "number" we mean integers or decimals. + return true; // simple values are valid. + } + return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. + } + Val.link = Val.rel = {_: '#'}; + ;(function(){ + Val.link.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} + if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. + var o = {}; + obj_map(v, map, o); + if(o.id){ // a valid id was found. + return o.id; // yay! Return it. + } + } + return false; // the value was not a valid soul relation. + } + function map(s, k){ var o = this; // map over the object... + if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. + if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value. + o.id = s; // we found the soul! + } else { + return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. + } + } + }()); + Val.link.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. + Type.obj.has._ = '.'; + var rel_ = Val.link._, u; + var bi_is = Type.bi.is; + var num_is = Type.num.is; + var text_is = Type.text.is; + var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; + module.exports = Val; + })(USE, './val'); + + ;USE(function(module){ + var Type = USE('./type'); + var Val = USE('./val'); + var Node = {_: '_'}; + Node.soul = function(n, o){ return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it. + Node.soul.ify = function(n, o){ // put a soul on an object. + o = (typeof o === 'string')? {soul: o} : o || {}; + n = n || {}; // make sure it exists. + n._ = n._ || {}; // make sure meta exists. + n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it. + return n; + } + Node.soul._ = Val.link._; + ;(function(){ + Node.is = function(n, cb, as){ var s; // checks to see if an object is a valid node. + if(!obj_is(n)){ return false } // must be an object. + if(s = Node.soul(n)){ // must have a soul on it. + return !obj_map(n, map, {as:as,cb:cb,s:s,n:n}); + } + return false; // nope! This was not a valid node. + } + function map(v, k){ // we invert this because the way we check for this is via a negation. + if(k === Node._){ return } // skip over the metadata. + if(!Val.is(v)){ return true } // it is true that this is an invalid node. + if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value. + } + }()); + ;(function(){ + Node.ify = function(obj, o, as){ // returns a node from a shallow object. + if(!o){ o = {} } + else if(typeof o === 'string'){ o = {soul: o} } + else if(o instanceof Function){ o = {map: o} } + if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) } + if(o.node = Node.soul.ify(o.node || {}, o)){ + obj_map(obj, map, {o:o,as:as}); + } + return o.node; // This will only be a valid node if the object wasn't already deep! + } + function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value. + if(o.map){ + tmp = o.map.call(this.as, v, ''+k, o.node); + if(u === tmp){ + obj_del(o.node, k); + } else + if(o.node){ o.node[k] = tmp } + return; + } + if(Val.is(v)){ + o.node[k] = v; + } + } + }()); + var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map; + var text = Type.text, text_random = text.random; + var soul_ = Node.soul._; + var u; + module.exports = Node; + })(USE, './node'); + + ;USE(function(module){ + var Type = USE('./type'); + var Node = USE('./node'); + function State(){ + var t; + /*if(perf){ + t = start + perf.now(); // Danger: Accuracy decays significantly over time, even if precise. + } else {*/ + t = time(); + //} + if(last < t){ + return N = 0, last = t + State.drift; + } + return last = t + ((N += 1) / D) + State.drift; + } + var time = Type.time.is, last = -Infinity, N = 0, D = 1000; // WARNING! In the future, on machines that are D times faster than 2016AD machines, you will want to increase D by another several orders of magnitude so the processing speed never out paces the decimal resolution (increasing an integer effects the state accuracy). + var perf = (typeof performance !== 'undefined')? (performance.timing && performance) : false, start = (perf && perf.timing && perf.timing.navigationStart) || (perf = false); + State._ = '>'; + State.drift = 0; + State.is = function(n, k, o){ // convenience function to get the state on a key on a node and return it. + var tmp = (k && n && n[N_] && n[N_][State._]) || o; + if(!tmp){ return } + return num_is(tmp = tmp[k])? tmp : -Infinity; + } + State.lex = function(){ return State().toString(36).replace('.','') } + State.ify = function(n, k, s, v, soul){ // put a key's state on a node. + if(!n || !n[N_]){ // reject if it is not node-like. + if(!soul){ // unless they passed a soul + return; + } + n = Node.soul.ify(n, soul); // then make it so! + } + var tmp = obj_as(n[N_], State._); // grab the states data. + if(u !== k && k !== N_){ + if(num_is(s)){ + tmp[k] = s; // add the valid state. + } + if(u !== v){ // Note: Not its job to check for valid values! + n[k] = v; + } + } + return n; + } + State.to = function(from, k, to){ + var val = (from||{})[k]; + if(obj_is(val)){ + val = obj_copy(val); + } + return State.ify(to, k, State.is(from, k), val, Node.soul(from)); + } + ;(function(){ + State.map = function(cb, s, as){ var u; // for use with Node.ify + var o = obj_is(o = cb || s)? o : null; + cb = fn_is(cb = cb || s)? cb : null; + if(o && !cb){ + s = num_is(s)? s : State(); + o[N_] = o[N_] || {}; + obj_map(o, map, {o:o,s:s}); + return o; + } + as = as || obj_is(s)? s : u; + s = num_is(s)? s : State(); + return function(v, k, o, opt){ + if(!cb){ + map.call({o: o, s: s}, v,k); + return v; + } + cb.call(as || this || {}, v, k, o, opt); + if(obj_has(o,k) && u === o[k]){ return } + map.call({o: o, s: s}, v,k); + } + } + function map(v,k){ + if(N_ === k){ return } + State.ify(this.o, k, this.s) ; + } + }()); + var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy; + var num = Type.num, num_is = num.is; + var fn = Type.fn, fn_is = fn.is; + var N_ = Node._, u; + module.exports = State; + })(USE, './state'); + + ;USE(function(module){ + var Type = USE('./type'); + var Val = USE('./val'); + var Node = USE('./node'); + var Graph = {}; + ;(function(){ + Graph.is = function(g, cb, fn, as){ // checks to see if an object is a valid graph. + if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object. + return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object. + } + function map(n, s){ // we invert this because the way'? we check for this is via a negation. + if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph. + if(!this.cb){ return } + nf.n = n; nf.as = this.as; // sequential race conditions aren't races. + this.cb.call(nf.as, n, s, nf); + } + function nf(fn){ // optional callback for each node. + if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value. + } + }()); + ;(function(){ + Graph.ify = function(obj, env, as){ + var at = {path: [], obj: obj}; + if(!env){ + env = {}; + } else + if(typeof env === 'string'){ + env = {soul: env}; + } else + if(env instanceof Function){ + env.map = env; + } + if(env.soul){ + at.link = Val.link.ify(env.soul); + } + env.shell = (as||{}).shell; + env.graph = env.graph || {}; + env.seen = env.seen || []; + env.as = env.as || as; + node(env, at); + env.root = at.node; + return env.graph; + } + function node(env, at){ var tmp; + if(tmp = seen(env, at)){ return tmp } + at.env = env; + at.soul = soul; + if(Node.ify(at.obj, map, at)){ + at.link = at.link || Val.link.ify(Node.soul(at.node)); + if(at.obj !== env.shell){ + env.graph[Val.link.is(at.link)] = at.node; + } + } + return at; + } + function map(v,k,n){ + var at = this, env = at.env, is, tmp; + if(Node._ === k && obj_has(v,Val.link._)){ + return n._; // TODO: Bug? + } + if(!(is = valid(v,k,n, at,env))){ return } + if(!k){ + at.node = at.node || n || {}; + if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? + at.node._ = obj_copy(v._); + } + at.node = Node.soul.ify(at.node, Val.link.is(at.link)); + at.link = at.link || Val.link.ify(Node.soul(at.node)); + } + if(tmp = env.map){ + tmp.call(env.as || {}, v,k,n, at); + if(obj_has(n,k)){ + v = n[k]; + if(u === v){ + obj_del(n, k); + return; + } + if(!(is = valid(v,k,n, at,env))){ return } + } + } + if(!k){ return at.node } + if(true === is){ + return v; + } + tmp = node(env, {obj: v, path: at.path.concat(k)}); + if(!tmp.node){ return } + return tmp.link; //{'#': Node.soul(tmp.node)}; + } + function soul(id){ var at = this; + var prev = Val.link.is(at.link), graph = at.env.graph; + at.link = at.link || Val.link.ify(id); + at.link[Val.link._] = id; + if(at.node && at.node[Node._]){ + at.node[Node._][Val.link._] = id; + } + if(obj_has(graph, prev)){ + graph[id] = graph[prev]; + obj_del(graph, prev); + } + } + function valid(v,k,n, at,env){ var tmp; + if(Val.is(v)){ return true } + if(obj_is(v)){ return 1 } + if(tmp = env.invalid){ + v = tmp.call(env.as || {}, v,k,n); + return valid(v,k,n, at,env); + } + env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!"; + if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." } + } + function seen(env, at){ + var arr = env.seen, i = arr.length, has; + while(i--){ has = arr[i]; + if(at.obj === has.obj){ return has } + } + arr.push(at); + } + }()); + Graph.node = function(node){ + var soul = Node.soul(node); + if(!soul){ return } + return obj_put({}, soul, node); + } + ;(function(){ + Graph.to = function(graph, root, opt){ + if(!graph){ return } + var obj = {}; + opt = opt || {seen: {}}; + obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt}); + return obj; + } + function map(v,k){ var tmp, obj; + if(Node._ === k){ + if(obj_empty(v, Val.link._)){ + return; + } + this.obj[k] = obj_copy(v); + return; + } + if(!(tmp = Val.link.is(v))){ + this.obj[k] = v; + return; + } + if(obj = this.opt.seen[tmp]){ + this.obj[k] = obj; + return; + } + this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt); + } + }()); + var fn_is = Type.fn.is; + var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; + var u; + module.exports = Graph; + })(USE, './graph'); + + ;USE(function(module){ + // request / response module, for asking and acking messages. + USE('./onto'); // depends upon onto! + module.exports = function ask(cb, as){ + if(!this.on){ return } + if(!(cb instanceof Function)){ + if(!cb || !as){ return } + var id = cb['#'] || cb, tmp = (this.tag||empty)[id]; + if(!tmp){ return } + tmp = this.on(id, as); + clearTimeout(tmp.err); + return true; + } + var id = (as && as['#']) || Math.random().toString(36).slice(2); + if(!cb){ return id } + var to = this.on(id, cb, as); + to.err = to.err || setTimeout(function(){ + to.next({err: "Error: No ACK received yet.", lack: true}); + to.off(); + }, (this.opt||{}).lack || 9000); + return id; + } + })(USE, './ask'); + + ;USE(function(module){ + var Type = USE('./type'); + function Dup(opt){ + var dup = {s:{}}; + opt = opt || {max: 1000, age: 1000 * 9};//1000 * 60 * 2}; + dup.check = function(id){ var tmp; + if(!(tmp = dup.s[id])){ return false } + if(tmp.pass){ return tmp.pass = false } + return dup.track(id); + } + dup.track = function(id, pass){ + 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); + } + return it; + } + return dup; + } + var time_is = Type.time.is; + module.exports = Dup; + })(USE, './dup'); + + ;USE(function(module){ + + function Gun(o){ + if(o instanceof Gun){ return (this._ = {gun: this, $: this}).$ } + if(!(this instanceof Gun)){ return new Gun(o) } + return Gun.create(this._ = {gun: this, $: this, opt: o}); + } + + Gun.is = function($){ return ($ instanceof Gun) || ($ && $._ && ($ === $._.$)) || false } + + Gun.version = 0.9; + + Gun.chain = Gun.prototype; + Gun.chain.toJSON = function(){}; + + var Type = USE('./type'); + Type.obj.to(Type, Gun); + Gun.HAM = USE('./HAM'); + Gun.val = USE('./val'); + Gun.node = USE('./node'); + Gun.state = USE('./state'); + Gun.graph = USE('./graph'); + Gun.on = USE('./onto'); + Gun.ask = USE('./ask'); + Gun.dup = USE('./dup'); + + ;(function(){ + Gun.create = function(at){ + at.root = at.root || at; + at.graph = at.graph || {}; + at.on = at.on || Gun.on; + at.ask = at.ask || Gun.ask; + at.dup = at.dup || Gun.dup(); + var gun = at.$.opt(at.opt); + if(!at.once){ + at.on('in', root, at); + at.on('out', root, {at: at, out: root}); + Gun.on('create', at); + at.on('create', at); + } + at.once = 1; + return gun; + } + function root(msg){ + //add to.next(at); // TODO: MISSING FEATURE!!! + var ev = this, as = ev.as, at = as.at || as, gun = at.$, dup, tmp; + if(!(tmp = msg['#'])){ tmp = msg['#'] = text_rand(9) } + if((dup = at.dup).check(tmp)){ + if(as.out === msg.out){ + msg.out = u; + ev.to.next(msg); + } + return; + } + dup.track(tmp); + if(!at.ask(msg['@'], msg)){ + if(msg.get){ + Gun.on.get(msg, gun); //at.on('get', get(msg)); + } + if(msg.put){ + Gun.on.put(msg, gun); //at.on('put', put(msg)); + } + } + ev.to.next(msg); + if(!as.out){ + msg.out = root; + at.on('out', msg); + } + } + }()); + + ;(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.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){ + setTimeout(function(){ + Gun.on.put(msg, gun); + }, ctx.defer - ctx.machine); + } + if(!ctx.diff){ return } + at.on('put', obj_to(msg, {put: ctx.diff})); + }; + 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+"'!" } + var vertex = ctx.graph[soul] || empty, was = Gun.state.is(vertex, key, true), known = vertex[key]; + var HAM = Gun.HAM(ctx.machine, state, was, val, known); + if(!HAM.incoming){ + if(HAM.defer){ // pick the lowest + ctx.defer = (state < (ctx.defer || Infinity))? state : ctx.defer; + } + return; + } + ctx.put[soul] = Gun.state.to(node, key, ctx.put[soul]); + (ctx.diff || (ctx.diff = {}))[soul] = Gun.state.to(node, key, ctx.diff[soul]); + ctx.souls[soul] = true; + } + function merge(node, soul){ + var ctx = this, cat = ctx.$._, at = (cat.next || empty)[soul]; + if(!at){ + if(!(cat.opt||empty).super){ + ctx.souls[soul] = false; + return; + } + at = (ctx.$.get(soul)._); + } + var msg = ctx.map[soul] = { + put: node, + get: soul, + $: at.$ + }, as = {ctx: ctx, msg: msg}; + ctx.async = !!cat.tag.node; + if(ctx.ack){ msg['@'] = ctx.ack } + obj_map(node, each, as); + if(!ctx.async){ return } + if(!ctx.and){ + // If it is async, we only need to setup one listener per context (ctx) + cat.on('node', function(m){ + this.to.next(m); // make sure to call other context's listeners. + if(m !== ctx.map[m.get]){ return } // filter out events not from this context! + ctx.souls[m.get] = false; // set our many-async flag + obj_map(m.put, patch, m); // merge into view + if(obj_map(ctx.souls, function(v){ if(v){ return v } })){ return } // if flag still outstanding, keep waiting. + if(ctx.c){ return } ctx.c = 1; // failsafe for only being called once per context. + this.off(); + obj_map(ctx.map, map, ctx); // all done, trigger chains. + }); + } + ctx.and = true; + cat.on('node', msg); // each node on the current context's graph needs to be emitted though. + } + function each(val, key){ + var ctx = this.ctx, graph = ctx.graph, msg = this.msg, soul = msg.get, node = msg.put, at = (msg.$._), tmp; + graph[soul] = Gun.state.to(node, key, graph[soul]); + if(ctx.async){ return } + at.put = Gun.state.to(node, key, at.put); + } + function patch(val, key){ + var msg = this, node = msg.put, at = (msg.$._); + at.put = Gun.state.to(node, key, at.put); + } + function map(msg, soul){ + if(!msg.$){ return } + this.cat.stop = this.stop; // temporary fix till a better solution? + (msg.$._).on('in', msg); + this.cat.stop = null; // temporary fix till a better solution? + } + + Gun.on.get = function(msg, gun){ + var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; + var next = root.next || (root.next = {}), at = next[soul]; + // queue concurrent GETs? + if(!node){ return root.on('get', msg) } + if(has){ + if('string' != typeof has || !obj_has(node, has)){ return root.on('get', msg) } + node = Gun.state.to(node, has); + // If we have a key in-memory, do we really need to fetch? + // 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.graph.node(node); + tmp = (at||empty).ack; + root.on('in', { + '@': msg['#'], + how: 'mem', + put: node, + $: gun + }); + //if(0 < tmp){ return } + root.on('get', msg); + } + }()); + + ;(function(){ + Gun.chain.opt = function(opt){ + opt = opt || {}; + var gun = this, at = gun._, tmp = opt.peers || opt; + if(!obj_is(opt)){ opt = {} } + if(!obj_is(at.opt)){ at.opt = opt } + if(text_is(tmp)){ tmp = [tmp] } + if(list_is(tmp)){ + tmp = obj_map(tmp, function(url, i, map){ + i = {}; i.id = i.url = url; map(url, i); + }); + if(!obj_is(at.opt.peers)){ at.opt.peers = {}} + at.opt.peers = obj_to(tmp, at.opt.peers); + } + at.opt.peers = at.opt.peers || {}; + obj_map(opt, function each(v,k){ + if(!obj_has(this, k) || text.is(v) || obj.empty(v)){ this[k] = v ; return } + if(v && v.constructor !== Object && !list_is(v)){ return } + obj_map(v, each, this[k]); + }, at.opt); + Gun.on('opt', at); + at.opt.uuid = at.opt.uuid || function(){ return state_lex() + text_rand(12) } + return gun; + } + }()); + + var list_is = Gun.list.is; + var text = Gun.text, text_is = text.is, text_rand = text.random; + var obj = Gun.obj, obj_is = obj.is, obj_has = obj.has, obj_to = obj.to, obj_map = obj.map, obj_copy = obj.copy; + var state_lex = Gun.state.lex, _soul = Gun.val.link._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is; + var empty = {}, u; + + console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) }; + + Gun.log = function(){ return (!Gun.log.off && console.log.apply(console, arguments)), [].slice.call(arguments).join(' ') } + Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) } + + ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; + Gun.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'!"); + ;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; + + if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } + try{ if(typeof common !== "undefined"){ common.exports = Gun } }catch(e){} + module.exports = Gun; + + /*Gun.on('opt', function(ctx){ // FOR TESTING PURPOSES + this.to.next(ctx); + if(ctx.once){ return } + ctx.on('node', function(msg){ + var to = this.to; + //Gun.node.is(msg.put, function(v,k){ msg.put[k] = v + v }); + setTimeout(function(){ + to.next(msg); + },1); + }); + });*/ + })(USE, './root'); + + ;USE(function(module){ + var Gun = USE('./root'); + Gun.chain.back = function(n, opt){ var tmp; + n = n || 1; + if(-1 === n || Infinity === n){ + return this._.root.$; + } else + if(1 === n){ + return (this._.back || this._).$; + } + var gun = this, at = gun._; + if(typeof n === 'string'){ + n = n.split('.'); + } + if(n instanceof Array){ + var i = 0, l = n.length, tmp = at; + for(i; i < l; i++){ + tmp = (tmp||empty)[n[i]]; + } + if(u !== tmp){ + return opt? gun : tmp; + } else + if((tmp = at.back)){ + return tmp.$.back(n, opt); + } + return; + } + if(n instanceof Function){ + var yes, tmp = {back: at}; + while((tmp = tmp.back) + && u === (yes = n(tmp, opt))){} + return yes; + } + if(Gun.num.is(n)){ + return (at.back || at).$.back(n - 1); + } + return this; + } + var empty = {}, u; + })(USE, './back'); + + ;USE(function(module){ + // WARNING: GUN is very simple, but the JavaScript chaining API around GUN + // is complicated and was extremely hard to build. If you port GUN to another + // language, consider implementing an easier API to build. + var Gun = USE('./root'); + Gun.chain.chain = function(sub){ + var gun = this, at = gun._, chain = new (sub || gun).constructor(gun), cat = chain._, root; + cat.root = root = at.root; + cat.id = ++root.once; + cat.back = gun._; + cat.on = Gun.on; + cat.on('in', input, cat); // For 'in' if I add my own listeners to each then I MUST do it before in gets called. If I listen globally for all incoming data instead though, regardless of individual listeners, I can transform the data there and then as well. + cat.on('out', output, cat); // However for output, there isn't really the global option. I must listen by adding my own listener individually BEFORE this one is ever called. + return chain; + } + + function output(msg){ + var put, get, at = this.as, back = at.back, root = at.root, tmp; + if(!msg.$){ msg.$ = at.$ } + this.to.next(msg); + if(get = msg.get){ + /*if(u !== at.put){ + at.on('in', at); + return; + }*/ + if(at.lex){ msg.get = obj_to(at.lex, msg.get) } + if(get['#'] || at.soul){ + get['#'] = get['#'] || at.soul; + msg['#'] || (msg['#'] = text_rand(9)); + back = (root.$.get(get['#'])._); + if(!(get = get['.'])){ + tmp = back.ack; + if(!tmp){ back.ack = -1 } + if(obj_has(back, 'put')){ + back.on('in', back); + } + if(tmp){ return } + msg.$ = back.$; + } else + if(obj_has(back.put, get)){ // TODO: support #LEX ! + put = (back.$.get(get)._); + if(!(tmp = put.ack)){ put.ack = -1 } + back.on('in', { + $: back.$, + put: Gun.state.to(back.put, get), + get: back.get + }); + if(tmp){ return } + } else + if('string' != typeof get){ + var put = {}, meta = (back.put||{})._; + Gun.obj.map(back.put, function(v,k){ + if(!Gun.text.match(k, get)){ return } + put[k] = v; + }) + if(!Gun.obj.empty(put)){ + put._ = meta; + back.on('in', {$: back.$, put: put, get: back.get}) + } + } + root.ask(ack, msg); + return root.on('in', msg); + } + if(root.now){ root.now[at.id] = root.now[at.id] || true; at.pass = {} } + if(get['.']){ + if(at.get){ + msg = {get: {'.': at.get}, $: at.$}; + //if(back.ask || (back.ask = {})[at.get]){ return } + (back.ask || (back.ask = {})); + back.ask[at.get] = msg.$._; // TODO: PERFORMANCE? More elegant way? + return back.on('out', msg); + } + msg = {get: {}, $: at.$}; + return back.on('out', msg); + } + at.ack = at.ack || -1; + if(at.get){ + msg.$ = at.$; + get['.'] = at.get; + (back.ask || (back.ask = {}))[at.get] = msg.$._; // TODO: PERFORMANCE? More elegant way? + return back.on('out', msg); + } + } + return back.on('out', msg); + } + + function input(msg){ + var eve = this, cat = eve.as, root = cat.root, gun = msg.$, at = (gun||empty)._ || empty, change = msg.put, rel, tmp; + if(cat.get && msg.get !== cat.get){ + msg = obj_to(msg, {get: cat.get}); + } + if(cat.has && at !== cat){ + msg = obj_to(msg, {$: cat.$}); + if(at.ack){ + cat.ack = at.ack; + //cat.ack = cat.ack || at.ack; + } + } + if(u === change){ + tmp = at.put; + eve.to.next(msg); + if(cat.soul){ return } // TODO: BUG, I believee the fresh input refactor caught an edge case that a `gun.get('soul').get('key')` that points to a soul that doesn't exist will not trigger val/get etc. + if(u === tmp && u !== at.put){ return } + echo(cat, msg, eve); + if(cat.has){ + not(cat, msg); + } + obj_del(at.echo, cat.id); + obj_del(cat.map, at.id); + return; + } + if(cat.soul){ + eve.to.next(msg); + echo(cat, msg, eve); + if(cat.next){ obj_map(change, map, {msg: msg, cat: cat}) } + return; + } + if(!(rel = Gun.val.link.is(change))){ + if(Gun.val.is(change)){ + if(cat.has || cat.soul){ + not(cat, msg); + } else + if(at.has || at.soul){ + (at.echo || (at.echo = {}))[cat.id] = at.echo[at.id] || cat; + (cat.map || (cat.map = {}))[at.id] = cat.map[at.id] || {at: at}; + //if(u === at.put){ return } // Not necessary but improves performance. If we have it but at does not, that means we got things out of order and at will get it. Once at gets it, it will tell us again. + } + eve.to.next(msg); + echo(cat, msg, eve); + return; + } + if(cat.has && at !== cat && obj_has(at, 'put')){ + cat.put = at.put; + }; + if((rel = Gun.node.soul(change)) && at.has){ + at.put = (cat.root.$.get(rel)._).put; + } + tmp = (root.stop || {})[at.id]; + //if(tmp && tmp[cat.id]){ } else { + eve.to.next(msg); + //} + relate(cat, msg, at, rel); + echo(cat, msg, eve); + if(cat.next){ obj_map(change, map, {msg: msg, cat: cat}) } + return; + } + var was = root.stop; + tmp = root.stop || {}; + tmp = tmp[at.id] || (tmp[at.id] = {}); + //if(tmp[cat.id]){ return } + tmp.is = tmp.is || at.put; + tmp[cat.id] = at.put || true; + //if(root.stop){ + eve.to.next(msg) + //} + relate(cat, msg, at, rel); + echo(cat, msg, eve); + } + + function relate(at, msg, from, rel){ + if(!rel || node_ === at.get){ return } + var tmp = (at.root.$.get(rel)._); + if(at.has){ + from = tmp; + } else + if(from.has){ + relate(from, msg, from, rel); + } + if(from === at){ return } + if(!from.$){ from = {} } + (from.echo || (from.echo = {}))[at.id] = from.echo[at.id] || at; + if(at.has && !(at.map||empty)[from.id]){ // if we haven't seen this before. + not(at, msg); + } + tmp = from.id? ((at.map || (at.map = {}))[from.id] = at.map[from.id] || {at: from}) : {}; + if(rel === tmp.link){ + if(!(tmp.pass || at.pass)){ + return; + } + } + if(at.pass){ + Gun.obj.map(at.map, function(tmp){ tmp.pass = true }) + obj_del(at, 'pass'); + } + if(tmp.pass){ obj_del(tmp, 'pass') } + if(at.has){ at.link = rel } + ask(at, tmp.link = rel); + } + function echo(at, msg, ev){ + if(!at.echo){ return } // || node_ === at.get ? + //if(at.has){ msg = obj_to(msg, {event: ev}) } + obj_map(at.echo, reverb, msg); + } + function reverb(to){ + if(!to || !to.on){ return } + to.on('in', this); + } + function map(data, key){ // Map over only the changes on every update. + var cat = this.cat, next = cat.next || empty, via = this.msg, chain, at, tmp; + if(node_ === key && !next[key]){ return } + if(!(at = next[key])){ + return; + } + //if(data && data[_soul] && (tmp = Gun.val.link.is(data)) && (tmp = (cat.root.$.get(tmp)._)) && obj_has(tmp, 'put')){ + // data = tmp.put; + //} + if(at.has){ + //if(!(data && data[_soul] && Gun.val.link.is(data) === Gun.node.soul(at.put))){ + if(u === at.put || !Gun.val.link.is(data)){ + at.put = data; + } + chain = at.$; + } else + if(tmp = via.$){ + tmp = (chain = via.$.get(key))._; + if(u === tmp.put || !Gun.val.link.is(data)){ + tmp.put = data; + } + } + at.on('in', { + put: data, + get: key, + $: chain, + via: via + }); + } + function not(at, msg){ + if(!(at.has || at.soul)){ return } + var tmp = at.map, root = at.root; + at.map = null; + if(at.has){ + if(at.dub && at.root.stop){ at.dub = null } + at.link = null; + } + //if(!root.now || !root.now[at.id]){ + if(!at.pass){ + if((!msg['@']) && null === tmp){ return } + //obj_del(at, 'pass'); + } + if(u === tmp && Gun.val.link.is(at.put)){ return } // This prevents the very first call of a thing from triggering a "clean up" call. // TODO: link.is(at.put) || !val.is(at.put) ? + obj_map(tmp, function(proxy){ + if(!(proxy = proxy.at)){ return } + obj_del(proxy.echo, at.id); + }); + tmp = at.put; + obj_map(at.next, function(neat, key){ + if(u === tmp && u !== at.put){ return true } + neat.put = u; + if(neat.ack){ + neat.ack = -1; // TODO: BUG? Should this be 0? + } + neat.on('in', { + get: key, + $: neat.$, + put: u + }); + }); + } + function ask(at, soul){ + var tmp = (at.root.$.get(soul)._), lex = at.lex; + if(at.ack || lex){ + (lex = lex||{})['#'] = soul; + tmp.on('out', {get: lex}); + if(!at.ask){ return } // TODO: PERFORMANCE? More elegant way? + } + tmp = at.ask; Gun.obj.del(at, 'ask'); + obj_map(tmp || at.next, function(neat, key){ + var lex = neat.lex || {}; lex['#'] = soul; lex['.'] = lex['.'] || key; + neat.on('out', {get: lex}); + }); + Gun.obj.del(at, 'ask'); // TODO: PERFORMANCE? More elegant way? + } + function ack(msg, ev){ + var as = this.as, get = as.get || empty, at = as.$._, tmp = (msg.put||empty)[get['#']]; + if(at.ack){ at.ack = (at.ack + 1) || 1; } + if(!msg.put || ('string' == typeof get['.'] && !obj_has(tmp, at.get))){ + if(at.put !== u){ return } + at.on('in', { + get: at.get, + put: at.put = u, + $: at.$, + '@': msg['@'] + }); + return; + } + if(node_ == get['.']){ // is this a security concern? + at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']}); + return; + } + Gun.on.put(msg, at.root.$); + } + var empty = {}, u; + var obj = Gun.obj, obj_has = obj.has, obj_put = obj.put, obj_del = obj.del, obj_to = obj.to, obj_map = obj.map; + var text_rand = Gun.text.random; + var _soul = Gun.val.link._, node_ = Gun.node._; + })(USE, './chain'); + + ;USE(function(module){ + var Gun = USE('./root'); + Gun.chain.get = function(key, cb, as){ + var gun, tmp; + if(typeof key === 'string'){ + var back = this, cat = back._; + var next = cat.next || empty; + if(!(gun = next[key])){ + gun = cache(key, back); + } + gun = gun.$; + } else + if(key instanceof Function){ + if(true === cb){ return soul(this, key, cb, as), this } + gun = this; + var at = gun._, root = at.root, tmp = root.now, ev; + as = cb || {}; + as.at = at; + as.use = key; + as.out = as.out || {}; + as.out.get = as.out.get || {}; + (ev = at.on('in', use, as)).rid = rid; + (root.now = {$:1})[as.now = at.id] = ev; + var mum = root.mum; root.mum = {}; + at.on('out', as.out); + root.mum = mum; + root.now = tmp; + return gun; + } else + if(num_is(key)){ + return this.get(''+key, cb, as); + } else + if(tmp = rel.is(key)){ + return this.get(tmp, cb, as); + } else + if(obj.is(key)){ + gun = this; + if(tmp = ((tmp = key['#'])||empty)['='] || tmp){ gun = gun.get(tmp) } + gun._.lex = key; + return gun; + } else { + (as = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP + if(cb){ cb.call(as, as._.err) } + return as; + } + if(tmp = this._.stun){ // TODO: Refactor? + gun._.stun = gun._.stun || tmp; + } + if(cb && cb instanceof Function){ + gun.get(cb, as); + } + return gun; + } + function cache(key, back){ + var cat = back._, next = cat.next, gun = back.chain(), at = gun._; + if(!next){ next = cat.next = {} } + next[at.get = key] = at; + if(back === cat.root.$){ + at.soul = key; + } else + if(cat.soul || cat.has){ + at.has = key; + //if(obj_has(cat.put, key)){ + //at.put = cat.put[key]; + //} + } + return at; + } + function soul(gun, cb, opt, as){ + var cat = gun._, acks = 0, tmp; + if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat) } + if(cat.jam){ return cat.jam.push([cb, as]) } + cat.jam = [[cb,as]]; + gun.get(function(msg, eve){ + if(u === msg.put && (tmp = Object.keys(cat.root.opt.peers).length) && ++acks < tmp){ + return; + } + eve.rid(msg); + var at = ((at = msg.$) && at._) || {}; + tmp = cat.jam; Gun.obj.del(cat, 'jam'); + Gun.obj.map(tmp, function(as, cb){ + cb = as[0]; as = as[1]; + if(!cb){ return } + var id = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub; + cb(id, as, msg, eve); + }); + }, {out: {get: {'.':true}}}); + return gun; + } + function use(msg){ + var eve = this, as = eve.as, cat = as.at, root = cat.root, gun = msg.$, at = (gun||{})._ || {}, data = msg.put || at.put, tmp; + if((tmp = root.now) && eve !== tmp[as.now]){ return eve.to.next(msg) } + //console.log("USE:", cat.id, cat.soul, cat.has, cat.get, msg, root.mum); + //if(at.async && msg.root){ return } + //if(at.async === 1 && cat.async !== true){ return } + //if(root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); + //if(!at.async && !cat.async && at.put && msg.put === at.put){ return } + //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); + + + //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); + //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); + if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } + //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? + if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ + tmp = ((msg.$$ = at.root.gun.get(tmp))._); + if(u !== tmp.put){ + msg = obj_to(msg, {put: data = tmp.put}); + } + } + if((tmp = root.mum) && at.id){ // TODO: can we delete mum entirely now? + var id = at.id + (eve.id || (eve.id = Gun.text.random(9))); + if(tmp[id]){ return } + if(u !== data && !rel.is(data)){ tmp[id] = true; } + } + as.use(msg, eve); + if(eve.stun){ + eve.stun = null; + return; + } + eve.to.next(msg); + } + function rid(at){ + var cat = this.on; + if(!at || cat.soul || cat.has){ return this.off() } + if(!(at = (at = (at = at.$ || at)._ || at).id)){ return } + var map = cat.map, tmp, seen; + //if(!map || !(tmp = map[at]) || !(tmp = tmp.at)){ return } + if(tmp = (seen = this.seen || (this.seen = {}))[at]){ return true } + seen[at] = true; + return; + //tmp.echo[cat.id] = {}; // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. + //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. + return; + } + var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_to = Gun.obj.to; + var num_is = Gun.num.is; + var rel = Gun.val.link, node_soul = Gun.node.soul, node_ = Gun.node._; + var empty = {}, u; + })(USE, './get'); + + ;USE(function(module){ + var Gun = USE('./root'); + Gun.chain.put = function(data, cb, as){ + // #soul.has=value>state + // ~who#where.where=what>when@was + // TODO: BUG! Put probably cannot handle plural chains! `!as` is quickfix test. + var gun = this, at = (gun._), root = at.root.$, ctx = root._, M = 100, tmp; + /*if(!ctx.puta && !as){ if(tmp = ctx.puts){ if(tmp > M){ // without this, when synchronous, writes to a 'not found' pile up, when 'not found' resolves it recursively calls `put` which incrementally resolves each write. Stack overflow limits can be as low as 10K, so this limit is hardcoded to 1% of 10K. + (ctx.stack || (ctx.stack = [])).push([gun, data, cb, as]); + if(ctx.puto){ return } + ctx.puto = setTimeout(function drain(){ + var d = ctx.stack.splice(0,M), i = 0, at; ctx.puta = true; + while(at = d[i++]){ at[0].put(at[1], at[2], at[3]) } delete ctx.puta; + if(ctx.stack.length){ return ctx.puto = setTimeout(drain, 0) } + ctx.stack = ctx.puts = ctx.puto = null; + }, 0); + return gun; + } ++ctx.puts } else { ctx.puts = 1 } }*/ + as = as || {}; + as.data = data; + as.via = as.$ = as.via || as.$ || gun; + if(typeof cb === 'string'){ + as.soul = cb; + } else { + as.ack = as.ack || cb; + } + if(at.soul){ + as.soul = at.soul; + } + if(as.soul || root === gun){ + if(!obj_is(as.data)){ + (as.ack||noop).call(as, as.out = {err: Gun.log("Data saved to the root level of the graph must be a node (an object), not a", (typeof as.data), 'of "' + as.data + '"!')}); + if(as.res){ as.res() } + return gun; + } + as.soul = as.soul || (as.not = Gun.node.soul(as.data) || (as.via.back('opt.uuid') || Gun.text.random)()); + if(!as.soul){ // polyfill async uuid for SEA + as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback + if(err){ return Gun.log(err) } // TODO: Handle error! + (as.ref||as.$).put(as.data, as.soul = soul, as); + }); + return gun; + } + as.$ = root.get(as.soul); + as.ref = as.$; + ify(as); + return gun; + } + if(Gun.is(data)){ + data.get(function(soul, o, msg){ + if(!soul){ + return Gun.log("The reference you are saving is a", typeof msg.put, '"'+ msg.put +'", not a node (object)!'); + } + gun.put(Gun.val.link.ify(soul), cb, as); + }, true); + return gun; + } + if(at.has && (tmp = Gun.val.link.is(data))){ at.dub = tmp } + as.ref = as.ref || (root._ === (tmp = at.back))? gun : tmp.$; + if(as.ref._.soul && Gun.val.is(as.data) && at.get){ + as.data = obj_put({}, at.get, as.data); + as.ref.put(as.data, as.soul, as); + return gun; + } + as.ref.get(any, true, {as: as}); + if(!as.out){ + // TODO: Perf idea! Make a global lock, that blocks everything while it is on, but if it is on the lock it does the expensive lookup to see if it is a dependent write or not and if not then it proceeds full speed. Meh? For write heavy async apps that would be terrible. + as.res = as.res || stun; // Gun.on.stun(as.ref); // TODO: BUG! Deal with locking? + as.$._.stun = as.ref._.stun; + } + return gun; + }; + + function ify(as){ + as.batch = batch; + var opt = as.opt||{}, env = as.env = Gun.state.map(map, opt.state); + env.soul = as.soul; + as.graph = Gun.graph.ify(as.data, env, as); + if(env.err){ + (as.ack||noop).call(as, as.out = {err: Gun.log(env.err)}); + if(as.res){ as.res() } + return; + } + as.batch(); + } + + function stun(cb){ + if(cb){ cb() } + return; + var as = this; + if(!as.ref){ return } + if(cb){ + as.after = as.ref._.tag; + as.now = as.ref._.tag = {}; + cb(); + return; + } + if(as.after){ + as.ref._.tag = as.after; + } + } + + function batch(){ var as = this; + if(!as.graph || obj_map(as.stun, no)){ return } + as.res = as.res || function(cb){ if(cb){ cb() } }; + as.res(function(){ + var cat = (as.$.back(-1)._), ask = cat.ask(function(ack){ + cat.root.on('ack', ack); + if(ack.err){ Gun.log(ack) } + if(++acks > (as.acks || 0)){ this.off() } // Adjustable ACKs! Only 1 by default. + if(!as.ack){ return } + as.ack(ack, this); + //--C; + }, as.opt), acks = 0; + //C++; + // NOW is a hack to get synchronous replies to correctly call. + // and STOP is a hack to get async behavior to correctly call. + // neither of these are ideal, need to be fixed without hacks, + // but for now, this works for current tests. :/ + var tmp = cat.root.now; obj.del(cat.root, 'now'); + var mum = cat.root.mum; cat.root.mum = {}; + (as.ref._).on('out', { + $: as.ref, put: as.out = as.env.graph, opt: as.opt, '#': ask + }); + cat.root.mum = mum? obj.to(mum, cat.root.mum) : mum; + cat.root.now = tmp; + }, as); + if(as.res){ as.res() } + } function no(v,k){ if(v){ return true } } + + function map(v,k,n, at){ var as = this; + var is = Gun.is(v); + if(k || !at.path.length){ return } + (as.res||iife)(function(){ + var path = at.path, ref = as.ref, opt = as.opt; + var i = 0, l = path.length; + for(i; i < l; i++){ + ref = ref.get(path[i]); + } + if(is){ ref = v } + //if(as.not){ (ref._).dub = Gun.text.random() } // This might optimize stuff? Maybe not needed anymore. Make sure it doesn't introduce bugs. + var id = (ref._).dub; + if(id || (id = Gun.node.soul(at.obj))){ + ref.back(-1).get(id); + at.soul(id); + return; + } + (as.stun = as.stun || {})[path] = true; + ref.get(soul, true, {as: {at: at, as: as, p:path}}); + }, {as: as, at: at}); + //if(is){ return {} } + } + + function soul(id, as, msg, eve){ + var as = as.as, cat = as.at; as = as.as; + var at = ((msg || {}).$ || {})._ || {}; + id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.link.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous? + if(eve){ eve.stun = true } + if(!id){ // polyfill async uuid for SEA + as.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback + if(err){ return Gun.log(err) } // TODO: Handle error. + solve(at, at.dub = at.dub || id, cat, as); + }); + return; + } + solve(at, at.dub = id, cat, as); + } + + function solve(at, id, cat, as){ + at.$.back(-1).get(id); + cat.soul(id); + as.stun[cat.path] = false; + as.batch(); + } + + function any(soul, as, msg, eve){ + as = as.as; + if(!msg.$ || !msg.$._){ return } // TODO: Handle + if(msg.err){ // TODO: Handle + console.log("Please report this as an issue! Put.any.err"); + return; + } + var at = (msg.$._), data = at.put, opt = as.opt||{}, root, tmp; + if((tmp = as.ref) && tmp._.now){ return } + if(eve){ eve.stun = true } + if(as.ref !== as.$){ + tmp = (as.$._).get || at.get; + if(!tmp){ // TODO: Handle + console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? + return; + } + as.data = obj_put({}, tmp, as.data); + tmp = null; + } + if(u === data){ + if(!at.get){ return } // TODO: Handle + if(!soul){ + tmp = at.$.back(function(at){ + if(at.link || at.soul){ return at.link || at.soul } + as.data = obj_put({}, at.get, as.data); + }); + as.not = true; // maybe consider this? + } + tmp = tmp || at.soul || at.link || at.dub;// || at.get; + at = tmp? (at.root.$.get(tmp)._) : at; + as.soul = tmp; + data = as.data; + } + if(!as.not && !(as.soul = as.soul || soul)){ + if(as.path && obj_is(as.data)){ + as.soul = (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); + } else { + //as.data = obj_put({}, as.$._.get, as.data); + if(node_ == at.get){ + as.soul = (at.put||empty)['#'] || at.dub; + } + as.soul = as.soul || at.soul || at.link || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); + } + if(!as.soul){ // polyfill async uuid for SEA + as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback + if(err){ return Gun.log(err) } // Handle error. + as.ref.put(as.data, as.soul = soul, as); + }); + return; + } + } + as.ref.put(as.data, as.soul, as); + } + var obj = Gun.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; + var u, empty = {}, noop = function(){}, iife = function(fn,as){fn.call(as||empty)}; + var node_ = Gun.node._; + })(USE, './put'); + + ;USE(function(module){ + var Gun = USE('./root'); + USE('./chain'); + USE('./back'); + USE('./put'); + USE('./get'); + module.exports = Gun; + })(USE, './index'); + + ;USE(function(module){ + var Gun = USE('./index'); + Gun.chain.on = function(tag, arg, eas, as){ + var gun = this, at = gun._, tmp, act, off; + if(typeof tag === 'string'){ + if(!arg){ return at.on(tag) } + act = at.on(tag, arg, eas || at, as); + if(eas && eas.$){ + (eas.subs || (eas.subs = [])).push(act); + } + return gun; + } + var opt = arg; + opt = (true === opt)? {change: true} : opt || {}; + opt.at = at; + opt.ok = tag; + //opt.last = {}; + gun.get(ok, opt); // TODO: PERF! Event listener leak!!!? + return gun; + } + + function ok(msg, ev){ var opt = this; + var gun = msg.$, at = (gun||{})._ || {}, data = at.put || msg.put, cat = opt.at, tmp; + if(u === data){ + return; + } + if(tmp = msg.$$){ + tmp = (msg.$$._); + if(u === tmp.put){ + return; + } + data = tmp.put; + } + if(opt.change){ // TODO: BUG? Move above the undef checks? + data = msg.put; + } + // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE + //if(tmp.put === data && tmp.get === id && !Gun.node.soul(data)){ return } + //tmp.put = data; + //tmp.get = id; + // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE + //at.last = data; + if(opt.as){ + opt.ok.call(opt.as, msg, ev); + } else { + opt.ok.call(gun, data, msg.get, msg, ev); + } + } + + Gun.chain.val = function(cb, opt){ + Gun.log.once("onceval", "Future Breaking API Change: .val -> .once, apologies unexpected."); + return this.once(cb, opt); + } + Gun.chain.once = function(cb, opt){ + var gun = this, at = gun._, data = at.put; + if(0 < at.ack && u !== data){ + (cb || noop).call(gun, data, at.get); + return gun; + } + if(cb){ + (opt = opt || {}).ok = cb; + opt.at = at; + opt.out = {'#': Gun.text.random(9)}; + gun.get(val, {as: opt}); + opt.async = true; //opt.async = at.stun? 1 : true; + } else { + Gun.log.once("valonce", "Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); + var chain = gun.chain(); + chain._.nix = gun.once(function(){ + chain._.on('in', gun._); + }); + return chain; + } + return gun; + } + + function val(msg, eve, to){ + if(!msg.$){ eve.off(); return } + var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; + if(tmp = msg.$$){ + link = tmp = (msg.$$._); + if(u !== link.put){ + data = link.put; + } + } + if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } + eve.ack = (eve.ack||0)+1; + if(!to && u === data && eve.ack <= (opt.acks || Object.keys(at.root.opt.peers).length)){ return } + if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) + || (u === data && (tmp = Object.keys(at.root.opt.peers).length) && (!to && (link||at).ack < tmp))){ + tmp = (eve.wait = {})[at.id] = setTimeout(function(){ + val.call({as:opt}, msg, eve, tmp || 1); + }, opt.wait || 99); + return; + } + if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) } + eve.rid(msg); + opt.ok.call(gun || opt.$, data, msg.get); + } + + Gun.chain.off = function(){ + // make off more aggressive. Warning, it might backfire! + var gun = this, at = gun._, tmp; + var cat = at.back; + if(!cat){ return } + at.ack = 0; // so can resubscribe. + if(tmp = cat.next){ + if(tmp[at.get]){ + obj_del(tmp, at.get); + } else { + + } + } + if(tmp = cat.ask){ + obj_del(tmp, at.get); + } + if(tmp = cat.put){ + obj_del(tmp, at.get); + } + if(tmp = at.soul){ + obj_del(cat.root.graph, tmp); + } + if(tmp = at.map){ + obj_map(tmp, function(at){ + if(at.link){ + cat.root.$.get(at.link).off(); + } + }); + } + if(tmp = at.next){ + obj_map(tmp, function(neat){ + neat.$.off(); + }); + } + at.on('off', {}); + return gun; + } + var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to; + var rel = Gun.val.link; + var empty = {}, noop = function(){}, u; + })(USE, './on'); + + ;USE(function(module){ + var Gun = USE('./index'); + Gun.chain.map = function(cb, opt, t){ + var gun = this, cat = gun._, chain; + if(!cb){ + if(chain = cat.each){ return chain } + cat.each = chain = gun.chain(); + chain._.nix = gun.back('nix'); + gun.on('in', map, chain._); + return chain; + } + Gun.log.once("mapfn", "Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); + chain = gun.chain(); + gun.map().on(function(data, key, at, ev){ + var next = (cb||noop).call(this, data, key, at, ev); + if(u === next){ return } + if(data === next){ return chain._.on('in', at) } + if(Gun.is(next)){ return chain._.on('in', next._) } + chain._.on('in', {get: key, put: next}); + }); + return chain; + } + function map(msg){ + if(!msg.put || Gun.val.is(msg.put)){ return this.to.next(msg) } + if(this.as.nix){ this.off() } // TODO: Ugly hack! + obj_map(msg.put, each, {at: this.as, msg: msg}); + this.to.next(msg); + } + function each(v,k){ + if(n_ === k){ return } + var msg = this.msg, gun = msg.$, at = gun._, cat = this.at, tmp = at.lex; + if(tmp && !Gun.text.match(k, tmp['.'] || tmp['#'] || tmp)){ return } // review? + ((tmp = gun.get(k)._).echo || (tmp.echo = {}))[cat.id] = tmp.echo[cat.id] || cat; + } + var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u; + })(USE, './map'); + + ;USE(function(module){ + var Gun = USE('./index'); + Gun.chain.set = function(item, cb, opt){ + var gun = this, soul; + cb = cb || function(){}; + opt = opt || {}; opt.item = opt.item || item; + if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } + if(!Gun.is(item)){ + if(Gun.obj.is(item)){; + item = gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).put(item); + } + return gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); + } + item.get(function(soul, o, msg){ + if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } + gun.put(Gun.obj.put({}, soul, Gun.val.link.ify(soul)), cb, opt); + },true); + return item; + } + })(USE, './set'); + + ;USE(function(module){ + if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? + + var root, noop = function(){}, store, u; + try{store = (Gun.window||noop).localStorage}catch(e){} + if(!store){ + console.log("Warning: No localStorage exists to persist data to!"); + store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; + } + /* + NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! + If you update anything here, consider updating the other adapters as well. + */ + + Gun.on('create', function(root){ + // This code is used to queue offline writes for resync. + // See the next 'opt' code below for actual saving of data. + var ev = this.to, opt = root.opt; + if(root.once){ return ev.next(root) } + if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! // actually, this doesn't help, per @go1dfish 's observation. Disabling for now, will need better solution later. + opt.prefix = opt.file || 'gun/'; + var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {}; + var empty = Gun.obj.empty, id, to, go; + // add re-sync command. + if(!empty(gap)){ + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {}; + Gun.obj.map(gap, function(node, soul){ + Gun.obj.map(node, function(val, key){ + send[soul] = Gun.state.to(disk[soul], key, send[soul]); + }); + }); + setTimeout(function(){ + // TODO: Holy Grail dangling by this thread! If gap / offline resync doesn't trigger, it doesn't work. Ouch, and this is a localStorage specific adapter. :( + root.on('out', {put: send, '#': root.ask(ack)}); + },1); + } + + root.on('out', function(msg){ + if(msg.lS){ return } // TODO: for IndexedDB and others, shouldn't send to peers ACKs to our own GETs. + if(Gun.is(msg.$) && msg.put && !msg['@']){ + id = msg['#']; + Gun.graph.is(msg.put, null, map); + if(!to){ to = setTimeout(flush, opt.wait || 1) } + } + this.to.next(msg); + }); + root.on('ack', ack); + + function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook. + if(ack.err || !ack.ok){ return } + var id = ack['@']; + setTimeout(function(){ + Gun.obj.map(gap, function(node, soul){ + Gun.obj.map(node, function(val, key){ + if(id !== val){ return } + delete node[key]; + }); + if(empty(node)){ + delete gap[soul]; + } + }); + flush(); + }, opt.wait || 1); + }; + ev.next(root); + + var map = function(val, key, node, soul){ + (gap[soul] || (gap[soul] = {}))[key] = id; + } + var flush = function(){ + clearTimeout(to); + to = false; + try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap)); + }catch(e){ Gun.log(err = e || "localStorage failure") } + } + }); + + Gun.on('create', function(root){ + this.to.next(root); + var opt = root.opt; + if(root.once){ return } + if(false === opt.localStorage){ return } + opt.prefix = opt.file || 'gun/'; + var graph = root.graph, acks = {}, count = 0, to; + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; + var lS = function(){}, u; + root.on('localStorage', disk); // NON-STANDARD EVENT! + + root.on('put', function(at){ + this.to.next(at); + Gun.graph.is(at.put, null, map); + if(!at['@']){ acks[at['#']] = true; } // only ack non-acks. + count += 1; + if(count >= (opt.batch || 1000)){ + return flush(); + } + if(to){ return } + to = setTimeout(flush, opt.wait || 1); + }); + + root.on('get', function(msg){ + this.to.next(msg); + var lex = msg.get, soul, data, u; + function to(){ + if(!lex || !(soul = lex['#'])){ return } + //if(0 >= msg.cap){ return } + var has = lex['.']; + data = disk[soul] || u; + if(data && has){ + data = Gun.state.to(data, has); + } + //if(!data && !Gun.obj.empty(opt.peers)){ return } // if data not found, don't ack if there are peers. // Hmm, what if we have peers but we are disconnected? + //console.log("lS get", lex, data); + root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$});// || root.$}); + }; + Gun.debug? setTimeout(to,1) : to(); + }); + + var map = function(val, key, node, soul){ + disk[soul] = Gun.state.to(node, key, disk[soul]); + } + + var flush = function(data){ + var err; + count = 0; + clearTimeout(to); + to = false; + var ack = acks; + acks = {}; + if(data){ disk = data } + try{store.setItem(opt.prefix, JSON.stringify(disk)); + }catch(e){ + Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"); + root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); + } + if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers. + Gun.obj.map(ack, function(yes, id){ + root.on('in', { + '@': id, + err: err, + ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number. + }); + }); + } + }); + })(USE, './adapters/localStorage'); + + ;USE(function(module){ + var Type = USE('../type'); + + function Mesh(root){ + var mesh = function(){}; + var opt = root.opt || {}; + opt.log = opt.log || console.log; + opt.gap = opt.gap || opt.wait || 1; + opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. + + var dup = root.dup; + + mesh.hear = function(raw, peer){ + if(!raw){ return } + var msg, id, hash, tmp = raw[0]; + if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) } + if('{' != raw[2]){ mesh.hear.d += raw.length||0; ++mesh.hear.c; } // STATS! // ugh, stupid double JSON encoding + if('[' === tmp){ + try{msg = JSON.parse(raw);}catch(e){opt.log('DAM JSON parse error', e)} + if(!msg){ return } + var i = 0, m; + while(m = msg[i++]){ + mesh.hear(m, peer); + } + return; + } + if('{' === tmp || (Type.obj.is(raw) && (msg = raw))){ + try{msg = msg || JSON.parse(raw); + }catch(e){return opt.log('DAM JSON parse error', e)} + if(!msg){ return } + if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } + if(dup.check(id)){ return } + dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore? + if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } + if(hash && (tmp = msg['@'] || (msg.get && id))){ // Reduces backward daisy in case varying hashes at different daisy depths are the same. + if(dup.check(tmp+hash)){ return } + dup.track(tmp+hash, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore? + } + (msg._ = function(){}).via = peer; + if(tmp = msg['><']){ (msg._).to = Type.obj.map(tmp.split(','), tomap) } + if(msg.dam){ + if(tmp = mesh.hear[msg.dam]){ + tmp(msg, peer, root); + } + return; + } + root.on('in', msg); + return; + } + } + var tomap = function(k,i,m){m(k,true)}; + mesh.hear.c = mesh.hear.d = 0; + + ;(function(){ + var message; + function each(peer){ mesh.say(message, peer) } + mesh.say = function(msg, peer){ + if(this.to){ this.to.next(msg) } // compatible with middleware adapters. + if(!msg){ return false } + var id, hash, tmp, raw; + var meta = msg._||(msg._=function(){}); + if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } + if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } + if(!(raw = meta.raw)){ + raw = meta.raw = mesh.raw(msg); + if(hash && (tmp = msg['@'])){ + dup.track(tmp+hash).it = msg; + if(tmp = (dup.s[tmp]||ok).it){ + if(hash === tmp['##']){ return false } + tmp['##'] = hash; + } + } + } + dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more! + if(!peer){ peer = (tmp = dup.s[msg['@']]) && (tmp = tmp.it) && (tmp = tmp._) && (tmp = tmp.via) } + if(!peer && mesh.way){ return mesh.way(msg) } + if(!peer || !peer.id){ message = msg; + if(!Type.obj.is(peer || opt.peers)){ return false } + Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. + return; + } + if(!peer.wire && mesh.wire){ mesh.wire(peer) } + if(id === peer.last){ return } peer.last = id; // was it just sent? + if(peer === meta.via){ return false } + if((tmp = meta.to) && (tmp[peer.url] || tmp[peer.pid] || tmp[peer.id]) /*&& !o*/){ return false } + if(peer.batch){ + peer.tail = (tmp = peer.tail || 0) + raw.length; + if(peer.tail <= opt.pack){ + peer.batch.push(raw); // peer.batch += (tmp?'':',')+raw; // TODO: Prevent double JSON! // FOR v1.0 !? + return; + } + flush(peer); + } + peer.batch = []; // peer.batch = '['; // TODO: Prevent double JSON! + setTimeout(function(){flush(peer)}, opt.gap); + send(raw, peer); + } + function flush(peer){ + var tmp = peer.batch; // var tmp = peer.batch + ']'; // TODO: Prevent double JSON! + peer.batch = peer.tail = null; + if(!tmp){ return } + if(!tmp.length){ return } // if(3 > tmp.length){ return } // TODO: ^ + try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp)); + }catch(e){return opt.log('DAM JSON stringify error', e)} + if(!tmp){ return } + send(tmp, peer); + } + mesh.say.c = mesh.say.d = 0; + }()); + + // for now - find better place later. + function send(raw, peer){ try{ + var wire = peer.wire; + if(peer.say){ + peer.say(raw); + } else + if(wire.send){ + wire.send(raw); + } + mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! + }catch(e){ + (peer.queue = peer.queue || []).push(raw); + }} + + ;(function(){ + mesh.raw = function(msg){ // TODO: Clean this up / delete it / move logic out! + if(!msg){ return '' } + var meta = (msg._) || {}, put, hash, tmp; + if(tmp = meta.raw){ return tmp } + 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. + }); if(i > 1){ msg['><'] = to.join() } + } + var raw = $(msg); // optimize by reusing put = the JSON.stringify from .hash? + /*if(u !== put){ + tmp = raw.indexOf(_, raw.indexOf('put')); + raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); + //raw = raw.replace('"'+ _ +'"', put); // NEVER USE THIS! ALSO NEVER DELETE IT TO NOT MAKE SAME MISTAKE! https://github.com/amark/gun/wiki/@$$ Heisenbug + }*/ + if(meta){ meta.raw = raw } + return raw; + } + var $ = JSON.stringify, _ = ':])([:'; + + }()); + + mesh.hi = function(peer){ + var tmp = peer.wire || {}; + if(peer.id){ + opt.peers[peer.url || peer.id] = peer; + } else { + tmp = peer.id = peer.id || Type.text.random(9); + mesh.say({dam: '?'}, opt.peers[tmp] = peer); + } + peer.met = peer.met || +(new Date); + if(!tmp.hied){ root.on(tmp.hied = 'hi', peer) } + // @rogowski I need this here by default for now to fix go1dfish's bug + tmp = peer.queue; peer.queue = []; + Type.obj.map(tmp, function(msg){ + send(msg, peer); + }); + } + mesh.bye = function(peer){ + root.on('bye', peer); + var tmp = +(new Date); tmp = (tmp - (peer.met||tmp)); + mesh.bye.time = ((mesh.bye.time || tmp) + tmp) / 2; + } + mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } + mesh.hear['?'] = function(msg, peer){ + if(!msg.pid){ + mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer); + // @rogowski I want to re-enable this AXE logic with some fix/merge later. + /* var tmp = peer.queue; peer.queue = []; + Type.obj.map(tmp, function(msg){ + mesh.say(msg, peer); + }); */ + // @rogowski 2: I think with my PID fix we can delete this and use the original. + return; + } + if(peer.pid){ return } + peer.pid = msg.pid; + } + + root.on('create', function(root){ + root.opt.pid = root.opt.pid || Type.text.random(9); + this.to.next(root); + root.on('out', mesh.say); + }); + + root.on('bye', function(peer, tmp){ + peer = opt.peers[peer.id || peer] || peer; + this.to.next(peer); + peer.bye? peer.bye() : (tmp = peer.wire) && tmp.close && tmp.close(); + Type.obj.del(opt.peers, peer.id); + peer.wire = null; + }); + + var gets = {}; + root.on('bye', function(peer, tmp){ this.to.next(peer); + if(!(tmp = peer.url)){ return } gets[tmp] = true; + setTimeout(function(){ delete gets[tmp] },opt.lack || 9000); + }); + root.on('hi', function(peer, tmp){ this.to.next(peer); + if(!(tmp = peer.url) || !gets[tmp]){ return } delete gets[tmp]; + Type.obj.map(root.next, function(node, soul){ + tmp = {}; tmp[soul] = root.graph[soul]; + mesh.say({'##': Type.obj.hash(tmp), get: {'#': soul}}, peer); + }) + }); + + return mesh; + } + + ;(function(){ + Type.text.hash = function(s){ // via SO + if(typeof s !== 'string'){ return {err: 1} } + var c = 0; + if(!s.length){ return c } + for(var i=0,l=s.length,n; i")){if(!(n>t[">"]))return!1;o=!0}if(p.obj.has(t,"<")){if(!(n",s.drift=0,s.is=function(t,n,o){var e=n&&t&&t[m]&&t[m][s._]||o;if(e)return g(e=e[n])?e:-1/0},s.lex=function(){return s().toString(36).replace(".","")},s.ify=function(t,n,o,e,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(o)&&(r[n]=o),void 0!==e&&(t[n]=e)),t},s.to=function(t,n,o){var e=(t||{})[n];return p(e)&&(e=d(e)),s.ify(o,n,s.is(t,n),e,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,o,e){if(!i)return u.call({o:o,s:r},t,n),t;i.call(a||this||{},t,n,o,e),l(o,n)&&void 0===o[n]||u.call({o:o,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&&(o.n=t,o.as=this.as,this.cb.call(o.as,t,n,o))}function o(t){t&&c.is(o.n,t,o.as)}r.is=function(t,n,o,e){return!(!t||!l(t)||u(t))&&!s(t,i,{cb:n,fn:o,as:e})}}(),function(){function u(t,n){var o;return(o=function(t,n){var o,e=t.seen,i=e.length;for(;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}(t,n))?o:(n.env=t,n.soul=i,c.ify(n.obj,e,n)&&(n.rel=n.rel||f.rel.ify(c.soul(n.node)),n.obj!==t.shell&&(t.graph[f.rel.is(n.rel)]=n.node)),n)}function e(t,n,o){var e,i,r=this,a=r.env;if(c._===n&&h(t,f.rel._))return o._;if(e=s(t,n,o,r,a)){if(n||(r.node=r.node||o||{},h(t,c._)&&c.soul(t)&&(r.node._=d(t._)),r.node=c.soul.ify(r.node,f.rel.is(r.rel)),r.rel=r.rel||f.rel.ify(c.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,o,r),h(o,n))){if(void 0===(t=o[n]))return void p(o,n);if(!(e=s(t,n,o,r,a)))return}if(!n)return r.node;if(!0===e)return t;if((i=u(a,{obj:t,path:r.path.concat(n)})).node)return i.rel}}function i(t){var n=this,o=f.link.is(n.rel),e=n.env.graph;n.rel=n.rel||f.rel.ify(t),n.rel[f.rel._]=t,n.node&&n.node[c._]&&(n.node[c._][f.rel._]=t),h(e,o)&&(e[t]=e[o],p(e,o))}function s(t,n,o,e,i){var r;return!!f.is(t)||(l(t)?1:(r=i.invalid)?s(t=r.call(i.as||{},t,n,o),n,o,e,i):(i.err="Invalid value at '"+e.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}r.ify=function(t,n,o){var e={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(e.rel=f.rel.ify(n.soul)),n.shell=(o||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||o,u(n,e),n.root=e.node,n.graph}}(),r.node=function(t){var n=c.soul(t);if(n)return o({},n,t)},function(){function i(t,n){var o,e;if(c._!==n)(o=f.rel.is(t))?(e=this.opt.seen[o])?this.obj[n]=e:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(u(t,f.rel._))return;this.obj[n]=d(t)}}r.to=function(t,n,o){if(t){var e={};return o=o||{seen:{}},s(t[n],i,{obj:e,graph:t,opt:o}),e}}}();a.fn.is;var n=a.obj,l=n.is,p=n.del,h=n.has,u=n.empty,o=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 o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,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),o}}})(_,"./ask"),_(function(t){var r=_("./type");var a=r.time.is;t.exports=function(e){var i={s:{}};return e=e||{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 o=i.s[t]||(i.s[t]={});return o.was=a(),n&&(o.pass=!0),i.to||(i.to=setTimeout(function(){var o=a();r.obj.map(i.s,function(t,n){t&&e.age>o-t.was||r.obj.del(i.s,n)}),i.to=null},e.age+9)),o},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,o,e=this.as,i=e.at||e,r=i.$;(o=t["#"])||(o=t["#"]=u(9)),(n=i.dup).check(o)?e.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(o),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),e.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,o,e){var i=this,r=c.state.is(o,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=i.graph[e]||v,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[e]=c.state.to(o,n,i.put[e]),(i.diff||(i.diff={}))[e]=c.state.to(o,n,i.diff[e]),i.souls[e]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var o=this,e=o.$._,i=(e.next||v)[n];if(!i){if(!(e.opt||v).super)return void(o.souls[n]=!1);i=o.$.get(n)._}var r=o.map[n]={put:t,get:n,$:i.$},a={ctx:o,msg:r};o.async=!!e.tag.node,o.ack&&(r["@"]=o.ack),h(t,u,a),o.async&&(o.and||e.on("node",function(t){this.to.next(t),t===o.map[t.get]&&(o.souls[t.get]=!1,h(t.put,s,t),h(o.souls,function(t){if(t)return t})||o.c||(o.c=1,this.off(),h(o.map,f,o)))}),o.and=!0,e.on("node",r))}function u(t,n){var o=this.ctx,e=o.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;e[r]=c.state.to(a,n,e[r]),o.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var o=this.put,e=this.$._;e.put=c.state.to(o,n,e.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 o=n._,e={$:n,graph:o.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:o,stop:{}};if(c.graph.is(t.put,null,i,e)||(e.err="Error: Invalid graph!"),e.err)return o.on("in",{"@":t["#"],err:c.log(e.err)});h(e.put,r,e),e.async||h(e.map,f,e),void 0!==e.defer&&setTimeout(function(){c.on.put(t,n)},e.defer-e.machine),e.diff&&o.on("put",p(t,{put:e.diff}))},c.on.get=function(t,n){var o=n._,e=t.get,i=e[d],r=o.graph[i],a=e[g],u=(o.next||(o.next={}))[i];if(l(i,"*")){var s={};c.obj.map(o.graph,function(t,n){c.text.match(n,i)&&(s[n]=c.obj.copy(t))}),c.obj.empty(s)||o.on("in",{"@":t["#"],how:"*",put:s,$:n})}if(!r)return o.on("get",t);if(a){if(!l(r,a))return o.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||v).ack,o.on("in",{"@":t["#"],how:"mem",put:r,$:n}),o.on("get",t)}}(),c.chain.opt=function(t){t=t||{};var n=this._,o=t.peers||t;return a(t)||(t={}),a(n.opt)||(n.opt=t),i(o)&&(o=[o]),e(o)&&(o=h(o,function(t,n,o){o(t,{url:t})}),a(n.opt.peers)||(n.opt.peers={}),n.opt.peers=p(o,n.opt.peers)),n.opt.peers=n.opt.peers||{},p(t,n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return s()+u(12)},this};var e=c.list.is,o=c.text,i=o.is,u=o.random,r=c.obj,a=r.is,l=r.has,p=r.to,h=r.map,s=(r.copy,c.state.lex),d=c.val.rel._,g=".",v=(c.node._,c.val.link.is,{});b.debug=function(t,n){return b.debug.i&&t===b.debug.i&&b.debug.i++&&(b.log.apply(b,arguments)||n)},(c.log=function(){return!c.log.off&&b.log.apply(b,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,o){return(o=c.log.once)[t]=o[t]||0,o[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!==f&&(f.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 o=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var e=0,i=t.length,r=o;e .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var o=this,e=o._,i=e.put;if(0=(a.batch||1e3))return f();e||(e=setTimeout(f,a.wait||1))}),r.on("get",function(n){this.to.next(n);var o,e,i=n.get;function t(){if(i&&(o=i["#"])){var t=i["."];(e=s[o]||void 0)&&t&&(e=Gun.state.to(e,t)),(e||Gun.obj.empty(a.peers))&&r.on("in",{"@":n["#"],put:Gun.graph.node(e),how:"lS",lS:n.$||r.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,o,e){s[e]=Gun.state.to(o,n,s[e])},f=function(t){var o;u=0,clearTimeout(e),e=!1;var n=i;i={},t&&(s=t);try{p.setItem(a.prefix,JSON.stringify(s))}catch(t){Gun.log(o=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."),r.on("localStorage:error",{err:o,file:a.prefix,flush:s,retry:f})}(o||Gun.obj.empty(a.peers))&&Gun.obj.map(n,function(t,n){r.on("in",{"@":n,err:o,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var g=_("../type");function o(p){var h=function(){},d=p.opt||{};return d.log=d.log||b.log,d.gap=d.gap||d.wait||1,d.pack=d.pack||.3*(d.memory?1e3*d.memory*1e3:1399e6),h.out=function(t){var n;if(this.to&&this.to.next(t),(n=t["@"])&&(n=p.dup.s[n])&&(n=n.it)&&n._)return h.say(t,n._.via,1),void(n["##"]=t["##"]);h.say(t)},p.on("create",function(t){t.opt.pid=t.opt.pid||g.text.random(9),this.to.next(t),p.on("out",h.out)}),h.hear=function(t,n){if(t){var o,e,i,r=p.dup,a=t[0];if(d.pack<=t.length)return h.say({dam:"!",err:"Message too big!"},n);try{i=JSON.parse(t)}catch(t){d.log("DAM JSON parse error",t)}if("{"===a){if(!i)return;if(r.check(o=i["#"]))return;if((a=(r.track(o,!0).it=i)["@"])&&i.put&&(a+=e=i["##"]||(i["##"]=h.hash(i)))!=o){if(r.check(a))return;(a=r.s)[e]=a[o]}return(i._=function(){}).via=n,(a=i["><"])&&(i._.to=g.obj.map(a.split(","),function(t,n,o){o(t,!0)})),i.dam?void((a=h.hear[i.dam])&&a(i,n,p)):void p.on("in",i)}if("["!==a);else{if(!i)return;for(var u,s=0;u=i[s++];)h.hear(u,n)}}},function(){function a(t){var n=t.batch;if(n&&(t.batch=t.tail=null,n.length))try{u(JSON.stringify(n),t)}catch(t){d.log("DAM JSON stringify error",t)}}function u(n,o){var t=o.wire;try{t.send?t.send(n):o.say&&o.say(n)}catch(t){(o.queue=o.queue||[]).push(n)}}h.say=function(n,t,o){var e,i,r;if(t){if((t.wire||d.wire&&d.wire(t))&&(i=n._||s,t!==i.via&&((r=i.raw)||(r=h.raw(n)),!((e=n["@"])&&(e=p.dup.s[e])&&(e=e.it)&&e.get&&e["##"]&&e["##"]===n["##"])&&(!(e=i.to)||!e[t.url]&&!e[t.id]||o)))){if(t.batch){if(t.tail=(t.tail||0)+r.length,t.tail<=d.pack)return void t.batch.push(r);a(t)}t.batch=[],setTimeout(function(){a(t)},d.gap),u(r,t)}}else g.obj.map(d.peers,function(t){h.say(n,t)})}}(),function(){function f(t,n){var o;return n instanceof Object?(g.obj.map(Object.keys(n).sort(),e,{to:o={},on:n}),o):n}function e(t){this.to[t]=this.on[t]}h.raw=function(t){if(!t)return"";var n,o,e,i=p.dup,r=t._||{};if(e=r.raw)return e;if("string"==typeof t)return t;t["@"]&&(e=t.put)&&((o=t["##"])||(n=c(e,f)||"",o=h.hash(t,n),t["##"]=o),(e=i.s)[o=t["@"]+o]=e[t["#"]],t["#"]=o||t["#"],n&&((t=g.obj.to(t)).put=l));var a=0,u=[];g.obj.map(d.peers,function(t){if(u.push(t.url||t.id),9<++a)return!0}),t["><"]=u.join();var s=c(t);return v!==n&&(e=s.indexOf(l,s.indexOf("put")),s=s.slice(0,e-1)+n+s.slice(e+l.length+1)),r&&(r.raw=s),s},h.hash=function(t,n){return o.hash(n||c(t.put,f)||"")||t["#"]||g.text.random(9)};var c=JSON.stringify,l=":])([:"}(),h.hi=function(n){var t=n.wire||{};n.id||n.url?(d.peers[n.url||n.id]=n,g.obj.del(d.peers,t.id)):(t=t.id=t.id||g.text.random(9),h.say({dam:"?"},d.peers[t]=n)),t.hied||p.on(t.hied="hi",n),t=n.queue,n.queue=[],g.obj.map(t,function(t){h.say(t,n)})},h.bye=function(t){g.obj.del(d.peers,t.id),p.on("bye",t)},h.hear["!"]=function(t,n){d.log("Error:",t.err)},h.hear["?"]=function(t,n){if(!t.pid)return h.say({dam:"?",pid:d.pid,"@":t["#"]},n);n.id=n.id||t.pid,h.hi(n)},h}o.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o"]||n["<"])||e===n["="]&&(o=n["*"]||n[">"]||n["<"],t.slice(0,(o||"").length)===o||e===n["*"]&&(e!==n[">"]&&e!==n["<"]?t>=n[">"]&&t<=n["<"]:e!==n[">"]&&t>=n[">"]||e!==n["<"]&&t<=n["<"])))},p.list={is:function(t){return t instanceof Array}},p.list.slit=Array.prototype.slice,p.list.sort=function(o){return function(t,n){return t&&n?(t=t[o])<(n=n[o])?-1:n",s.drift=0,s.is=function(t,n,o){var e=n&&t&&t[m]&&t[m][s._]||o;if(e)return g(e=e[n])?e:-1/0},s.lex=function(){return s().toString(36).replace(".","")},s.ify=function(t,n,o,e,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(o)&&(r[n]=o),void 0!==e&&(t[n]=e)),t},s.to=function(t,n,o){var e=(t||{})[n];return p(e)&&(e=d(e)),s.ify(o,n,s.is(t,n),e,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,o,e){if(!i)return u.call({o:o,s:r},t,n),t;i.call(a||this||{},t,n,o,e),l(o,n)&&void 0===o[n]||u.call({o:o,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&&(o.n=t,o.as=this.as,this.cb.call(o.as,t,n,o))}function o(t){t&&c.is(o.n,t,o.as)}r.is=function(t,n,o,e){return!(!t||!l(t)||u(t))&&!s(t,i,{cb:n,fn:o,as:e})}}(),function(){function u(t,n){var o;return(o=function(t,n){var o,e=t.seen,i=e.length;for(;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}(t,n))?o:(n.env=t,n.soul=i,c.ify(n.obj,e,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 e(t,n,o){var e,i,r=this,a=r.env;if(c._===n&&h(t,f.link._))return o._;if(e=s(t,n,o,r,a)){if(n||(r.node=r.node||o||{},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,o,r),h(o,n))){if(void 0===(t=o[n]))return void p(o,n);if(!(e=s(t,n,o,r,a)))return}if(!n)return r.node;if(!0===e)return t;if((i=u(a,{obj:t,path:r.path.concat(n)})).node)return i.link}}function i(t){var n=this,o=f.link.is(n.link),e=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(e,o)&&(e[t]=e[o],p(e,o))}function s(t,n,o,e,i){var r;return!!f.is(t)||(l(t)?1:(r=i.invalid)?s(t=r.call(i.as||{},t,n,o),n,o,e,i):(i.err="Invalid value at '"+e.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}r.ify=function(t,n,o){var e={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(e.link=f.link.ify(n.soul)),n.shell=(o||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||o,u(n,e),n.root=e.node,n.graph}}(),r.node=function(t){var n=c.soul(t);if(n)return o({},n,t)},function(){function i(t,n){var o,e;if(c._!==n)(o=f.link.is(t))?(e=this.opt.seen[o])?this.obj[n]=e:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(u(t,f.link._))return;this.obj[n]=d(t)}}r.to=function(t,n,o){if(t){var e={};return o=o||{seen:{}},s(t[n],i,{obj:e,graph:t,opt:o}),e}}}();a.fn.is;var n=a.obj,l=n.is,p=n.del,h=n.has,u=n.empty,o=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 o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,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),o}}})(_,"./ask"),_(function(t){var r=_("./type");var a=r.time.is;t.exports=function(e){var i={s:{}};return e=e||{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 o=i.s[t]||(i.s[t]={});return o.was=a(),n&&(o.pass=!0),i.to||(i.to=setTimeout(function(){var o=a();r.obj.map(i.s,function(t,n){t&&e.age>o-t.was||r.obj.del(i.s,n)}),i.to=null},e.age+9)),o},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,o,e=this.as,i=e.at||e,r=i.$;(o=t["#"])||(o=t["#"]=u(9)),(n=i.dup).check(o)?e.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(o),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),e.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,o,e){var i=this,r=c.state.is(o,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=i.graph[e]||v,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[e]=c.state.to(o,n,i.put[e]),(i.diff||(i.diff={}))[e]=c.state.to(o,n,i.diff[e]),i.souls[e]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var o=this,e=o.$._,i=(e.next||v)[n];if(!i){if(!(e.opt||v).super)return void(o.souls[n]=!1);i=o.$.get(n)._}var r=o.map[n]={put:t,get:n,$:i.$},a={ctx:o,msg:r};o.async=!!e.tag.node,o.ack&&(r["@"]=o.ack),h(t,u,a),o.async&&(o.and||e.on("node",function(t){this.to.next(t),t===o.map[t.get]&&(o.souls[t.get]=!1,h(t.put,s,t),h(o.souls,function(t){if(t)return t})||o.c||(o.c=1,this.off(),h(o.map,f,o)))}),o.and=!0,e.on("node",r))}function u(t,n){var o=this.ctx,e=o.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;e[r]=c.state.to(a,n,e[r]),o.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var o=this.put,e=this.$._;e.put=c.state.to(o,n,e.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 o=n._,e={$:n,graph:o.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:o,stop:{}};if(c.graph.is(t.put,null,i,e)||(e.err="Error: Invalid graph!"),e.err)return o.on("in",{"@":t["#"],err:c.log(e.err)});h(e.put,r,e),e.async||h(e.map,f,e),void 0!==e.defer&&setTimeout(function(){c.on.put(t,n)},e.defer-e.machine),e.diff&&o.on("put",p(t,{put:e.diff}))},c.on.get=function(t,n){var o=n._,e=t.get,i=e[d],r=o.graph[i],a=e[g],u=(o.next||(o.next={}))[i];if(!r)return o.on("get",t);if(a){if("string"!=typeof a||!l(r,a))return o.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||v).ack,o.on("in",{"@":t["#"],how:"mem",put:r,$:n}),o.on("get",t)}}(),c.chain.opt=function(t){t=t||{};var n=this._,o=t.peers||t;return s(t)||(t={}),s(n.opt)||(n.opt=t),r(o)&&(o=[o]),e(o)&&(o=h(o,function(t,n,o){(n={}).id=n.url=t,o(t,n)}),s(n.opt.peers)||(n.opt.peers={}),n.opt.peers=p(o,n.opt.peers)),n.opt.peers=n.opt.peers||{},h(t,function t(n,o){!l(this,o)||i.is(n)||a.empty(n)?this[o]=n:n&&n.constructor!==Object&&!e(n)||h(n,t,this[o])},n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return f()+u(12)},this};var e=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,{});b.debug=function(t,n){return b.debug.i&&t===b.debug.i&&b.debug.i++&&(b.log.apply(b,arguments)||n)},(c.log=function(){return!c.log.off&&b.log.apply(b,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,o){return(o=c.log.once)[t]=o[t]||0,o[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!==o&&(o.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 o=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var e=0,i=t.length,r=o;e(r.acks||0)&&this.off(),r.ack&&r.ack(t,this)},r.opt),o=0,e=n.root.now;u.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?u.to(i,n.root.mum):i,n.root.now=e},r),r.res&&r.res())}function n(t,n){if(t)return!0}function l(r,t,n,a){var u=this,s=f.is(r);!t&&a.path.length&&(u.res||e)(function(){for(var t=a.path,n=u.ref,o=(u.opt,0),e=t.length;o .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var o=this,e=o._,i=e.put;if(0=(e.batch||1e3))return f();i||(i=setTimeout(f,e.wait||1))}),r.on("get",function(n){this.to.next(n);var o,e,i=n.get;function t(){if(i&&(o=i["#"])){var t=i["."];(e=s[o]||void 0)&&t&&(e=Gun.state.to(e,t)),r.on("in",{"@":n["#"],put:Gun.graph.node(e),how:"lS",lS:n.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,o,e){s[e]=Gun.state.to(o,n,s[e])},f=function(t){var o;u=0,clearTimeout(i),i=!1;var n=a;a={},t&&(s=t);try{p.setItem(e.prefix,JSON.stringify(s))}catch(t){Gun.log(o=(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:o,file:e.prefix,flush:s,retry:f})}(o||Gun.obj.empty(e.peers))&&Gun.obj.map(n,function(t,n){r.on("in",{"@":n,err:o,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var d=_("../type");!function(){d.text.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o<"])&&(o._.to=d.obj.map(r.split(","),f)),o.dam?void((r=c.hear[o.dam])&&r(o,n,s)):void s.on("in",o)}}else{try{o=JSON.parse(t)}catch(t){l.log("DAM JSON parse error",t)}if(!o)return;for(var a,u=0;a=o[u++];)c.hear(a,n)}}};var f=function(t,n,o){o(t,!0)};function h(n,o){try{var t=o.wire;o.say?o.say(n):t.send&&t.send(n),c.say.d+=n.length||0,++c.say.c}catch(t){(o.queue=o.queue||[]).push(n)}}c.hear.c=c.hear.d=0,function(){var u;function s(t){c.say(u,t)}function f(t){var n=t.batch;if(t.batch=t.tail=null,n&&n.length){try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return l.log("DAM JSON stringify error",t)}n&&h(n,t)}}c.say=function(t,n){if(this.to&&this.to.next(t),!t)return!1;var o,e,i,r,a=t._||(t._=function(){});if((o=t["#"])||(o=t["#"]=d.text.random(9)),(e=t["##"])||void 0===t.put||(e=t["##"]=d.obj.hash(t.put)),!(r=a.raw)&&(r=a.raw=c.raw(t),e&&(i=t["@"])&&(p.track(i+e).it=t,i=(p.s[i]||!0).it))){if(e===i["##"])return!1;i["##"]=e}if(p.track(o).it=t,n||(n=(i=p.s[t["@"]])&&(i=i.it)&&(i=i._)&&(i=i.via)),!n&&c.way)return c.way(t);if(!n||!n.id)return u=t,!!d.obj.is(n||l.peers)&&void d.obj.map(n||l.peers,s);if(!n.wire&&c.wire&&c.wire(n),o!==n.last){if(n.last=o,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<=l.pack)return void n.batch.push(r);f(n)}n.batch=[],setTimeout(function(){f(n)},l.gap),h(r,n)}},c.say.c=c.say.d=0}(),function(){c.raw=function(t){if(!t)return"";var n,o=t._||{};if(n=o.raw)return n;if("string"==typeof t)return t;if(!t.dam){var e=0,i=[];d.obj.map(l.peers,function(t){if(i.push(t.url||t.pid||t.id),9<++e)return!0}),1<"]=i.join())}var r=a(t);return o&&(o.raw=r),r};var a=JSON.stringify}(),c.hi=function(n){var t=n.wire||{};n.id?l.peers[n.url||n.id]=n:(t=n.id=n.id||d.text.random(9),c.say({dam:"?"},l.peers[t]=n)),n.met=n.met||+new Date,t.hied||s.on(t.hied="hi",n),t=n.queue,n.queue=[],d.obj.map(t,function(t){h(t,n)})},c.bye=function(t){s.on("bye",t);var n=+new Date;n-=t.met||n,c.bye.time=((c.bye.time||n)+n)/2},c.hear["!"]=function(t,n){l.log("Error:",t.err)},c.hear["?"]=function(t,n){t.pid?n.pid||(n.pid=t.pid):c.say({dam:"?",pid:l.pid,"@":t["#"]},n)},s.on("create",function(t){t.opt.pid=t.opt.pid||d.text.random(9),this.to.next(t),t.on("out",c.say)}),s.on("bye",function(t,n){t=l.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),d.obj.del(l.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]},l.lack||9e3))}),s.on("hi",function(o,e){this.to.next(o),(e=o.url)&&i[e]&&(delete i[e],d.obj.map(s.next,function(t,n){(e={})[n]=s.graph[n],c.say({"##":d.obj.hash(e),get:{"#":n}},o)}))}),c}}catch(t){}})(_,"./adapters/mesh"),_(function(t){var f=_("../index");f.Mesh=_("./mesh"),f.on("opt",function(t){this.to.next(t);var e=t.opt;if(!t.once&&!1!==e.WebSocket){var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var o=e.WebSocket||n.WebSocket||n.webkitWebSocket||n.mozWebSocket;if(o){e.WebSocket=o;var i=e.mesh=e.mesh||f.Mesh(t);i.wire||e.wire;i.wire=e.wire=u;var r=2e3,a="undefined"!=typeof document&&document}}function u(n){try{if(!n||!n.url)return o&&o(n);var t=n.url.replace("http","ws"),o=n.wire=new e.WebSocket(t);return o.onclose=function(){e.mesh.bye(n),s(n)},o.onerror=function(t){s(n)},o.onopen=function(){e.mesh.hi(n)},o.onmessage=function(t){t&&e.mesh.hear(t.data||t,n)},o}catch(t){}}function s(n){clearTimeout(n.defer),a&&n.retry<=0||(n.retry=(n.retry||e.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/afore.js b/lib/afore.js new file mode 100644 index 00000000..d7cab15d --- /dev/null +++ b/lib/afore.js @@ -0,0 +1,13 @@ +function afore(tag, hear){ + if(!tag){ return } + tag = tag.the; // grab the linked list root + var tmp = tag.to; // grab first listener + hear = tmp.on.on(tag.tag, hear); // add us to end + hear.to = tmp || hear.to; // make our next be current first + hear.back.to = hear.to; // make our back point to our next + tag.last = hear.back; // make last be same as before + hear.back = tag; // make our back be the start + tag.to = hear; // make the start be us + return hear; +} +if(typeof module !== "undefined"){ module.exports = afore } // afore(gun._.on('in'), function(){ }) \ No newline at end of file diff --git a/lib/bye.js b/lib/bye.js index 776e6d4f..a7fa5c0d 100644 --- a/lib/bye.js +++ b/lib/bye.js @@ -5,8 +5,8 @@ Gun.on('opt', function(root){ if(root.once){ return } root.on('in', function(msg){ //Msg did not have a peer property saved before, so nothing ever went further - if(!msg.mesh || !msg.BYE){ return this.to.next(msg) } - var peer = msg.mesh.via; + if(!msg._ || !msg.BYE){ return this.to.next(msg) } + var peer = msg._.via; (peer.bye = peer.bye || []).push(msg.BYE); }) root.on('bye', function(peer){ diff --git a/lib/doll.js b/lib/doll.js new file mode 100644 index 00000000..2a1b7f69 --- /dev/null +++ b/lib/doll.js @@ -0,0 +1,44 @@ +;(function(){ // jQuery shim + if(window.$){ return } + (($ = window.$ = function(q, tag){ + if(!(this instanceof $)){ return new $(q, tag) } + this.tags = (q && q.tags) || (('string' != typeof q)? + (q?[q]:[]) : (tag||document).querySelectorAll(q)); + return this; + }).fn = $.prototype).each = function(cb){ return $.each(this.tags, cb), this } + $.each = function(o, cb){ Object.keys(o).forEach(function(k){ cb(k, o[k]) }) } + $.fn.get = function(i, l, u){ return l = this.tags, (i === u)? l : l[i] } + $.fn.on = function(eve, cb){ return this.each(function(i, tag){ tag.addEventListener(eve, cb) })} + $.fn.is = function(q, b){ return this.each(function(i, tag){ b = b || tag.matches(q) }), b } + $.fn.css = function(obj){ return this.each(function(i, tag){ $.each(obj, function(k,v){ tag.style[k] = v }) })} + $.fn.text = function(text, key, f, u){ + text = (text === u)? '' : (f = 1) && text; + key = key || 'textContent'; + this.each(function(i, tag){ + if(f){ tag[key] = text } + else { text += (tag[key]||'') } + }); + return f? this : text; + } + $.fn.html = function(html){ return this.text(html, 'innerHTML') } + $.fn.find = function(q){ + var I = $(), l = I.tags; + this.each(function(i, tag){ + $(q, tag).each(function(i, tag){ + if(0 > l.indexOf(tag)){ l.push(tag) } + }); + }); + return I; + } + $.fn.add = function(html, div){ + (div = document.createElement('div')).innerHTML = html; + this.tags = [].slice.call(this.tags).concat([].slice.call(div.childNodes)); + return this; + } + $.fn.append = function(html, op, f){ return this.each(function(i, tag){ + (('<' === html[0])? $().add(html) : $(html)).each(function(i, node){ + (f? node : tag)[op || 'appendChild'](f? tag : node); + }) + })} + $.fn.appendTo = function(html){ return this.append(html, 0, 1) } +}()); \ No newline at end of file diff --git a/lib/email.js b/lib/email.js new file mode 100644 index 00000000..461b4ab4 --- /dev/null +++ b/lib/email.js @@ -0,0 +1,12 @@ +;(function(){ + var email, fail = {send: function(opt, cb){ cb && cb("You do not have email installed.") } }; + if(!process.env.EMAIL){ return module.exports = fail } + try{ email = require('emailjs') }catch(e){}; + if(!email){ return module.exports = fail } + return module.exports = email.server.connect({ + user: process.env.EMAIL, + password: process.env.EMAIL_KEY, + host: process.env.EMAIL_HOST || "smtp.gmail.com", + ssl: process.env.EMAIL_SSL || true + }); +}()); \ No newline at end of file diff --git a/lib/fsrm.js b/lib/fsrm.js new file mode 100644 index 00000000..89133f7c --- /dev/null +++ b/lib/fsrm.js @@ -0,0 +1,18 @@ +var fs = require('fs'); +var nodePath = require('path'); + +var dir = __dirname + '/../'; + +module.exports = function rm(path, full) { + path = full || nodePath.join(dir, path); + if(!fs.existsSync(path)){ return } + fs.readdirSync(path).forEach(function(file,index){ + var curPath = path + "/" + file; + if(fs.lstatSync(curPath).isDirectory()) { // recurse + rm(null, curPath); + } else { // delete file + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(path); +}; \ No newline at end of file diff --git a/lib/hot.js b/lib/hot.js new file mode 100644 index 00000000..794bbc57 --- /dev/null +++ b/lib/hot.js @@ -0,0 +1,95 @@ +;(function(){ + // on fires when shortcut keydowns or on touch after command selected and then touchdown + var m = meta; + m.edit({name: "Add", combo: ['A']}); + m.edit({name: "Row", combo: ['A', 'R'], + on: function(eve){ + m.tap().append('
    '); + } + }); + m.edit({name: "Columns", combo: ['A','C'], + on: function(eve){ + var on = m.tap(), tmp, c; + var html = '
    '; + if(!on.children('.col').length){ html += html } + c = (tmp = on.append(html).children('.col')).length; + tmp.each(function(){ + $(this).css('width', (100/c)+'%'); + }) + } + }); + m.edit({name: "Text", combo: ['A','T'], + on: function(eve){ + m.tap().append('

    Text

    '); + } + }); + m.edit({name: "Drag", combo: ['D']}); + ;(function(){ + $(document).on('click', function(){ + var tmp = $('.m-on'); + if(!tmp.length){ return } + tmp.removeClass('m-on'); + }) + m.edit({combo: [38], // up + on: function(eve){ + var on = m.tap().removeClass('m-on'); + on = on.prev().or(on.parent()).or(on); + on.addClass('m-on'); + }, up: function(){ + } + }); + m.edit({combo: [40], // down + on: function(eve){ + var on = m.tap().removeClass('m-on'); + on = on.next().or(on.children().first()).or(on); + on.addClass('m-on'); + }, up: function(){ + } + }); + m.edit({combo: [39], // right + on: function(eve){ + var on = m.tap().removeClass('m-on'); + on = on.children().first().or(on.next()).or(on.parent()).or(on); + on.addClass('m-on'); + }, up: function(){ + } + }); + m.edit({combo: [37], // left + on: function(eve){ + var on = m.tap().removeClass('m-on'); + on = on.parent().or(on); + on.addClass('m-on'); + }, up: function(){ + } + }); + }()); + m.edit({name: "Turn", combo: ['T']}); + m.edit({name: "Size", combo: ['S']}); + m.edit({name: "X", combo: ['S','X'], + on: function(eve){ + var on = m.tap(), was = on.width(); + $(document).on('mousemove.tmp', function(eve){ + var be = was + ((eve.pageX||0) - was); + on.css({'max-width': be, width: '100%'}); + }) + }, up: function(){ $(document).off('mousemove.tmp') } + }); + m.edit({name: "Y", combo: ['S','Y'], + on: function(eve){ + var on = m.tap(), was = on.height(); + $(document).on('mousemove.tmp', function(eve){ + var be = was + ((eve.pageY||0) - was); + on.css({'min-height': be}); + }) + }, up: function(){ $(document).off('mousemove.tmp') } + }); + m.edit({name: "Fill", combo: ['F'], + on: function(eve){ + var on = m.tap(); + m.ask('Color name, code, or URL?', function(color){ + var css = on.closest('p').length? 'color' : 'background'; + on.css(css, color); + }); + } + }); +}()); \ No newline at end of file diff --git a/lib/hub.js b/lib/hub.js new file mode 100644 index 00000000..38db6ee1 --- /dev/null +++ b/lib/hub.js @@ -0,0 +1,5 @@ +var fs = require('fs'); + +fs.watch('.', {persistent: false, recursive: true}, function(eve, name){ + console.log("changed!", eve, name); +}) \ No newline at end of file diff --git a/lib/ipfs.js b/lib/ipfs.js new file mode 100644 index 00000000..5d183217 --- /dev/null +++ b/lib/ipfs.js @@ -0,0 +1,46 @@ +console.log("IPFS PLUGIN NOT OFFICIALLY MAINTAINED! PROBABLY WON'T WORK! USE AT YOUR OWN RISK! PLEASE CONTRIBUTE FIXES!"); +var opt = gun._.opt, u; +if (u === opt.ipfs.directory) { + opt.ipfs.directory = '/gun'; +} +opt.store = {}; +opt.store.put = function(file, data, cb){ + var uri = opt.ipfs.directory + '/' + file; + opt.ipfs.instance.files.write(uri, Buffer.from(JSON.stringify(data)), {create:true}) + .then(res => { + console.log('File written to IPFS directory', uri, res); + return opt.ipfs.instance.files.stat(opt.ipfs.directory, {hash:true}); + }).then(res => { + console.log('Directory hash:', res.hash); + return opt.ipfs.instance.name.publish(res.hash); + // currently throws "This command must be run in online mode. Try running 'ipfs daemon' first." for some reason, maybe js-ipfs IPNS not ready yet + }).then(res => { + console.log('IPFS put request successful:', res); + cb(undefined, 1); + }).catch(error => { + console.error('IPFS put request failed', error); + }); +} +opt.store.get = function(file, cb){ + var uri = opt.ipfs.directory + '/' + file; + opt.ipfs.instance.files.read(uri, {}) + .then(res => { + var data = JSON.parse(res.toString()); + console.log(uri + ' was loaded from ipfs:', data); + cb(data); + }); +} +opt.store.list = function(cb){ + var stream = opt.ipfs.files.lsReadableStream(opt.ipfs.directory); + + stream.on('data', (file) => { + console.log('ls', file.name); + if (cb(file.name)) { + stream.destroy(); + } + }); + + stream.on('finish', () => { + cb(); + }); +} \ No newline at end of file diff --git a/lib/les.js b/lib/les.js index 75a44ece..8ffddfa2 100644 --- a/lib/les.js +++ b/lib/les.js @@ -71,7 +71,7 @@ const gc_enable = root.opt.gc_enable ? root.opt.gc_enable : true; const gc_delay = root.opt.gc_delay ? root.opt.gc_delay : 1000; - const gc_info_enable = root.opt.gc_info_enable ? root.opt.gc_info_enable : true; + const gc_info_enable = ("gc_info_enable" in root.opt) ? root.opt.gc_info_enable : true; const gc_info = root.opt.gc_info ? root.opt.gc_info : 5000; const gc_info_mini = root.opt.gc_info_mini ? root.opt.gc_info_mini : false; @@ -127,6 +127,7 @@ //Executed every time a node gets modified root.on("put", function(e) { + this.to.next(e); var ctime = Date.now(); var souls = Object.keys(e.put || empty); // get all of the nodes in the update for (var i = 0; i < souls.length; i++) { // iterate over them and add them @@ -219,4 +220,4 @@ return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); } }); -}()); \ No newline at end of file +}()); diff --git a/lib/match.js b/lib/match.js new file mode 100644 index 00000000..e9d5ca49 --- /dev/null +++ b/lib/match.js @@ -0,0 +1,25 @@ +var Type = require('../src/type'); +function match(t, o){ var r = false; + t = t || ''; + o = Type.text.is(o)? {'=': o} : o || {}; // {'~', '=', '*', '<', '>', '+', '-', '?', '!'} // ignore case, exactly equal, anything after, lexically larger, lexically lesser, added in, subtacted from, questionable fuzzy match, and ends with. + if(Type.obj.has(o,'~')){ t = t.toLowerCase(); o['='] = (o['='] || o['~']).toLowerCase() } + if(Type.obj.has(o,'=')){ return t === o['='] } + if(Type.obj.has(o,'*')){ if(t.slice(0, o['*'].length) === o['*']){ r = true; t = t.slice(o['*'].length) } else { return false }} + if(Type.obj.has(o,'!')){ if(t.slice(-o['!'].length) === o['!']){ r = true } else { return false }} + if(Type.obj.has(o,'+')){ + if(Type.list.map(Type.list.is(o['+'])? o['+'] : [o['+']], function(m){ + if(t.indexOf(m) >= 0){ r = true } else { return true } + })){ return false } + } + if(Type.obj.has(o,'-')){ + if(Type.list.map(Type.list.is(o['-'])? o['-'] : [o['-']], function(m){ + if(t.indexOf(m) < 0){ r = true } else { return true } + })){ return false } + } + if(Type.obj.has(o,'>')){ if(t > o['>']){ r = true } else { return false }} + if(Type.obj.has(o,'<')){ if(t < o['<']){ r = true } else { return false }} + function fuzzy(t,f){ var n = -1, i = 0, c; for(;c = f[i++];){ if(!~(n = t.indexOf(c, n+1))){ return false }} return true } // via http://stackoverflow.com/questions/9206013/javascript-fuzzy-search + if(Type.obj.has(o,'?')){ if(fuzzy(t, o['?'])){ r = true } else { return false }} // change name! + return r; +} +module.exports = match; \ No newline at end of file diff --git a/lib/meta.js b/lib/meta.js index b89e4725..c9fa3f9b 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -1,73 +1,72 @@ -$(function(){ - var m = window.meta = {edit:[], os:{}}, ua = '', u; - try{ua = navigator.userAgent.toLowerCase()}catch(e){} - m.os.is = { - win: (ua.search("win") >= 0)? "windows":false, - lin: (ua.search("linux") >= 0)? "linux":false, - mac: (ua.search("mac") >= 0)? "macintosh":false, - and: (ua.search("android") >= 0)? "android":false, - ios: (ua.search('ipod') >= 0 - || ua.search('iphone') >= 0 - || ua.search('ipad') >= 0)? "ios":false - } - var k = m.key = {ctrl: 17, cmd: 91}; - k.meta = (m.os.is.win||m.os.is.lin||m.os.is.and)? k.ctrl : k.cmd; +;(function(){ + var noop = function(){}, u; + var m = window.meta = {edit:[]}; + var k = m.key = {}; + k.meta = {17:17, 91:17, 93:17, 224:17}; k.down = function(eve){ - if($(eve.target).is('input') || eve.repeat){ return } - (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; - if(!eve.fake && eve.which === k.last){ return } - if(k.meta === (k.last = eve.which)){ k.down.meta = m.flip(k.wipe()) || true } - if(m.flip.is()){ - (k.combo || (k.combo = [])).push(eve.which); - m.check('on', eve.which, k.at || (k.at = m.edit)); + if(eve.repeat){ return } + var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; + if(!eve.fake && key === k.last){ return } k.last = key; + if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length){ + if(k.meta[key]){ k.down.meta = key = -1 } + if(!k.down.meta){ return } + } + (k.combo || (k.combo = [])).push(key); + m.check('on', key, k.at || (k.at = m.edit)); + if(k.meta[key]){ + m.list(k.at.back || m.edit); + if(k.at && !k.at.back){ m.flip() } } - if(eve.metaKey && (k.meta !== eve.which)){ k.up(eve) } // on some systems, meta hijacks keyup } k.up = function(eve){ var tmp; - if($(eve.target).is('input')){ return } - k.eve = m.eve = eve; - k.last = null; - eve.which = eve.which || eve.fake || eve.keyCode; - if(m.flip.is()){ m.check('up', eve.which) } - if(tmp = (k.meta === eve.which)){ k.down.meta = false } - if(tmp && k.at === m.edit){ k.wipe() } - if(27 === eve.which){ return m.flip(false) } - } - m.flip = function(tmp, aid){ - if(aid){ - m.flip.aid = true; - setTimeout(function(){$(document).one('click',function(eve){m.flip(m.flip.aid = false)})},250); // ugly but important for visual aid. + var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; + if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length){ + if(k.meta[key]){ + k.down.meta = null; + key = -1; + } else + if(!k.down.meta){ return } } + k.last = null; + if($(':focus').closest('#meta').length){ return } + m.check('up', key); + if(-1 === key || 27 === eve.which){ k.wipe() } + } + m.flip = function(tmp){ var board = $('#meta .meta-menu'); ((tmp === false) || (!tmp && board.is(':visible')))? board.addClass('meta-none') : board.removeClass('meta-none'); } m.flip.is = function(){ - if(m.flip.aid && ((m.eve||{}).fake || k.at !== m.edit)){ m.flip.aid = false } - return !m.flip.aid && $('#meta .meta-menu').is(':visible'); + return $('#meta .meta-menu').is(':visible'); } m.flip.wait = 500; m.check = function(how, key, at){ at = k.at || m.edit; - //m.list(at); - var edit = at[key], tmp; + var edit = at[key]; if(!edit){ return } - if(k.eve && k.eve.preventDefault){ k.eve.preventDefault() } + var tmp = k.eve || noop; + if(tmp.preventDefault){ tmp.preventDefault() } if(edit[how]){ - edit[how](m.eve); - if(k.at !== m.edit && 'up' === how){ - if(k.down.meta){ m.list(k.at = m.edit) } - else { k.wipe() } + if(tmp.fake && !edit.fake){ + m.tap.edit = edit; + } else { + edit[how](m.eve); + /*if(k.at !== m.edit && 'up' === how){ + if(k.down.meta){ m.list(k.at = m.edit) } + else { k.wipe() } + }*/ } } if('up' != how){ return } - edit.back = at; - m.list(edit, at); + if(at != edit){ edit.back = at } + m.list(edit, true); } - m.list = function(at){ + m.list = function(at, opt){ + if(!at){ return m.flip(false) } var l = []; - $.each(at, function(i,k){ 'back' != i && k.combo && l.push(k) }); + $.each(at, function(i,k){ 'back' != i && k.combo && k.name && l.push(k) }); if(!l.length){ return } k.at = at; l = l.sort(function(a,b){ @@ -82,8 +81,10 @@ $(function(){ $.each(l, function(i, k){ $ul.append($('
  • ').text(k.name)); }); - if(!at.back){ return } - $ul.append($('
  • ').html('←').one('click', function(){ m.list(k.at = at.back) })); + if(opt){ m.flip(true) } + $ul.append($('
  • ').html('←').one('click', function(){ + m.list(k.at = at.back); + })); } m.ask = function(help, cb){ var $ul = $('#meta .meta-menu ul').empty(); @@ -96,34 +97,33 @@ $(function(){ }); var $li = $('
  • ').append($form); $ul.append($li); + m.flip(true); $put.focus(); } - k.wipe = function(){ + k.wipe = function(opt){ + k.down.meta = false; k.combo = []; - m.flip(false); - m.flip.aid = false; + if(!opt){ m.flip(false) } m.list(k.at = m.edit); }; - $(document).on('keydown', k.down).on('keyup', k.up); - m.tap = {}; - m.tap.select = function(eve){ - m.tap.range = null; - if(!(m.tap.text()||'').trim()){ - if(m.tap.was){ - m.tap.was = null; - m.flip(false); - } - return; - } - m.flip(m.tap.range = monotype((eve||{}).target), m.tap.was = true); - } - m.tap.text = function(tmp){ - return ((tmp = window.getSelection) && tmp().toString()) || - ((tmp = document.selection) && tmp.createRange().text) || ''; + m.tap = function(){ + var on = $('.meta-on') + .or($($(document.querySelectorAll(':hover')).get().reverse()).first()) + .or($(document.elementFromPoint(meta.tap.x, meta.tap.y))); + return on; } $(window).on('blur', k.wipe).on('focus', k.wipe); - $(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.tap.select); - //.on('keydown', '[contenteditable=true]', function(e){}); + $(document).on('mousedown mousemove mouseup', function(eve){ + m.tap.eve = eve; + m.tap.x = eve.pageX||0; + m.tap.y = eve.pageY||0; + m.tap.on = $(eve.target); + }).on('mousedown touchstart', function(eve){ + var tmp = m.tap.edit; + if(!tmp || !tmp.on){ return } + tmp.on(eve); + m.tap.edit = null; + }); $(document).on('touchstart', '#meta .meta-start', function(eve){ m.tap.stun = true }); $(document).on('click', '#meta .meta-menu li', function(eve){ if(m.tap.stun){ return m.tap.stun = false } @@ -132,10 +132,11 @@ $(function(){ k.down(eve); k.up(eve); }); + $(document).on('keydown', k.down).on('keyup', k.up); meta.edit = function(edit){ var tmp = edit.combow = []; $.each(edit.combo || (edit.combo = []), function(i,k){ - if(!k || !k.length){ return } + if(!k || !k.length){ if('number' == typeof k){ tmp.push(k) } return } tmp.push(k.toUpperCase().charCodeAt(0)); }); var at = meta.edit, l = edit.combo.length; @@ -143,37 +144,7 @@ $(function(){ edit.combow = edit.combow.join(','); m.list(meta.edit); } - meta.text = {zws: '​'}; - meta.text.editor = function(opt, as){ var tmp; - if(!opt){ return } - opt = (typeof opt == 'string')? {edit: opt} : opt.tag? opt : {tag: opt}; - var r = opt.range = opt.range || m.tap.range || monotype(), cmd = opt.edit; - as = opt.as = opt.as || as; - if(cmd && document.execCommand){ - r.restore(); - if(document.execCommand(cmd, null, as||null)){ return } - } - if(!opt.tag){ return } - opt.tag = $(opt.tag); - opt.name = opt.name || opt.tag.prop('tagName'); - if((tmp = $(r.get()).closest(opt.name)).length){ - if(r.s === r.e){ - tmp.after(meta.text.zws); - r = r.select(monotype.next(tmp[0]),1); - } else { - tmp.contents().unwrap(opt.name); - } - } else - if(r.s === r.e){ - r.insert(opt.tag); - r = r.select(opt.tag); - } else { - r.wrap(opt.tag); - } - r.restore(); - opt.range = null; - if(m.tap.range){ m.tap.range = monotype() } - } + $.fn.or = function(s){ return this.length ? this : $(s||'body') }; ;(function(){try{ /* UI */ if(meta.css){ return } @@ -199,10 +170,12 @@ $(function(){ width: '2em', height: '2em', opacity: 0.7, + outline: 'none', color: '#000044', overflow: 'visible', transition: 'all 0.2s ease-in' }, + '#meta *': {outline: 'none'}, '#meta .meta-none': {display: 'none'}, '#meta span': {'line-height': '2em'}, '#meta .meta-menu': { @@ -258,103 +231,153 @@ $(function(){ }); tmp += '}\n'; }); - (node = document.createElement('style')).innerHTML = tmp; - document.body.appendChild(node); + var tag = document.createElement('style'); + tag.innerHTML = tmp; + document.body.appendChild(tag); } }catch(e){}}()); ;(function(){ - // on fires when shortcut keydowns or on touch after command selected and then touchdown - meta.edit({ - name: "Bold", - combo: ['B'], - on: function(e){ - meta.text.editor('bold'); - }, - up: function(){} - }); - meta.edit({ - name: "Italic", - combo: ['I'], - on: function(e){ - meta.text.editor('italic'); - }, - up: function(){} - }); - meta.edit({ - name: "Underline", - combo: ['U'], - on: function(e){ - meta.text.editor('underline'); - }, - up: function(){} - }); - meta.edit({ - name: "linK", - combo: ['K'], - up: function(e){ - var range = meta.tap.range || monotype(); - meta.ask('Paste or type link...', function(url){ - meta.text.editor({tag: $('link'), edit: url? 'createLink' : 'unlink', as: url, range: range}); - }) - }, - on: function(){} - }); - meta.edit({name: "aliGn", combo: ['G']}); - meta.edit({ - name: "Left", - combo: ['G','L'], - on: function(e){ meta.text.editor('justifyLeft') }, - up: function(){} - }); - meta.edit({ - name: "Right", - combo: ['G','R'], - on: function(e){ meta.text.editor('justifyRight') }, - up: function(){ } - }); - meta.edit({ - name: "Middle", - combo: ['G','M'], - on: function(e){ meta.text.editor('justifyCenter') }, - up: function(){ } - }); - meta.edit({ - name: "Justify", - combo: ['G','J'], - on: function(e){ meta.text.editor('justifyFull') }, - up: function(){} - }); - // Align Number - // Align Points - // Align Strike - meta.edit({name: "Size", combo: ['S']}); - meta.edit({ - name: "Small", - combo: ['S','S'], - on: function(e){ meta.text.editor('fontSize', 2) }, - up: function(){ } - }); - meta.edit({ - name: "Normal", - combo: ['S','N'], - on: function(e){ meta.text.editor('fontSize', 5) }, - up: function(){} - }); - meta.edit({ - name: "Header", - combo: ['S','H'], - on: function(e){ meta.text.editor('fontSize', 6) }, - up: function(){} - }); - meta.edit({ - name: "Title", - combo: ['S','T'], - on: function(e){ meta.text.editor('fontSize', 7) }, - up: function(){} - }); - // Size Spacing - // Size Super - // Size Sub - meta.edit({name: "Edit", combo: ['E']}); + // include basic text editing by default. + var monotype = window.monotype || function(){console.log("monotype needed")}; + var m = meta; + m.text = {zws: '​'}; + m.text.on = function(eve){ var tmp; + if($((eve||{}).target).closest('#meta').length){ return } + m.text.range = null; + if(!(m.text.copy()||'').trim()){ + m.flip(false); + m.list(m.text.it); + return; + } + m.text.range = monotype((eve||{}).target); + m.text.it.on(eve); + } + m.text.copy = function(tmp){ + return ((tmp = window.getSelection) && tmp().toString()) || + ((tmp = document.selection) && tmp.createRange().text) || ''; + } + $(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.text.on); + m.text.editor = function(opt, as){ var tmp; + if(!opt){ return } + opt = (typeof opt == 'string')? {edit: opt} : opt.tag? opt : {tag: opt}; + var r = opt.range = opt.range || m.text.range || monotype(), cmd = opt.edit; + as = opt.as = opt.as || as; + if(cmd && document.execCommand){ + r.restore(); + if(document.execCommand(cmd, null, as||null)){ + if(m.text.range){ m.text.range = monotype() } + return; + } + } + if(!opt.tag){ return } + opt.tag = $(opt.tag); + opt.name = opt.name || opt.tag.prop('tagName'); + if((tmp = $(r.get()).closest(opt.name)).length){ + if(r.s === r.e){ + tmp.after(m.text.zws); + r = r.select(monotype.next(tmp[0]),1); + } else { + tmp.contents().unwrap(opt.name); + } + } else + if(r.s === r.e){ + r.insert(opt.tag); + r = r.select(opt.tag); + } else { + r.wrap(opt.tag); + } + r.restore(); + opt.range = null; + if(m.text.range){ m.text.range = monotype() } + } + meta.edit(meta.text.it = {combo: [-1], on: function(){ m.list(this, true) }, back: meta.edit}); // -1 is key for typing. + meta.text.it[-1] = meta.text.it; + meta.edit({ + name: "Bold", + combo: [-1,'B'], fake: -1, + on: function(eve){ + meta.text.editor('bold'); + }, + up: function(){} + }); + meta.edit({ + name: "Italic", + combo: [-1,'I'], fake: -1, + on: function(eve){ + meta.text.editor('italic'); + }, + up: function(){} + }); + /*meta.edit({ + name: "Underline", + combo: [-1,'U'], fake: -1, + on: function(eve){ + meta.text.editor('underline'); + }, + up: function(){} + });*/ + meta.edit({ + name: "linK", + combo: [-1,'K'], fake: -1, + on: function(eve){ + var range = meta.text.range || monotype(); + meta.ask('Paste or type link...', function(url){ + meta.text.editor({tag: $('link'), edit: url? 'createLink' : 'unlink', as: url, range: range}); + }) + } + }); + //meta.edit({name: "aliGn", combo: [-1,'G']}); // MOVE TO ADVANCED MENu! + meta.edit({ + name: "Left", + combo: [-1,'G','L'], fake: -1, + on: function(eve){ meta.text.editor('justifyLeft') }, + up: function(){} + }); + meta.edit({ + name: "Right", + combo: [-1,'G','R'], fake: -1, + on: function(eve){ meta.text.editor('justifyRight') }, + up: function(){ } + }); + meta.edit({ + name: "Middle", + combo: [-1,'G','M'], fake: -1, + on: function(eve){ meta.text.editor('justifyCenter') }, + up: function(){ } + }); + meta.edit({ + name: "Justify", + combo: [-1,'G','J'], fake: -1, + on: function(eve){ meta.text.editor('justifyFull') }, + up: function(){} + }); + // Align Number + // Align Points + // Align Strike + meta.edit({name: "Size", combo: [-1,'S'], on: function(){ m.list(this, true) }}); + meta.edit({ + name: "Small", + combo: [-1,'S','S'], fake: -1, + on: function(eve){ meta.text.editor('fontSize', 2) }, + up: function(){ } + }); + meta.edit({ + name: "Normal", + combo: [-1,'S','N'], fake: -1, + on: function(eve){ meta.text.editor('fontSize', 5) }, + up: function(){} + }); + meta.edit({ + name: "Header", + combo: [-1,'S','H'], fake: -1, + on: function(eve){ meta.text.editor('fontSize', 6) }, + up: function(){} + }); + meta.edit({ + name: "Title", + combo: [-1,'S','T'], fake: -1, + on: function(eve){ meta.text.editor('fontSize', 7) }, + up: function(){} + }); }()); -}); \ No newline at end of file +}()); \ No newline at end of file diff --git a/lib/mix.js b/lib/mix.js new file mode 100644 index 00000000..e37ed46e --- /dev/null +++ b/lib/mix.js @@ -0,0 +1,17 @@ +;(function(){ + var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + Gun.state.node = function(node, vertex, opt){ + opt = opt || {}; + opt.state = opt.state || Gun.state(); + var now = Gun.obj.copy(vertex); + Gun.node.is(node, function(val, key){ + var ham = Gun.HAM(opt.state, Gun.state.is(node, key), Gun.state.is(vertex, key), val, vertex[key]); + if(!ham.incoming){ + // if(ham.defer){} + return; + } + now = Gun.state.to(node, key, now); + }); + return now; + } +}()); \ No newline at end of file diff --git a/lib/multicast.js b/lib/multicast.js new file mode 100644 index 00000000..75ebb9a0 --- /dev/null +++ b/lib/multicast.js @@ -0,0 +1,88 @@ +var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + +Gun.on('create', function(root){ + this.to.next(root); + var opt = root.opt; + if(false === opt.multicast){ return } + if((typeof process !== "undefined") && 'false' === ''+(process.env||{}).MULTICAST){ return } + //if(true !== opt.multicast){ return } // disable multicast by default for now. + + var udp = opt.multicast = opt.multicast || {}; + udp.address = udp.address || '233.255.255.255'; + udp.pack = udp.pack || 50000; // UDP messages limited to 65KB. + udp.port = udp.port || 8765; + + var noop = function(){}, u; + var pid = '2'+Math.random().toString().slice(-8); + var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); + var dgram; + + try{ dgram = require("dgram") }catch(e){ return } + var socket = dgram.createSocket({type: "udp4", reuseAddr: true}); + socket.bind(udp.port); + + socket.on("listening", function(){ + try { socket.addMembership(udp.address) }catch(e){ return } + udp.peer = {id: udp.address + ':' + udp.port, wire: socket}; + + udp.peer.say = function(raw){ + var buf = Buffer.from(raw, 'utf8'); + if(udp.pack <= buf.length){ // message too big!!! + return; + } + socket.send(buf, 0, buf.length, udp.port, udp.address, noop); + } + + console.log('Multicast on', udp.peer.id); + return; // below code only needed for when WebSocket connections desired! + setInterval(function broadcast(){ + port = port || (opt.web && opt.web.address()||{}).port; + if(!port){ return } + udp.peer.say(JSON.stringify({id: opt.pid || (opt.pid = Math.random().toString(36).slice(2)), port: port})); + }, 1000); + }); + + socket.on("message", function(raw, info) { try { + if(!raw){ return } + raw = raw.toString('utf8'); + if('2'===raw[0]){ return check(raw, info) } + opt.mesh.hear(raw, udp.peer); + + return; // below code only needed for when WebSocket connections desired! + var message; + message = JSON.parse(raw.toString('utf8')); + + if(opt.pid === message.id){ return } // ignore self + + 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); + + } catch(e){ + //console.log('multicast error', e, raw); + return; + } }); + + function say(msg){ + this.to.next(msg); + if(!udp.peer){ return } + mesh.say(msg, udp.peer); + } + + function check(id, info){ var tmp; + if(!udp.peer){ return } + if(!id){ + id = check.id = check.id || Buffer.from(pid, 'utf8'); + socket.send(id, 0, id.length, udp.port, udp.address, noop); + return; + } + if((tmp = root.stats) && (tmp = tmp.gap) && info){ (tmp.near || (tmp.near = {}))[info.address] = info.port || 1 } // STATS! + if(check.on || id === pid){ return } + root.on('out', check.on = say); + } + + setInterval(check, 1000 * 1); + +}); diff --git a/lib/normalize.js b/lib/normalize.js index 871ec6cf..bac4214d 100644 --- a/lib/normalize.js +++ b/lib/normalize.js @@ -29,12 +29,13 @@ hierarchy: ['div', 'pre', 'ol', 'ul', 'li', 'h1', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', // block 'b', 'code', 'i', 'span', 's', 'sub', 'sup', 'u', // inline - 'br'] // empty + 'br', 'img'] // empty ,tags: { 'a': {attrs:{'href':1}, exclude:{'a':1}}, 'b': {exclude:{'b':1,'p':1}}, 'br': {empty: 1}, 'i': {exclude:{'i':1,'p':1}}, + 'img': {attrs:{'src':1}, empty: 1}, 'span': {exclude:{'p':1,'ul':1,'ol':1,'li':1,'br':1}}, 's': {space:1}, 'u': {exclude:{'u':1,'p':1},space:1}, @@ -138,12 +139,17 @@ return $(($(e)[0]||{})[d]); } + var xssattr = /[^a-z:]/ig, xssjs = /javascript:/ig; + // url("javascript: // and all permutations + // stylesheets can apparently have XSS? + // create key val attributes object from elements attributes function attrsAsObj(e, filterCb){ var attrObj = {}; (e = $(e)) && e.length && $(e[0].attributes||[]).each(function(value,name){ name = name.nodeName||name.name; value = e.attr(name); + if(value.replace(xssattr,'').match(xssjs)){ e.removeAttr(name); return } value = filterCb? filterCb(value,name,e) : value; if(value !== undefined && value !== false) attrObj[name] = value; diff --git a/lib/open.js b/lib/open.js index 499139fa..a9b5a8a6 100644 --- a/lib/open.js +++ b/lib/open.js @@ -35,15 +35,19 @@ Gun.chain.open = function(cb, opt, at){ } var tmp = this, id; Gun.obj.map(data, function(val, key){ + var doc = at || opt.doc; + if (!doc) { + return; + } if(!(id = Gun.val.link.is(val))){ - (at || opt.doc)[key] = val; + doc[key] = val; return; } if(opt.ids[id]){ - (at || opt.doc)[key] = opt.ids[id]; + doc[key] = opt.ids[id]; return; } - tmp.get(key).open(opt.any, opt, opt.ids[id] = (at || opt.doc)[key] = {}); + tmp.get(key).open(opt.any, opt, opt.ids[id] = doc[key] = {}); }); }) -} \ No newline at end of file +} diff --git a/lib/promise.js b/lib/promise.js new file mode 100644 index 00000000..49cea272 --- /dev/null +++ b/lib/promise.js @@ -0,0 +1,124 @@ +/* Promise Library v1.0 for GUN DB +* Turn any part of a gun chain into a promise, that you can then use +* .then().catch() pattern. +* In normal gun doing var item = gun.get('someKey'), gun returns a reference +* to the someKey synchroneously. Using a reference is quite helpful in making +* graph structures, so I have chosen to follow the following paradigm. +* Whenever a promise is resolved, gun will return an object of data, I will +* wrap that data in an object together with the reference like so: +* {ref: gunRef, data: data}. +* This code is freely given in the spirit of open source MIT license. +* Author: Jachen Duschletta / 2019 +*/ + +/* +* Function promOnce +* @param limit - due to promises resolving too fast if we do not set a timer +* we will not be able receive any data back from gun before returning the promise +* works both following a Chain.get and a Chain.map (limit only applies to map) +* If no limit is chosen, defaults to 100 ms (quite sufficient to fetch about 2000 nodes or more) +* @param opt - option object +* @return {ref: gunReference, data: object / string (data), key: string (soulOfData)} +*/ + +Gun.chain.promOnce = async function (limit, opt) { + var gun = this, cat = gun._; + if(!limit){limit = 100} + if(cat.subs){ + var array = []; + gun.map().once((data, key)=>{ + var gun = this; + array.push(new Promise((res, rej)=>{ + res({ref: gun, data:data, key:key}); + }) + ) + }, opt); + await sleep(limit); + return Promise.all(array) +} else { + return (new Promise((res, rej)=>{ + gun.once(function (data, key) { + var gun = this; + res({ref:gun,data:data,key:key}); + }, opt); + })) + } + var chain = gun.chain(); + return chain; +} + +function sleep (limit) { + return (new Promise((res, rej)=>{ + setTimeout(res, limit); + })) +} + +/* +* Function promPut +* @param item (string / object) - item to be put to that key in the chain +* @param opt - option object +* @return object - Returns an object with the ref to that node that was just +* created as well as the 'ack' which acknowledges the put was succesful +* object {ref: gunReference, ack: acknowledgmentObject} +* If put had an error we can catch the return via .catch +*/ + +Gun.chain.promPut = async function (item, opt) { + var gun = this; + return (new Promise((res, rej)=>{ + gun.put(item, function(ack) { + if(ack.err){rej(ack.err)} + res({ref:gun, ack:ack}); + }, opt); + })) +} + +/* +* Function promSet +* @param item (string / object) - item to be set into a list at this key +* @param opt - option object +* @return object - Returns object with the ref to that node that was just +* created as well as the 'ack' which acknowledges the set was succesful +* object {ref: gunReference, ack: acknowledgmentObject} +* If set had an error we can catch the return via .catch +*/ + +Gun.chain.promSet = async function(item, opt){ + var gun = this, soul; + var cb = cb || function(){}; + opt = opt || {}; opt.item = opt.item || item; + return (new Promise(async function (res,rej) { + if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } + if(!Gun.is(item)){ + if(Gun.obj.is(item)){; + item = await gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).promPut(item); + item = item.ref; + } + res(gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).promPut(item)); + } + item.get(function(soul, o, msg){ + var ack = {}; + if(!soul){ rej({ack:{err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}} ) } + gun.put(Gun.obj.put({}, soul, Gun.val.link.ify(soul)), cb, opt); + },true); + res({ref:item, ack:{ok:0}}); + })) +} + +/* +* Function promOn +* @param callback (function) - function to be called upon changes to data +* @param option (object) - {change: true} only allow changes to trigger the callback +* @return - data and key +* subscribes callback to data +*/ + +Gun.chain.promOn = async function (callback, option) { + var gun = this; + return (new Promise((res, rej)=>{ + gun.on(function (data, key){ + callback(data, key); + res(data, key); + }, option); + })); +} diff --git a/lib/radisk.js b/lib/radisk.js index 9906e249..5f6a5f09 100644 --- a/lib/radisk.js +++ b/lib/radisk.js @@ -5,15 +5,21 @@ opt = opt || {}; opt.log = opt.log || console.log; opt.file = String(opt.file || 'radata'); + var has = (Radisk.has || (Radisk.has = {}))[opt.file]; + if(has){ return has } + opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. - opt.until = opt.until || opt.wait || 9; - opt.batch = opt.batch || 10 * 1000; + opt.until = opt.until || opt.wait || 250; + opt.batch = opt.batch || (10 * 1000); opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB opt.code = opt.code || {}; opt.code.from = opt.code.from || '!'; + //opt.jsonify = true; if(opt.jsonify){ console.log("JSON RAD!!!") } // TODO: REMOVE!!!! function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') } + function atomic(v){ return u !== v && (!v || 'object' != typeof v) } var map = Gun.obj.map; + var LOG = false;//true; if(!opt.store){ return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!"); @@ -36,25 +42,29 @@ var r = function(key, val, cb){ key = ''+key; if(val instanceof Function){ + var o = cb || {}; cb = val; val = r.batch(key); if(u !== val){ + cb(u, r.range(val, o), o); + if(atomic(val)){ return } // if a node is requested and some of it is cached... the other parts might not be. - return cb(u, val); } if(r.thrash.at){ val = r.thrash.at(key); if(u !== val){ + cb(u, r.range(val, o), o); + if(atomic(val)){ cb(u, val, o); return } // if a node is requested and some of it is cached... the other parts might not be. - return cb(u, val); } } - return r.read(key, cb); + return r.read(key, cb, o); } r.batch(key, val); if(cb){ r.batch.acks.push(cb) } if(++r.batch.ed >= opt.batch){ return r.thrash() } // (2) - clearTimeout(r.batch.to); // (1) + if(r.batch.to){ return } + //clearTimeout(r.batch.to); // (1) // THIS LINE IS EVIL! NEVER USE IT! ALSO NEVER DELETE THIS SO WE NEVER MAKE THE SAME MISTAKE AGAIN! r.batch.to = setTimeout(r.thrash, opt.until || 1); } @@ -73,10 +83,13 @@ r.batch = Radix(); r.batch.acks = []; r.batch.ed = 0; + //console.debug(99); var ID = Gun.text.random(2), S = (+new Date); console.log("[[[[[[[[", ID, batch.acks.length); r.save(batch, function(err, ok){ - if(++i > 1){ return } + if(++i > 1){ opt.log('RAD ERR: Radisk has callbacked multiple times, please report this as a BUG at github.com/amark/gun/issues ! ' + i); return } if(err){ opt.log('err', err) } + //console.debug(99); var TMP; console.log("]]]]]]]]", ID, batch.acks.length, (TMP = +new Date) - S, 'more?', thrash.more); map(batch.acks, function(cb){ cb(err, ok) }); + //console.log("][", +new Date - TMP, thrash.more); thrash.at = null; thrash.ing = false; if(thrash.more){ thrash() } @@ -90,7 +103,7 @@ 4. Read the previous file to that into memory 5. Scan through the in memory radix for all values lexically less than the limit. 6. Merge and write all of those to the in-memory file and back to disk. - 7. If file to large, split. More details needed here. + 7. If file too large, split. More details needed here. */ r.save = function(rad, cb){ var s = function Span(){}; @@ -136,28 +149,32 @@ Therefore it is unavoidable that a read will have to happen, the question is just how long you delay it. */ - r.write = function(file, rad, cb, force){ + r.write = function(file, rad, cb, o){ + o = ('object' == typeof o)? o : {force: o}; var f = function Fractal(){}; f.text = ''; f.count = 0; f.file = file; f.each = function(val, key, k, pre){ + //console.log("RAD:::", JSON.stringify([val, key, k, pre])); if(u !== val){ f.count++ } if(opt.pack <= (val||'').length){ return cb("Record too big!"), true } var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n'; - if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !force){ + if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){ f.text = ''; f.limit = Math.ceil(f.count/2); f.count = 0; f.sub = Radix(); - Radix.map(rad, f.slice) + Radix.map(rad, f.slice); return true; } f.text += enc; } f.write = function(){ var tmp = ename(file); + var start; LOG && (start = +new Date); // comment this out! opt.store.put(tmp, f.text, function(err){ + LOG && console.log("wrote to disk in", (+new Date) - start, tmp); // comment this out! if(err){ return cb(err) } r.list.add(tmp, cb); }); @@ -168,7 +185,7 @@ var name = f.file; f.file = key; f.count = 0; - r.write(name, f.sub, f.next, force); + r.write(name, f.sub, f.next, o); return true; } f.sub(key, val); @@ -177,54 +194,98 @@ if(err){ return cb(err) } f.sub = Radix(); if(!Radix.map(rad, f.slice)){ - r.write(f.file, f.sub, cb, force); + r.write(f.file, f.sub, cb, o); } } + if(opt.jsonify){ return r.write.jsonify(f, file, rad, cb, o) } // temporary testing idea if(!Radix.map(rad, f.each, true)){ f.write() } } + r.write.jsonify = function(f, file, rad, cb, o){ + var raw; + var start; LOG && (start = +new Date); // comment this out! + try{raw = JSON.stringify(rad.$); + }catch(e){ return cb("Record too big!") } + LOG && console.log("stringified JSON in", +new Date - start); // comment this out! + if(opt.chunk < raw.length && !o.force){ + if(Radix.map(rad, f.each, true)){ return } + } + f.text = raw; + f.write(); + } + + r.range = function(tree, o){ + if(!tree || !o){ return } + if(u === o.start && u === o.end){ return tree } + if(atomic(tree)){ return tree } + var sub = Radix(); + Radix.map(tree, function(v,k){ + sub(k,v); + }, o); + return sub(''); + } + ;(function(){ var Q = {}; - r.read = function(key, cb, next){ - if(RAD && !next){ // cache + r.read = function(key, cb, o){ + o = o || {}; + if(RAD && !o.next){ // cache var val = RAD(key); - if(u !== val){ + //if(u !== val){ + //cb(u, val, o); + if(atomic(val)){ cb(u, val, o); return } // if a node is requested and some of it is cached... the other parts might not be. - return cb(u, val); - } + //} } - var g = function Get(){}, tmp; - g.lex = function(file){ + o.span = (u !== o.start) || (u !== o.end); + var g = function Get(){}; + g.lex = function(file){ var tmp; file = (u === file)? u : decodeURIComponent(file); - if(!file || file > (next || key)){ - if(next){ g.file = file } + tmp = o.next || key || (o.reverse? o.end || '\uffff' : o.start || ''); + if(!file || (o.reverse? file < tmp : file > tmp)){ + if(o.next || o.reverse){ g.file = file } if(tmp = Q[g.file]){ - tmp.push({key: key, ack: cb, file: g.file}); + tmp.push({key: key, ack: cb, file: g.file, opt: o}); return true; } - Q[g.file] = [{key: key, ack: cb, file: g.file}]; + Q[g.file] = [{key: key, ack: cb, file: g.file, opt: o}]; + if(!g.file){ + g.it(null, u, {}); + return true; + } r.parse(g.file, g.it); return true; } g.file = file; } - g.it = function(err, disk){ + g.it = function(err, disk, info){ if(g.err = err){ opt.log('err', err) } + g.info = info; if(disk){ RAD = g.disk = disk } disk = Q[g.file]; delete Q[g.file]; map(disk, g.ack); } g.ack = function(as){ if(!as.ack){ return } - var tmp = as.key, rad = g.disk || noop, data = rad(tmp), last = rad.last; - if(data){ as.ack(g.err, data) } - else if(!as.file){ return as.ack(g.err, u) } - if(!last || last === tmp){ return as.ack(g.err, u) } // is this correct? - if(last > tmp && 0 > last.indexOf(tmp)){ return as.ack(g.err, u) } - r.read(tmp, as.ack, as.file); + var tmp = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(tmp), o), last = rad.last || Radix.map(rad, rev, revo); + o.parsed = (o.parsed || 0) + (info.parsed||0); + o.chunks = (o.chunks || 0) + 1; + if(!o.some){ o.some = (u !== data) } + if(u !== data){ as.ack(g.err, data, o) } + else if(!as.file){ !o.some && as.ack(g.err, u, o); return } + if(!o.span){ + if(/*!last || */last === tmp){ !o.some && as.ack(g.err, u, o); return } + if(last && last > tmp && 0 != last.indexOf(tmp)){ !o.some && as.ack(g.err, u, o); return } + } + if(o.some && o.parsed >= o.limit){ return } + o.next = as.file; + r.read(tmp, as.ack, o); } + if(o.reverse){ g.lex.reverse = true } r.list(g.lex); } + function rev(a,b){ return b } + var revo = {reverse: true}; }()); ;(function(){ @@ -236,14 +297,14 @@ Then we can work on the harder problem of being multi-process. */ var Q = {}, s = String.fromCharCode(31); - r.parse = function(file, cb){ var q; + r.parse = function(file, cb, raw){ var q; if(q = Q[file]){ return q.push(cb) } q = Q[file] = [cb]; - var p = function Parse(){}; + var p = function Parse(){}, info = {}; p.disk = Radix(); p.read = function(err, data){ var tmp; + LOG && console.log('read disk in', +new Date - start, ename(file)); // keep this commented out in delete Q[file]; if((p.err = err) || (p.not = !data)){ - //return cb(err, u);//map(q, p.ack); return map(q, p.ack); } if(typeof data !== 'string'){ @@ -251,12 +312,33 @@ if(opt.pack <= data.length){ p.err = "Chunk too big!"; } else { - data = data.toString(); + data = data.toString(); // If it crashes, it crashes here. How!?? We check size first! } }catch(e){ p.err = e } if(p.err){ return map(q, p.ack) } } + info.parsed = data.length; + + LOG && (start = +new Date); // keep this commented out in production! + if(opt.jsonify){ // temporary testing idea + try{ + var json = JSON.parse(data); + p.disk.$ = json; + LOG && console.log('parsed JSON in', +new Date - start); // keep this commented out in production! + map(q, p.ack); + return; + }catch(e){ tmp = e } + if('{' === data[0]){ + p.err = tmp || "JSON error!"; + return map(q, p.ack); + } + } + LOG && (start = +new Date); // keep this commented out in production! var tmp = p.split(data), pre = [], i, k, v; + if(!tmp || 0 !== tmp[1]){ + p.err = "File '"+file+"' does not have root radix! "; + return map(q, p.ack); + } while(tmp){ k = v = u; i = tmp[1]; @@ -274,6 +356,7 @@ if(u !== k && u !== v){ p.disk(pre.join(''), v) } tmp = p.split(tmp[2]); } + LOG && console.log('parsed RAD in', +new Date - start); // keep this commented out in production! //cb(err, p.disk); map(q, p.ack); }; @@ -290,9 +373,11 @@ } p.ack = function(cb){ if(!cb){ return } - if(p.err || p.not){ return cb(p.err, u) } - cb(u, p.disk); + if(p.err || p.not){ return cb(p.err, u, info) } + cb(u, p.disk, info); } + var start; LOG && (start = +new Date); // keep this commented out in production! + if(raw){ return p.read(null, raw) } opt.store.get(ename(file), p.read); } }()); @@ -301,9 +386,10 @@ var dir, q, f = String.fromCharCode(28), ef = ename(f); r.list = function(cb){ if(dir){ + var tmp = {reverse: (cb.reverse)? 1 : 0}; Radix.map(dir, function(val, key){ return cb(key); - }) || cb(); + }, tmp) || cb(); return; } if(q){ return q.push(cb) } q = [cb]; @@ -315,8 +401,11 @@ return cb(u, 1); } dir(file, true); + cb.listed = (cb.listed || 0) + 1; r.write(f, dir, function(err, ok){ if(err){ return cb(err) } + cb.listed = (cb.listed || 0) - 1; + if(cb.listed !== 0){ return } cb(u, 1); }, true); } @@ -345,14 +434,13 @@ r.list.dir = dir = rad; tmp = q; q = null; Gun.list.map(tmp, function(cb){ - Radix.map(dir, function(val, key){ - return cb(key); - }) || cb(); + r.list(cb); }); } }()); var noop = function(){}, RAD, u; + Radisk.has[opt.file] = r; return r; } @@ -421,6 +509,7 @@ } else { var Gun = require('../gun'); var Radix = require('./radix'); + //var Radix = require('./radix2'); Radisk = require('./radisk2'); try{ module.exports = Radisk }catch(e){} } diff --git a/lib/radisk2.js b/lib/radisk2.js new file mode 100644 index 00000000..87fc7be0 --- /dev/null +++ b/lib/radisk2.js @@ -0,0 +1,525 @@ +;(function(){ + console.log("RADISK 2!!!!"); + + function Radisk(opt){ + + opt = opt || {}; + opt.log = opt.log || console.log; + opt.file = String(opt.file || 'radata'); + var has = (Radisk.has || (Radisk.has = {}))[opt.file]; + if(has){ return has } + + opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. + opt.until = opt.until || opt.wait || 250; + opt.batch = opt.batch || (10 * 1000); + opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB + opt.code = opt.code || {}; + opt.code.from = opt.code.from || '!'; + //opt.jsonify = true; // TODO: REMOVE!!!! + + function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') } + function atomic(v){ return u !== v && (!v || 'object' != typeof v) } + var map = Gun.obj.map; + var LOG = false; + + if(!opt.store){ + return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!"); + } + if(!opt.store.put){ + return opt.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!"); + } + if(!opt.store.get){ + return opt.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!"); + } + if(!opt.store.list){ + //opt.log("WARNING: `store.list` interface might be needed!"); + } + + /* + Any and all storage adapters should... + 1. Because writing to disk takes time, we should batch data to disk. This improves performance, and reduces potential disk corruption. + 2. If a batch exceeds a certain number of writes, we should immediately write to disk when physically possible. This caps total performance, but reduces potential loss. + */ + var r = function(key, val, cb){ + key = ''+key; + if(val instanceof Function){ + var o = cb || {}; + cb = val; + val = r.batch(key); + if(u !== val){ + cb(u, r.range(val, o), o); + if(atomic(val)){ return } + // if a node is requested and some of it is cached... the other parts might not be. + } + if(r.thrash.at){ + val = r.thrash.at(key); + if(u !== val){ + cb(u, r.range(val, o), o); + if(atomic(val)){ cb(u, val, o); return } + // if a node is requested and some of it is cached... the other parts might not be. + } + } + return r.read(key, cb, o); + } + r.batch(key, val); + if(cb){ r.batch.acks.push(cb) } + if(++r.batch.ed >= opt.batch){ return r.thrash() } // (2) + if(r.batch.to){ return } + //clearTimeout(r.batch.to); // (1) // THIS LINE IS EVIL! NEVER USE IT! ALSO NEVER DELETE THIS SO WE NEVER MAKE THE SAME MISTAKE AGAIN! + r.batch.to = setTimeout(r.thrash, opt.until || 1); + } + + r.batch = Radix(); + r.batch.acks = []; + r.batch.ed = 0; + + r.thrash = function(){ + var thrash = r.thrash; + if(thrash.ing){ return thrash.more = true } + thrash.more = false; + thrash.ing = true; + var batch = thrash.at = r.batch, i = 0; + clearTimeout(r.batch.to); + r.batch = null; + r.batch = Radix(); + r.batch.acks = []; + r.batch.ed = 0; + //var id = Gun.text.random(2), S = (+new Date); console.log("<<<<<<<<<<<<", id); + r.save(batch, function(err, ok){ + if(++i > 1){ opt.log('RAD ERR: Radisk has callbacked multiple times, please report this as a BUG at github.com/amark/gun/issues ! ' + i); return } + if(err){ opt.log('err', err) } + //console.log(">>>>>>>>>>>>", id, ((+new Date) - S), batch.acks.length); + map(batch.acks, function(cb){ cb(err, ok) }); + thrash.at = null; + thrash.ing = false; + if(thrash.more){ thrash() } + }); + } + + /* + 1. Find the first radix item in memory. + 2. Use that as the starting index in the directory of files. + 3. Find the first file that is lexically larger than it, + 4. Read the previous file to that into memory + 5. Scan through the in memory radix for all values lexically less than the limit. + 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. + */ + 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; + } + 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); + return true; + } + s.file = file; + } + s.mix = function(file, start, end){ + s.start = s.end = s.file = u; + r.parse(file, function(err, disk){ + if(err){ return cb(err) } + disk = disk || Radix(); + Radix.map(rad, function(val, key){ + if(key < start){ return } + if(end && end < key){ return s.start = 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); + }); + } + s.next = function(err, ok){ + if(s.err = err){ return cb(err) } + if(s.start){ return Radix.map(rad, s.find) } + cb(err, ok); + } + Radix.map(rad, s.find); + } + + /* + Any storage engine at some point will have to do a read in order to write. + This is true of even systems that use an append only log, if they support updates. + Therefore it is unavoidable that a read will have to happen, + the question is just how long you delay it. + */ + r.write = function(file, rad, cb, o){ + o = ('object' == typeof o)? o : {force: o}; + var f = function Fractal(){}; + f.text = ''; + f.count = 0; + f.file = file; + f.each = function(val, key, k, pre){ + //console.log("RAD:::", JSON.stringify([val, key, k, pre])); + if(u !== val){ f.count++ } + if(opt.pack <= (val||'').length){ return cb("Record too big!"), true } + var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n'; + if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){ + f.text = ''; + f.limit = Math.ceil(f.count/2); + f.count = 0; + f.sub = Radix(); + Radix.map(rad, f.slice); + return true; + } + f.text += enc; + } + f.write = function(){ + var tmp = ename(file); + var start; LOG && (start = (+new Date)); // comment this out! + opt.store.put(tmp, f.text, function(err){ + LOG && console.log("wrote JSON in", (+new Date) - start); // comment this out! + if(err){ return cb(err) } + r.list.add(tmp, 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); + return true; + } + f.sub(key, val); + } + f.next = function(err){ + if(err){ return cb(err) } + f.sub = Radix(); + if(!Radix.map(rad, f.slice)){ + r.write(f.file, f.sub, cb, o); + } + } + if(opt.jsonify){ return r.write.jsonify(f, file, rad, cb, o) } // temporary testing idea + if(!Radix.map(rad, f.each, true)){ f.write() } + } + + r.write.jsonify = function(f, file, rad, cb, o){ + var raw; + var start; LOG && (start = (+new Date)); // comment this out! + try{raw = JSON.stringify(rad.$); + }catch(e){ return cb("Record too big!") } + LOG && console.log("stringified JSON in", (+new Date) - start); // comment this out! + if(opt.chunk < raw.length && !o.force){ + if(Radix.map(rad, f.each, true)){ return } + } + f.text = raw; + f.write(); + } + + r.range = function(tree, o){ + if(!tree || !o){ return } + if(u === o.start && u === o.end){ return tree } + if(atomic(tree)){ return tree } + var sub = Radix(); + Radix.map(tree, function(v,k){ + sub(k,v); + }, o) + return sub(''); + } + + ;(function(){ + var Q = {}; + r.read = function(key, cb, o){ + o = o || {}; + if(RAD && !o.next){ // cache + var val = RAD(key); + //if(u !== val){ + //cb(u, val, o); + if(atomic(val)){ cb(u, val, o); return } + // if a node is requested and some of it is cached... the other parts might not be. + //} + } + o.span = (u !== o.start) || (u !== o.end); + var g = function Get(){}; + g.lex = function(file){ var tmp; + 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)){ + if(o.next || o.reverse){ g.file = file } + if(tmp = Q[g.file]){ + tmp.push({key: key, ack: cb, file: g.file, opt: o}); + return true; + } + Q[g.file] = [{key: key, ack: cb, file: g.file, opt: o}]; + if(!g.file){ + g.it(null, u, {}); + return true; + } + r.parse(g.file, g.it); + return true; + } + g.file = file; + } + g.it = function(err, disk, info){ + if(g.err = err){ opt.log('err', err) } + g.info = info; + if(disk){ RAD = g.disk = disk } + disk = Q[g.file]; delete Q[g.file]; + map(disk, g.ack); + } + g.ack = function(as){ + if(!as.ack){ return } + var tmp = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(tmp), o), last = rad.last; + o.parsed = (o.parsed || 0) + (info.parsed||0); + o.chunks = (o.chunks || 0) + 1; + if(!o.some){ o.some = (u !== data) } + if(u !== data){ as.ack(g.err, data, o) } + else if(!as.file){ !o.some && as.ack(g.err, u, o); return } + if(!o.span){ + if(/*!last || */last === tmp){ !o.some && as.ack(g.err, u, o); return } + if(last && last > tmp && 0 != last.indexOf(tmp)){ !o.some && as.ack(g.err, u, o); return } + } + if(o.some && o.parsed >= o.limit){ return } + o.next = as.file; + r.read(tmp, as.ack, o); + } + if(o.reverse){ g.lex.reverse = true } + r.list(g.lex); + } + }()); + + ;(function(){ + /* + Let us start by assuming we are the only process that is + changing the directory or bucket. Not because we do not want + to be multi-process/machine, but because we want to experiment + with how much performance and scale we can get out of only one. + Then we can work on the harder problem of being multi-process. + */ + 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 = {}; + p.disk = Radix(); + p.read = function(err, data){ var tmp; + delete Q[file]; + if((p.err = err) || (p.not = !data)){ + return map(q, p.ack); + } + if(typeof data !== 'string'){ + try{ + if(opt.pack <= data.length){ + p.err = "Chunk too big!"; + } else { + data = data.toString(); // If it crashes, it crashes here. How!?? We check size first! + } + }catch(e){ p.err = e } + if(p.err){ return map(q, p.ack) } + } + info.parsed = data.length; + + var start; LOG && (start = (+new Date)); // keep this commented out in production! + if(opt.jsonify){ // temporary testing idea + try{ + var json = JSON.parse(data); + p.disk.$ = json; + LOG && console.log('parsed JSON in', (+new Date) - start); // keep this commented out in production! + map(q, p.ack); + return; + }catch(e){ tmp = e } + if('{' === data[0]){ + p.err = tmp || "JSON error!"; + return map(q, p.ack); + } + } + var start; LOG && (start = (+new Date)); // keep this commented out in production! + var tmp = p.split(data), pre = [], i, k, v, at, ats=[]; + if(!tmp || 0 !== tmp[1]){ + p.err = "File '"+file+"' does not have root radix! "; + return map(q, p.ack); + } + while(tmp){ + k = v = u; + i = tmp[1]; + tmp = p.split(tmp[2])||''; + if('#' == tmp[0]){ + k = tmp[1]; + pre = pre.slice(0,i); + if(i <= pre.length){ + pre.push(k); + } + } + tmp = p.split(tmp[2])||''; + if('\n' == tmp[0]){ + at = ats[i] || p.disk.at; + p.disk(k, u, at); + ats[i] = p.disk.at; + ats[i+1] = p.disk.at[k] || (p.disk.at[k]={}); + continue; + } + if('=' == tmp[0] || ':' == tmp[0]){ v = tmp[1] } + if(u !== k && u !== v){ +// p.disk(pre.join(''), v)// mark's code + at = ats[i];// || p.disk.at; + p.disk(k, v, at); + ats[i] = p.disk.at; + ats[i+1] = p.disk.at[k]; + } + tmp = p.split(tmp[2]); + } + LOG && console.log('parsed JSON in', (+new Date) - start); // keep this commented out in production! + //cb(err, p.disk); + map(q, p.ack); + }; + p.split = function(t){ + if(!t){ return } + var l = [], o = {}, i = -1, a = '', b, c; + i = t.indexOf(s); + if(!t[i]){ return } + a = t.slice(0, i); + l[0] = a; + l[1] = b = Radisk.decode(t.slice(i), o); + l[2] = t.slice(i + o.i); + return l; + } + p.ack = function(cb){ + if(!cb){ return } + if(p.err || p.not){ return cb(p.err, u, info) } + cb(u, p.disk, info); + } + if(raw){ return p.read(null, raw) } + opt.store.get(ename(file), p.read); + } + }()); + + ;(function(){ + var dir, q, f = String.fromCharCode(28), ef = ename(f); + r.list = function(cb){ + if(dir){ + var tmp = {reverse: (cb.reverse)? 1 : 0}; + Radix.map(dir, function(val, key){ + return cb(key); + }, tmp) || cb(); + return; + } + if(q){ return q.push(cb) } q = [cb]; + r.parse(f, r.list.init); + } + r.list.add = function(file, cb){ + var has = dir(file); + if(has || file === ef){ + return cb(u, 1); + } + dir(file, true); + cb.listed = (cb.listed || 0) + 1; + r.write(f, dir, function(err, ok){ + if(err){ return cb(err) } + cb.listed = (cb.listed || 0) - 1; + if(cb.listed !== 0){ return } + cb(u, 1); + }, true); + } + r.list.init = function(err, disk){ + if(err){ + opt.log('list', err); + setTimeout(function(){ r.parse(f, r.list.init) }, 1000); + return; + } + if(disk){ + r.list.drain(disk); + return; + } + if(!opt.store.list){ + r.list.drain(Radix()); + return; + } + // import directory. + opt.store.list(function(file){ + dir = dir || Radix(); + if(!file){ return r.list.drain(dir) } + r.list.add(file, noop); + }); + } + r.list.drain = function(rad, tmp){ + r.list.dir = dir = rad; + tmp = q; q = null; + Gun.list.map(tmp, function(cb){ + r.list(cb); + }); + } + }()); + + var noop = function(){}, RAD, u; + Radisk.has[opt.file] = r; + return r; + } + + + + ;(function(){ + var _ = String.fromCharCode(31), u; + Radisk.encode = function(d, o, s){ s = s || _; + var t = s, tmp; + if(typeof d == 'string'){ + var i = d.indexOf(s); + while(i != -1){ t += s; i = d.indexOf(s, i+1) } + return t + '"' + d + s; + } else + if(d && d['#'] && (tmp = Gun.val.link.is(d))){ + return t + '#' + tmp + t; + } else + if(Gun.num.is(d)){ + return t + '+' + (d||0) + t; + } else + if(null === d){ + return t + ' ' + t; + } else + if(true === d){ + return t + '+' + t; + } else + if(false === d){ + return t + '-' + t; + }// else + //if(binary){} + } + Radisk.decode = function(t, o, s){ s = s || _; + var d = '', i = -1, n = 0, c, p; + if(s !== t[0]){ return } + while(s === t[++i]){ ++n } + p = t[c = n] || true; + while(--n >= 0){ i = t.indexOf(s, i+1) } + if(i == -1){ i = t.length } + d = t.slice(c+1, i); + if(o){ o.i = i+1 } + if('"' === p){ + return d; + } else + if('#' === p){ + return Gun.val.link.ify(d); + } else + if('+' === p){ + if(0 === d.length){ + return true; + } + return parseFloat(d); + } else + if(' ' === p){ + return null; + } else + if('-' === p){ + return false; + } + } + }()); + + if(typeof window !== "undefined"){ + var Gun = window.Gun; + var Radix = window.Radix; + window.Radisk = Radisk; + } else { + var Gun = require('../gun'); + var Radix = require('./radix2'); + try{ module.exports = Radisk }catch(e){} + } + + Radisk.Radix = Radix; + +}()); diff --git a/lib/radix.js b/lib/radix.js index a0b270cb..e4533f32 100644 --- a/lib/radix.js +++ b/lib/radix.js @@ -5,9 +5,10 @@ key = ''+key; if(!t && u !== val){ radix.last = (key < radix.last)? radix.last : key; - radix.sort = null; + delete (radix.$||{})[_]; } - t = t || radix[_] || (radix[_] = {}); + t = t || radix.$ || (radix.$ = {}); + if(!key && Object.keys(t).length){ return t } var i = 0, l = key.length-1, k = key[i], at, tmp; while(!(at = t[k]) && i < l){ k += key[++i]; @@ -15,9 +16,9 @@ if(!at){ if(!map(t, function(r, s){ var ii = 0, kk = ''; - while(s[ii] == key[ii]){ + if((s||'').length){ while(s[ii] == key[ii]){ kk += s[ii++]; - } + } } if(kk){ if(u === val){ if(ii <= l){ return } @@ -25,47 +26,64 @@ } var __ = {}; __[s.slice(ii)] = r; - (__[key.slice(ii)] = {})[$] = val; - (t[kk] = {})[_] = __; + ii = key.slice(ii); + ('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val); + t[kk] = __; delete t[s]; return true; } })){ if(u === val){ return; } - (t[k] || (t[k] = {}))[$] = val; + (t[k] || (t[k] = {}))[''] = val; } if(u === val){ return tmp; } } else if(i == l){ - if(u === val){ return (u === (tmp = at[$]))? at[_] : tmp } - at[$] = val; + if(u === val){ return (u === (tmp = at['']))? at : tmp } + at[''] = val; } else { - if(u !== val){ at.sort = null } - return radix(key.slice(++i), val, at[_] || (at[_] = {})); + if(u !== val){ delete at[_] } + return radix(key.slice(++i), val, at || (at = {})); } } return radix; }; Radix.map = function map(radix, cb, opt, pre){ pre = pre || []; - var t = radix[_] || radix, keys = radix.sort || (radix.sort = Object.keys(t).sort()), i = 0, l = keys.length; - for(;i < l; i++){ var key = keys[i], tree = t[key], tmp; - if(u !== (tmp = tree[$])){ - tmp = cb(tmp, pre.join('') + key, key, pre); + var t = ('function' == typeof radix)? radix.$ || {} : radix; + if(!t){ return } + var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort, rev; + //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 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(rev){ // children must be checked first when going in reverse. + tmp = map(tree, cb, opt, p); if(u !== tmp){ return tmp } - } else - if(opt){ - cb(u, pre.join(''), key, pre); } - if(tmp = tree[_]){ - pre.push(key); + if(u !== (tmp = tree[''])){ + tmp = cb(tmp, pt, key, pre); + if(u !== tmp){ return tmp } + } else + if(opt.branch){ + tmp = cb(u, pt, key, pre); + if(u !== tmp){ return tmp } + } + pre = p; + if(!rev){ tmp = map(tree, cb, opt, pre); - //tmp = map(tmp, cb, opt, pre); if(u !== tmp){ return tmp } - pre.pop(); } + pre.pop(); } }; @@ -80,6 +98,6 @@ } var map = Gun.obj.map, no = {}, u; - var $ = String.fromCharCode(30), _ = String.fromCharCode(29); + var _ = String.fromCharCode(24); }()); \ No newline at end of file diff --git a/lib/radix2.js b/lib/radix2.js new file mode 100644 index 00000000..8fac9b94 --- /dev/null +++ b/lib/radix2.js @@ -0,0 +1,98 @@ +;(function(){ + + function Radix(){ + var radix = function(key, val, t){ + key = ''+key; + if(!t && u !== val){ + radix.last = (key < radix.last)? radix.last : key; + delete (radix.$||{})[_]; + } + t = t || radix.$ || (radix.$ = {}); + if(!key && Object.keys(t).length){ return t } + var i = 0, l = key.length-1, k = key[i], at, tmp; + while(!(at = t[k]) && i < l){ + k += key[++i]; + } + radix.at = t; /// caching to external access. + if(!at){ + if(!map(t, function(r, s){ + var ii = 0, kk = ''; + if((s||'').length){ while(s[ii] == key[ii]){ + kk += s[ii++]; + } } + if(kk){ + if(u === val){ + if(ii <= l){ return } + return (tmp || (tmp = {}))[s.slice(ii)] = r; + } + var __ = {}; + __[s.slice(ii)] = r; + ii = key.slice(ii); + ('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val); + t[kk] = __; + delete t[s]; + return true; + } + })){ + if(u === val){ return; } + (t[k] || (t[k] = {}))[''] = val; + } + if(u === val){ + return tmp; + } + } else + if(i == l){ + if(u === val){ return (u === (tmp = at['']))? at : tmp } + at[''] = val; + } else { + if(u !== val){ delete at[_] } + return radix(key.slice(++i), val, at || (at = {})); + } + } + return radix; + }; + + Radix.map = function map(radix, cb, opt, pre){ pre = pre || []; + var t = ('function' == typeof radix)? radix.$ || {} : radix; + if(!t){ return } + var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort; + //var keys = Object.keys(t).sort(); + opt = (true === opt)? {branch: true} : (opt || {}); + if(opt.reverse){ keys = keys.slice().reverse() } + var start = opt.start, end = opt.end; + 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 !== (tmp = tree[''])){ + tmp = cb(tmp, pt, key, pre); + if(u !== tmp){ return tmp } + } else + if(opt.branch){ + tmp = cb(u, pt, key, pre); + if(u !== tmp){ return tmp } + } + pre = p; + tmp = map(tree, cb, opt, pre); + if(u !== tmp){ return tmp } + pre.pop(); + } + }; + + Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) } + + if(typeof window !== "undefined"){ + var Gun = window.Gun; + window.Radix = Radix; + } else { + var Gun = require('../gun'); + try{ module.exports = Radix }catch(e){} + } + + var map = Gun.obj.map, no = {}, u; + var _ = String.fromCharCode(24); + +}()); diff --git a/lib/rfs.js b/lib/rfs.js index 05595a28..6ed7d131 100644 --- a/lib/rfs.js +++ b/lib/rfs.js @@ -1,10 +1,15 @@ -var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); - function Store(opt){ opt = opt || {}; + opt.log = opt.log || console.log; opt.file = String(opt.file || 'radata'); var fs = require('fs'), u; + var store = function Store(){}; + if(Store[opt.file]){ + console.log("Warning: reusing same fs store and options as 1st."); + return Store[opt.file]; + } + Store[opt.file] = store; store.put = function(file, data, cb){ var random = Math.random().toString(36).slice(-3); @@ -20,18 +25,13 @@ function Store(opt){ if('ENOENT' === (err.code||'').toUpperCase()){ return cb(null); } - Gun.log("ERROR:", err) + opt.log("ERROR:", err) } cb(err, data); }); }; - store.list = function(cb, match){ - fs.readdir(opt.file, function(err, dir){ - Gun.obj.map(dir, cb) || cb(); // Stream interface requires a final call to know when to be done. - }); - }; + if(!fs.existsSync(opt.file)){ fs.mkdirSync(opt.file) } - //store.list(function(){ return true }); function move(oldPath, newPath, cb) { fs.rename(oldPath, newPath, function (err) { @@ -60,29 +60,4 @@ function Store(opt){ return store; } -function Mem(opt){ - opt = opt || {}; - opt.file = String(opt.file || 'radata'); - var storage = Mem.storage || (Mem.storage = {}); - var store = function Store(){}, u; - store.put = function(file, data, cb){ - setTimeout(function(){ - storage[file] = data; - cb(null, 1); - }, 1); - }; - store.get = function(file, cb){ - setTimeout(function(){ - var tmp = storage[file] || u; - cb(null, tmp); - }, 1); - }; - store.list = function(cb, match){ // supporting this is no longer needed! Optional. - setTimeout(function(){ - Gun.obj.map(Object.keys(storage), cb) || cb(); - }, 1); - }; - return store; -} - -module.exports = Store;//Gun.TESTING? Mem : Store; +module.exports = Store; \ No newline at end of file diff --git a/lib/rindexed.js b/lib/rindexed.js index 532e4e30..281120ba 100644 --- a/lib/rindexed.js +++ b/lib/rindexed.js @@ -1,129 +1,69 @@ ;(function(){ - var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); - - Gun.on('create', function(root){ - this.to.next(root); - root.opt.store = root.opt.store || Store(root.opt); - }); function Store(opt){ opt = opt || {}; opt.file = String(opt.file || 'radata'); - var db = null; + opt.chunk = opt.chunk || (1024 * 1024); // 1MB + var db = null, u; - opt.indexedDB = opt.indexedDB || window.indexedDB; - // Initialize indexedDB. Version 1. - var request = opt.indexedDB.open(opt.file, 1) + try{opt.indexedDB = opt.indexedDB || indexedDB}catch(e){} + try{if(!opt.indexedDB || 'file:' == location.protocol){ + var store = {}, s = {}; + store.put = function(f, d, cb){ s[f] = d; cb(null, 1) }; + store.get = function(f, cb){ cb(null, s[f] || u) }; + console.log('Warning: No indexedDB exists to persist data to!'); + return store; + }}catch(e){} + + var store = function Store(){}; + if(Store[opt.file]){ + console.log("Warning: reusing same IndexedDB store and options as 1st."); + return Store[opt.file]; + } + Store[opt.file] = store; - // Create schema. onupgradeneeded is called only when DB is first created or when the DB version increases. - request.onupgradeneeded = function(event){ - var db = event.target.result; - db.createObjectStore(opt.file); + store.start = function(){ + var o = indexedDB.open(opt.file, 1); + o.onupgradeneeded = function(eve){ (eve.target.result).createObjectStore(opt.file) } + o.onsuccess = function(){ db = o.result } + o.onerror = function(eve){ console.log(eve||1); } + }; store.start(); + + store.put = function(key, data, cb){ + if(!db){ setTimeout(function(){ store.put(key, data, cb) },1); return } + var tx = db.transaction([opt.file], 'readwrite'); + var obj = tx.objectStore(opt.file); + var req = obj.put(data, ''+key); + req.onsuccess = obj.onsuccess = tx.onsuccess = function(){ cb(null, 1) } + req.onabort = obj.onabort = tx.onabort = function(eve){ cb(eve||'put.tx.abort') } + req.onerror = obj.onerror = tx.onerror = function(eve){ cb(eve||'put.tx.error') } } - // onsuccess is called when the DB is ready. - request.onsuccess = function(){ - db = request.result; + store.get = function(key, cb){ + if(!db){ setTimeout(function(){ store.get(key, cb) },9); return } + var tx = db.transaction([opt.file], 'readonly'); + var obj = tx.objectStore(opt.file); + var req = obj.get(''+key); + req.onsuccess = function(){ cb(null, req.result) } + req.onabort = function(eve){ cb(eve||4) } + req.onerror = function(eve){ cb(eve||5) } } - - request.onerror = function(event){ - console.log('ERROR: RAD IndexedDB generic error:', event); - }; - - var store = function Store(){}, u; - - store.put = function(file, data, cb){ - cb = cb || function(){}; - var doPut = function(){ - // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action. - var transaction = db.transaction([opt.file], 'readwrite'); - - // Add or update data. - var radStore = transaction.objectStore(opt.file); - var putRequest = radStore.put(data, file); - putRequest.onsuccess = radStore.onsuccess = transaction.onsuccess = function(){ - //console.log('RAD IndexedDB put transaction was succesful.'); - cb(null, 1); - }; - putRequest.onabort = radStore.onabort = transaction.onabort = function(){ - var es = 'ERROR: RAD IndexedDB put transaction was aborted.'; - console.log(es); - cb(es, undefined); - }; - putRequest.onerror = radStore.onerror = transaction.onerror = function(event){ - var es = 'ERROR: RAD IndexedDB put transaction was in error: ' + JSON.stringify(event) - console.log(es); - cb(es, undefined); - }; - } - if(!db){ - waitDbReady(doPut, 100, function(){ - var es = 'ERROR: Timeout: RAD IndexedDB not ready.'; - console.log(es); - cb(es, undefined); - }, 10) - } else { - doPut(); - } - }; - - store.get = function(file, cb){ - cb = cb || function(){}; - var doGet = function(){ - // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action. - var transaction = db.transaction([opt.file], 'readwrite'); - - // Read data. - var radStore = transaction.objectStore(opt.file); - var getRequest = radStore.get(file); - getRequest.onsuccess = function(){ - //console.log('RAD IndexedDB get transaction was succesful.'); - cb(null, getRequest.result); - }; - getRequest.onabort = function(){ - var es = 'ERROR: RAD IndexedDB get transaction was aborted.'; - console.log(es); - cb(es, undefined); - }; - getRequest.onerror = function(event){ - var es = 'ERROR: RAD IndexedDB get transaction was in error: ' + JSON.stringify(event) - console.log(es); - cb(es, undefined); - }; - } - if(!db){ - waitDbReady(doGet, 100, function(){ - var es = 'ERROR: Timeout: RAD IndexedDB not ready.'; - console.log(es); - cb(es, undefined); - }, 10) - } else { - doGet(); - } - }; - - var waitDbReady = function(readyFunc, checkInterval, timeoutFunc, timeoutSecs){ - var startTime = new Date(); - var checkFunc = function(){ - if(db){ - readyFunc(); - } else { - if((new Date() - startTime) / 1000 >= timeoutSecs){ - timeoutFunc(); - } else { - setTimeout(checkFunc, checkInterval); - } - } - }; - checkFunc(); - }; - + setInterval(function(){ db && db.close(); db = null; store.start() }, 1000 * 15); // reset webkit bug? return store; } - if(Gun.window){ - Gun.window.RindexedDB = Store; + if(typeof window !== "undefined"){ + (Store.window = window).RindexedDB = Store; } else { - module.exports = Store; + try{ module.exports = Store }catch(e){} } -}()); + + try{ + var Gun = Store.window.Gun || require('../gun'); + Gun.on('create', function(root){ + this.to.next(root); + root.opt.store = root.opt.store || Store(root.opt); + }); + }catch(e){} + +}()); \ No newline at end of file diff --git a/lib/rls.js b/lib/rls.js new file mode 100644 index 00000000..ad32f29d --- /dev/null +++ b/lib/rls.js @@ -0,0 +1,29 @@ +;(function(){ + + function Store(opt){ + opt = opt || {}; + opt.file = String(opt.file || 'radata'); + var store = function Store(){}; + + var ls = localStorage; + store.put = function(key, data, cb){ ls[''+key] = data; cb(null, 1) } + store.get = function(key, cb){ cb(null, ls[''+key]) } + + return store; + } + + if(typeof window !== "undefined"){ + (Store.window = window).RlocalStorage = Store; + } else { + try{ module.exports = Store }catch(e){} + } + + try{ + var Gun = Store.window.Gun || require('../gun'); + Gun.on('create', function(root){ + this.to.next(root); + root.opt.store = root.opt.store || Store(root.opt); + }); + }catch(e){} + +}()); \ No newline at end of file diff --git a/lib/rmem.js b/lib/rmem.js new file mode 100644 index 00000000..f2361d22 --- /dev/null +++ b/lib/rmem.js @@ -0,0 +1,22 @@ +function Rmem(){ + var opt = {}, store = {}, u; + opt.put = function(file, data, cb){ + //setTimeout(function(){ // make async + store[file] = data; + cb(null, 1); + //}, 1); + }; + opt.get = function(file, cb){ + //setTimeout(function(){ // make async + var tmp = store[file] || u; + cb(null, tmp); + //}, 1); + }; + return opt; +} + +if(typeof window !== "undefined"){ + window.Rmem = Rmem; +} else { + try{ module.exports = Rmem }catch(e){} +} \ No newline at end of file diff --git a/lib/rs3.js b/lib/rs3.js index 9f81e43c..339582a0 100644 --- a/lib/rs3.js +++ b/lib/rs3.js @@ -7,9 +7,9 @@ var u, AWS; Gun.on('create', function(root){ this.to.next(root); var opt = root.opt; - if(!process.env.AWS_S3_BUCKET){ return } - opt.batch = opt.batch || (1000 * 1); - opt.until = opt.until || (1000 * 15); + if(!opt.s3 && !process.env.AWS_S3_BUCKET){ return } + opt.batch = opt.batch || (1000 * 10); + opt.until = opt.until || (1000 * 3); opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB try{AWS = require('aws-sdk'); @@ -41,8 +41,14 @@ function Store(opt){ opt.file = String(opt.file || 'radata'); var opts = opt.s3, s3 = opts.s3; var c = {p: {}, g: {}, l: {}}; - + var store = function Store(){}; + if(Store[opt.file]){ + console.log("Warning: reusing same S3 store and options as 1st."); + return Store[opt.file]; + } + Store[opt.file] = store; + store.put = function(file, data, cb){ var params = {Bucket: opts.bucket, Key: file, Body: data}; //console.log("RS3 PUT ---->", (data||"").slice(0,20)); @@ -95,4 +101,4 @@ function Store(opt){ return store; } -module.exports = Store; \ No newline at end of file +module.exports = Store; diff --git a/lib/serve.js b/lib/serve.js index 5e1c76cf..b315cf88 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -1,22 +1,26 @@ var fs = require('fs'); var path = require('path'); +var dot = /\.\.+/g; +var slash = /\/\/+/g; function CDN(dir){ return function(req, res){ + req.url = (req.url||'').replace(dot,'').replace(slash,'/'); if(serve(req, res)){ return } // filters GUN requests! fs.createReadStream(path.join(dir, req.url)).on('error',function(tmp){ // static files! try{ tmp = fs.readFileSync(path.join(dir, 'index.html')) }catch(e){} - res.writeHead(200, {'Content-Type': 'text/html'}); - res.end(tmp+''); // or default to index + try{ res.writeHead(200, {'Content-Type': 'text/html'}); + res.end(tmp+''); }catch(e){} // or default to index }).pipe(res); // stream } } -function serve(req, res, next){ +function serve(req, res, next){ var tmp; if(typeof req === 'string'){ return CDN(req) } if(!req || !res){ return false } next = next || serve; if(!req.url){ return next() } + if(res.setHeader){ res.setHeader('Access-Control-Allow-Origin', '*') } if(0 <= req.url.indexOf('gun.js')){ res.writeHead(200, {'Content-Type': 'text/javascript'}); res.end(serve.js = serve.js || require('fs').readFileSync(__dirname + '/../gun.js')); @@ -31,6 +35,11 @@ function serve(req, res, next){ return true; } } + if((tmp = req.socket) && (tmp = tmp.server) && (tmp = tmp.route)){ var url; + if(tmp = tmp[(((req.url||'').slice(1)).split('/')[0]||'').split('.')[0]]){ + try{ return tmp(req, res, next) }catch(e){ console.log(req.url+' crashed with '+e) } + } + } return next(); } diff --git a/lib/server.js b/lib/server.js index 7870006c..3ccc7b34 100644 --- a/lib/server.js +++ b/lib/server.js @@ -13,9 +13,11 @@ require('./rs3'); require('./wire'); try{require('../sea');}catch(e){} - //try{require('../axe');}catch(e){} + try{require('../axe');}catch(e){} require('./file'); require('./evict'); + require('./multicast'); + require('./stats'); if('debug' === process.env.GUN_ENV){ require('./debug') } module.exports = Gun; -}()); \ No newline at end of file +}()); diff --git a/lib/stats.js b/lib/stats.js new file mode 100644 index 00000000..2e7a943a --- /dev/null +++ b/lib/stats.js @@ -0,0 +1,58 @@ +var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); + +Gun.on('opt', function(root){ + this.to.next(root); + if(root.once){ return } + if(typeof process === 'undefined'){ return } + if(typeof require === 'undefined'){ return } + var noop = function(){}; + var os = require('os') || {}; + var fs = require('fs') || {}; + fs.existsSync = fs.existsSync || require('path').existsSync; + if(!fs.existsSync){ return } + if(!process){ return } + process.uptime = process.uptime || noop; + process.cpuUsage = process.cpuUsage || noop; + process.memoryUsage = process.memoryUsage || noop; + os.totalmem = os.totalmem || noop; + os.freemem = os.freemem || noop; + os.loadavg = os.loadavg || noop; + os.cpus = os.cpus || noop; + setTimeout(function(){ + root.stats = Gun.obj.ify((fs.existsSync(__dirname+'/../stats.'+root.opt.file) && fs.readFileSync(__dirname+'/../stats.'+root.opt.file).toString())) || {}; + root.stats.up = root.stats.up || {}; + root.stats.up.start = root.stats.up.start || +(new Date); + root.stats.up.count = (root.stats.up.count || 0) + 1; + root.stats.stay = root.stats.stay || {}; + root.stats.gap = {}; + },1); + setInterval(function(){ + if(!root.stats){ root.stats = {} } + var stats = root.stats, tmp; + (stats.up||{}).time = process.uptime(); + stats.memory = process.memoryUsage() || {}; + stats.memory.totalmem = os.totalmem(); + stats.memory.freemem = os.freemem(); + stats.cpu = process.cpuUsage() || {}; + stats.cpu.loadavg = os.loadavg(); + stats.peers = {}; + stats.peers.count = Object.keys(root.opt.peers||{}).length; + stats.node = {}; + stats.node.count = Object.keys(root.graph||{}).length; + var dam = root.opt.mesh; + if(dam){ + stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d}, 'out': {count: dam.say.c, done: dam.say.d}}; + dam.hear.c = dam.hear.d = dam.say.c = dam.say.d = 0; // reset + stats.peers.time = dam.bye.time || 0; + } + var rad = root.opt.store; rad = rad && rad.stats; + if(rad){ + stats.rad = rad; + root.opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // reset + } + + fs.writeFile(__dirname+'/../stats.'+root.opt.file, JSON.stringify(stats, null, 2), function(err){}); + stats.gap = {}; + }, 1000 * 15); + Object.keys = Object.keys || function(o){ return Gun.obj.map(o, function(v,k,t){t(k)}) } +}); diff --git a/lib/store.js b/lib/store.js index 10230707..56c4dcda 100644 --- a/lib/store.js +++ b/lib/store.js @@ -1,58 +1,103 @@ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); - + Gun.on('create', function(root){ - this.to.next(root); - var opt = root.opt, u; - if(false === opt.radisk){ return } - var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk'); - var Radix = Radisk.Radix; - - opt.store = opt.store || (!Gun.window && require('./rfs')(opt)); - var rad = Radisk(opt), esc = String.fromCharCode(27); - - root.on('put', function(msg){ - this.to.next(msg); - var id = msg['#'], track = !msg['@'], acks = track? 0 : u; // only ack non-acks. - if(msg.rad && !track){ return } // don't save our own acks - Gun.graph.is(msg.put, null, function(val, key, node, soul){ - if(track){ ++acks } - val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc); - rad(soul+'.'+key, val, (track? ack : u)); - }); - function ack(err, ok){ - acks--; - if(ack.err){ return } - if(ack.err = err){ - root.on('in', {'@': id, err: err}); - return; - } - if(acks){ return } - root.on('in', {'@': id, ok: 1}); - } - }); - - root.on('get', function(msg){ - this.to.next(msg); - var id = msg['#'], soul = msg.get['#'], key = msg.get['.']||'', tmp = soul+'.'+key, node; - rad(tmp, function(err, val){ - if(val){ - if(val && typeof val !== 'string'){ - if(key){ - val = u; - } else { - Radix.map(val, each) - } - } - if(!node && val){ each(val, key) } - } - root.on('in', {'@': id, put: Gun.graph.node(node), err: err? err : u, rad: Radix}); - }); - function each(val, key){ - tmp = val.lastIndexOf('>'); - var state = Radisk.decode(val.slice(tmp+1), null, esc); - val = Radisk.decode(val.slice(0,tmp), null, esc); - node = Gun.state.ify(node, key, state, val, soul); - } - }); - + if(Gun.TESTING){ root.opt.file = 'radatatest' } + this.to.next(root); + var opt = root.opt, u; + if(false === opt.radisk){ return } + var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk'); + var Radix = Radisk.Radix; + + opt.store = opt.store || (!Gun.window && require('./rfs')(opt)); + var rad = Radisk(opt), esc = String.fromCharCode(27); + + root.on('put', function(msg){ + this.to.next(msg); + var id = msg['#'] || Gun.text.random(3), track = !msg['@'], acks = track? 0 : u; // only ack non-acks. + if(msg.rad && !track){ return } // don't save our own acks + var start = (+new Date); // STATS! + Gun.graph.is(msg.put, null, function(val, key, node, soul){ + if(track){ ++acks } + //console.log('put:', soul, key, val); + val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc); + rad(soul+esc+key, val, (track? ack : u)); + }); + function ack(err, ok){ + acks--; + if(ack.err){ return } + if(ack.err = err){ + try{opt.store.stats.put.err = err}catch(e){} // STATS! + root.on('in', {'@': id, err: err}); + return; + } + if(acks){ return } + try{opt.store.stats.put.time[statp % 50] = (+new Date) - start; ++statp; + opt.store.stats.put.count++; + }catch(e){} // STATS! + //console.log("PAT!", id); + root.on('in', {'@': id, ok: 1}); + } + }); + + root.on('get', function(msg){ + this.to.next(msg); + var id = msg['#'], get = msg.get, soul = msg.get['#'], has = msg.get['.']||'', o = {}, graph, lex, key, tmp, force; + if('string' == typeof soul){ + key = soul; + } else + if(soul){ + if(u !== (tmp = soul['*'])){ o.limit = force = 1 } + if(u !== soul['>']){ o.start = soul['>'] } + if(u !== soul['<']){ o.end = soul['<'] } + key = force? (''+tmp) : tmp || soul['=']; + force = null; + } + if(key && !o.limit){ // a soul.has must be on a soul, and not during soul* + if('string' == typeof has){ + key = key+esc+(o.atom = has); + } else + if(has){ + if(u !== has['>']){ o.start = has['>']; o.limit = 1 } + if(u !== has['<']){ o.end = has['<']; o.limit = 1 } + if(u !== (tmp = has['*'])){ o.limit = force = 1 } + if(key){ key = key+esc + (force? (''+(tmp||'')) : tmp || (o.atom = has['='] || '')) } + } + } + if((tmp = get['%']) || o.limit){ + o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1; + } + if(has['-'] || (soul||{})['-']){ o.reverse = true } + var start = (+new Date); // STATS! // console.log("GET!", id, JSON.stringify(key)); + rad(key||'', function(err, data, o){ + try{opt.store.stats.get.time[statg % 50] = (+new Date) - start; ++statg; + opt.store.stats.get.count++; + if(err){ opt.store.stats.get.err = err } + }catch(e){} // STATS! + if(data){ + if(typeof data !== 'string'){ + if(o.atom){ + data = u; + } else { + Radix.map(data, each) + } + } + if(!graph && data){ each(data, '') } + } + root.on('in', {'@': id, put: graph, err: err? err : u, rad: Radix}); + }, o); + function each(val, has, a,b){ + if(!val){ return } + has = (key+has).split(esc); + var soul = has.slice(0,1)[0]; + has = has.slice(-1)[0]; + o.count = (o.count || 0) + val.length; + tmp = val.lastIndexOf('>'); + var state = Radisk.decode(val.slice(tmp+1), null, esc); + val = Radisk.decode(val.slice(0,tmp), null, esc); + (graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul); + if(o.limit && o.limit <= o.count){ return true } + } + }); + opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS! + var statg = 0, statp = 0; // STATS! }); \ No newline at end of file diff --git a/lib/super.js b/lib/super.js index 3f94ff3d..1724f40c 100644 --- a/lib/super.js +++ b/lib/super.js @@ -1,28 +1,36 @@ ;(function(){ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); var Rad = (Gun.window||{}).Radix || require('./radix'); + /// Store the subscribes + Gun.subs = Rad(); function input(msg){ - var at = this.as, to = this.to, peer = (msg.mesh||empty).via; + var at = this.as, to = this.to, peer = (msg._||empty).via; var get = msg.get, soul, key; if(!peer || !get){ return to.next(msg) } - console.log("super", msg); + // console.log("super", msg); if(soul = get['#']){ if(key = get['.']){ } else { } - subscribe(soul, peer, msg); + if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);} + var subs = Gun.subs(soul) || null; + var tmp = subs ? subs.split(',') : [], p = at.opt.peers; + if (subs) { + Gun.obj.map(subs.split(','), function(peerid) { + if (peerid in p) { tmp.push(peerid); } + }); + } + if (tmp.indexOf(peer.id) === -1) { tmp.push(peer.id);} + tmp = tmp.join(','); + Gun.subs(soul, tmp); + var dht = {}; + dht[soul] = tmp; + at.opt.mesh.say({dht:dht}, peer); } to.next(msg); } - /// Store the subscribes - Gun.subscribe = {}; /// TODO: use Rad instead of plain object? - function subscribe(soul, peer, msg) { - if (!peer.id) { console.log('super jump peer without id: ', peer, msg); return; } /// TODO: this occurs in first subscription. Use peer reference or peer.wire.id? - Gun.subscribe[soul] = Gun.subscribe[soul] || {}; - Gun.subscribe[soul][peer.id] = 1; - } var empty = {}, u; if(Gun.window){ return } try{module.exports = input}catch(e){} diff --git a/lib/then.js b/lib/then.js index b0034094..731454a8 100644 --- a/lib/then.js +++ b/lib/then.js @@ -1,16 +1,21 @@ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); +// Returns a gun reference in a promise and then calls a callback if specified Gun.chain.promise = function(cb) { var gun = this, cb = cb || function(ctx) { return ctx }; return (new Promise(function(res, rej) { gun.once(function(data, key){ - res({put: data, get: key, gun: this}); + res({put: data, get: key, gun: this}); // gun reference is returned by promise }); - })).then(cb); + })).then(cb); //calling callback with resolved data }; -Gun.chain.then = function(cb) { - return this.promise(function(res){ - return cb? cb(res.put) : res.put; - }); -}; \ No newline at end of file +// Returns a promise for the data, key of the gun call +Gun.chain.then = function() { + var gun = this; + return (new Promise((res, rej)=>{ + gun.once(function (data, key) { + res(data, key); //call resolve when data is returned + }) + })) +}; diff --git a/lib/unbuild.js b/lib/unbuild.js index ef51e1bb..0e4a8e20 100644 --- a/lib/unbuild.js +++ b/lib/unbuild.js @@ -11,19 +11,7 @@ var write = function(path, data){ return fs.writeFileSync(nodePath.join(dir, path), data); } -var rm = function(path, full) { - path = full || nodePath.join(dir, path); - if(!fs.existsSync(path)){ return } - fs.readdirSync(path).forEach(function(file,index){ - var curPath = path + "/" + file; - if(fs.lstatSync(curPath).isDirectory()) { // recurse - rm(null, curPath); - } else { // delete file - fs.unlinkSync(curPath); - } - }); - fs.rmdirSync(path); -}; +var rm = require('./fsrm'); var mk = function(path){ path = nodePath.join(dir, path); diff --git a/lib/webrtc.js b/lib/webrtc.js index 9d37d3de..cc61ec60 100644 --- a/lib/webrtc.js +++ b/lib/webrtc.js @@ -30,11 +30,13 @@ ]}; 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){ + root.on('out', {rtc: {id: opt.pid, to:to}}); // announce ourself + }; var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); root.on('create', function(at){ this.to.next(at); - setTimeout(function(){ root.on('out', {rtc: {id: opt.pid}}) },1); // announce ourself + setTimeout(opt.announce, 1); }); root.on('in', function(msg){ if(msg.rtc){ open(msg) } @@ -44,31 +46,28 @@ function open(msg){ var rtc = msg.rtc, peer, tmp; if(!rtc || !rtc.id){ return } + delete opt.announce[rtc.id]; /// remove after connect if(tmp = rtc.answer){ - if(!(peer = opt.peers[rtc.id]) || peer.remoteSet){ return } + if(!(peer = opt.peers[rtc.id] || open[rtc.id]) || peer.remoteSet){ return } return peer.setRemoteDescription(peer.remoteSet = new opt.RTCSessionDescription(tmp)); } if(tmp = rtc.candidate){ - peer = opt.peers[rtc.id] || open({rtc: {id: rtc.id}}); + peer = opt.peers[rtc.id] || open[rtc.id] || open({rtc: {id: rtc.id}}); return peer.addIceCandidate(new opt.RTCIceCandidate(tmp)); } - if(opt.peers[rtc.id]){ return } + //if(opt.peers[rtc.id]){ return } + if(open[rtc.id]){ return } (peer = new opt.RTCPeerConnection(opt.rtc)).id = rtc.id; var wire = peer.wire = peer.createDataChannel('dc', opt.rtc.dataChannel); - mesh.hi(peer); + open[rtc.id] = peer; wire.onclose = function(){ + delete open[rtc.id]; mesh.bye(peer); - peer.wire = null; //reconnect(peer); }; - wire.onerror = function(error){ - //reconnect(peer); // placement? - if(!error){ return } - if(error.code === 'ECONNREFUSED'){ - //reconnect(peer, as); - } - }; + wire.onerror = function(err){}; wire.onopen = function(e){ + //delete open[rtc.id]; mesh.hi(peer); } wire.onmessage = function(msg){ @@ -101,4 +100,4 @@ } }); var noop = function(){}; -}()); \ No newline at end of file +}()); diff --git a/lib/wire.js b/lib/wire.js index 5e74b6b3..6afdf5e1 100644 --- a/lib/wire.js +++ b/lib/wire.js @@ -58,7 +58,7 @@ Gun.on('opt', function(root){ var url = require('url'); opt.WebSocket = opt.WebSocket || require('ws'); - var ws = opt.ws || {}; + var ws = opt.ws = opt.ws || {}; ws.server = ws.server || opt.web; if(ws.server && !ws.web){ @@ -78,6 +78,7 @@ Gun.on('opt', function(root){ opt.mesh.bye(peer); }); wire.on('error', function(e){}); + setTimeout(function heart(){ if(!opt.peers[peer.id]){ return } try{ wire.send("[]"); setTimeout(heart, 1000 * 20) }catch(e){} }, 1000 * 20); // Some systems, like Heroku, require heartbeats to not time out. // TODO: Make this configurable? }); } diff --git a/package-lock.json b/package-lock.json index 7fa8f7c3..c25fcdf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,37 +1,115 @@ { "name": "gun", - "version": "0.9.999999", + "version": "0.2019.910", "lockfileVersion": 1, "requires": true, "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, + "@peculiar/asn1-schema": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-1.0.3.tgz", + "integrity": "sha512-Tfgj9eNJ6cTKEtEuidKenLHMx/Q5M8KEE9hnohHqvdpqHJXWYr5RlT3GjAHPjGXy5+mr7sSfuXfzE6aAkEGN7A==", + "optional": true, "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "asn1js": "^2.0.22", + "tslib": "^1.9.3" } }, + "@peculiar/json-schema": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.5.tgz", + "integrity": "sha512-y5XYA3pf9+c+YKVpWnPtQbNmlNCs2ehNHyMLJvq4K5Fjwc1N64YGy7MNecKW3uYLga+sqbGTQSUdOdlnaRRbpA==", + "optional": true, + "requires": { + "tslib": "^1.9.3" + } + }, + "@peculiar/webcrypto": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.0.19.tgz", + "integrity": "sha512-+lF69A18LJBLp0/gJIQatCARLah6cTUmLwY0Cdab0zsk+Z53BcpjKQyyP4LIN8oW601ZXv28mWEQ4Cm7MllF6w==", + "optional": true, + "requires": { + "@peculiar/asn1-schema": "^1.0.3", + "@peculiar/json-schema": "^1.1.5", + "asn1js": "^2.0.26", + "pvtsutils": "^1.0.6", + "tslib": "^1.10.0", + "webcrypto-core": "^1.0.14" + }, + "dependencies": { + "webcrypto-core": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.14.tgz", + "integrity": "sha512-iGZQcH/o3Jv6mpvCbzan6uAcUcLTTnUCil6RVYakcNh5/QXIKRRC06EFxHru9lHgVKucZy3gG4OBiup0IsOr0g==", + "optional": true, + "requires": { + "pvtsutils": "^1.0.4", + "tslib": "^1.10.0" + } + } + } + }, + "@types/node": { + "version": "10.14.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.18.tgz", + "integrity": "sha512-ryO3Q3++yZC/+b8j8BdKd/dn9JlzlHBPdm80656xwYUdmPkpTGTjkAdt6BByiNupGPE8w0FhBgvYy/fX9hRNGQ==" + }, + "addressparser": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-0.3.2.tgz", + "integrity": "sha1-WYc/Nej89sc2HBAjkmHXbhU0i7I=", + "optional": true + }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", "dev": true }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "arraybuffer.slice": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", "dev": true }, + "asn1js": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.26.tgz", + "integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==", + "requires": { + "pvutils": "^1.0.17" + } + }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -106,24 +184,6 @@ "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", "dev": true }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -151,22 +211,70 @@ "isarray": "^1.0.0" } }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, "callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", "dev": true }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "component-bind": { @@ -193,50 +301,48 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", "dev": true }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } }, "diff": { "version": "3.5.0", @@ -244,17 +350,47 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "emailjs": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/emailjs/-/emailjs-2.2.0.tgz", + "integrity": "sha1-ulsj5KSwpFEPZS6HOxVOlAe2ygM=", + "optional": true, + "requires": { + "addressparser": "^0.3.2", + "emailjs-mime-codec": "^2.0.7" + } + }, + "emailjs-base64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/emailjs-base64/-/emailjs-base64-1.1.4.tgz", + "integrity": "sha512-4h0xp1jgVTnIQBHxSJWXWanNnmuc5o+k4aHEpcLXSToN8asjB5qbXAexs7+PEsUKcEyBteNYsSvXUndYT2CGGA==", + "optional": true + }, + "emailjs-mime-codec": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/emailjs-mime-codec/-/emailjs-mime-codec-2.0.9.tgz", + "integrity": "sha512-7qJo4pFGcKlWh/kCeNjmcgj34YoJWY0ekZXEHYtluWg4MVBnXqGM4CRMtZQkfYwitOhUgaKN5EQktJddi/YIDQ==", + "optional": true, + "requires": { + "emailjs-base64": "^1.1.4", + "ramda": "^0.26.1", + "text-encoding": "^0.7.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } }, "engine.io": { "version": "1.8.5", @@ -380,11 +516,34 @@ "wtf-8": "1.0.0" } }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "es-abstract": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", + "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.0.0", + "string.prototype.trimright": "^2.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } }, "escape-string-regexp": { "version": "1.0.5", @@ -392,10 +551,10 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "events": { @@ -404,78 +563,38 @@ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, - "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - } + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" + "locate-path": "^3.0.0" } }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } }, "fs.realpath": { "version": "1.0.0", @@ -483,10 +602,31 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -503,6 +643,15 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-binary": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", @@ -532,28 +681,16 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "ieee754": { @@ -579,9 +716,15 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ip": { @@ -590,10 +733,28 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-promise": { @@ -602,53 +763,117 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "jmespath": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", "dev": true }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "json3": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", "dev": true }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "dev": true }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } }, "mime-db": { "version": "1.33.0", @@ -665,6 +890,12 @@ "mime-db": "~1.33.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -677,76 +908,92 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", "dev": true, "requires": { + "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", + "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", + "find-up": "3.0.0", + "glob": "7.1.3", "growl": "1.10.5", - "he": "1.1.1", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", - "optional": true - }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", "dev": true }, - "node-webcrypto-ossl": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/node-webcrypto-ossl/-/node-webcrypto-ossl-1.0.39.tgz", - "integrity": "sha512-cEq67y6GJ5jcKdANi5XqejqMvM/eIGxuOOE8F+c0XS950jSpvOcjUNHLmIe3/dN/UKyUkb+dri0BU4OgmCJd2g==", - "optional": true, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, "requires": { - "mkdirp": "^0.5.1", - "nan": "^2.11.1", - "tslib": "^1.9.3", - "webcrypto-core": "^0.1.25" + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "object-assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", @@ -759,13 +1006,38 @@ "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", "dev": true }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "ee-first": "1.1.1" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "once": { @@ -783,6 +1055,59 @@ "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", "dev": true }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "panic-client": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/panic-client/-/panic-client-1.0.2.tgz", @@ -853,10 +1178,10 @@ "better-assert": "~1.0.0" } }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { @@ -865,10 +1190,10 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "platform": { @@ -877,14 +1202,14 @@ "integrity": "sha1-SSIQiSM1vTExwKCN2i2T7DVD5CM=", "dev": true }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "punycode": { @@ -893,11 +1218,19 @@ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true + "pvtsutils": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.0.6.tgz", + "integrity": "sha512-0yNrOdJyLE7FZzmeEHTKanwBr5XbmDAd020cKa4ZiTYuGMBYBZmq7vHOhcOqhVllh6gghDBbaz1lnVdOqiB7cw==", + "requires": { + "@types/node": "^10.14.17", + "tslib": "^1.10.0" + } + }, + "pvutils": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", + "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==" }, "querystring": { "version": "0.2.0", @@ -905,49 +1238,23 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "optional": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } - } + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "sax": { "version": "1.2.1", @@ -955,43 +1262,37 @@ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", "dev": true }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "shebang-regex": "^1.0.0" } }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "socket.io": { @@ -1136,16 +1437,67 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -1164,19 +1516,9 @@ "dev": true }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "uglify-js": { "version": "3.3.24", @@ -1196,12 +1538,6 @@ } } }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -1212,31 +1548,81 @@ "querystring": "0.2.0" } }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", "dev": true }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "webcrypto-core": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-0.1.26.tgz", - "integrity": "sha512-BZVgJZkkHyuz8loKvsaOKiBDXDpmMZf5xG4QAOlSeYdXlFUl9c1FRrVnAXcOdb4fTHMG+TRu81odJwwSfKnWTA==", - "optional": true, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, "requires": { - "tslib": "^1.7.1" + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "wrappy": { @@ -1246,11 +1632,11 @@ "dev": true }, "ws": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz", - "integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.0.tgz", + "integrity": "sha512-Swie2C4fs7CkwlHu1glMePLYJJsWjzhl1vm3ZaLplD0h7OMkZyZ6kLTB/OagiU923bZrPFXuDTeEqaEN4NWG4g==", "requires": { - "async-limiter": "~1.0.0" + "async-limiter": "^1.0.0" } }, "wtf-8": { @@ -1284,6 +1670,130 @@ "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", "dev": true }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index 2cf8e82d..cee14d76 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,20 @@ { "name": "gun", - "version": "0.9.9999991", + "version": "0.2019.915", "description": "A realtime, decentralized, offline-first, graph data synchronization engine.", "main": "index.js", - "browser": "gun.min.js", + "browser": "gun.js", "scripts": { - "start": "node examples/http.js 8765", + "start": "node examples/http.js", "https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start", "prepublishOnly": "npm run unbuild", "test": "mocha", "testsea": "mocha test/sea.js", + "testaxe": "mocha test/axe/holy-grail.js", "e2e": "mocha e2e/distributed.js", "docker": "hooks/build", - "unbuild": "node lib/unbuild.js && uglifyjs gun.js -o gun.min.js -c -m" + "minify": "uglifyjs gun.js -o gun.min.js -c -m", + "unbuild": "node lib/unbuild.js & npm run minify" }, "repository": { "type": "git", @@ -49,18 +51,17 @@ "node": ">=0.8.4" }, "dependencies": { - "ws": "~>5.2.0" + "ws": "~>7.1.0" }, "optionalDependencies": { - "text-encoding": "^0.7.0", - "node-webcrypto-ossl": "^1.0.39" + "@peculiar/webcrypto": "^1.0.19", + "emailjs": "^2.2.0", + "text-encoding": "^0.7.0" }, "devDependencies": { "aws-sdk": ">=2.153.0", - "concat-map": "^0.0.1", - "express": ">=4.15.2", "ip": "^1.1.5", - "mocha": "^5.2.0", + "mocha": "^6.2.0", "panic-manager": "^1.2.0", "panic-server": "^1.1.1", "uglify-js": ">=2.8.22" diff --git a/sea.js b/sea.js index 28264b4f..ee2fbf8f 100644 --- a/sea.js +++ b/sea.js @@ -47,6 +47,15 @@ })(USE, './https'); ;USE(function(module){ + if(typeof global !== "undefined"){ + var g = global; + g.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); }; + g.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); }; + } + })(USE, './base64'); + + ;USE(function(module){ + USE('./base64'); // This is Array extended to have .toString(['utf8'|'hex'|'base64']) function SeaArray() {} Object.assign(SeaArray, { from: Array.from }) @@ -72,6 +81,7 @@ })(USE, './array'); ;USE(function(module){ + USE('./base64'); // This is Buffer implementation used in SEA. Functionality is mostly // compatible with NodeJS 'safe-buffer' and is used for encoding conversions // between binary and 'hex' | 'utf8' | 'base64' @@ -174,7 +184,7 @@ random: (len) => Buffer.from(crypto.randomBytes(len)) }); //try{ - const WebCrypto = USE('node-webcrypto-ossl', 1); + const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1); api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH //}catch(e){ //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); @@ -510,7 +520,7 @@ key = pair.epriv || pair; } var msg = (typeof data == 'string')? data : JSON.stringify(data); - var rand = {s: shim.random(8), iv: shim.random(16)}; + var rand = {s: shim.random(9), iv: shim.random(15)}; // consider making this 9 and 15 or 18 or 12 to reduce == padding. var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) }, aes, new shim.TextEncoder().encode(msg))); @@ -591,7 +601,7 @@ var epriv = pair.epriv; var ecdhSubtle = shim.ossl || shim.subtle; var pubKeyData = keysToEcdhJwk(pub); - var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy ! var privKeyData = keysToEcdhJwk(epub, epriv); var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! @@ -677,7 +687,7 @@ // But all other behavior needs to be equally easy, like opinionated ways of // Adding friends (trusted public keys), sending private messages, etc. // Cheers! Tell me what you think. - var Gun = (SEA.window||{}).Gun || USE('./gun', 1); + var Gun = (SEA.window||{}).Gun || USE((typeof common == "undefined"?'.':'')+'./gun', 1); Gun.SEA = SEA; SEA.GUN = SEA.Gun = Gun; @@ -1121,6 +1131,10 @@ // This is broken down into some pretty clear edge cases, let's go over them: function security(msg){ var at = this.as, sea = at.sea, to = this.to; + if(at.opt.faith && (msg._||noop).faith){ // you probably shouldn't have faith in this! + this.to.next(msg); // why do we allow skipping security? I'm very scared about it actually. + return; // but so that way storage adapters that already verified something can get performance boost. This was a community requested feature. If anybody finds an exploit with it, please report immediately. It should only be exploitable if you have XSS control anyways, which if you do, you can bypass security regardless of this. + } if(msg.get){ // if there is a request to read data from us, then... var soul = msg.get['#']; diff --git a/sea/array.js b/sea/array.js index fecdde5d..d10c983c 100644 --- a/sea/array.js +++ b/sea/array.js @@ -1,4 +1,6 @@ - + function btoa(b) { + return new Buffer(b).toString('base64'); + }; // This is Array extended to have .toString(['utf8'|'hex'|'base64']) function SeaArray() {} Object.assign(SeaArray, { from: Array.from }) diff --git a/sea/buffer.js b/sea/buffer.js index 1854bd1c..37f753b4 100644 --- a/sea/buffer.js +++ b/sea/buffer.js @@ -1,4 +1,6 @@ - + function atob(a) { + return new Buffer(a, 'base64').toString('binary'); + }; // This is Buffer implementation used in SEA. Functionality is mostly // compatible with NodeJS 'safe-buffer' and is used for encoding conversions // between binary and 'hex' | 'utf8' | 'base64' diff --git a/sea/encrypt.js b/sea/encrypt.js index dc31cc04..d5d5641c 100644 --- a/sea/encrypt.js +++ b/sea/encrypt.js @@ -14,7 +14,7 @@ key = pair.epriv || pair; } var msg = (typeof data == 'string')? data : JSON.stringify(data); - var rand = {s: shim.random(8), iv: shim.random(16)}; + var rand = {s: shim.random(9), iv: shim.random(15)}; // consider making this 9 and 15 or 18 or 12 to reduce == padding. var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) }, aes, new shim.TextEncoder().encode(msg))); diff --git a/sea/index.js b/sea/index.js index a1d880af..2a0b42eb 100644 --- a/sea/index.js +++ b/sea/index.js @@ -63,6 +63,10 @@ // This is broken down into some pretty clear edge cases, let's go over them: function security(msg){ var at = this.as, sea = at.sea, to = this.to; + if(at.opt.faith && (msg._||noop).faith){ // you probably shouldn't have faith in this! + this.to.next(msg); // why do we allow skipping security? I'm very scared about it actually. + return; // but so that way storage adapters that already verified something can get performance boost. This was a community requested feature. If anybody finds an exploit with it, please report immediately. It should only be exploitable if you have XSS control anyways, which if you do, you can bypass security regardless of this. + } if(msg.get){ // if there is a request to read data from us, then... var soul = msg.get['#']; diff --git a/sea/sea.js b/sea/sea.js index 75f8b0dd..57826cde 100644 --- a/sea/sea.js +++ b/sea/sea.js @@ -48,7 +48,7 @@ // But all other behavior needs to be equally easy, like opinionated ways of // Adding friends (trusted public keys), sending private messages, etc. // Cheers! Tell me what you think. - var Gun = (SEA.window||{}).Gun || require('./gun', 1); + var Gun = (SEA.window||{}).Gun || require((typeof common == "undefined"?'.':'')+'./gun', 1); Gun.SEA = SEA; SEA.GUN = SEA.Gun = Gun; diff --git a/sea/secret.js b/sea/secret.js index 4009ee31..b52a6fc6 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(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); var privKeyData = keysToEcdhJwk(epub, epriv); var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! @@ -47,4 +47,4 @@ } module.exports = SEA.secret; - \ No newline at end of file + diff --git a/sea/shim.js b/sea/shim.js index b6026e8c..21d94355 100644 --- a/sea/shim.js +++ b/sea/shim.js @@ -22,7 +22,7 @@ random: (len) => Buffer.from(crypto.randomBytes(len)) }); //try{ - const WebCrypto = require('node-webcrypto-ossl', 1); + const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1); api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH //}catch(e){ //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); diff --git a/src/HAM.js b/src/HAM.js index d143981b..1799cc72 100644 --- a/src/HAM.js +++ b/src/HAM.js @@ -1,46 +1,46 @@ - -/* Based on the Hypothetical Amnesia Machine thought experiment */ -function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ - if(machineState < incomingState){ - return {defer: true}; // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. - } - if(incomingState < currentState){ - return {historical: true}; // the incoming value is within the boundary of the machine's state, but not within the range. - - } - if(currentState < incomingState){ - return {converge: true, incoming: true}; // the incoming value is within both the boundary and the range of the machine's state. - - } - if(incomingState === currentState){ - incomingValue = Lexical(incomingValue) || ""; - currentValue = Lexical(currentValue) || ""; - if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different - return {state: true}; - } - /* - The following is a naive implementation, but will always work. - Never change it unless you have specific needs that absolutely require it. - If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same. - As a result, it is highly discouraged to modify despite the fact that it is naive, - because convergence (data integrity) is generally more important. - Any difference in this algorithm must be given a new and different name. - */ - if(incomingValue < currentValue){ // Lexical only works on simple value types! - return {converge: true, current: true}; - } - if(currentValue < incomingValue){ // Lexical only works on simple value types! - return {converge: true, incoming: true}; - } - } - return {err: "Invalid CRDT Data: "+ incomingValue +" to "+ currentValue +" at "+ incomingState +" to "+ currentState +"!"}; -} -if(typeof JSON === 'undefined'){ - throw new Error( - 'JSON is not included in this browser. Please load it first: ' + - 'ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js' - ); -} -var Lexical = JSON.stringify, undefined; -module.exports = HAM; + +/* Based on the Hypothetical Amnesia Machine thought experiment */ +function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ + if(machineState < incomingState){ + return {defer: true}; // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. + } + if(incomingState < currentState){ + return {historical: true}; // the incoming value is within the boundary of the machine's state, but not within the range. + + } + if(currentState < incomingState){ + return {converge: true, incoming: true}; // the incoming value is within both the boundary and the range of the machine's state. + + } + if(incomingState === currentState){ + incomingValue = Lexical(incomingValue) || ""; + currentValue = Lexical(currentValue) || ""; + if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different + return {state: true}; + } + /* + The following is a naive implementation, but will always work. + Never change it unless you have specific needs that absolutely require it. + If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same. + As a result, it is highly discouraged to modify despite the fact that it is naive, + because convergence (data integrity) is generally more important. + Any difference in this algorithm must be given a new and different name. + */ + if(incomingValue < currentValue){ // Lexical only works on simple value types! + return {converge: true, current: true}; + } + if(currentValue < incomingValue){ // Lexical only works on simple value types! + return {converge: true, incoming: true}; + } + } + return {err: "Invalid CRDT Data: "+ incomingValue +" to "+ currentValue +" at "+ incomingState +" to "+ currentState +"!"}; +} +if(typeof JSON === 'undefined'){ + throw new Error( + 'JSON is not included in this browser. Please load it first: ' + + 'ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js' + ); +} +var Lexical = JSON.stringify, undefined; +module.exports = HAM; \ No newline at end of file diff --git a/src/adapters/localStorage.js b/src/adapters/localStorage.js index e2a5fdea..5cf6b718 100644 --- a/src/adapters/localStorage.js +++ b/src/adapters/localStorage.js @@ -1,147 +1,146 @@ - -if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? - -var root, noop = function(){}, store, u; -try{store = (Gun.window||noop).localStorage}catch(e){} -if(!store){ - console.log("Warning: No localStorage exists to persist data to!"); - store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; -} -/* - NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! - If you update anything here, consider updating the other adapters as well. -*/ - -Gun.on('create', function(root){ - // This code is used to queue offline writes for resync. - // See the next 'opt' code below for actual saving of data. - var ev = this.to, opt = root.opt; - if(root.once){ return ev.next(root) } - //if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! - opt.prefix = opt.file || 'gun/'; - var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {}; - var empty = Gun.obj.empty, id, to, go; - // add re-sync command. - if(!empty(gap)){ - var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {}; - Gun.obj.map(gap, function(node, soul){ - Gun.obj.map(node, function(val, key){ - send[soul] = Gun.state.to(disk[soul], key, send[soul]); - }); - }); - setTimeout(function(){ - root.on('out', {put: send, '#': root.ask(ack)}); - },1); - } - - root.on('out', function(msg){ - if(msg.lS){ return } - if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){ - id = msg['#']; - Gun.graph.is(msg.put, null, map); - if(!to){ to = setTimeout(flush, opt.wait || 1) } - } - this.to.next(msg); - }); - root.on('ack', ack); - - function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook. - if(ack.err || !ack.ok){ return } - var id = ack['@']; - setTimeout(function(){ - Gun.obj.map(gap, function(node, soul){ - Gun.obj.map(node, function(val, key){ - if(id !== val){ return } - delete node[key]; - }); - if(empty(node)){ - delete gap[soul]; - } - }); - flush(); - }, opt.wait || 1); - }; - ev.next(root); - - var map = function(val, key, node, soul){ - (gap[soul] || (gap[soul] = {}))[key] = id; - } - var flush = function(){ - clearTimeout(to); - to = false; - try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap)); - }catch(e){ Gun.log(err = e || "localStorage failure") } - } -}); - -Gun.on('create', function(root){ - this.to.next(root); - var opt = root.opt; - if(root.once){ return } - if(false === opt.localStorage){ return } - opt.prefix = opt.file || 'gun/'; - var graph = root.graph, acks = {}, count = 0, to; - var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; - var lS = function(){}, u; - root.on('localStorage', disk); // NON-STANDARD EVENT! - - root.on('put', function(at){ - this.to.next(at); - Gun.graph.is(at.put, null, map); - if(!at['@']){ acks[at['#']] = true; } // only ack non-acks. - count += 1; - if(count >= (opt.batch || 1000)){ - return flush(); - } - if(to){ return } - to = setTimeout(flush, opt.wait || 1); - }); - - root.on('get', function(msg){ - this.to.next(msg); - var lex = msg.get, soul, data, u; - function to(){ - if(!lex || !(soul = lex['#'])){ return } - //if(0 >= msg.cap){ return } - var has = lex['.']; - data = disk[soul] || u; - if(data && has){ - data = Gun.state.to(data, has); - } - if(!data && !Gun.obj.empty(opt.peers)){ // if data not found, don't ack if there are peers. - return; // Hmm, what if we have peers but we are disconnected? - } - //console.log("lS get", lex, data); - root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$}); - }; - Gun.debug? setTimeout(to,1) : to(); - }); - - var map = function(val, key, node, soul){ - disk[soul] = Gun.state.to(node, key, disk[soul]); - } - - var flush = function(data){ - var err; - count = 0; - clearTimeout(to); - to = false; - var ack = acks; - acks = {}; - if(data){ disk = data } - try{store.setItem(opt.prefix, JSON.stringify(disk)); - }catch(e){ - Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."); - root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); - } - if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers. - Gun.obj.map(ack, function(yes, id){ - root.on('in', { - '@': id, - err: err, - ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number. - }); - }); - } -}); + +if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs? + +var root, noop = function(){}, store, u; +try{store = (Gun.window||noop).localStorage}catch(e){} +if(!store){ + console.log("Warning: No localStorage exists to persist data to!"); + store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; +} +/* + NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design! + If you update anything here, consider updating the other adapters as well. +*/ + +Gun.on('create', function(root){ + // This code is used to queue offline writes for resync. + // See the next 'opt' code below for actual saving of data. + var ev = this.to, opt = root.opt; + if(root.once){ return ev.next(root) } + //if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! + opt.prefix = opt.file || 'gun/'; + var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {}; + var empty = Gun.obj.empty, id, to, go; + // add re-sync command. + if(!empty(gap)){ + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {}; + Gun.obj.map(gap, function(node, soul){ + Gun.obj.map(node, function(val, key){ + send[soul] = Gun.state.to(disk[soul], key, send[soul]); + }); + }); + setTimeout(function(){ + // TODO: Holy Grail dangling by this thread! If gap / offline resync doesn't trigger, it doesn't work. Ouch, and this is a localStorage specific adapter. :( + root.on('out', {put: send, '#': root.ask(ack)}); + },1); + } + + root.on('out', function(msg){ + if(msg.lS){ return } // TODO: for IndexedDB and others, shouldn't send to peers ACKs to our own GETs. + if(Gun.is(msg.$) && msg.put && !msg['@']){ + id = msg['#']; + Gun.graph.is(msg.put, null, map); + if(!to){ to = setTimeout(flush, opt.wait || 1) } + } + this.to.next(msg); + }); + root.on('ack', ack); + + function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook. + if(ack.err || !ack.ok){ return } + var id = ack['@']; + setTimeout(function(){ + Gun.obj.map(gap, function(node, soul){ + Gun.obj.map(node, function(val, key){ + if(id !== val){ return } + delete node[key]; + }); + if(empty(node)){ + delete gap[soul]; + } + }); + flush(); + }, opt.wait || 1); + }; + ev.next(root); + + var map = function(val, key, node, soul){ + (gap[soul] || (gap[soul] = {}))[key] = id; + } + var flush = function(){ + clearTimeout(to); + to = false; + try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap)); + }catch(e){ Gun.log(err = e || "localStorage failure") } + } +}); + +Gun.on('create', function(root){ + this.to.next(root); + var opt = root.opt; + if(root.once){ return } + if(false === opt.localStorage){ return } + opt.prefix = opt.file || 'gun/'; + var graph = root.graph, acks = {}, count = 0, to; + var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; + var lS = function(){}, u; + root.on('localStorage', disk); // NON-STANDARD EVENT! + + root.on('put', function(at){ + this.to.next(at); + Gun.graph.is(at.put, null, map); + if(!at['@']){ acks[at['#']] = true; } // only ack non-acks. + count += 1; + if(count >= (opt.batch || 1000)){ + return flush(); + } + if(to){ return } + to = setTimeout(flush, opt.wait || 1); + }); + + root.on('get', function(msg){ + this.to.next(msg); + var lex = msg.get, soul, data, u; + function to(){ + if(!lex || !(soul = lex['#'])){ return } + //if(0 >= msg.cap){ return } + var has = lex['.']; + data = disk[soul] || u; + if(data && has){ + data = Gun.state.to(data, has); + } + //if(!data && !Gun.obj.empty(opt.peers)){ return } // if data not found, don't ack if there are peers. // Hmm, what if we have peers but we are disconnected? + //console.log("lS get", lex, data); + root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$});// || root.$}); + }; + Gun.debug? setTimeout(to,1) : to(); + }); + + var map = function(val, key, node, soul){ + disk[soul] = Gun.state.to(node, key, disk[soul]); + } + + var flush = function(data){ + var err; + count = 0; + clearTimeout(to); + to = false; + var ack = acks; + acks = {}; + if(data){ disk = data } + try{store.setItem(opt.prefix, JSON.stringify(disk)); + }catch(e){ + Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"); + root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); + } + if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers. + Gun.obj.map(ack, function(yes, id){ + root.on('in', { + '@': id, + err: err, + ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number. + }); + }); + } +}); \ No newline at end of file diff --git a/src/adapters/mesh.js b/src/adapters/mesh.js index b4401b46..f65fd492 100644 --- a/src/adapters/mesh.js +++ b/src/adapters/mesh.js @@ -1,233 +1,252 @@ - -var Type = require('../type'); - -function Mesh(ctx){ - var mesh = function(){}; - var opt = ctx.opt || {}; - opt.log = opt.log || console.log; - opt.gap = opt.gap || opt.wait || 1; - opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB. - - mesh.out = function(msg){ var tmp; - if(this.to){ this.to.next(msg) } - //if(mesh.last != msg['#']){ return mesh.last = msg['#'], this.to.next(msg) } - if((tmp = msg['@']) - && (tmp = ctx.dup.s[tmp]) - && (tmp = tmp.it) - && tmp._){ - mesh.say(msg, (tmp._).via, 1); - tmp['##'] = msg['##']; - return; - } - // add hook for AXE? - //if (Gun.AXE && opt && opt.super) { Gun.AXE.say(msg, mesh.say, this); return; } // rogowski - mesh.say(msg); - } - - ctx.on('create', function(root){ - root.opt.pid = root.opt.pid || Type.text.random(9); - this.to.next(root); - ctx.on('out', mesh.out); - }); - - mesh.hear = function(raw, peer){ - if(!raw){ return } - var dup = ctx.dup, id, hash, msg, tmp = raw[0]; - if(opt.pack <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) } - try{msg = JSON.parse(raw); - }catch(e){opt.log('DAM JSON parse error', e)} - if('{' === tmp){ - if(!msg){ return } - if(dup.check(id = msg['#'])){ return } - dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. - if((tmp = msg['@']) && msg.put){ - hash = msg['##'] || (msg['##'] = mesh.hash(msg)); - if((tmp = tmp + hash) != id){ - if(dup.check(tmp)){ return } - (tmp = dup.s)[hash] = tmp[id]; - } - } - (msg._ = function(){}).via = peer; - if((tmp = msg['><'])){ - (msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); - } - if(msg.dam){ - if(tmp = mesh.hear[msg.dam]){ - tmp(msg, peer, ctx); - } - return; - } - ctx.on('in', msg); - - return; - } else - if('[' === tmp){ - if(!msg){ return } - var i = 0, m; - while(m = msg[i++]){ - mesh.hear(m, peer); - } - - return; - } - } - - ;(function(){ - mesh.say = function(msg, peer, o){ - /* - TODO: Plenty of performance optimizations - that can be made just based off of ordering, - and reducing function calls for cached writes. - */ - if(!peer){ - Type.obj.map(opt.peers, function(peer){ - mesh.say(msg, peer); - }); - return; - } - var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! - if(!wire){ return } - msh = (msg._) || empty; - if(peer === msh.via){ return } - if(!(raw = msh.raw)){ raw = mesh.raw(msg) } - if((tmp = msg['@']) - && (tmp = ctx.dup.s[tmp]) - && (tmp = tmp.it)){ - if(tmp.get && tmp['##'] && tmp['##'] === msg['##']){ // PERF: move this condition outside say? - return; // TODO: this still needs to be tested in the browser! - } - } - if((tmp = msh.to) && (tmp[peer.url] || tmp[peer.id]) && !o){ return } // TODO: still needs to be tested - if(peer.batch){ - peer.tail = (peer.tail || 0) + raw.length; - if(peer.tail <= opt.pack){ - peer.batch.push(raw); - return; - } - flush(peer); - } - peer.batch = []; - setTimeout(function(){flush(peer)}, opt.gap); - send(raw, peer); - } - function flush(peer){ - var tmp = peer.batch; - if(!tmp){ return } - peer.batch = peer.tail = null; - if(!tmp.length){ return } - try{send(JSON.stringify(tmp), peer); - }catch(e){opt.log('DAM JSON stringify error', e)} - } - function send(raw, peer){ - var wire = peer.wire; - try{ - if(wire.send){ - wire.send(raw); - } else - if(peer.say){ - peer.say(raw); - } - }catch(e){ - (peer.queue = peer.queue || []).push(raw); - } - } - - }()); - - ;(function(){ - - mesh.raw = function(msg){ - if(!msg){ return '' } - var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp; - if(tmp = msh.raw){ return tmp } - if(typeof msg === 'string'){ return msg } - if(msg['@'] && (tmp = msg.put)){ - if(!(hash = msg['##'])){ - put = $(tmp, sort) || ''; - hash = mesh.hash(msg, put); - msg['##'] = hash; - } - (tmp = dup.s)[hash = msg['@']+hash] = tmp[msg['#']]; - msg['#'] = hash || msg['#']; - if(put){ (msg = Type.obj.to(msg)).put = _ } - } - var i = 0, to = []; Type.obj.map(opt.peers, function(p){ - to.push(p.url || p.id); if(++i > 9){ return true } // limit server, fast fix, improve later! - }); msg['><'] = to.join(); - var raw = $(msg); - if(u !== put){ - tmp = raw.indexOf(_, raw.indexOf('put')); - raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); - //raw = raw.replace('"'+ _ +'"', put); // https://github.com/amark/gun/wiki/@$$ Heisenbug - } - if(msh){ - msh.raw = raw; - } - return raw; - } - - mesh.hash = function(msg, hash){ - return Mesh.hash(hash || $(msg.put, sort) || '') || msg['#'] || Type.text.random(9); - } - - function sort(k, v){ var tmp; - if(!(v instanceof Object)){ return v } - Type.obj.map(Object.keys(v).sort(), map, {to: tmp = {}, on: v}); - return tmp; - } - - function map(k){ - this.to[k] = this.on[k]; - } - var $ = JSON.stringify, _ = ':])([:'; - - }()); - - mesh.hi = function(peer){ - var tmp = peer.wire || {}; - if(peer.id || peer.url){ - opt.peers[peer.url || peer.id] = peer; - Type.obj.del(opt.peers, tmp.id); - } else { - tmp = tmp.id = tmp.id || Type.text.random(9); - mesh.say({dam: '?'}, opt.peers[tmp] = peer); - } - if(!tmp.hied){ ctx.on(tmp.hied = 'hi', peer) } - tmp = peer.queue; peer.queue = []; - Type.obj.map(tmp, function(msg){ - mesh.say(msg, peer); - }); - } - mesh.bye = function(peer){ - Type.obj.del(opt.peers, peer.id); // assume if peer.url then reconnect - ctx.on('bye', peer); - } - - mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } - mesh.hear['?'] = function(msg, peer){ - if(!msg.pid){ return mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer) } - peer.id = peer.id || msg.pid; - mesh.hi(peer); - } - - return mesh; -} - -Mesh.hash = function(s){ // via SO - if(typeof s !== 'string'){ return {err: 1} } - var c = 0; - if(!s.length){ return c } - for(var i=0,l=s.length,n; i<']){ (msg._).to = Type.obj.map(tmp.split(','), tomap) } + if(msg.dam){ + if(tmp = mesh.hear[msg.dam]){ + tmp(msg, peer, root); + } + return; + } + root.on('in', msg); + return; + } + } + var tomap = function(k,i,m){m(k,true)}; + mesh.hear.c = mesh.hear.d = 0; + + ;(function(){ + var message; + function each(peer){ mesh.say(message, peer) } + mesh.say = function(msg, peer){ + if(this.to){ this.to.next(msg) } // compatible with middleware adapters. + if(!msg){ return false } + var id, hash, tmp, raw; + var meta = msg._||(msg._=function(){}); + if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } + if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } + if(!(raw = meta.raw)){ + raw = meta.raw = mesh.raw(msg); + if(hash && (tmp = msg['@'])){ + dup.track(tmp+hash).it = msg; + if(tmp = (dup.s[tmp]||ok).it){ + if(hash === tmp['##']){ return false } + tmp['##'] = hash; + } + } + } + dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more! + if(!peer){ peer = (tmp = dup.s[msg['@']]) && (tmp = tmp.it) && (tmp = tmp._) && (tmp = tmp.via) } + if(!peer && mesh.way){ return mesh.way(msg) } + if(!peer || !peer.id){ message = msg; + if(!Type.obj.is(peer || opt.peers)){ return false } + Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. + return; + } + if(!peer.wire && mesh.wire){ mesh.wire(peer) } + if(id === peer.last){ return } peer.last = id; // was it just sent? + if(peer === meta.via){ return false } + if((tmp = meta.to) && (tmp[peer.url] || tmp[peer.pid] || tmp[peer.id]) /*&& !o*/){ return false } + if(peer.batch){ + peer.tail = (tmp = peer.tail || 0) + raw.length; + if(peer.tail <= opt.pack){ + peer.batch.push(raw); // peer.batch += (tmp?'':',')+raw; // TODO: Prevent double JSON! // FOR v1.0 !? + return; + } + flush(peer); + } + peer.batch = []; // peer.batch = '['; // TODO: Prevent double JSON! + setTimeout(function(){flush(peer)}, opt.gap); + send(raw, peer); + } + function flush(peer){ + var tmp = peer.batch; // var tmp = peer.batch + ']'; // TODO: Prevent double JSON! + peer.batch = peer.tail = null; + if(!tmp){ return } + if(!tmp.length){ return } // if(3 > tmp.length){ return } // TODO: ^ + try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp)); + }catch(e){return opt.log('DAM JSON stringify error', e)} + if(!tmp){ return } + send(tmp, peer); + } + mesh.say.c = mesh.say.d = 0; + }()); + + // for now - find better place later. + function send(raw, peer){ try{ + var wire = peer.wire; + if(peer.say){ + peer.say(raw); + } else + if(wire.send){ + wire.send(raw); + } + mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! + }catch(e){ + (peer.queue = peer.queue || []).push(raw); + }} + + ;(function(){ + mesh.raw = function(msg){ // TODO: Clean this up / delete it / move logic out! + if(!msg){ return '' } + var meta = (msg._) || {}, put, hash, tmp; + if(tmp = meta.raw){ return tmp } + 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. + }); if(i > 1){ msg['><'] = to.join() } + } + var raw = $(msg); // optimize by reusing put = the JSON.stringify from .hash? + /*if(u !== put){ + tmp = raw.indexOf(_, raw.indexOf('put')); + raw = raw.slice(0, tmp-1) + put + raw.slice(tmp + _.length + 1); + //raw = raw.replace('"'+ _ +'"', put); // NEVER USE THIS! ALSO NEVER DELETE IT TO NOT MAKE SAME MISTAKE! https://github.com/amark/gun/wiki/@$$ Heisenbug + }*/ + if(meta){ meta.raw = raw } + return raw; + } + var $ = JSON.stringify, _ = ':])([:'; + + }()); + + mesh.hi = function(peer){ + var tmp = peer.wire || {}; + if(peer.id){ + opt.peers[peer.url || peer.id] = peer; + } else { + tmp = peer.id = peer.id || Type.text.random(9); + mesh.say({dam: '?'}, opt.peers[tmp] = peer); + } + peer.met = peer.met || +(new Date); + if(!tmp.hied){ root.on(tmp.hied = 'hi', peer) } + // @rogowski I need this here by default for now to fix go1dfish's bug + tmp = peer.queue; peer.queue = []; + Type.obj.map(tmp, function(msg){ + send(msg, peer); + }); + } + mesh.bye = function(peer){ + root.on('bye', peer); + var tmp = +(new Date); tmp = (tmp - (peer.met||tmp)); + mesh.bye.time = ((mesh.bye.time || tmp) + tmp) / 2; + } + mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } + mesh.hear['?'] = function(msg, peer){ + if(!msg.pid){ + mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer); + // @rogowski I want to re-enable this AXE logic with some fix/merge later. + /* var tmp = peer.queue; peer.queue = []; + Type.obj.map(tmp, function(msg){ + mesh.say(msg, peer); + }); */ + // @rogowski 2: I think with my PID fix we can delete this and use the original. + return; + } + if(peer.pid){ return } + peer.pid = msg.pid; + } + + root.on('create', function(root){ + root.opt.pid = root.opt.pid || Type.text.random(9); + this.to.next(root); + root.on('out', mesh.say); + }); + + root.on('bye', function(peer, tmp){ + peer = opt.peers[peer.id || peer] || peer; + this.to.next(peer); + peer.bye? peer.bye() : (tmp = peer.wire) && tmp.close && tmp.close(); + Type.obj.del(opt.peers, peer.id); + peer.wire = null; + }); + + var gets = {}; + root.on('bye', function(peer, tmp){ this.to.next(peer); + if(!(tmp = peer.url)){ return } gets[tmp] = true; + setTimeout(function(){ delete gets[tmp] },opt.lack || 9000); + }); + root.on('hi', function(peer, tmp){ this.to.next(peer); + if(!(tmp = peer.url) || !gets[tmp]){ return } delete gets[tmp]; + Type.obj.map(root.next, function(node, soul){ + tmp = {}; tmp[soul] = root.graph[soul]; + mesh.say({'##': Type.obj.hash(tmp), get: {'#': soul}}, peer); + }) + }); + + return mesh; +} + +;(function(){ + Type.text.hash = function(s){ // via SO + if(typeof s !== 'string'){ return {err: 1} } + var c = 0; + if(!s.length){ return c } + for(var i=0,l=s.length,n; i (now - it.was)){ return } - Type.obj.del(dup.s, id); - }); - dup.to = null; - }, opt.age + 9); - } - return it; - } - return dup; -} -var time_is = Type.time.is; -module.exports = Dup; + +var Type = require('./type'); +function Dup(opt){ + var dup = {s:{}}; + opt = opt || {max: 1000, age: 1000 * 9};//1000 * 60 * 2}; + dup.check = function(id){ var tmp; + if(!(tmp = dup.s[id])){ return false } + if(tmp.pass){ return tmp.pass = false } + return dup.track(id); + } + dup.track = function(id, pass){ + 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); + } + return it; + } + return dup; +} +var time_is = Type.time.is; +module.exports = Dup; \ No newline at end of file diff --git a/src/get.js b/src/get.js index 06c96a84..3725896b 100644 --- a/src/get.js +++ b/src/get.js @@ -1,128 +1,133 @@ - -var Gun = require('./root'); -Gun.chain.get = function(key, cb, as){ - var gun, tmp; - if(typeof key === 'string'){ - var back = this, cat = back._; - var next = cat.next || empty; - if(!(gun = next[key])){ - gun = cache(key, back); - } - gun = gun.$; - } else - if(key instanceof Function){ - if(true === cb){ return soul(this, key, cb, as) } - gun = this; - var at = gun._, root = at.root, tmp = root.now, ev; - as = cb || {}; - as.at = at; - as.use = key; - as.out = as.out || {}; - as.out.get = as.out.get || {}; - (ev = at.on('in', use, as)).rid = rid; - (root.now = {$:1})[as.now = at.id] = ev; - var mum = root.mum; root.mum = {}; - at.on('out', as.out); - root.mum = mum; - root.now = tmp; - return gun; - } else - if(num_is(key)){ - return this.get(''+key, cb, as); - } else - if(tmp = rel.is(key)){ - return this.get(tmp, cb, as); - } else { - (as = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP - if(cb){ cb.call(as, as._.err) } - return as; - } - if(tmp = cat.stun){ // TODO: Refactor? - gun._.stun = gun._.stun || tmp; - } - if(cb && cb instanceof Function){ - gun.get(cb, as); - } - return gun; -} -function cache(key, back){ - var cat = back._, next = cat.next, gun = back.chain(), at = gun._; - if(!next){ next = cat.next = {} } - next[at.get = key] = at; - if(back === cat.root.$){ - at.soul = key; - } else - if(cat.soul || cat.has){ - at.has = key; - //if(obj_has(cat.put, key)){ - //at.put = cat.put[key]; - //} - } - return at; -} -function soul(gun, cb, opt, as){ - var cat = gun._, acks = 0, tmp; - if(tmp = cat.soul){ return cb(tmp, as, cat), gun } - if(tmp = cat.link){ return cb(tmp, as, cat), gun } - gun.get(function(msg, ev){ - if(u === msg.put && (tmp = (obj_map(cat.root.opt.peers, function(v,k,t){t(k)})||[]).length) && ++acks < tmp){ - return; - } - ev.rid(msg); - var at = ((at = msg.$) && at._) || {}; - tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub; - cb(tmp, as, msg, ev); - }, {out: {get: {'.':true}}}); - return gun; -} -function use(msg){ - var eve = this, as = eve.as, cat = as.at, root = cat.root, gun = msg.$, at = (gun||{})._ || {}, data = msg.put || at.put, tmp; - if((tmp = root.now) && eve !== tmp[as.now]){ return eve.to.next(msg) } - //console.log("USE:", cat.id, cat.soul, cat.has, cat.get, msg, root.mum); - //if(at.async && msg.root){ return } - //if(at.async === 1 && cat.async !== true){ return } - //if(root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - //if(!at.async && !cat.async && at.put && msg.put === at.put){ return } - //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - - - //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); - //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); - if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } - //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? - if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ - tmp = ((msg.$$ = at.root.gun.get(tmp))._); - if(u !== tmp.put){ - msg = obj_to(msg, {put: data = tmp.put}); - } - } - if((tmp = root.mum) && at.id){ // TODO: can we delete mum entirely now? - var id = at.id + (eve.id || (eve.id = Gun.text.random(9))); - if(tmp[id]){ return } - if(u !== data && !rel.is(data)){ tmp[id] = true; } - } - as.use(msg, eve); - if(eve.stun){ - eve.stun = null; - return; - } - eve.to.next(msg); -} -function rid(at){ - var cat = this.on; - if(!at || cat.soul || cat.has){ return this.off() } - if(!(at = (at = (at = at.$ || at)._ || at).id)){ return } - var map = cat.map, tmp, seen; - //if(!map || !(tmp = map[at]) || !(tmp = tmp.at)){ return } - if(tmp = (seen = this.seen || (this.seen = {}))[at]){ return true } - seen[at] = true; - return; - //tmp.echo[cat.id] = {}; // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. - //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. - return; -} -var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_to = Gun.obj.to; -var num_is = Gun.num.is; -var rel = Gun.val.link, node_soul = Gun.node.soul, node_ = Gun.node._; -var empty = {}, u; + +var Gun = require('./root'); +Gun.chain.get = function(key, cb, as){ + var gun, tmp; + if(typeof key === 'string'){ + var back = this, cat = back._; + var next = cat.next || empty; + if(!(gun = next[key])){ + gun = cache(key, back); + } + gun = gun.$; + } else + if(key instanceof Function){ + if(true === cb){ return soul(this, key, cb, as) } + gun = this; + var at = gun._, root = at.root, tmp = root.now, ev; + as = cb || {}; + as.at = at; + as.use = key; + as.out = as.out || {}; + as.out.get = as.out.get || {}; + (ev = at.on('in', use, as)).rid = rid; + (root.now = {$:1})[as.now = at.id] = ev; + var mum = root.mum; root.mum = {}; + at.on('out', as.out); + root.mum = mum; + root.now = tmp; + return gun; + } else + if(num_is(key)){ + return this.get(''+key, cb, as); + } else + if(tmp = rel.is(key)){ + return this.get(tmp, cb, as); + } else + if(obj.is(key)){ + gun = this; + if(tmp = ((tmp = key['#'])||empty)['='] || tmp){ gun = gun.get(tmp) } + gun._.lex = key; + return gun; + } else { + (as = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP + if(cb){ cb.call(as, as._.err) } + return as; + } + if(tmp = this._.stun){ // TODO: Refactor? + gun._.stun = gun._.stun || tmp; + } + if(cb && cb instanceof Function){ + gun.get(cb, as); + } + return gun; +} +function cache(key, back){ + var cat = back._, next = cat.next, gun = back.chain(), at = gun._; + if(!next){ next = cat.next = {} } + next[at.get = key] = at; + if(back === cat.root.$){ + at.soul = key; + } else + if(cat.soul || cat.has){ + at.has = key; + //if(obj_has(cat.put, key)){ + //at.put = cat.put[key]; + //} + } + return at; +} +function soul(gun, cb, opt, as){ + var cat = gun._, acks = 0, tmp; + if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat), gun } + gun.get(function(msg, ev){ + if(u === msg.put && (tmp = Object.keys(cat.root.opt.peers).length) && ++acks < tmp){ + return; + } + ev.rid(msg); + var at = ((at = msg.$) && at._) || {}; + tmp = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub; + cb(tmp, as, msg, ev); + }, {out: {get: {'.':true}}}); + return gun; +} +function use(msg){ + var eve = this, as = eve.as, cat = as.at, root = cat.root, gun = msg.$, at = (gun||{})._ || {}, data = msg.put || at.put, tmp; + if((tmp = root.now) && eve !== tmp[as.now]){ return eve.to.next(msg) } + //console.log("USE:", cat.id, cat.soul, cat.has, cat.get, msg, root.mum); + //if(at.async && msg.root){ return } + //if(at.async === 1 && cat.async !== true){ return } + //if(root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); + //if(!at.async && !cat.async && at.put && msg.put === at.put){ return } + //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); + + + //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); + //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); + if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } + //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? + if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ + tmp = ((msg.$$ = at.root.gun.get(tmp))._); + if(u !== tmp.put){ + msg = obj_to(msg, {put: data = tmp.put}); + } + } + if((tmp = root.mum) && at.id){ // TODO: can we delete mum entirely now? + var id = at.id + (eve.id || (eve.id = Gun.text.random(9))); + if(tmp[id]){ return } + if(u !== data && !rel.is(data)){ tmp[id] = true; } + } + as.use(msg, eve); + if(eve.stun){ + eve.stun = null; + return; + } + eve.to.next(msg); +} +function rid(at){ + var cat = this.on; + if(!at || cat.soul || cat.has){ return this.off() } + if(!(at = (at = (at = at.$ || at)._ || at).id)){ return } + var map = cat.map, tmp, seen; + //if(!map || !(tmp = map[at]) || !(tmp = tmp.at)){ return } + if(tmp = (seen = this.seen || (this.seen = {}))[at]){ return true } + seen[at] = true; + return; + //tmp.echo[cat.id] = {}; // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. + //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. + return; +} +var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_to = Gun.obj.to; +var num_is = Gun.num.is; +var rel = Gun.val.link, node_soul = Gun.node.soul, node_ = Gun.node._; +var empty = {}, u; \ No newline at end of file diff --git a/src/graph.js b/src/graph.js index a7f6f682..befbec60 100644 --- a/src/graph.js +++ b/src/graph.js @@ -1,155 +1,155 @@ - -var Type = require('./type'); -var Val = require('./val'); -var Node = require('./node'); -var Graph = {}; -;(function(){ - Graph.is = function(g, cb, fn, as){ // checks to see if an object is a valid graph. - if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object. - return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object. - } - function map(n, s){ // we invert this because the way'? we check for this is via a negation. - if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph. - if(!this.cb){ return } - nf.n = n; nf.as = this.as; // sequential race conditions aren't races. - this.cb.call(nf.as, n, s, nf); - } - function nf(fn){ // optional callback for each node. - if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value. - } -}()); -;(function(){ - Graph.ify = function(obj, env, as){ - var at = {path: [], obj: obj}; - if(!env){ - env = {}; - } else - if(typeof env === 'string'){ - env = {soul: env}; - } else - if(env instanceof Function){ - env.map = env; - } - if(env.soul){ - at.rel = Val.rel.ify(env.soul); - } - env.shell = (as||{}).shell; - env.graph = env.graph || {}; - env.seen = env.seen || []; - env.as = env.as || as; - node(env, at); - env.root = at.node; - return env.graph; - } - function node(env, at){ var tmp; - if(tmp = seen(env, at)){ return tmp } - at.env = env; - at.soul = soul; - if(Node.ify(at.obj, map, at)){ - at.rel = at.rel || Val.rel.ify(Node.soul(at.node)); - if(at.obj !== env.shell){ - env.graph[Val.rel.is(at.rel)] = at.node; - } - } - return at; - } - function map(v,k,n){ - var at = this, env = at.env, is, tmp; - if(Node._ === k && obj_has(v,Val.rel._)){ - return n._; // TODO: Bug? - } - if(!(is = valid(v,k,n, at,env))){ return } - if(!k){ - at.node = at.node || n || {}; - if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? - at.node._ = obj_copy(v._); - } - at.node = Node.soul.ify(at.node, Val.rel.is(at.rel)); - at.rel = at.rel || Val.rel.ify(Node.soul(at.node)); - } - if(tmp = env.map){ - tmp.call(env.as || {}, v,k,n, at); - if(obj_has(n,k)){ - v = n[k]; - if(u === v){ - obj_del(n, k); - return; - } - if(!(is = valid(v,k,n, at,env))){ return } - } - } - if(!k){ return at.node } - if(true === is){ - return v; - } - tmp = node(env, {obj: v, path: at.path.concat(k)}); - if(!tmp.node){ return } - return tmp.rel; //{'#': Node.soul(tmp.node)}; - } - function soul(id){ var at = this; - var prev = Val.link.is(at.rel), graph = at.env.graph; - at.rel = at.rel || Val.rel.ify(id); - at.rel[Val.rel._] = id; - if(at.node && at.node[Node._]){ - at.node[Node._][Val.rel._] = id; - } - if(obj_has(graph, prev)){ - graph[id] = graph[prev]; - obj_del(graph, prev); - } - } - function valid(v,k,n, at,env){ var tmp; - if(Val.is(v)){ return true } - if(obj_is(v)){ return 1 } - if(tmp = env.invalid){ - v = tmp.call(env.as || {}, v,k,n); - return valid(v,k,n, at,env); - } - env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!"; - if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." } - } - function seen(env, at){ - var arr = env.seen, i = arr.length, has; - while(i--){ has = arr[i]; - if(at.obj === has.obj){ return has } - } - arr.push(at); - } -}()); -Graph.node = function(node){ - var soul = Node.soul(node); - if(!soul){ return } - return obj_put({}, soul, node); -} -;(function(){ - Graph.to = function(graph, root, opt){ - if(!graph){ return } - var obj = {}; - opt = opt || {seen: {}}; - obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt}); - return obj; - } - function map(v,k){ var tmp, obj; - if(Node._ === k){ - if(obj_empty(v, Val.rel._)){ - return; - } - this.obj[k] = obj_copy(v); - return; - } - if(!(tmp = Val.rel.is(v))){ - this.obj[k] = v; - return; - } - if(obj = this.opt.seen[tmp]){ - this.obj[k] = obj; - return; - } - this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt); - } -}()); -var fn_is = Type.fn.is; -var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; -var u; -module.exports = Graph; + +var Type = require('./type'); +var Val = require('./val'); +var Node = require('./node'); +var Graph = {}; +;(function(){ + Graph.is = function(g, cb, fn, as){ // checks to see if an object is a valid graph. + if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object. + return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object. + } + function map(n, s){ // we invert this because the way'? we check for this is via a negation. + if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph. + if(!this.cb){ return } + nf.n = n; nf.as = this.as; // sequential race conditions aren't races. + this.cb.call(nf.as, n, s, nf); + } + function nf(fn){ // optional callback for each node. + if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value. + } +}()); +;(function(){ + Graph.ify = function(obj, env, as){ + var at = {path: [], obj: obj}; + if(!env){ + env = {}; + } else + if(typeof env === 'string'){ + env = {soul: env}; + } else + if(env instanceof Function){ + env.map = env; + } + if(env.soul){ + at.link = Val.link.ify(env.soul); + } + env.shell = (as||{}).shell; + env.graph = env.graph || {}; + env.seen = env.seen || []; + env.as = env.as || as; + node(env, at); + env.root = at.node; + return env.graph; + } + function node(env, at){ var tmp; + if(tmp = seen(env, at)){ return tmp } + at.env = env; + at.soul = soul; + if(Node.ify(at.obj, map, at)){ + at.link = at.link || Val.link.ify(Node.soul(at.node)); + if(at.obj !== env.shell){ + env.graph[Val.link.is(at.link)] = at.node; + } + } + return at; + } + function map(v,k,n){ + var at = this, env = at.env, is, tmp; + if(Node._ === k && obj_has(v,Val.link._)){ + return n._; // TODO: Bug? + } + if(!(is = valid(v,k,n, at,env))){ return } + if(!k){ + at.node = at.node || n || {}; + if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? + at.node._ = obj_copy(v._); + } + at.node = Node.soul.ify(at.node, Val.link.is(at.link)); + at.link = at.link || Val.link.ify(Node.soul(at.node)); + } + if(tmp = env.map){ + tmp.call(env.as || {}, v,k,n, at); + if(obj_has(n,k)){ + v = n[k]; + if(u === v){ + obj_del(n, k); + return; + } + if(!(is = valid(v,k,n, at,env))){ return } + } + } + if(!k){ return at.node } + if(true === is){ + return v; + } + tmp = node(env, {obj: v, path: at.path.concat(k)}); + if(!tmp.node){ return } + return tmp.link; //{'#': Node.soul(tmp.node)}; + } + function soul(id){ var at = this; + var prev = Val.link.is(at.link), graph = at.env.graph; + at.link = at.link || Val.link.ify(id); + at.link[Val.link._] = id; + if(at.node && at.node[Node._]){ + at.node[Node._][Val.link._] = id; + } + if(obj_has(graph, prev)){ + graph[id] = graph[prev]; + obj_del(graph, prev); + } + } + function valid(v,k,n, at,env){ var tmp; + if(Val.is(v)){ return true } + if(obj_is(v)){ return 1 } + if(tmp = env.invalid){ + v = tmp.call(env.as || {}, v,k,n); + return valid(v,k,n, at,env); + } + env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!"; + if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." } + } + function seen(env, at){ + var arr = env.seen, i = arr.length, has; + while(i--){ has = arr[i]; + if(at.obj === has.obj){ return has } + } + arr.push(at); + } +}()); +Graph.node = function(node){ + var soul = Node.soul(node); + if(!soul){ return } + return obj_put({}, soul, node); +} +;(function(){ + Graph.to = function(graph, root, opt){ + if(!graph){ return } + var obj = {}; + opt = opt || {seen: {}}; + obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt}); + return obj; + } + function map(v,k){ var tmp, obj; + if(Node._ === k){ + if(obj_empty(v, Val.link._)){ + return; + } + this.obj[k] = obj_copy(v); + return; + } + if(!(tmp = Val.link.is(v))){ + this.obj[k] = v; + return; + } + if(obj = this.opt.seen[tmp]){ + this.obj[k] = obj; + return; + } + this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt); + } +}()); +var fn_is = Type.fn.is; +var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; +var u; +module.exports = Graph; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 6f5ad296..f0cb9972 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,8 @@ - -var Gun = require('./root'); -require('./chain'); -require('./back'); -require('./put'); -require('./get'); -module.exports = Gun; + +var Gun = require('./root'); +require('./chain'); +require('./back'); +require('./put'); +require('./get'); +module.exports = Gun; \ No newline at end of file diff --git a/src/map.js b/src/map.js index b3072500..40793447 100644 --- a/src/map.js +++ b/src/map.js @@ -1,35 +1,36 @@ - -var Gun = require('./index'); -Gun.chain.map = function(cb, opt, t){ - var gun = this, cat = gun._, chain; - if(!cb){ - if(chain = cat.each){ return chain } - cat.each = chain = gun.chain(); - chain._.nix = gun.back('nix'); - gun.on('in', map, chain._); - return chain; - } - Gun.log.once("mapfn", "Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); - chain = gun.chain(); - gun.map().on(function(data, key, at, ev){ - var next = (cb||noop).call(this, data, key, at, ev); - if(u === next){ return } - if(data === next){ return chain._.on('in', at) } - if(Gun.is(next)){ return chain._.on('in', next._) } - chain._.on('in', {get: key, put: next}); - }); - return chain; -} -function map(msg){ - if(!msg.put || Gun.val.is(msg.put)){ return this.to.next(msg) } - if(this.as.nix){ this.off() } // TODO: Ugly hack! - obj_map(msg.put, each, {at: this.as, msg: msg}); - this.to.next(msg); -} -function each(v,k){ - if(n_ === k){ return } - var msg = this.msg, gun = msg.$, at = this.at, tmp = (gun.get(k)._); - (tmp.echo || (tmp.echo = {}))[at.id] = tmp.echo[at.id] || at; -} -var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u; + +var Gun = require('./index'); +Gun.chain.map = function(cb, opt, t){ + var gun = this, cat = gun._, chain; + if(!cb){ + if(chain = cat.each){ return chain } + cat.each = chain = gun.chain(); + chain._.nix = gun.back('nix'); + gun.on('in', map, chain._); + return chain; + } + Gun.log.once("mapfn", "Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); + chain = gun.chain(); + gun.map().on(function(data, key, at, ev){ + var next = (cb||noop).call(this, data, key, at, ev); + if(u === next){ return } + if(data === next){ return chain._.on('in', at) } + if(Gun.is(next)){ return chain._.on('in', next._) } + chain._.on('in', {get: key, put: next}); + }); + return chain; +} +function map(msg){ + if(!msg.put || Gun.val.is(msg.put)){ return this.to.next(msg) } + if(this.as.nix){ this.off() } // TODO: Ugly hack! + obj_map(msg.put, each, {at: this.as, msg: msg}); + this.to.next(msg); +} +function each(v,k){ + if(n_ === k){ return } + var msg = this.msg, gun = msg.$, at = gun._, cat = this.at, tmp = at.lex; + if(tmp && !Gun.text.match(k, tmp['.'] || tmp['#'] || tmp)){ return } // review? + ((tmp = gun.get(k)._).echo || (tmp.echo = {}))[cat.id] = tmp.echo[cat.id] || cat; +} +var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u; \ No newline at end of file diff --git a/src/node.js b/src/node.js index 66472d1f..b5f6bcb0 100644 --- a/src/node.js +++ b/src/node.js @@ -1,58 +1,58 @@ - -var Type = require('./type'); -var Val = require('./val'); -var Node = {_: '_'}; -Node.soul = function(n, o){ return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it. -Node.soul.ify = function(n, o){ // put a soul on an object. - o = (typeof o === 'string')? {soul: o} : o || {}; - n = n || {}; // make sure it exists. - n._ = n._ || {}; // make sure meta exists. - n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it. - return n; -} -Node.soul._ = Val.link._; -;(function(){ - Node.is = function(n, cb, as){ var s; // checks to see if an object is a valid node. - if(!obj_is(n)){ return false } // must be an object. - if(s = Node.soul(n)){ // must have a soul on it. - return !obj_map(n, map, {as:as,cb:cb,s:s,n:n}); - } - return false; // nope! This was not a valid node. - } - function map(v, k){ // we invert this because the way we check for this is via a negation. - if(k === Node._){ return } // skip over the metadata. - if(!Val.is(v)){ return true } // it is true that this is an invalid node. - if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value. - } -}()); -;(function(){ - Node.ify = function(obj, o, as){ // returns a node from a shallow object. - if(!o){ o = {} } - else if(typeof o === 'string'){ o = {soul: o} } - else if(o instanceof Function){ o = {map: o} } - if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) } - if(o.node = Node.soul.ify(o.node || {}, o)){ - obj_map(obj, map, {o:o,as:as}); - } - return o.node; // This will only be a valid node if the object wasn't already deep! - } - function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value. - if(o.map){ - tmp = o.map.call(this.as, v, ''+k, o.node); - if(u === tmp){ - obj_del(o.node, k); - } else - if(o.node){ o.node[k] = tmp } - return; - } - if(Val.is(v)){ - o.node[k] = v; - } - } -}()); -var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map; -var text = Type.text, text_random = text.random; -var soul_ = Node.soul._; -var u; -module.exports = Node; + +var Type = require('./type'); +var Val = require('./val'); +var Node = {_: '_'}; +Node.soul = function(n, o){ return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it. +Node.soul.ify = function(n, o){ // put a soul on an object. + o = (typeof o === 'string')? {soul: o} : o || {}; + n = n || {}; // make sure it exists. + n._ = n._ || {}; // make sure meta exists. + n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it. + return n; +} +Node.soul._ = Val.link._; +;(function(){ + Node.is = function(n, cb, as){ var s; // checks to see if an object is a valid node. + if(!obj_is(n)){ return false } // must be an object. + if(s = Node.soul(n)){ // must have a soul on it. + return !obj_map(n, map, {as:as,cb:cb,s:s,n:n}); + } + return false; // nope! This was not a valid node. + } + function map(v, k){ // we invert this because the way we check for this is via a negation. + if(k === Node._){ return } // skip over the metadata. + if(!Val.is(v)){ return true } // it is true that this is an invalid node. + if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value. + } +}()); +;(function(){ + Node.ify = function(obj, o, as){ // returns a node from a shallow object. + if(!o){ o = {} } + else if(typeof o === 'string'){ o = {soul: o} } + else if(o instanceof Function){ o = {map: o} } + if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) } + if(o.node = Node.soul.ify(o.node || {}, o)){ + obj_map(obj, map, {o:o,as:as}); + } + return o.node; // This will only be a valid node if the object wasn't already deep! + } + function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value. + if(o.map){ + tmp = o.map.call(this.as, v, ''+k, o.node); + if(u === tmp){ + obj_del(o.node, k); + } else + if(o.node){ o.node[k] = tmp } + return; + } + if(Val.is(v)){ + o.node[k] = v; + } + } +}()); +var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map; +var text = Type.text, text_random = text.random; +var soul_ = Node.soul._; +var u; +module.exports = Node; \ No newline at end of file diff --git a/src/on.js b/src/on.js index f9b3998a..2b145457 100644 --- a/src/on.js +++ b/src/on.js @@ -1,137 +1,141 @@ - -var Gun = require('./index'); -Gun.chain.on = function(tag, arg, eas, as){ - var gun = this, at = gun._, tmp, act, off; - if(typeof tag === 'string'){ - if(!arg){ return at.on(tag) } - act = at.on(tag, arg, eas || at, as); - if(eas && eas.$){ - (eas.subs || (eas.subs = [])).push(act); - } - return gun; - } - var opt = arg; - opt = (true === opt)? {change: true} : opt || {}; - opt.at = at; - opt.ok = tag; - //opt.last = {}; - gun.get(ok, opt); // TODO: PERF! Event listener leak!!!? - return gun; -} - -function ok(msg, ev){ var opt = this; - var gun = msg.$, at = (gun||{})._ || {}, data = at.put || msg.put, cat = opt.at, tmp; - if(u === data){ - return; - } - if(tmp = msg.$$){ - tmp = (msg.$$._); - if(u === tmp.put){ - return; - } - data = tmp.put; - } - if(opt.change){ // TODO: BUG? Move above the undef checks? - data = msg.put; - } - // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE - //if(tmp.put === data && tmp.get === id && !Gun.node.soul(data)){ return } - //tmp.put = data; - //tmp.get = id; - // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE - //at.last = data; - if(opt.as){ - opt.ok.call(opt.as, msg, ev); - } else { - opt.ok.call(gun, data, msg.get, msg, ev); - } -} - -Gun.chain.val = function(cb, opt){ - Gun.log.once("onceval", "Future Breaking API Change: .val -> .once, apologies unexpected."); - return this.once(cb, opt); -} -Gun.chain.once = function(cb, opt){ - var gun = this, at = gun._, data = at.put; - if(0 < at.ack && u !== data){ - (cb || noop).call(gun, data, at.get); - return gun; - } - if(cb){ - (opt = opt || {}).ok = cb; - opt.at = at; - opt.out = {'#': Gun.text.random(9)}; - gun.get(val, {as: opt}); - opt.async = true; //opt.async = at.stun? 1 : true; - } else { - Gun.log.once("valonce", "Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); - var chain = gun.chain(); - chain._.nix = gun.once(function(){ - chain._.on('in', gun._); - }); - return chain; - } - return gun; -} - -function val(msg, eve, to){ - var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; - if(tmp = msg.$$){ - link = tmp = (msg.$$._); - if(u !== link.put){ - data = link.put; - } - } - if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } - if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) - || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (!to && (link||at).ack <= tmp))){ - tmp = (eve.wait = {})[at.id] = setTimeout(function(){ - val.call({as:opt}, msg, eve, tmp || 1); - }, opt.wait || 99); - return; - } - if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) } - eve.rid(msg); - opt.ok.call(gun || opt.$, data, msg.get); -} - -Gun.chain.off = function(){ - // make off more aggressive. Warning, it might backfire! - var gun = this, at = gun._, tmp; - var cat = at.back; - if(!cat){ return } - if(tmp = cat.next){ - if(tmp[at.get]){ - obj_del(tmp, at.get); - } else { - - } - } - if(tmp = cat.ask){ - obj_del(tmp, at.get); - } - if(tmp = cat.put){ - obj_del(tmp, at.get); - } - if(tmp = at.soul){ - obj_del(cat.root.graph, tmp); - } - if(tmp = at.map){ - obj_map(tmp, function(at){ - if(at.link){ - cat.root.$.get(at.link).off(); - } - }); - } - if(tmp = at.next){ - obj_map(tmp, function(neat){ - neat.$.off(); - }); - } - at.on('off', {}); - return gun; -} -var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to; -var rel = Gun.val.link; -var empty = {}, noop = function(){}, u; + +var Gun = require('./index'); +Gun.chain.on = function(tag, arg, eas, as){ + var gun = this, at = gun._, tmp, act, off; + if(typeof tag === 'string'){ + if(!arg){ return at.on(tag) } + act = at.on(tag, arg, eas || at, as); + if(eas && eas.$){ + (eas.subs || (eas.subs = [])).push(act); + } + return gun; + } + var opt = arg; + opt = (true === opt)? {change: true} : opt || {}; + opt.at = at; + opt.ok = tag; + //opt.last = {}; + gun.get(ok, opt); // TODO: PERF! Event listener leak!!!? + return gun; +} + +function ok(msg, ev){ var opt = this; + var gun = msg.$, at = (gun||{})._ || {}, data = at.put || msg.put, cat = opt.at, tmp; + if(u === data){ + return; + } + if(tmp = msg.$$){ + tmp = (msg.$$._); + if(u === tmp.put){ + return; + } + data = tmp.put; + } + if(opt.change){ // TODO: BUG? Move above the undef checks? + data = msg.put; + } + // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE + //if(tmp.put === data && tmp.get === id && !Gun.node.soul(data)){ return } + //tmp.put = data; + //tmp.get = id; + // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE + //at.last = data; + if(opt.as){ + opt.ok.call(opt.as, msg, ev); + } else { + opt.ok.call(gun, data, msg.get, msg, ev); + } +} + +Gun.chain.val = function(cb, opt){ + Gun.log.once("onceval", "Future Breaking API Change: .val -> .once, apologies unexpected."); + return this.once(cb, opt); +} +Gun.chain.once = function(cb, opt){ + var gun = this, at = gun._, data = at.put; + if(0 < at.ack && u !== data){ + (cb || noop).call(gun, data, at.get); + return gun; + } + if(cb){ + (opt = opt || {}).ok = cb; + opt.at = at; + opt.out = {'#': Gun.text.random(9)}; + gun.get(val, {as: opt}); + opt.async = true; //opt.async = at.stun? 1 : true; + } else { + Gun.log.once("valonce", "Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); + var chain = gun.chain(); + chain._.nix = gun.once(function(){ + chain._.on('in', gun._); + }); + return chain; + } + return gun; +} + +function val(msg, eve, to){ + if(!msg.$){ eve.off(); return } + var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp; + if(tmp = msg.$$){ + link = tmp = (msg.$$._); + if(u !== link.put){ + data = link.put; + } + } + if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) } + eve.ack = (eve.ack||0)+1; + if(!to && u === data && eve.ack <= (opt.acks || Object.keys(at.root.opt.peers).length)){ return } + if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) + || (u === data && (tmp = Object.keys(at.root.opt.peers).length) && (!to && (link||at).ack < tmp))){ + tmp = (eve.wait = {})[at.id] = setTimeout(function(){ + val.call({as:opt}, msg, eve, tmp || 1); + }, opt.wait || 99); + return; + } + if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) } + eve.rid(msg); + opt.ok.call(gun || opt.$, data, msg.get); +} + +Gun.chain.off = function(){ + // make off more aggressive. Warning, it might backfire! + var gun = this, at = gun._, tmp; + var cat = at.back; + if(!cat){ return } + at.ack = 0; // so can resubscribe. + if(tmp = cat.next){ + if(tmp[at.get]){ + obj_del(tmp, at.get); + } else { + + } + } + if(tmp = cat.ask){ + obj_del(tmp, at.get); + } + if(tmp = cat.put){ + obj_del(tmp, at.get); + } + if(tmp = at.soul){ + obj_del(cat.root.graph, tmp); + } + if(tmp = at.map){ + obj_map(tmp, function(at){ + if(at.link){ + cat.root.$.get(at.link).off(); + } + }); + } + if(tmp = at.next){ + obj_map(tmp, function(neat){ + neat.$.off(); + }); + } + at.on('off', {}); + return gun; +} +var obj = Gun.obj, obj_map = obj.map, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to; +var rel = Gun.val.link; +var empty = {}, noop = function(){}, u; \ No newline at end of file diff --git a/src/onto.js b/src/onto.js index 4851d32b..cc9f6db6 100644 --- a/src/onto.js +++ b/src/onto.js @@ -1,39 +1,39 @@ - -// On event emitter generic javascript utility. -module.exports = function onto(tag, arg, as){ - if(!tag){ return {to: onto} } - var u, tag = (this.tag || (this.tag = {}))[tag] || - (this.tag[tag] = {tag: tag, to: onto._ = { - next: function(arg){ var tmp; - if((tmp = this.to)){ - tmp.next(arg); - }} - }}); - if(arg instanceof Function){ - var be = { - off: onto.off || - (onto.off = function(){ - if(this.next === onto._.next){ return !0 } - if(this === this.the.last){ - this.the.last = this.back; - } - this.to.back = this.back; - this.next = onto._.next; - this.back.to = this.to; - if(this.the.last === this.the){ - delete this.on.tag[this.the.tag]; - } - }), - to: onto._, - next: arg, - the: tag, - on: this, - as: as, - }; - (be.back = tag.last || tag).to = be; - return tag.last = be; - } - if((tag = tag.to) && u !== arg){ tag.next(arg) } - return tag; -}; + +// On event emitter generic javascript utility. +module.exports = function onto(tag, arg, as){ + if(!tag){ return {to: onto} } + var u, tag = (this.tag || (this.tag = {}))[tag] || + (this.tag[tag] = {tag: tag, to: onto._ = { + next: function(arg){ var tmp; + if((tmp = this.to)){ + tmp.next(arg); + }} + }}); + if(arg instanceof Function){ + var be = { + off: onto.off || + (onto.off = function(){ + if(this.next === onto._.next){ return !0 } + if(this === this.the.last){ + this.the.last = this.back; + } + this.to.back = this.back; + this.next = onto._.next; + this.back.to = this.to; + if(this.the.last === this.the){ + delete this.on.tag[this.the.tag]; + } + }), + to: onto._, + next: arg, + the: tag, + on: this, + as: as, + }; + (be.back = tag.last || tag).to = be; + return tag.last = be; + } + if((tag = tag.to) && u !== arg){ tag.next(arg) } + return tag; +}; \ No newline at end of file diff --git a/src/polyfill/unbuild.js b/src/polyfill/unbuild.js index 20d0d0e9..c55a7d95 100644 --- a/src/polyfill/unbuild.js +++ b/src/polyfill/unbuild.js @@ -1,17 +1,17 @@ - - var root; - if(typeof window !== "undefined"){ root = window } - if(typeof global !== "undefined"){ root = global } - root = root || {}; - var console = root.console || {log: function(){}}; - function USE(arg, req){ - return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } - } - if(typeof module !== "undefined"){ var common = module } + + var root; + if(typeof window !== "undefined"){ root = window } + if(typeof global !== "undefined"){ root = global } + root = root || {}; + var console = root.console || {log: function(){}}; + function USE(arg, req){ + return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ + arg(mod = {exports: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var common = module } \ No newline at end of file diff --git a/src/put.js b/src/put.js index 967310b1..4e23d8b4 100644 --- a/src/put.js +++ b/src/put.js @@ -1,216 +1,230 @@ - -var Gun = require('./root'); -Gun.chain.put = function(data, cb, as){ - // #soul.has=value>state - // ~who#where.where=what>when@was - // TODO: BUG! Put probably cannot handle plural chains! - var gun = this, at = (gun._), root = at.root.$, tmp; - as = as || {}; - as.data = data; - as.via = as.$ = as.via || as.$ || gun; - if(typeof cb === 'string'){ - as.soul = cb; - } else { - as.ack = as.ack || cb; - } - if(at.soul){ - as.soul = at.soul; - } - if(as.soul || root === gun){ - if(!obj_is(as.data)){ - (as.ack||noop).call(as, as.out = {err: Gun.log("Data saved to the root level of the graph must be a node (an object), not a", (typeof as.data), 'of "' + as.data + '"!')}); - if(as.res){ as.res() } - return gun; - } - as.soul = as.soul || (as.not = Gun.node.soul(as.data) || (as.via.back('opt.uuid') || Gun.text.random)()); - if(!as.soul){ // polyfill async uuid for SEA - as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback - if(err){ return Gun.log(err) } // TODO: Handle error! - (as.ref||as.$).put(as.data, as.soul = soul, as); - }); - return gun; - } - as.$ = root.get(as.soul); - as.ref = as.$; - ify(as); - return gun; - } - if(Gun.is(data)){ - data.get(function(soul, o, msg){ - if(!soul && Gun.val.is(msg.put)){ - return Gun.log("The reference you are saving is a", typeof msg.put, '"'+ msg.put +'", not a node (object)!'); - } - gun.put(Gun.val.rel.ify(soul), cb, as); - }, true); - return gun; - } - as.ref = as.ref || (root._ === (tmp = at.back))? gun : tmp.$; - if(as.ref._.soul && Gun.val.is(as.data) && at.get){ - as.data = obj_put({}, at.get, as.data); - as.ref.put(as.data, as.soul, as); - return gun; - } - as.ref.get(any, true, {as: as}); - if(!as.out){ - // TODO: Perf idea! Make a global lock, that blocks everything while it is on, but if it is on the lock it does the expensive lookup to see if it is a dependent write or not and if not then it proceeds full speed. Meh? For write heavy async apps that would be terrible. - as.res = as.res || stun; // Gun.on.stun(as.ref); // TODO: BUG! Deal with locking? - as.$._.stun = as.ref._.stun; - } - return gun; -}; - -function ify(as){ - as.batch = batch; - var opt = as.opt||{}, env = as.env = Gun.state.map(map, opt.state); - env.soul = as.soul; - as.graph = Gun.graph.ify(as.data, env, as); - if(env.err){ - (as.ack||noop).call(as, as.out = {err: Gun.log(env.err)}); - if(as.res){ as.res() } - return; - } - as.batch(); -} - -function stun(cb){ - if(cb){ cb() } - return; - var as = this; - if(!as.ref){ return } - if(cb){ - as.after = as.ref._.tag; - as.now = as.ref._.tag = {}; - cb(); - return; - } - if(as.after){ - as.ref._.tag = as.after; - } -} - -function batch(){ var as = this; - if(!as.graph || obj_map(as.stun, no)){ return } - as.res = as.res || function(cb){ if(cb){ cb() } }; - as.res(function(){ - var cat = (as.$.back(-1)._), ask = cat.ask(function(ack){ - cat.root.on('ack', ack); - if(ack.err){ Gun.log(ack) } - if(!ack.lack){ this.off() } // One response is good enough for us currently. Later we may want to adjust this. - if(!as.ack){ return } - as.ack(ack, this); - }, as.opt); - // NOW is a hack to get synchronous replies to correctly call. - // and STOP is a hack to get async behavior to correctly call. - // neither of these are ideal, need to be fixed without hacks, - // but for now, this works for current tests. :/ - var tmp = cat.root.now; obj.del(cat.root, 'now'); - var mum = cat.root.mum; cat.root.mum = {}; - (as.ref._).on('out', { - $: as.ref, put: as.out = as.env.graph, opt: as.opt, '#': ask - }); - cat.root.mum = mum? obj.to(mum, cat.root.mum) : mum; - cat.root.now = tmp; - }, as); - if(as.res){ as.res() } -} function no(v,k){ if(v){ return true } } - -function map(v,k,n, at){ var as = this; - var is = Gun.is(v); - if(k || !at.path.length){ return } - (as.res||iife)(function(){ - var path = at.path, ref = as.ref, opt = as.opt; - var i = 0, l = path.length; - for(i; i < l; i++){ - ref = ref.get(path[i]); - } - if(is){ ref = v } - var id = (ref._).dub; - if(id || (id = Gun.node.soul(at.obj))){ - ref.back(-1).get(id); - at.soul(id); - return; - } - (as.stun = as.stun || {})[path] = true; - ref.get(soul, true, {as: {at: at, as: as, p:path}}); - }, {as: as, at: at}); - //if(is){ return {} } -} - -function soul(id, as, msg, eve){ - var as = as.as, cat = as.at; as = as.as; - var at = ((msg || {}).$ || {})._ || {}; - id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.rel.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous? - if(eve){ eve.stun = true } - if(!id){ // polyfill async uuid for SEA - at.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback - if(err){ return Gun.log(err) } // TODO: Handle error. - solve(at, at.dub = at.dub || id, cat, as); - }); - return; - } - solve(at, at.dub = id, cat, as); -} - -function solve(at, id, cat, as){ - at.$.back(-1).get(id); - cat.soul(id); - as.stun[cat.path] = false; - as.batch(); -} - -function any(soul, as, msg, eve){ - as = as.as; - if(!msg.$ || !msg.$._){ return } // TODO: Handle - if(msg.err){ // TODO: Handle - console.log("Please report this as an issue! Put.any.err"); - return; - } - var at = (msg.$._), data = at.put, opt = as.opt||{}, root, tmp; - if((tmp = as.ref) && tmp._.now){ return } - if(eve){ eve.stun = true } - if(as.ref !== as.$){ - tmp = (as.$._).get || at.get; - if(!tmp){ // TODO: Handle - console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? - return; - } - as.data = obj_put({}, tmp, as.data); - tmp = null; - } - if(u === data){ - if(!at.get){ return } // TODO: Handle - if(!soul){ - tmp = at.$.back(function(at){ - if(at.link || at.soul){ return at.link || at.soul } - as.data = obj_put({}, at.get, as.data); - }); - } - tmp = tmp || at.soul || at.link || at.dub;// || at.get; - at = tmp? (at.root.$.get(tmp)._) : at; - as.soul = tmp; - data = as.data; - } - if(!as.not && !(as.soul = as.soul || soul)){ - if(as.path && obj_is(as.data)){ - as.soul = (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); - } else { - //as.data = obj_put({}, as.$._.get, as.data); - if(node_ == at.get){ - as.soul = (at.put||empty)['#'] || at.dub; - } - as.soul = as.soul || at.soul || at.soul || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); - } - if(!as.soul){ // polyfill async uuid for SEA - as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback - if(err){ return Gun.log(err) } // Handle error. - as.ref.put(as.data, as.soul = soul, as); - }); - return; - } - } - as.ref.put(as.data, as.soul, as); -} -var obj = Gun.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; -var u, empty = {}, noop = function(){}, iife = function(fn,as){fn.call(as||empty)}; -var node_ = Gun.node._; + +var Gun = require('./root'); +Gun.chain.put = function(data, cb, as){ + // #soul.has=value>state + // ~who#where.where=what>when@was + // TODO: BUG! Put probably cannot handle plural chains! + var gun = this, at = (gun._), root = at.root.$, ctx = root._, M = 100, tmp; + if(!ctx.puta){ if(tmp = ctx.puts){ if(tmp > M){ // without this, when synchronous, writes to a 'not found' pile up, when 'not found' resolves it recursively calls `put` which incrementally resolves each write. Stack overflow limits can be as low as 10K, so this limit is hardcoded to 1% of 10K. + (ctx.stack || (ctx.stack = [])).push([gun, data, cb, as]); + if(ctx.puto){ return } + ctx.puto = setTimeout(function drain(){ + var d = ctx.stack.splice(0,M), i = 0, at; ctx.puta = true; + while(at = d[i++]){ at[0].put(at[1], at[2], at[3]) } delete ctx.puta; + if(ctx.stack.length){ return ctx.puto = setTimeout(drain, 0) } + ctx.stack = ctx.puts = ctx.puto = null; + }, 0); + return gun; + } ++ctx.puts } else { ctx.puts = 1 } } + as = as || {}; + as.data = data; + as.via = as.$ = as.via || as.$ || gun; + if(typeof cb === 'string'){ + as.soul = cb; + } else { + as.ack = as.ack || cb; + } + if(at.soul){ + as.soul = at.soul; + } + if(as.soul || root === gun){ + if(!obj_is(as.data)){ + (as.ack||noop).call(as, as.out = {err: Gun.log("Data saved to the root level of the graph must be a node (an object), not a", (typeof as.data), 'of "' + as.data + '"!')}); + if(as.res){ as.res() } + return gun; + } + as.soul = as.soul || (as.not = Gun.node.soul(as.data) || (as.via.back('opt.uuid') || Gun.text.random)()); + if(!as.soul){ // polyfill async uuid for SEA + as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback + if(err){ return Gun.log(err) } // TODO: Handle error! + (as.ref||as.$).put(as.data, as.soul = soul, as); + }); + return gun; + } + as.$ = root.get(as.soul); + as.ref = as.$; + ify(as); + return gun; + } + if(Gun.is(data)){ + data.get(function(soul, o, msg){ + if(!soul){ + return Gun.log("The reference you are saving is a", typeof msg.put, '"'+ msg.put +'", not a node (object)!'); + } + gun.put(Gun.val.link.ify(soul), cb, as); + }, true); + return gun; + } + if(at.has && (tmp = Gun.val.link.is(data))){ at.dub = tmp } + as.ref = as.ref || (root._ === (tmp = at.back))? gun : tmp.$; + if(as.ref._.soul && Gun.val.is(as.data) && at.get){ + as.data = obj_put({}, at.get, as.data); + as.ref.put(as.data, as.soul, as); + return gun; + } + as.ref.get(any, true, {as: as}); + if(!as.out){ + // TODO: Perf idea! Make a global lock, that blocks everything while it is on, but if it is on the lock it does the expensive lookup to see if it is a dependent write or not and if not then it proceeds full speed. Meh? For write heavy async apps that would be terrible. + as.res = as.res || stun; // Gun.on.stun(as.ref); // TODO: BUG! Deal with locking? + as.$._.stun = as.ref._.stun; + } + return gun; +}; + +function ify(as){ + as.batch = batch; + var opt = as.opt||{}, env = as.env = Gun.state.map(map, opt.state); + env.soul = as.soul; + as.graph = Gun.graph.ify(as.data, env, as); + if(env.err){ + (as.ack||noop).call(as, as.out = {err: Gun.log(env.err)}); + if(as.res){ as.res() } + return; + } + as.batch(); +} + +function stun(cb){ + if(cb){ cb() } + return; + var as = this; + if(!as.ref){ return } + if(cb){ + as.after = as.ref._.tag; + as.now = as.ref._.tag = {}; + cb(); + return; + } + if(as.after){ + as.ref._.tag = as.after; + } +} + +function batch(){ var as = this; + if(!as.graph || obj_map(as.stun, no)){ return } + as.res = as.res || function(cb){ if(cb){ cb() } }; + as.res(function(){ + var cat = (as.$.back(-1)._), ask = cat.ask(function(ack){ + cat.root.on('ack', ack); + if(ack.err){ Gun.log(ack) } + if(++acks > (as.acks || 0)){ this.off() } // Adjustable ACKs! Only 1 by default. + if(!as.ack){ return } + as.ack(ack, this); + //--C; + }, as.opt), acks = 0; + //C++; + // NOW is a hack to get synchronous replies to correctly call. + // and STOP is a hack to get async behavior to correctly call. + // neither of these are ideal, need to be fixed without hacks, + // but for now, this works for current tests. :/ + var tmp = cat.root.now; obj.del(cat.root, 'now'); + var mum = cat.root.mum; cat.root.mum = {}; + (as.ref._).on('out', { + $: as.ref, put: as.out = as.env.graph, opt: as.opt, '#': ask + }); + cat.root.mum = mum? obj.to(mum, cat.root.mum) : mum; + cat.root.now = tmp; + }, as); + if(as.res){ as.res() } +} function no(v,k){ if(v){ return true } } + +function map(v,k,n, at){ var as = this; + var is = Gun.is(v); + if(k || !at.path.length){ return } + (as.res||iife)(function(){ + var path = at.path, ref = as.ref, opt = as.opt; + var i = 0, l = path.length; + for(i; i < l; i++){ + ref = ref.get(path[i]); + } + if(is){ ref = v } + var id = (ref._).dub; + if(id || (id = Gun.node.soul(at.obj))){ + ref.back(-1).get(id); + at.soul(id); + return; + } + (as.stun = as.stun || {})[path] = true; + ref.get(soul, true, {as: {at: at, as: as, p:path}}); + }, {as: as, at: at}); + //if(is){ return {} } +} + +function soul(id, as, msg, eve){ + var as = as.as, cat = as.at; as = as.as; + var at = ((msg || {}).$ || {})._ || {}; + id = at.dub = at.dub || id || Gun.node.soul(cat.obj) || Gun.node.soul(msg.put || at.put) || Gun.val.link.is(msg.put || at.put) || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous? + if(eve){ eve.stun = true } + if(!id){ // polyfill async uuid for SEA + as.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback + if(err){ return Gun.log(err) } // TODO: Handle error. + solve(at, at.dub = at.dub || id, cat, as); + }); + return; + } + solve(at, at.dub = id, cat, as); +} + +function solve(at, id, cat, as){ + at.$.back(-1).get(id); + cat.soul(id); + as.stun[cat.path] = false; + as.batch(); +} + +function any(soul, as, msg, eve){ + as = as.as; + if(!msg.$ || !msg.$._){ return } // TODO: Handle + if(msg.err){ // TODO: Handle + console.log("Please report this as an issue! Put.any.err"); + return; + } + var at = (msg.$._), data = at.put, opt = as.opt||{}, root, tmp; + if((tmp = as.ref) && tmp._.now){ return } + if(eve){ eve.stun = true } + if(as.ref !== as.$){ + tmp = (as.$._).get || at.get; + if(!tmp){ // TODO: Handle + console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? + return; + } + as.data = obj_put({}, tmp, as.data); + tmp = null; + } + if(u === data){ + if(!at.get){ return } // TODO: Handle + if(!soul){ + tmp = at.$.back(function(at){ + if(at.link || at.soul){ return at.link || at.soul } + as.data = obj_put({}, at.get, as.data); + }); + } + tmp = tmp || at.soul || at.link || at.dub;// || at.get; + at = tmp? (at.root.$.get(tmp)._) : at; + as.soul = tmp; + data = as.data; + } + if(!as.not && !(as.soul = as.soul || soul)){ + if(as.path && obj_is(as.data)){ + as.soul = (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); + } else { + //as.data = obj_put({}, as.$._.get, as.data); + if(node_ == at.get){ + as.soul = (at.put||empty)['#'] || at.dub; + } + as.soul = as.soul || at.soul || at.link || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)(); + } + if(!as.soul){ // polyfill async uuid for SEA + as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback + if(err){ return Gun.log(err) } // Handle error. + as.ref.put(as.data, as.soul = soul, as); + }); + return; + } + } + as.ref.put(as.data, as.soul, as); +} +var obj = Gun.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; +var u, empty = {}, noop = function(){}, iife = function(fn,as){fn.call(as||empty)}; +var node_ = Gun.node._; \ No newline at end of file diff --git a/src/root.js b/src/root.js index 642a96b7..94a2f244 100644 --- a/src/root.js +++ b/src/root.js @@ -1,247 +1,236 @@ - - -function Gun(o){ - if(o instanceof Gun){ return (this._ = {gun: this, $: this}).$ } - if(!(this instanceof Gun)){ return new Gun(o) } - return Gun.create(this._ = {gun: this, $: this, opt: o}); -} - -Gun.is = function($){ return ($ instanceof Gun) || ($ && $._ && ($ === $._.$)) || false } - -Gun.version = 0.9; - -Gun.chain = Gun.prototype; -Gun.chain.toJSON = function(){}; - -var Type = require('./type'); -Type.obj.to(Type, Gun); -Gun.HAM = require('./HAM'); -Gun.val = require('./val'); -Gun.node = require('./node'); -Gun.state = require('./state'); -Gun.graph = require('./graph'); -Gun.on = require('./onto'); -Gun.ask = require('./ask'); -Gun.dup = require('./dup'); - -;(function(){ - Gun.create = function(at){ - at.root = at.root || at; - at.graph = at.graph || {}; - at.on = at.on || Gun.on; - at.ask = at.ask || Gun.ask; - at.dup = at.dup || Gun.dup(); - var gun = at.$.opt(at.opt); - if(!at.once){ - at.on('in', root, at); - at.on('out', root, {at: at, out: root}); - Gun.on('create', at); - at.on('create', at); - } - at.once = 1; - return gun; - } - function root(msg){ - //add to.next(at); // TODO: MISSING FEATURE!!! - var ev = this, as = ev.as, at = as.at || as, gun = at.$, dup, tmp; - if(!(tmp = msg['#'])){ tmp = msg['#'] = text_rand(9) } - if((dup = at.dup).check(tmp)){ - if(as.out === msg.out){ - msg.out = u; - ev.to.next(msg); - } - return; - } - dup.track(tmp); - if(!at.ask(msg['@'], msg)){ - if(msg.get){ - Gun.on.get(msg, gun); //at.on('get', get(msg)); - } - if(msg.put){ - Gun.on.put(msg, gun); //at.on('put', put(msg)); - } - } - ev.to.next(msg); - if(!as.out){ - msg.out = root; - at.on('out', msg); - } - } -}()); - -;(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.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){ - setTimeout(function(){ - Gun.on.put(msg, gun); - }, ctx.defer - ctx.machine); - } - if(!ctx.diff){ return } - at.on('put', obj_to(msg, {put: ctx.diff})); - }; - 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+"'!" } - var vertex = ctx.graph[soul] || empty, was = Gun.state.is(vertex, key, true), known = vertex[key]; - var HAM = Gun.HAM(ctx.machine, state, was, val, known); - if(!HAM.incoming){ - if(HAM.defer){ // pick the lowest - ctx.defer = (state < (ctx.defer || Infinity))? state : ctx.defer; - } - return; - } - ctx.put[soul] = Gun.state.to(node, key, ctx.put[soul]); - (ctx.diff || (ctx.diff = {}))[soul] = Gun.state.to(node, key, ctx.diff[soul]); - ctx.souls[soul] = true; - } - function merge(node, soul){ - var ctx = this, cat = ctx.$._, at = (cat.next || empty)[soul]; - if(!at){ - if(!(cat.opt||empty).super){ - ctx.souls[soul] = false; - return; - } - at = (ctx.$.get(soul)._); - } - var msg = ctx.map[soul] = { - put: node, - get: soul, - $: at.$ - }, as = {ctx: ctx, msg: msg}; - ctx.async = !!cat.tag.node; - if(ctx.ack){ msg['@'] = ctx.ack } - obj_map(node, each, as); - if(!ctx.async){ return } - if(!ctx.and){ - // If it is async, we only need to setup one listener per context (ctx) - cat.on('node', function(m){ - this.to.next(m); // make sure to call other context's listeners. - if(m !== ctx.map[m.get]){ return } // filter out events not from this context! - ctx.souls[m.get] = false; // set our many-async flag - obj_map(m.put, patch, m); // merge into view - if(obj_map(ctx.souls, function(v){ if(v){ return v } })){ return } // if flag still outstanding, keep waiting. - if(ctx.c){ return } ctx.c = 1; // failsafe for only being called once per context. - this.off(); - obj_map(ctx.map, map, ctx); // all done, trigger chains. - }); - } - ctx.and = true; - cat.on('node', msg); // each node on the current context's graph needs to be emitted though. - } - function each(val, key){ - var ctx = this.ctx, graph = ctx.graph, msg = this.msg, soul = msg.get, node = msg.put, at = (msg.$._), tmp; - graph[soul] = Gun.state.to(node, key, graph[soul]); - if(ctx.async){ return } - at.put = Gun.state.to(node, key, at.put); - } - function patch(val, key){ - var msg = this, node = msg.put, at = (msg.$._); - at.put = Gun.state.to(node, key, at.put); - } - function map(msg, soul){ - if(!msg.$){ return } - this.cat.stop = this.stop; // temporary fix till a better solution? - (msg.$._).on('in', msg); - this.cat.stop = null; // temporary fix till a better solution? - } - - Gun.on.get = function(msg, gun){ - var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; - var next = root.next || (root.next = {}), at = next[soul]; - if(obj_has(soul, '*')){ // TEMPORARY HACK FOR MARTTI, TESTING - var graph = {}; - Gun.obj.map(root.graph, function(node, s){ - if(Gun.text.match(s, soul)){ - graph[s] = Gun.obj.copy(node); - } - }); - if(!Gun.obj.empty(graph)){ - root.on('in', { - '@': msg['#'], - how: '*', - put: graph, - $: gun - }); - } - } // TEMPORARY HACK FOR MARTTI, TESTING - if(!node){ return root.on('get', msg) } - if(has){ - if(!obj_has(node, has)){ return root.on('get', msg) } - node = Gun.state.to(node, has); - // If we have a key in-memory, do we really need to fetch? - // 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.graph.node(node); - tmp = (at||empty).ack; - root.on('in', { - '@': msg['#'], - how: 'mem', - put: node, - $: gun - }); - //if(0 < tmp){ return } - root.on('get', msg); - } -}()); - -;(function(){ - Gun.chain.opt = function(opt){ - opt = opt || {}; - var gun = this, at = gun._, tmp = opt.peers || opt; - if(!obj_is(opt)){ opt = {} } - if(!obj_is(at.opt)){ at.opt = opt } - if(text_is(tmp)){ tmp = [tmp] } - if(list_is(tmp)){ - tmp = obj_map(tmp, function(url, i, map){ - map(url, {url: url}); - }); - if(!obj_is(at.opt.peers)){ at.opt.peers = {}} - at.opt.peers = obj_to(tmp, at.opt.peers); - } - at.opt.peers = at.opt.peers || {}; - obj_to(opt, at.opt); // copies options on to `at.opt` only if not already taken. - Gun.on('opt', at); - at.opt.uuid = at.opt.uuid || function(){ return state_lex() + text_rand(12) } - return gun; - } -}()); - -var list_is = Gun.list.is; -var text = Gun.text, text_is = text.is, text_rand = text.random; -var obj = Gun.obj, obj_is = obj.is, obj_has = obj.has, obj_to = obj.to, obj_map = obj.map, obj_copy = obj.copy; -var state_lex = Gun.state.lex, _soul = Gun.val.rel._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is; -var empty = {}, u; - -console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) }; - -Gun.log = function(){ return (!Gun.log.off && console.log.apply(console, arguments)), [].slice.call(arguments).join(' ') } -Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) } - -;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; -Gun.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'!"); -;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; - -if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } -try{ if(typeof common !== "undefined"){ common.exports = Gun } }catch(e){} -module.exports = Gun; - -/*Gun.on('opt', function(ctx){ // FOR TESTING PURPOSES - this.to.next(ctx); - if(ctx.once){ return } - ctx.on('node', function(msg){ - var to = this.to; - //Gun.node.is(msg.put, function(v,k){ msg.put[k] = v + v }); - setTimeout(function(){ - to.next(msg); - },1); - }); -});*/ + + +function Gun(o){ + if(o instanceof Gun){ return (this._ = {gun: this, $: this}).$ } + if(!(this instanceof Gun)){ return new Gun(o) } + return Gun.create(this._ = {gun: this, $: this, opt: o}); +} + +Gun.is = function($){ return ($ instanceof Gun) || ($ && $._ && ($ === $._.$)) || false } + +Gun.version = 0.9; + +Gun.chain = Gun.prototype; +Gun.chain.toJSON = function(){}; + +var Type = require('./type'); +Type.obj.to(Type, Gun); +Gun.HAM = require('./HAM'); +Gun.val = require('./val'); +Gun.node = require('./node'); +Gun.state = require('./state'); +Gun.graph = require('./graph'); +Gun.on = require('./onto'); +Gun.ask = require('./ask'); +Gun.dup = require('./dup'); + +;(function(){ + Gun.create = function(at){ + at.root = at.root || at; + at.graph = at.graph || {}; + at.on = at.on || Gun.on; + at.ask = at.ask || Gun.ask; + at.dup = at.dup || Gun.dup(); + var gun = at.$.opt(at.opt); + if(!at.once){ + at.on('in', root, at); + at.on('out', root, {at: at, out: root}); + Gun.on('create', at); + at.on('create', at); + } + at.once = 1; + return gun; + } + function root(msg){ + //add to.next(at); // TODO: MISSING FEATURE!!! + var ev = this, as = ev.as, at = as.at || as, gun = at.$, dup, tmp; + if(!(tmp = msg['#'])){ tmp = msg['#'] = text_rand(9) } + if((dup = at.dup).check(tmp)){ + if(as.out === msg.out){ + msg.out = u; + ev.to.next(msg); + } + return; + } + dup.track(tmp); + if(!at.ask(msg['@'], msg)){ + if(msg.get){ + Gun.on.get(msg, gun); //at.on('get', get(msg)); + } + if(msg.put){ + Gun.on.put(msg, gun); //at.on('put', put(msg)); + } + } + ev.to.next(msg); + if(!as.out){ + msg.out = root; + at.on('out', msg); + } + } +}()); + +;(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.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){ + setTimeout(function(){ + Gun.on.put(msg, gun); + }, ctx.defer - ctx.machine); + } + if(!ctx.diff){ return } + at.on('put', obj_to(msg, {put: ctx.diff})); + }; + 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+"'!" } + var vertex = ctx.graph[soul] || empty, was = Gun.state.is(vertex, key, true), known = vertex[key]; + var HAM = Gun.HAM(ctx.machine, state, was, val, known); + if(!HAM.incoming){ + if(HAM.defer){ // pick the lowest + ctx.defer = (state < (ctx.defer || Infinity))? state : ctx.defer; + } + return; + } + ctx.put[soul] = Gun.state.to(node, key, ctx.put[soul]); + (ctx.diff || (ctx.diff = {}))[soul] = Gun.state.to(node, key, ctx.diff[soul]); + ctx.souls[soul] = true; + } + function merge(node, soul){ + var ctx = this, cat = ctx.$._, at = (cat.next || empty)[soul]; + if(!at){ + if(!(cat.opt||empty).super){ + ctx.souls[soul] = false; + return; + } + at = (ctx.$.get(soul)._); + } + var msg = ctx.map[soul] = { + put: node, + get: soul, + $: at.$ + }, as = {ctx: ctx, msg: msg}; + ctx.async = !!cat.tag.node; + if(ctx.ack){ msg['@'] = ctx.ack } + obj_map(node, each, as); + if(!ctx.async){ return } + if(!ctx.and){ + // If it is async, we only need to setup one listener per context (ctx) + cat.on('node', function(m){ + this.to.next(m); // make sure to call other context's listeners. + if(m !== ctx.map[m.get]){ return } // filter out events not from this context! + ctx.souls[m.get] = false; // set our many-async flag + obj_map(m.put, patch, m); // merge into view + if(obj_map(ctx.souls, function(v){ if(v){ return v } })){ return } // if flag still outstanding, keep waiting. + if(ctx.c){ return } ctx.c = 1; // failsafe for only being called once per context. + this.off(); + obj_map(ctx.map, map, ctx); // all done, trigger chains. + }); + } + ctx.and = true; + cat.on('node', msg); // each node on the current context's graph needs to be emitted though. + } + function each(val, key){ + var ctx = this.ctx, graph = ctx.graph, msg = this.msg, soul = msg.get, node = msg.put, at = (msg.$._), tmp; + graph[soul] = Gun.state.to(node, key, graph[soul]); + if(ctx.async){ return } + at.put = Gun.state.to(node, key, at.put); + } + function patch(val, key){ + var msg = this, node = msg.put, at = (msg.$._); + at.put = Gun.state.to(node, key, at.put); + } + function map(msg, soul){ + if(!msg.$){ return } + this.cat.stop = this.stop; // temporary fix till a better solution? + (msg.$._).on('in', msg); + this.cat.stop = null; // temporary fix till a better solution? + } + + Gun.on.get = function(msg, gun){ + var root = gun._, get = msg.get, soul = get[_soul], node = root.graph[soul], has = get[_has], tmp; + var next = root.next || (root.next = {}), at = next[soul]; + // queue concurrent GETs? + if(!node){ return root.on('get', msg) } + if(has){ + if('string' != typeof has || !obj_has(node, has)){ return root.on('get', msg) } + node = Gun.state.to(node, has); + // If we have a key in-memory, do we really need to fetch? + // 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.graph.node(node); + tmp = (at||empty).ack; + root.on('in', { + '@': msg['#'], + how: 'mem', + put: node, + $: gun + }); + //if(0 < tmp){ return } + root.on('get', msg); + } +}()); + +;(function(){ + Gun.chain.opt = function(opt){ + opt = opt || {}; + var gun = this, at = gun._, tmp = opt.peers || opt; + if(!obj_is(opt)){ opt = {} } + if(!obj_is(at.opt)){ at.opt = opt } + if(text_is(tmp)){ tmp = [tmp] } + if(list_is(tmp)){ + tmp = obj_map(tmp, function(url, i, map){ + i = {}; i.id = i.url = url; map(url, i); + }); + if(!obj_is(at.opt.peers)){ at.opt.peers = {}} + at.opt.peers = obj_to(tmp, at.opt.peers); + } + at.opt.peers = at.opt.peers || {}; + obj_map(opt, function each(v,k){ + if(!obj_has(this, k) || text.is(v) || obj.empty(v)){ this[k] = v ; return } + if(v && v.constructor !== Object && !list_is(v)){ return } + obj_map(v, each, this[k]); + }, at.opt); + Gun.on('opt', at); + at.opt.uuid = at.opt.uuid || function(){ return state_lex() + text_rand(12) } + return gun; + } +}()); + +var list_is = Gun.list.is; +var text = Gun.text, text_is = text.is, text_rand = text.random; +var obj = Gun.obj, obj_is = obj.is, obj_has = obj.has, obj_to = obj.to, obj_map = obj.map, obj_copy = obj.copy; +var state_lex = Gun.state.lex, _soul = Gun.val.link._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is; +var empty = {}, u; + +console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) }; + +Gun.log = function(){ return (!Gun.log.off && console.log.apply(console, arguments)), [].slice.call(arguments).join(' ') } +Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) } + +;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; +Gun.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'!"); +;"Please do not remove these messages unless you are paying for a monthly sponsorship, thanks!"; + +if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } +try{ if(typeof common !== "undefined"){ common.exports = Gun } }catch(e){} +module.exports = Gun; + +/*Gun.on('opt', function(ctx){ // FOR TESTING PURPOSES + this.to.next(ctx); + if(ctx.once){ return } + ctx.on('node', function(msg){ + var to = this.to; + //Gun.node.is(msg.put, function(v,k){ msg.put[k] = v + v }); + setTimeout(function(){ + to.next(msg); + },1); + }); +});*/ \ No newline at end of file diff --git a/src/set.js b/src/set.js index e489b2b4..a23c5bb4 100644 --- a/src/set.js +++ b/src/set.js @@ -1,20 +1,20 @@ - -var Gun = require('./index'); -Gun.chain.set = function(item, cb, opt){ - var gun = this, soul; - cb = cb || function(){}; - opt = opt || {}; opt.item = opt.item || item; - if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } - if(!Gun.is(item)){ - if(Gun.obj.is(item)){; - item = gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).put(item); - } - return gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); - } - item.get(function(soul, o, msg){ - if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } - gun.put(Gun.obj.put({}, soul, Gun.val.link.ify(soul)), cb, opt); - },true); - return item; -} + +var Gun = require('./index'); +Gun.chain.set = function(item, cb, opt){ + var gun = this, soul; + cb = cb || function(){}; + opt = opt || {}; opt.item = opt.item || item; + if(soul = Gun.node.soul(item)){ item = Gun.obj.put({}, soul, Gun.val.link.ify(soul)) } + if(!Gun.is(item)){ + if(Gun.obj.is(item)){; + item = gun.back(-1).get(soul = soul || Gun.node.soul(item) || gun.back('opt.uuid')()).put(item); + } + return gun.get(soul || (Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt); + } + item.get(function(soul, o, msg){ + if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } + gun.put(Gun.obj.put({}, soul, Gun.val.link.ify(soul)), cb, opt); + },true); + return item; +} \ No newline at end of file diff --git a/src/state.js b/src/state.js index 1cddd3f1..bec21424 100644 --- a/src/state.js +++ b/src/state.js @@ -1,83 +1,83 @@ - -var Type = require('./type'); -var Node = require('./node'); -function State(){ - var t; - /*if(perf){ - t = start + perf.now(); // Danger: Accuracy decays significantly over time, even if precise. - } else {*/ - t = time(); - //} - if(last < t){ - return N = 0, last = t + State.drift; - } - return last = t + ((N += 1) / D) + State.drift; -} -var time = Type.time.is, last = -Infinity, N = 0, D = 1000; // WARNING! In the future, on machines that are D times faster than 2016AD machines, you will want to increase D by another several orders of magnitude so the processing speed never out paces the decimal resolution (increasing an integer effects the state accuracy). -var perf = (typeof performance !== 'undefined')? (performance.timing && performance) : false, start = (perf && perf.timing && perf.timing.navigationStart) || (perf = false); -State._ = '>'; -State.drift = 0; -State.is = function(n, k, o){ // convenience function to get the state on a key on a node and return it. - var tmp = (k && n && n[N_] && n[N_][State._]) || o; - if(!tmp){ return } - return num_is(tmp = tmp[k])? tmp : -Infinity; -} -State.lex = function(){ return State().toString(36).replace('.','') } -State.ify = function(n, k, s, v, soul){ // put a key's state on a node. - if(!n || !n[N_]){ // reject if it is not node-like. - if(!soul){ // unless they passed a soul - return; - } - n = Node.soul.ify(n, soul); // then make it so! - } - var tmp = obj_as(n[N_], State._); // grab the states data. - if(u !== k && k !== N_){ - if(num_is(s)){ - tmp[k] = s; // add the valid state. - } - if(u !== v){ // Note: Not its job to check for valid values! - n[k] = v; - } - } - return n; -} -State.to = function(from, k, to){ - var val = (from||{})[k]; - if(obj_is(val)){ - val = obj_copy(val); - } - return State.ify(to, k, State.is(from, k), val, Node.soul(from)); -} -;(function(){ - State.map = function(cb, s, as){ var u; // for use with Node.ify - var o = obj_is(o = cb || s)? o : null; - cb = fn_is(cb = cb || s)? cb : null; - if(o && !cb){ - s = num_is(s)? s : State(); - o[N_] = o[N_] || {}; - obj_map(o, map, {o:o,s:s}); - return o; - } - as = as || obj_is(s)? s : u; - s = num_is(s)? s : State(); - return function(v, k, o, opt){ - if(!cb){ - map.call({o: o, s: s}, v,k); - return v; - } - cb.call(as || this || {}, v, k, o, opt); - if(obj_has(o,k) && u === o[k]){ return } - map.call({o: o, s: s}, v,k); - } - } - function map(v,k){ - if(N_ === k){ return } - State.ify(this.o, k, this.s) ; - } -}()); -var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy; -var num = Type.num, num_is = num.is; -var fn = Type.fn, fn_is = fn.is; -var N_ = Node._, u; -module.exports = State; + +var Type = require('./type'); +var Node = require('./node'); +function State(){ + var t; + /*if(perf){ + t = start + perf.now(); // Danger: Accuracy decays significantly over time, even if precise. + } else {*/ + t = time(); + //} + if(last < t){ + return N = 0, last = t + State.drift; + } + return last = t + ((N += 1) / D) + State.drift; +} +var time = Type.time.is, last = -Infinity, N = 0, D = 1000; // WARNING! In the future, on machines that are D times faster than 2016AD machines, you will want to increase D by another several orders of magnitude so the processing speed never out paces the decimal resolution (increasing an integer effects the state accuracy). +var perf = (typeof performance !== 'undefined')? (performance.timing && performance) : false, start = (perf && perf.timing && perf.timing.navigationStart) || (perf = false); +State._ = '>'; +State.drift = 0; +State.is = function(n, k, o){ // convenience function to get the state on a key on a node and return it. + var tmp = (k && n && n[N_] && n[N_][State._]) || o; + if(!tmp){ return } + return num_is(tmp = tmp[k])? tmp : -Infinity; +} +State.lex = function(){ return State().toString(36).replace('.','') } +State.ify = function(n, k, s, v, soul){ // put a key's state on a node. + if(!n || !n[N_]){ // reject if it is not node-like. + if(!soul){ // unless they passed a soul + return; + } + n = Node.soul.ify(n, soul); // then make it so! + } + var tmp = obj_as(n[N_], State._); // grab the states data. + if(u !== k && k !== N_){ + if(num_is(s)){ + tmp[k] = s; // add the valid state. + } + if(u !== v){ // Note: Not its job to check for valid values! + n[k] = v; + } + } + return n; +} +State.to = function(from, k, to){ + var val = (from||{})[k]; + if(obj_is(val)){ + val = obj_copy(val); + } + return State.ify(to, k, State.is(from, k), val, Node.soul(from)); +} +;(function(){ + State.map = function(cb, s, as){ var u; // for use with Node.ify + var o = obj_is(o = cb || s)? o : null; + cb = fn_is(cb = cb || s)? cb : null; + if(o && !cb){ + s = num_is(s)? s : State(); + o[N_] = o[N_] || {}; + obj_map(o, map, {o:o,s:s}); + return o; + } + as = as || obj_is(s)? s : u; + s = num_is(s)? s : State(); + return function(v, k, o, opt){ + if(!cb){ + map.call({o: o, s: s}, v,k); + return v; + } + cb.call(as || this || {}, v, k, o, opt); + if(obj_has(o,k) && u === o[k]){ return } + map.call({o: o, s: s}, v,k); + } + } + function map(v,k){ + if(N_ === k){ return } + State.ify(this.o, k, this.s) ; + } +}()); +var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy; +var num = Type.num, num_is = num.is; +var fn = Type.fn, fn_is = fn.is; +var N_ = Node._, u; +module.exports = State; \ No newline at end of file diff --git a/src/type.js b/src/type.js index bab8fd65..ab909256 100644 --- a/src/type.js +++ b/src/type.js @@ -1,146 +1,141 @@ - -// Generic javascript utilities. -var Type = {}; -//Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }} -Type.fn = {is: function(fn){ return (!!fn && 'function' == typeof fn) }} -Type.bi = {is: function(b){ return (b instanceof Boolean || typeof b == 'boolean') }} -Type.num = {is: function(n){ return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }} -Type.text = {is: function(t){ return (typeof t == 'string') }} -Type.text.ify = function(t){ - if(Type.text.is(t)){ return t } - if(typeof JSON !== "undefined"){ return JSON.stringify(t) } - return (t && t.toString)? t.toString() : t; -} -Type.text.random = function(l, c){ - var s = ''; - l = l || 24; // you are not going to make a 0 length random number, so no need to check type - c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; - while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } - return s; -} -Type.text.match = function(t, o){ var r = false; - t = t || ''; - o = Type.text.is(o)? {'=': o} : o || {}; // {'~', '=', '*', '<', '>', '+', '-', '?', '!'} // ignore case, exactly equal, anything after, lexically larger, lexically lesser, added in, subtacted from, questionable fuzzy match, and ends with. - if(Type.obj.has(o,'~')){ t = t.toLowerCase(); o['='] = (o['='] || o['~']).toLowerCase() } - if(Type.obj.has(o,'=')){ return t === o['='] } - if(Type.obj.has(o,'*')){ if(t.slice(0, o['*'].length) === o['*']){ r = true; t = t.slice(o['*'].length) } else { return false }} - if(Type.obj.has(o,'!')){ if(t.slice(-o['!'].length) === o['!']){ r = true } else { return false }} - if(Type.obj.has(o,'+')){ - if(Type.list.map(Type.list.is(o['+'])? o['+'] : [o['+']], function(m){ - if(t.indexOf(m) >= 0){ r = true } else { return true } - })){ return false } - } - if(Type.obj.has(o,'-')){ - if(Type.list.map(Type.list.is(o['-'])? o['-'] : [o['-']], function(m){ - if(t.indexOf(m) < 0){ r = true } else { return true } - })){ return false } - } - if(Type.obj.has(o,'>')){ if(t > o['>']){ r = true } else { return false }} - if(Type.obj.has(o,'<')){ if(t < o['<']){ r = true } else { return false }} - function fuzzy(t,f){ var n = -1, i = 0, c; for(;c = f[i++];){ if(!~(n = t.indexOf(c, n+1))){ return false }} return true } // via http://stackoverflow.com/questions/9206013/javascript-fuzzy-search - if(Type.obj.has(o,'?')){ if(fuzzy(t, o['?'])){ r = true } else { return false }} // change name! - return r; -} -Type.list = {is: function(l){ return (l instanceof Array) }} -Type.list.slit = Array.prototype.slice; -Type.list.sort = function(k){ // creates a new sort function based off some key - return function(A,B){ - if(!A || !B){ return 0 } A = A[k]; B = B[k]; - if(A < B){ return -1 }else if(A > B){ return 1 } - else { return 0 } - } -} -Type.list.map = function(l, c, _){ return obj_map(l, c, _) } -Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation -Type.obj = {is: function(o){ return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }} -Type.obj.put = function(o, k, v){ return (o||{})[k] = v, o } -Type.obj.has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) } -Type.obj.del = function(o, k){ - if(!o){ return } - o[k] = null; - delete o[k]; - return o; -} -Type.obj.as = function(o, k, v, u){ return o[k] = o[k] || (u === v? {} : v) } -Type.obj.ify = function(o){ - if(obj_is(o)){ return o } - try{o = JSON.parse(o); - }catch(e){o={}}; - return o; -} -;(function(){ var u; - function map(v,k){ - if(obj_has(this,k) && u !== this[k]){ return } - this[k] = v; - } - Type.obj.to = function(from, to){ - to = to || {}; - obj_map(from, map, to); - return to; - } -}()); -Type.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2 - return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways! -} -;(function(){ - function empty(v,i){ var n = this.n; - if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return } - if(i){ return true } - } - Type.obj.empty = function(o, n){ - if(!o){ return true } - return obj_map(o,empty,{n:n})? false : true; - } -}()); -;(function(){ - function t(k,v){ - if(2 === arguments.length){ - t.r = t.r || {}; - t.r[k] = v; - return; - } t.r = t.r || []; - t.r.push(k); - }; - var keys = Object.keys; - Type.obj.map = function(l, c, _){ - var u, i = 0, x, r, ll, lle, f = fn_is(c); - t.r = null; - if(keys && obj_is(l)){ - ll = keys(l); lle = true; - } - if(list_is(l) || ll){ - x = (ll || l).length; - for(;i < x; i++){ - var ii = (i + Type.list.index); - if(f){ - r = lle? c.call(_ || this, l[ll[i]], ll[i], t) : c.call(_ || this, l[i], ii, t); - if(r !== u){ return r } - } else { - //if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing! - if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now - } - } - } else { - for(i in l){ - if(f){ - if(obj_has(l,i)){ - r = _? c.call(_, l[i], i, t) : c(l[i], i, t); - if(r !== u){ return r } - } - } else { - //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing! - if(c === l[i]){ return i } // use this for now - } - } - } - return f? t.r : Type.list.index? 0 : -1; - } -}()); -Type.time = {}; -Type.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } - -var fn_is = Type.fn.is; -var list_is = Type.list.is; -var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map; -module.exports = Type; + +// Generic javascript utilities. +var Type = {}; +//Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }} +Type.fn = {is: function(fn){ return (!!fn && 'function' == typeof fn) }} +Type.bi = {is: function(b){ return (b instanceof Boolean || typeof b == 'boolean') }} +Type.num = {is: function(n){ return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }} +Type.text = {is: function(t){ return (typeof t == 'string') }} +Type.text.ify = function(t){ + if(Type.text.is(t)){ return t } + if(typeof JSON !== "undefined"){ return JSON.stringify(t) } + return (t && t.toString)? t.toString() : t; +} +Type.text.random = function(l, c){ + var s = ''; + l = l || 24; // you are not going to make a 0 length random number, so no need to check type + c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; + while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } + return s; +} +Type.text.match = function(t, o){ var tmp, u; + if('string' !== typeof t){ return false } + if('string' == typeof o){ o = {'=': o} } + o = o || {}; + tmp = (o['='] || o['*'] || o['>'] || o['<']); + if(t === tmp){ return true } + if(u !== o['=']){ return false } + tmp = (o['*'] || o['>'] || o['<']); + if(t.slice(0, (tmp||'').length) === tmp){ return true } + if(u !== o['*']){ return false } + if(u !== o['>'] && u !== o['<']){ + return (t >= o['>'] && t <= o['<'])? true : false; + } + if(u !== o['>'] && t >= o['>']){ return true } + if(u !== o['<'] && t <= o['<']){ return true } + return false; +} +Type.list = {is: function(l){ return (l instanceof Array) }} +Type.list.slit = Array.prototype.slice; +Type.list.sort = function(k){ // creates a new sort function based off some key + return function(A,B){ + if(!A || !B){ return 0 } A = A[k]; B = B[k]; + if(A < B){ return -1 }else if(A > B){ return 1 } + else { return 0 } + } +} +Type.list.map = function(l, c, _){ return obj_map(l, c, _) } +Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation +Type.obj = {is: function(o){ return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }} +Type.obj.put = function(o, k, v){ return (o||{})[k] = v, o } +Type.obj.has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) } +Type.obj.del = function(o, k){ + if(!o){ return } + o[k] = null; + delete o[k]; + return o; +} +Type.obj.as = function(o, k, v, u){ return o[k] = o[k] || (u === v? {} : v) } +Type.obj.ify = function(o){ + if(obj_is(o)){ return o } + try{o = JSON.parse(o); + }catch(e){o={}}; + return o; +} +;(function(){ var u; + function map(v,k){ + if(obj_has(this,k) && u !== this[k]){ return } + this[k] = v; + } + Type.obj.to = function(from, to){ + to = to || {}; + obj_map(from, map, to); + return to; + } +}()); +Type.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2 + return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways! +} +;(function(){ + function empty(v,i){ var n = this.n; + if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return } + if(i){ return true } + } + Type.obj.empty = function(o, n){ + if(!o){ return true } + return obj_map(o,empty,{n:n})? false : true; + } +}()); +;(function(){ + function t(k,v){ + if(2 === arguments.length){ + t.r = t.r || {}; + t.r[k] = v; + return; + } t.r = t.r || []; + t.r.push(k); + }; + var keys = Object.keys, map; + Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) } + Type.obj.map = map = function(l, c, _){ + var u, i = 0, x, r, ll, lle, f = fn_is(c); + t.r = null; + if(keys && obj_is(l)){ + ll = keys(l); lle = true; + } + if(list_is(l) || ll){ + x = (ll || l).length; + for(;i < x; i++){ + var ii = (i + Type.list.index); + if(f){ + r = lle? c.call(_ || this, l[ll[i]], ll[i], t) : c.call(_ || this, l[i], ii, t); + if(r !== u){ return r } + } else { + //if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing! + if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now + } + } + } else { + for(i in l){ + if(f){ + if(obj_has(l,i)){ + r = _? c.call(_, l[i], i, t) : c(l[i], i, t); + if(r !== u){ return r } + } + } else { + //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing! + if(c === l[i]){ return i } // use this for now + } + } + } + return f? t.r : Type.list.index? 0 : -1; + } +}()); +Type.time = {}; +Type.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) } + +var fn_is = Type.fn.is; +var list_is = Type.list.is; +var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map; +module.exports = Type; \ No newline at end of file diff --git a/src/val.js b/src/val.js index fb7cf4ee..732e8b21 100644 --- a/src/val.js +++ b/src/val.js @@ -1,44 +1,44 @@ - -var Type = require('./type'); -var Val = {}; -Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first. - if(v === u){ return false } - if(v === null){ return true } // "deletes", nulling out keys. - if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. - if(text_is(v) // by "text" we mean strings. - || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. - return true; // simple values are valid. - } - return Val.rel.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. -} -Val.link = Val.rel = {_: '#'}; -;(function(){ - Val.rel.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} - if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. - var o = {}; - obj_map(v, map, o); - if(o.id){ // a valid id was found. - return o.id; // yay! Return it. - } - } - return false; // the value was not a valid soul relation. - } - function map(s, k){ var o = this; // map over the object... - if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. - if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value. - o.id = s; // we found the soul! - } else { - return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. - } - } -}()); -Val.rel.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. -Type.obj.has._ = '.'; -var rel_ = Val.link._, u; -var bi_is = Type.bi.is; -var num_is = Type.num.is; -var text_is = Type.text.is; -var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; -module.exports = Val; + +var Type = require('./type'); +var Val = {}; +Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first. + if(v === u){ return false } + if(v === null){ return true } // "deletes", nulling out keys. + if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. + if(text_is(v) // by "text" we mean strings. + || bi_is(v) // by "binary" we mean boolean. + || num_is(v)){ // by "number" we mean integers or decimals. + return true; // simple values are valid. + } + return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. +} +Val.link = Val.rel = {_: '#'}; +;(function(){ + Val.link.is = function(v){ // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} + if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. + var o = {}; + obj_map(v, map, o); + if(o.id){ // a valid id was found. + return o.id; // yay! Return it. + } + } + return false; // the value was not a valid soul relation. + } + function map(s, k){ var o = this; // map over the object... + if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. + if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value. + o.id = s; // we found the soul! + } else { + return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. + } + } +}()); +Val.link.ify = function(t){ return obj_put({}, rel_, t) } // convert a soul into a relation and return it. +Type.obj.has._ = '.'; +var rel_ = Val.link._, u; +var bi_is = Type.bi.is; +var num_is = Type.num.is; +var text_is = Type.text.is; +var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; +module.exports = Val; \ No newline at end of file diff --git a/test/axe/holy-grail.js b/test/axe/holy-grail.js new file mode 100644 index 00000000..93ba4bfc --- /dev/null +++ b/test/axe/holy-grail.js @@ -0,0 +1,425 @@ +/** + * AXE test 1 + * What we want here: (1) Superpeer and (n) peers + * - The peers receives only the requested data. + * - If the Superpeer crash, after restart, must recreate all subscriptions and update the peers. + * - If some peer crash or go offline, must receive the changes via RTC. + * + * Tip: to run this `npm run testaxe` + * Tip 2: if you clone the gun repo, you need to create a link do gun package. Do `npm install && cd node_modules && ln -s ../ gun` + * Tip 3: If you not in localhost, run the browsers in anonymous mode because of domain security policies. https://superuser.com/questions/565409/how-to-stop-an-automatic-redirect-from-http-to-https-in-chrome + */ +var config = { + IP: require('ip').address(), + port: 8765, + servers: 2, + browsers: 3, + route: { + '/': __dirname + '/index.html', + '/gun.js': __dirname + '/../../gun.js', + '/gun/axe.js': __dirname + '/../../axe.js', + '/gun/lib/radix.js': __dirname + '/../../lib/radix.js', + '/gun/lib/webrtc.js': __dirname + '/../../lib/webrtc.js', + '/jquery.js': __dirname + '/../../examples/jquery.js' + } +} +var panic = require('panic-server'); +panic.server().on('request', function(req, res){ + config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res); +}).listen(config.port); + +var clients = panic.clients; +var manager = require('panic-manager')(); +manager.start({ + clients: Array(config.servers).fill().map(function(u, i){ + return { + type: 'node', + port: config.port + (i + 1) + } + }), + panic: 'http://' + config.IP + ':' + config.port +}); + +var servers = clients.filter('Node.js'); +var server = servers.pluck(1); +var server2 = servers.excluding(server).pluck(1); +var browsers = clients.excluding(servers); +var alice = browsers.pluck(1); +var bob = browsers.excluding(alice).pluck(1); +var john = browsers.excluding(alice).excluding(bob).pluck(1); +var again = {}; + +describe("The Holy Grail AXE Test!", function(){ + this.timeout(5 * 60 * 1000); +// this.timeout(10 * 60 * 1000); + + it("Servers have joined!", function(){ + return servers.atLeast(config.servers); + }); + + it("GUN started!", function(){ + return server.run(function(test){ + var env = test.props; + test.async(); + try{ require('fs').unlinkSync(env.i+'dataaxe') }catch(e){} + try{ require('fs').unlinkSync((env.i+1)+'dataaxe') }catch(e){} + var port = env.config.port + env.i; + var server = require('http').createServer(function(req, res){ + res.end("I am "+ env.i +"!"); + }); + var Gun = require('gun'); + require('gun/axe'); + var gun = Gun({ + file: env.i+'dataaxe', + web: server + }); + server.listen(port, function(){ + test.done(); + }); + }, {i: 1, config: config}); + }); + + it(config.browsers +" browser(s) have joined!", function(){ + console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!"); + return browsers.atLeast(config.browsers); + }); + + it("Browsers initialized gun!", function(){ + var tests = [], i = 0; + browsers.each(function(client, id){ + tests.push(client.run(function(test){ + localStorage.clear(); console.log('Clear localStorage!!!'); + var env = test.props; + var opt = {peers:['http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'], wait: 1000}; + var pid = location.hash.slice(1); + if (pid) { opt.pid = pid; } + Gun.on('opt', function(ctx) { + this.to.next(ctx); + ctx.on('hi', function(opt) { + document.getElementById('pid').innerHTML = (document.getElementById('pid').innerHTML || "-") + ', ' + this.on.opt.pid; + }); + }); + var gun = window.gun = Gun(opt); + window.ref = gun.get('holy').get('grail'); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it("Wait for Alice, Bob and John...", function(done){ + setTimeout(done, 1000); + }); + + it("Alice Write: Hi Bob!", function(){ + return alice.run(function(test){ + console.log("I AM ALICE"); + $('#name').text('Alice'); + test.async(); + ref.once(function() { // TODO: Need `.once` first for subscription. If Alice do a `.put` before a `.once`, Alice will get old data from localStorage if Bob update + ref.put('Hi Bob!', function(ack) { + console.log(ack); + setTimeout(test.done, 2000); + }); + }); + }); + }); + + it("Bob receive ONCE from Alice: Hi Bob!", function(){ + return bob.run(function(test){ + console.log("I AM BOB"); + $('#name').text('Bob'); + test.async(); + ref.once(function(data){ + if('Hi Bob!' === data){ + console.log('[OK] Bob receive the question: ', data); + return test.done(); + } else { + var err = '[FAIL] Bob MUST receive: Hi Bob! but receive: ' + data + ' Storage: ' + localStorage.getItem('gun/'); + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("Bob Write response: Hi Alice!", function(){ + return bob.run(function(test){ + test.async(); + ref.put('Hi Alice!', function(ack) { + console.log('[OK] Bob Write response: Hi Alice!', ack); + setTimeout(test.done, 2000); + }); + }); + }); + + it("Alice Read response from Bob: Hi Alice!", function(){ + return alice.run(function(test){ + test.async(); + ref.once(function(data){ + if('Hi Alice!' === data){ + console.log('[OK] Alice receive the response: ', data); + return test.done(); + } else { + //TODO: aqui em duvida.. está pegando do localStorage, mas Bob alterou o dado. + var err = '[FAIL] Alice receive wrong response: "' + data + '" and must be "Hi Alice!"'; + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("John Read what Bob say to Alice: Hi Alice!", function(){ + return john.run(function(test){ + test.async(); + console.log("I AM JOHN"); + $('#name').text('John'); + ref.once(function(data){ + if('Hi Alice!' === data){ + console.log('[OK] John receive the data: ', data); + return test.done(); + } else { + //TODO: aqui em duvida.. está pegando do localStorage, mas Bob alterou o dado. + var err = '[FAIL] John receive wrong data: "' + data + '" and must be "Hi Alice!"'; + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("Bob Write in some data, Alice not subscribed", function(){ + return bob.run(function(test){ + test.async(); + gun.get('bob').get('mine').put('Alice dont want this data now!', function() { + setTimeout(test.done, 2000); + }); + }); + }); + + it("Alice not subscribed. Must NOT receive data from Bob", function(){ + return alice.run(function(test){ + test.async(); + /// This must be empty, because alice don't make a subscription to this node. + var bobdata = JSON.parse(localStorage.getItem('gun/')).bob; + if (bobdata) { + var err = '[FAIL] Alice receive not subscribed data in localStorage: ' + JSON.stringify(bobdata); + console.log(err); + return test.fail(err); + } + if (gun._.graph.bob) { + var err = '[FAIL] Alice receive not subscribed data in in graph: ' + JSON.stringify(gun._.graph.bob); + console.log(err); + return test.fail(err); + } + console.log('[OK] Alice Read must NOT receive data from Bob: ', bobdata); + return test.done(); + }) + }); + + it("Alice subscription Bob data with ONCE, MUST receive", function(){ + return alice.run(function(test){ + test.async(); + gun.get('bob').once(function(data){ + if(data){ + console.log('[OK] Alice receive the value: ', data); + return test.done(); + } else { + var err = '[FAIL] Alice receive the value: ' + data; + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("Bob Write in some data. Now Alice is subscribed.", function(){ + return bob.run(function(test){ + test.async(); + gun.get('bob').get('mine').put('Alice WANT this data now!', function() { + setTimeout(test.done, 5000); + }); + }); + }); + + it("Alice must receive 'Alice WANT this data now!' from Bob node", function(){ + return alice.run(function(test){ + test.async(); + if (gun._.graph.bob && gun._.graph.bob.mine === 'Alice WANT this data now!') { + console.log('[OK] GRAPH: ', gun._.graph.bob); + test.done(); + } else { + var err = '[FAIL] GRAPH: ' + JSON.stringify(gun._.graph.bob); + console.log(err); + test.fail(err); + } + }) + }); + + it("Server has crashed!", function(){ + return server.run(function(test){ +// var env = test.props; +// try{ require('fs').unlinkSync(env.i+'data'); }catch(e){} + process.exit(0); + }, {i: 1, config: config}) + }); + + it("Wait...", function(done){ + setTimeout(done, 2000); + }); + + it("Alice change the data (superpeer crashed yet).", function(){ + return alice.run(function(test){ + var env = test.props; + if(window.WebSocket){ + var err; + try{ new WebSocket('http://'+ env.config.IP + ':' + (env.config.port + 2) + '/gun') }catch(e){ err = e } + if(!err){ + test.fail("Server did not crash."); + } + } + test.async() + ref.put("Superpeer? Where are you?", function() { + setTimeout(test.done, 1000); + }); + }, {config: config}); + }); + + it("Bob receive what Alice change via WebRTC.", function(){ + return bob.run(function(test){ + test.async(); + ref.once(function(data){ + if('Superpeer? Where are you?' === data){ + console.log('[OK] Bob received data via WebRTC: ', data); + return test.done(); + } else { + var err = '[FAIL] Bob MUST not receive: "Superpeer? Where are you?", but receive: ' + data; + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("Bob change the data again (superpeer crashed yet).", function(){ + return alice.run(function(test){ + var env = test.props; + if(window.WebSocket){ + var err; + try{ new WebSocket('http://'+ env.config.IP + ':' + (env.config.port + 2) + '/gun') }catch(e){ err = e } + if(!err){ + test.fail("Server did not crash."); + } + } + test.async() + ref.put("Alice, can you hear me?", function() { + setTimeout(test.done, 1000); + }); + }, {config: config}); + }); + + it("Alice MUST receive 'Alice, can you hear me?' via WebRTC.", function(){ + return bob.run(function(test){ + test.async(); + ref.once(function(data){ + if('Alice, can you hear me?' === data){ + console.log('[OK] Alice received data via WebRTC: ', data); + return test.done(); + } else { + var err = '[FAIL] Alice MUST not receive: "Superpeer? Where are you?", but receive: ' + data; + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("Superpeer come started again!", function(){ + return server2.run(function(test){ + var env = test.props; + test.async(); +// try{ require('fs').unlinkSync(env.i+'dataaxe') }catch(e){} +// try{ require('fs').unlinkSync((env.i+1)+'dataaxe') }catch(e){} + var port = env.config.port + env.i; + var server = require('http').createServer(function(req, res){ + res.end("I am "+ env.i +"!"); + }); + var Gun = require('gun'); + require('gun/axe'); + var gun = Gun({ + file: env.i+'dataaxe', + web: server + }); + server.listen(port, function(){ + test.done(); + }); + }, {i: 1, config: config}); + }); + + it("Wait sync...", function(done){ + setTimeout(done, 5000); + }); + + it("Alice change the data again (superpeer is UP!).", function(){ + return alice.run(function(test){ + var env = test.props; + test.async() + ref.put("Yes Bob! Thanks for asking!", function() { + setTimeout(test.done, 1000); + }); + }, {config: config}); + }); + + it("Bob MUST receive 'Yes Bob! Thanks for asking!'", function(){ + return bob.run(function(test){ + test.async(); + ref.once(function(data){ + if('Yes Bob! Thanks for asking!' === data){ + console.log('[OK] Bob received the data change: ', data); + return test.done(); + } else { + var err = '[FAIL] Bob MUST not receive: "Yes Bob! Thanks for asking!", but receive: ' + data; + console.log(err); + return test.fail(err); + } + }) + }) + }); + + it("John dont want to know what Bob say in his node!", function(){ + return john.run(function(test){ + test.async(); + /// This must be empty, because John don't make a subscription to this node. + var bobdata = JSON.parse(localStorage.getItem('gun/')).bob; + if (bobdata) { + var err = '[FAIL] John receive not subscribed data: ' + JSON.stringify(bobdata); + console.log(err); + return test.fail(err); + } + if (gun._.graph.bob) { + var err = '[FAIL] John receive not subscribed data in in graph: ' + JSON.stringify(gun._.graph.bob); + console.log(err); + return test.fail(err); + } + console.log('[OK] John Read must NOT receive data from Bob: ', bobdata, gun._.graph.bob); + return test.done(); + }) + }); + + it("All finished!", function(done){ + console.log("Done! Cleaning things up..."); + setTimeout(function(){ + done(); + },1000); + }); + after("Everything shut down.", function(){ + browsers.run(function(){ + //location.reload(); + //setTimeout(function(){ + //}, 15 * 1000); + }); + return servers.run(function(){ + process.exit(); + }); + }); +}); diff --git a/test/axe/index.html b/test/axe/index.html new file mode 100644 index 00000000..9fb3201d --- /dev/null +++ b/test/axe/index.html @@ -0,0 +1,13 @@ + + + + + + + + +

    Running AXE Tests

    +
    Name:
    +
    PID:
    +
    + diff --git a/test/common.js b/test/common.js index 5254bbcd..8bf55968 100644 --- a/test/common.js +++ b/test/common.js @@ -1,5 +1,4 @@ describe('Gun', function(){ - var root; (function(){ var env; @@ -7,7 +6,10 @@ describe('Gun', function(){ if(typeof window !== 'undefined'){ env = window } root = env.window? env.window : global; try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){} + try{ localStorage.clear() }catch(e){} + try{ indexedDB.deleteDatabase('radatatest') }catch(e){} try{ require('fs').unlinkSync('data.json') }catch(e){} + try{ require('../lib/fsrm')('radatatest') }catch(e){} //root.Gun = root.Gun || require('../gun'); if(root.Gun){ root.Gun = root.Gun; @@ -19,6 +21,7 @@ describe('Gun', function(){ //require('../lib/file'); require('../lib/store'); require('../lib/rfs'); + require('./rad/rad.js'); require('./sea/sea.js'); } }(this)); @@ -185,13 +188,13 @@ describe('Gun', function(){ it('match',function(){ expect(Gun.text.match("user/mark", 'user/mark')).to.be.ok(); expect(Gun.text.match("user/mark/nadal", {'=': 'user/mark'})).to.not.be.ok(); - expect(Gun.text.match("user/mark", {'~': 'user/Mark'})).to.be.ok(); expect(Gun.text.match("user/mark/nadal", {'*': 'user/'})).to.be.ok(); expect(Gun.text.match("email/mark@gunDB.io", {'*': 'user/'})).to.not.be.ok(); - expect(Gun.text.match("user/mark/nadal", {'*': 'user/', '>': 'j', '<': 'o'})).to.be.ok(); - expect(Gun.text.match("user/amber/nadal", {'*': 'user/', '>': 'j', '<': 'o'})).to.not.be.ok(); - expect(Gun.text.match("user/amber/nadal", {'*': 'user/', '>': 'a', '<': 'c'})).to.be.ok(); - expect(Gun.text.match("user/mark/nadal", {'*': 'user/', '>': 'a', '<': 'c'})).to.not.be.ok(); + expect(Gun.text.match("user/mark/nadal", {'>': 'user/j', '<': 'user/o'})).to.be.ok(); + expect(Gun.text.match("user/amber/nadal", {'>': 'user/j', '<': 'user/o'})).to.not.be.ok(); + expect(Gun.text.match("user/amber/nadal", {'>': 'user/a', '<': 'user/c'})).to.be.ok(); + expect(Gun.text.match("user/mark/nadal", {'>': 'user/a', '<': 'user/c'})).to.not.be.ok(); + return; // below is OLD bloat, still available in lib/match.js expect(Gun.text.match("user/mark/nadal", {'*': 'user/', '>': 'j', '<': 'o', '?': 'm/n'})).to.be.ok(); expect(Gun.text.match("user/amber/cazzell", {'*': 'user/', '?': 'm/n'})).to.not.be.ok(); expect(Gun.text.match("user/mark/nadal", {'*': 'user/', '-': 'mad'})).to.be.ok(); @@ -207,6 +210,7 @@ describe('Gun', function(){ expect(Gun.text.match("user/mark/rachel/timothy/cazzell", {'*': 'user/', '+': ['mark', 'cazzell'], '-': ['amber', 'timothy']})).to.not.be.ok(); expect(Gun.text.match("photo/kitten.jpg", {'*': 'photo/', '!': '.jpg'})).to.be.ok(); expect(Gun.text.match("photo/kittens.gif", {'*': 'photo/', '!': '.jpg'})).to.not.be.ok(); + expect(Gun.text.match("user/mark", {'~': 'user/Mark'})).to.be.ok(); }); }); describe('List', function(){ @@ -613,6 +617,7 @@ describe('Gun', function(){ }); describe('Gun Safety', function(){ /* WARNING NOTE: Internal API has significant breaking changes! */ + var gun = Gun(); it('is',function(){ expect(Gun.is(gun)).to.be(true); @@ -3048,7 +3053,7 @@ describe('Gun', function(){ }); it('get put get get put reload get get then get', function(done){ - this.timeout(6000); + this.timeout(9000); var gun = Gun(); gun.get('stef').put({name:'Stef'}); @@ -3081,7 +3086,7 @@ describe('Gun', function(){ if(done.c){ return } done.c = 1; done(); }); - },5000); + },1200); }); it('get get get any parallel', function(done){ @@ -3677,22 +3682,22 @@ describe('Gun', function(){ this.timeout(5000); var gun = Gun({test_no_peer:true}).get('g/m/no/slow'); //console.log("---------- setup data done -----------"); - var prev, diff, max = 25, total = 9, largest = -1, gone = {}; + var prev, diff, max = 25, total = 9, largest = -1, gone = {}, u; //var prev, diff, max = Infinity, total = 10000, largest = -1, gone = {}; // TODO: It would be nice if we could change these numbers for different platforms/versions of javascript interpreters so we can squeeze as much out of them. gun.get('history').map().on(function(time, index){ - //console.log(">>>", index, time); diff = Gun.time.is() - time; + //console.log(">>>", index, time, diff); //return; expect(gone[index]).to.not.be.ok(); gone[index] = diff; largest = (largest < diff)? diff : largest; - //console.log(diff, '<', max); expect(diff > max).to.not.be.ok(); }); var turns = 0; var many = setInterval(function(){ if(turns > total || (diff || 0) > (max + 5)){ + if(u === diff){ return } clearTimeout(many); expect(Gun.num.is(diff)).to.be.ok(); if(done.c){ return } done.c = 1; @@ -3762,9 +3767,9 @@ describe('Gun', function(){ var msg = {what: 'hello world'}; //var ref = user.get('who').get('all').set(msg); //user.get('who').get('said').set(ref); - var ref = gun.get('who').get('all').set(msg); - gun.get('who').get('said').set(ref); - gun.get('who').get('said').map().once(function(data){ + var ref = gun.get('s/r/who').get('all').set(msg); + gun.get('s/r/who').get('said').set(ref); + gun.get('s/r/who').get('said').map().once(function(data){ expect(data.what).to.be.ok(); done(); }) diff --git a/test/mocha.html b/test/mocha.html index f09ea754..229040e6 100644 --- a/test/mocha.html +++ b/test/mocha.html @@ -23,8 +23,15 @@ - + + + + + + + + + + + + +
    the world is a beautiful place.
    +
    The world is a beautiful place.
    +
    + + + + +
    + + + + + \ No newline at end of file diff --git a/test/panic/1putackget.js b/test/panic/1putackget.js new file mode 100644 index 00000000..c2319397 --- /dev/null +++ b/test/panic/1putackget.js @@ -0,0 +1,198 @@ +/* +This is the first in a series of basic networking correctness tests. +Each test itself might be dumb and simple, but built up together, +they prove desired end goals for behavior at scale. +1. (this file) Is a browser write is confirmed as save by multiple peers even if by daisy chain. +2. +*/ + +var config = { + IP: require('ip').address(), + port: 8765, + servers: 2, + browsers: 2, + route: { + '/': __dirname + '/index.html', + '/gun.js': __dirname + '/../../gun.js', + '/jquery.js': __dirname + '/../../examples/jquery.js' + } +} + +var panic = require('panic-server'); +panic.server().on('request', function(req, res){ + config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res); +}).listen(config.port); + +var clients = panic.clients; +var manager = require('panic-manager')(); + +manager.start({ + clients: Array(config.servers).fill().map(function(u, i){ + return { + type: 'node', + port: config.port + (i + 1) + } + }), + panic: 'http://' + config.IP + ':' + config.port +}); + +var servers = clients.filter('Node.js'); +var bob = servers.pluck(1); +var carl = servers.excluding(bob).pluck(1); +var browsers = clients.excluding(servers); +var alice = browsers.pluck(1); +var dave = browsers.excluding(alice).pluck(1); + +describe("Put ACK", function(){ + //this.timeout(5 * 60 * 1000); + this.timeout(10 * 60 * 1000); + + it("Servers have joined!", function(){ + return servers.atLeast(config.servers); + }); + + it("GUN started!", function(){ + var tests = [], i = 0; + servers.each(function(client){ + tests.push(client.run(function(test){ + var env = test.props; + test.async(); + try{ require('fs').unlinkSync(env.i+'data') }catch(e){} + try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){} + var server = require('http').createServer(function(req, res){ + res.end("I am "+ env.i +"!"); + }); + var port = env.config.port + env.i; + var Gun = require('gun'); + var peers = [], i = env.config.servers; + while(i--){ + var tmp = (env.config.port + (i + 1)); + if(port != tmp){ // ignore ourselves + peers.push('http://'+ env.config.IP + ':' + tmp + '/gun'); + } + } + console.log(port, " connect to ", peers); + var gun = Gun({file: env.i+'data', peers: peers, web: server}); + server.listen(port, function(){ + test.done(); + }); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it(config.browsers +" browser(s) have joined!", function(){ + console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!"); + return browsers.atLeast(config.browsers); + }); + + it("Browsers initialized gun!", function(){ + var tests = [], i = 0; + browsers.each(function(client, id){ + tests.push(client.run(function(test){ + try{ localStorage.clear() }catch(e){} + try{ indexedDB.deleteDatabase('radata') }catch(e){} + var env = test.props; + var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'); + window.ref = gun.get('a'); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it("Put", function(){ + return alice.run(function(test){ + console.log("I AM ALICE"); + test.async(); + var c = test.props.acks, acks = {}; + c = c < 2? 2 : c; + ref.put({hello: 'world'}, function(ack){ + //console.log("acks:", ack, c); + acks[ack['#']] = 1; + if(Object.keys(acks).length == c){ + wire(); + return test.done(); + } + }, {acks: c}); + + function wire(){ + var hear = ref._.root.opt.mesh.hear; + ref._.root.opt.mesh.hear = function(raw, peer){ + var msg = Gun.obj.ify(raw); + console.log('hear:', msg); + hear(raw, peer); + (ref.hear || (ref.hear = [])).push(msg); + } + var say = ref._.root.opt.mesh.say; + ref._.root.opt.mesh.say = function(raw, peer){ + var yes = say(raw, peer); + if(yes === false){ return } + console.log("say:", msg, yes); + (ref.say || (ref.say = [])).push(Gun.obj.ify(msg)); + } + } + }, {acks: config.servers}); + }); + + it("Get", function(){ + /* + Here is the recursive rule for GET, keep replying while hashes mismatch. + 1. Receive a GET message. + 2. If it has a hash, and if you have a thing matching the GET, then see if the hashes are the same, if they are then don't ACK, don't relay, end. + 3. If you would have the thing but do not, then ACK that YOU have nothing. + 4. If you have a thing matching the GET or an ACK for the GET's message, add the hash to the GET message, and ACK with the thing or ideally the remaining difference. + 5. Pick ?3? OTHER peers preferably by priority that they have got the thing, send them the GET, plus all "up" peers. + 6. If no ACKs you are done, end. + 7. If you get ACKs back to the GET with things and different hashes, optionally merge into the thing you have GOT and update the hash. + 8. Go to 4. + */ + return dave.run(function(test){ + console.log("I AM DAVE"); + test.async(); + var c = 0, to; + var hear = ref._.root.opt.mesh.hear; + ref._.root.opt.mesh.hear = function(raw, peer){ + var msg = Gun.obj.ify(raw); + console.log('hear:', msg); + hear(raw, peer); + (ref.hear || (ref.hear = [])).push(msg); + + if(msg.put){ ++c } + } + ref.get(function(ack){ + if(!ack.put || ack.put.hello !== 'world'){ return } + if(c > 1){ too_many_acks } + + clearTimeout(to); + to = setTimeout(test.done, 1000); + }); + }, {acks: config.servers}); + }); + + it("DAM", function(){ + return alice.run(function(test){ + test.async(); + if(ref.say){ said_too_much } + if(ref.hear.length > 1){ heard_to_much } + test.done() + }, {acks: config.servers}); + }); + + it("All finished!", function(done){ + console.log("Done! Cleaning things up..."); + setTimeout(function(){ + done(); + },1000); + }); + + after("Everything shut down.", function(){ + browsers.run(function(){ + //location.reload(); + //setTimeout(function(){ + //}, 15 * 1000); + }); + return servers.run(function(){ + process.exit(); + }); + }); +}); \ No newline at end of file diff --git a/test/panic/2getget.js b/test/panic/2getget.js new file mode 100644 index 00000000..65bd2920 --- /dev/null +++ b/test/panic/2getget.js @@ -0,0 +1,165 @@ +/* +This is the first in a series of basic networking correctness tests. +Each test itself might be dumb and simple, but built up together, +they prove desired end goals for behavior at scale. +1. (this file) Is a browser write is confirmed as save by multiple peers even if by daisy chain. +2. +*/ + +var config = { + IP: require('ip').address(), + port: 8765, + servers: 1, + browsers: 3, + route: { + '/': __dirname + '/index.html', + '/gun.js': __dirname + '/../../gun.js', + '/jquery.js': __dirname + '/../../examples/jquery.js' + } +} + +var panic = require('panic-server'); +panic.server().on('request', function(req, res){ + config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res); +}).listen(config.port); + +var clients = panic.clients; +var manager = require('panic-manager')(); + +manager.start({ + clients: Array(config.servers).fill().map(function(u, i){ + return { + type: 'node', + port: config.port + (i + 1) + } + }), + panic: 'http://' + config.IP + ':' + config.port +}); + +var servers = clients.filter('Node.js'); +var bob = servers.pluck(1); +var browsers = clients.excluding(servers); +var alice = browsers.pluck(1); +var carl = browsers.excluding(alice).pluck(1); +var dave = browsers.excluding([alice, carl]).pluck(1); +var cd = new panic.ClientList([carl, dave]); + +describe("Put ACK", function(){ + //this.timeout(5 * 60 * 1000); + this.timeout(10 * 60 * 1000); + + it("Servers have joined!", function(){ + return servers.atLeast(config.servers); + }); + + it("GUN started!", function(){ + var tests = [], i = 0; + servers.each(function(client){ + tests.push(client.run(function(test){ + var env = test.props; + test.async(); + try{ require('fs').unlinkSync(env.i+'data') }catch(e){} + try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){} + var server = require('http').createServer(function(req, res){ + res.end("I am "+ env.i +"!"); + }); + var port = env.config.port + env.i; + var Gun = require('gun'); + var peers = [], i = env.config.servers; + while(i--){ + var tmp = (env.config.port + (i + 1)); + if(port != tmp){ // ignore ourselves + peers.push('http://'+ env.config.IP + ':' + tmp + '/gun'); + } + } + console.log(port, " connect to ", peers); + var gun = Gun({file: env.i+'data', peers: peers, web: server}); + server.listen(port, function(){ + test.done(); + }); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it(config.browsers +" browser(s) have joined!", function(){ + console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!"); + return browsers.atLeast(config.browsers); + }); + + it("Browsers initialized gun!", function(){ + var tests = [], i = 0; + browsers.each(function(client, id){ + tests.push(client.run(function(test){ + try{ localStorage.clear() }catch(e){} + try{ indexedDB.deleteDatabase('radata') }catch(e){} + var env = test.props; + var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'); + window.gun = gun; + window.ref = gun.get('a'); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + + it("connect", function(){ + return alice.run(function(test){ + console.log("I AM ALICE"); + test.async(); + gun.get('random').get(function(ack){ + setTimeout(function(){ + test.done(); + }, 1000); + }) + }); + }); + + it("Put", function(){ + return alice.run(function(test){ + test.async(); + + var say = ref._.root.opt.mesh.say; + ref._.root.opt.mesh.say = function(){}; // prevent from syncing + + var c = 0; + ref.put({hello: 'world'}, function(ack){ ++c }); + setTimeout(function(){ + ref._.root.opt.mesh.say = say; + if(c){ should_not_have_ack } + test.done(); + }, 1000); + }); + }); + + it("Get", function(){ + return cd.run(function(test){ + test.async(); + console.log("I am Carl or Dave"); + ref.get(function(ack){ + console.log('ack', ack); + if(ack.put){ + test.done(); + } + }); + }); + }); + + it("All finished!", function(done){ + console.log("Done! Cleaning things up..."); + setTimeout(function(){ + done(); + },1000); + }); + + after("Everything shut down.", function(){ + browsers.run(function(){ + //location.reload(); + //setTimeout(function(){ + //}, 15 * 1000); + }); + return servers.run(function(){ + process.exit(); + }); + }); +}); \ No newline at end of file diff --git a/test/panic/holy-grail.js b/test/panic/holy-grail.js index d96dfa2a..0f865095 100644 --- a/test/panic/holy-grail.js +++ b/test/panic/holy-grail.js @@ -50,6 +50,8 @@ describe("The Holy Grail Test!", function(){ test.async(); try{ require('fs').unlinkSync(env.i+'data') }catch(e){} try{ require('fs').unlinkSync((env.i+1)+'data') }catch(e){} + try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){} + try{ require('gun/lib/fsrm')((env.i+1)+'data') }catch(e){} var port = env.config.port + env.i; var server = require('http').createServer(function(req, res){ res.end("I am "+ env.i +"!"); @@ -104,7 +106,8 @@ describe("The Holy Grail Test!", function(){ return server.run(function(test){ console.log(3); var env = test.props; - try{ require('fs').unlinkSync(env.i+'data'); }catch(e){} + try{ require('fs').unlinkSync(env.i+'data') }catch(e){} + try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){} process.exit(0); }, {i: 1, config: config}) }); @@ -176,6 +179,7 @@ describe("The Holy Grail Test!", function(){ var env = test.props; test.async(); try{ require('fs').unlinkSync(env.i+'data') }catch(e){} + try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){} var port = env.config.port + env.i; var server = require('http').createServer(function(req, res){ res.end("I am "+ env.i +"!"); diff --git a/test/panic/radisk.js b/test/panic/radisk.js index 35204735..3470bf78 100644 --- a/test/panic/radisk.js +++ b/test/panic/radisk.js @@ -3,9 +3,9 @@ var config = { port: 8765, servers: 2, browsers: 3, - each: 1000000, - burst: 25, - wait: 25, + each: 100000, //1000000, + burst: 1, + wait: 1, dir: __dirname, chunk: 1024 * 1024 * 10, notrad: false, diff --git a/test/panic/scale.js b/test/panic/scale.js index d88c3f45..ce96e041 100644 --- a/test/panic/scale.js +++ b/test/panic/scale.js @@ -4,7 +4,7 @@ var config = { servers: 1, browsers: 2, each: 2500, - burst: 25, // do not go below 1! + burst: 1, // do not go below 1! wait: 1, route: { '/': __dirname + '/index.html', diff --git a/test/rad/browser.html b/test/rad/browser.html new file mode 100644 index 00000000..d864fa0e --- /dev/null +++ b/test/rad/browser.html @@ -0,0 +1,102 @@ + + +

    RindexedDB

    + + + + + + + + + + + + +
    +
    + + + + + + + \ No newline at end of file diff --git a/test/rad/parse.rad b/test/rad/parse.rad new file mode 100644 index 00000000..f05ca7d3 --- /dev/null +++ b/test/rad/parse.rad @@ -0,0 +1,10 @@ ++1#"age:"+29>+1549776205172 ++1#"name:""Bob!>+1549776205172 ++1#"pet:"#XAqxAKkRa6lTsfAElEjDweqt>+1549776205172 ++0#"u/m ++1#" ++2#"alice:"#dlgw87rue6oVQhsvc3XFLrOu>+1549776205172 ++2#"bob:"#nuTAd2Tn4S5SiDVA7nxNBbZt>+1549776205172 ++1#"/p ++2#"alice:"#USw3Dp7hTD7VMBLnd8dVBR4s>+1549776205200 ++2#"bob:"#1VwZRUw7vQ1hX8gspN1ZrHVj>+1549776205200 diff --git a/test/rad/rad.js b/test/rad/rad.js new file mode 100644 index 00000000..6d07686e --- /dev/null +++ b/test/rad/rad.js @@ -0,0 +1,349 @@ +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 = 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(){ + var radix = Radix(); + it('radix write read', function(done){ + var all = {}; + names.forEach(function(v,i){ + v = v.toLowerCase(); + all[v] = v; + radix(v, i) + }); + expect(Gun.obj.empty(all)).to.not.be.ok(); + Radix.map(radix, function(v,k){ + delete all[k]; + }); + expect(Gun.obj.empty(all)).to.be.ok(); + done(); + }); + + it('radix write read again', function(done){ + var all = {}; + names.forEach(function(v,i){ + v = v.toLowerCase(); + all[v] = v; + //rad(v, i) + }); + expect(Gun.obj.empty(all)).to.not.be.ok(); + Radix.map(radix, function(v,k){ + delete all[k]; + }); + 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){ + v = v.toLowerCase(); + if(v < start){ return } + if(end < v){ return } + all[v] = v; + //rad(v, i) + }); + expect(Gun.obj.empty(all)).to.not.be.ok(); + Radix.map(radix, function(v,k, a,b){ + //if(!all[k]){ throw "out of range!" } + delete all[k]; + }, {start: start, end: end}); + 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){ + v = v.toLowerCase(); + if(v < start){ return } + if(end < v){ return } + all[v] = v; + //rad(v, i) + }); + expect(Gun.obj.empty(all)).to.not.be.ok(); + Radix.map(radix, function(v,k, a,b){ + //if(!all[k]){ throw "out of range!" } + delete all[k]; + }, {start: start, end: end}); + expect(Gun.obj.empty(all)).to.be.ok(); + done(); + }); + + it('radix reverse', function(done){ + var r = Radix(), tmp; + r('alice', 1);r('bob', 2);r('carl', 3);r('carlo',4); + r('dave', 5);r('zach',6);r('zachary',7); + var by = ['alice','bob','carl','carlo','dave','zach','zachary']; + Gun.obj.map(by, function(k,i){ + r(k,i); + }); + Radix.map(r, function(v,k, a,b){ + expect(by.pop()).to.be(k); + tmp = v; + }, {reverse: 1}); + expect(tmp).to.be(1); + expect(by.length).to.be(0); + Radix.map(r, function(v,k, a,b){ + tmp = v; + }); + expect(tmp).to.be(7); + done(); + }); + }); + + describe('Radisk', function(){ + + /*it('parse', function(done){ + this.timeout(60000); + if(Gun.window){ return done() } + var raw = require('fs').readFileSync(__dirname + '/parse.rad').toString(); + rad.parse('!', function(err, disk){ + console.log("!!!!", err); + }, raw); + return; + });*/ + + + it('write contacts', function(done){ + var all = {}, to, start; + names.forEach(function(v,i){ + v = v.toLowerCase(); + all[v] = true; + rad(v, i, function(err, ok){ + expect(err).to.not.be.ok(); + delete all[v]; + if(!Gun.obj.empty(all)){ return } + done(); + }) + }) + }); + + /*it('read contacts reverse', function(done){ + var opt = {}; + opt.reverse = true; + opt.end = 'nothing'; + opt.start = 'marcy'; + var first, last; + rad('', function(err, data){ + console.log("???", err, data); + return; + Radix.map(data, function(v,k){ + console.log(k, v); + //delete all[find+k]; + }); + //if(!Gun.obj.empty(all)){ return } + //done(); + }, opt); + }); + console.log("UNDO THIS RETURN!!!");return;*/ + + it('read contacts start end', function(done){ + var opt = {}; + opt.start = 'Warring'.toLowerCase(); + opt.end = 'Zamir'.toLowerCase(); + var all = {}, find = ''; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v < opt.start){ return } + if(opt.end < v){ return } + if(v.indexOf(find) == 0){ all[v] = true } + }); + rad(find, function(err, data){ + Radix.map(data, function(v,k){ + //console.log(find+k, v); + delete all[find+k]; + }); + if(!Gun.obj.empty(all)){ return } + done(); + }, opt); + }); + + it('read contacts', function(done){ + var all = {}, find = 'a'; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v.indexOf(find) == 0){ all[v] = true } + }); + rad(find, function(err, data){ + //console.log(">>>>>>>>> KUNG FOO PANDA <<<<<<<<<<<"); + //console.debug.i=1;console.log(data); + Radix.map(data, function(v,k){ + delete all[find+k]; + }); + if(!Gun.obj.empty(all)){ return } + done(); + }); + }); + + it('read again', function(done){ + var all = {}, find = 'm'; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v.indexOf(find) == 0){ all[v] = true } + }); + rad(find, function(err, data, info){ + Radix.map(data, function(v,k){ + delete all[find+k]; + }); + if(!Gun.obj.empty(all)){ return } + done(); + }); + }); + + it('read bytes', function(done){ + var all = {}, find = 'm', to; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v.indexOf(find) == 0){ all[v] = true } + }); + rad(find, function(err, data, info){ + Radix.map(data, function(v,k){ + delete all[find+k]; + }); + clearTimeout(to); + to = setTimeout(function(){ + expect(Gun.obj.empty(all)).to.not.be.ok(); + done(); + },100); + }, {limit: 1}); + }); + + }); + + 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; + while(--c){ names.push('bob') } + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put({name: v, age: i}, function(ack){ + expect(ack.err).to.not.be.ok(); + delete all[i]; + if(!Gun.obj.empty(all)){ return } + done(); + }) + }); + }); + + it('write contacts', function(done){ + var all = {}, to, start, tmp; + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put({name: v, age: i}, function(ack){ + expect(ack.err).to.not.be.ok(); + delete all[i]; + if(!Gun.obj.empty(all)){ return } + done(); + }) + }) + }); + + it('read contacts', function(done){ + var all = {}, find = 'm', to; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v.indexOf(find) == 0){ all[v] = true } + }); + gun.get('names').get({'.': {'*': find}, '%': 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); + }); + }); + + it('read contacts again', function(done){ + var all = {}, find = 'a', to; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v.indexOf(find) == 0){ all[v] = true } + }); + gun.get('names').get({'.': {'*': find}, '%': 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); + }); + }); + + it('read contacts fresh', function(done){ + var gun = Gun({chunk: ochunk}); + var all = {}, find = 'b', to; + names.forEach(function(v){ + v = v.toLowerCase(); + if(v.indexOf(find) == 0){ all[v] = true } + }); + gun.get('names').get({'.': {'*': find}, '%': 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/radix.js b/test/radix.js index 27a17199..38450c1e 100644 --- a/test/radix.js +++ b/test/radix.js @@ -10,7 +10,7 @@ describe('Radix', function(){ rad('ablah', 'cool'); rad('node/circle.bob', 'awesome'); - expect(rad('asdf.')).to.be.eql({pub: {'\u001e': 'yum'}}); + expect(rad('asdf.')).to.be.eql({pub: {'': 'yum'}}); expect(rad('nv/foo.bar')).to.be(undefined); }); }); \ No newline at end of file diff --git a/test/sea/sea.js b/test/sea/sea.js index 4f7fe245..e4e5f70e 100644 --- a/test/sea/sea.js +++ b/test/sea/sea.js @@ -6,23 +6,23 @@ var Gun; if(typeof window !== 'undefined'){ env = window } root = env.window? env.window : global; try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){} - try{ require('fs').unlinkSync('data.json') }catch(e){} - //root.Gun = root.Gun || require('../gun'); + 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'); + require('../../lib/store'); + require('../../lib/rfs'); } - if(root.Gun.SEA){ - //Gun = root.Gun = root.Gun; - } else { - var expect = global.expect = require("../expect"); + try{ var expect = global.expect = require("../expect") }catch(e){} + + if(!root.Gun.SEA){ require('../../sea.js'); } }(this)); @@ -296,12 +296,15 @@ describe('SEA', function(){ }) it('refresh login', function(done){ - gun = Gun(); - user = gun.user(); - user.auth('carl', 'test123', function(ack){ - expect(ack.err).to.not.be.ok(); - done() - }) + this.timeout(9000); + setTimeout(function(){ + gun = Gun(); + user = gun.user(); + user.auth('carl', 'test123', function(ack){ + expect(ack.err).to.not.be.ok(); + done() + }) + }, 800); }) it('gun put JSON', function(done){ @@ -361,4 +364,4 @@ describe('SEA', function(){ }) -})() \ No newline at end of file +}()); \ No newline at end of file diff --git a/test/tmp/canon.html b/test/tmp/canon.html deleted file mode 100644 index 7d45257f..00000000 --- a/test/tmp/canon.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - -
    the world is a beautiful place.
    -
    The world is a beautiful place.
    -
    - - - - -
    - - - - \ No newline at end of file diff --git a/test/tmp/contacts.html b/test/tmp/contacts.html new file mode 100644 index 00000000..4e0be10d --- /dev/null +++ b/test/tmp/contacts.html @@ -0,0 +1,44041 @@ + + + + + + + + + + + + + + +
      +
    +
    + + + + \ No newline at end of file diff --git a/test/tmp/tmp.html b/test/tmp/tmp.html new file mode 100644 index 00000000..ae263152 --- /dev/null +++ b/test/tmp/tmp.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file