diff --git a/.gitignore b/.gitignore index eff83060..3080e15d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules npm-debug.log yarn.lock *data.json +*data* *.db .idea/ *.bak diff --git a/examples/http.js b/examples/http.js index 6d7af91d..199dd3e0 100644 --- a/examples/http.js +++ b/examples/http.js @@ -1,8 +1,21 @@ var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8080; var Gun = require('../'); + +var server = require('http').createServer(function(req, res){ + if(Gun.serve(req, res)){ return } // filters gun requests! + require('fs').createReadStream(require('path').join(__dirname, req.url)).on('error',function(){ // static files! + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end(require('fs') + .readFileSync(require('path') + .join(__dirname, 'index.html') // or default to index + )); + }).pipe(res); // stream +}); + var gun = Gun({ file: 'data.json', + web: server, s3: { key: '', // AWS Access Key secret: '', // AWS Secret Token @@ -10,16 +23,6 @@ var gun = Gun({ } }); -var server = require('http').createServer(function(req, res){ - if(gun.wsp.server(req, res)){ - return; // filters gun requests! - } - require('fs').createReadStream(require('path').join(__dirname, req.url)).on('error',function(){ // static files! - res.writeHead(200, {'Content-Type': 'text/html'}); - res.end(require('fs').readFileSync(require('path').join(__dirname, 'index.html'))); // or default to index - }).pipe(res); // stream -}); -gun.wsp(server); server.listen(port); console.log('Server started on port ' + port + ' with /gun'); \ No newline at end of file diff --git a/gun.js b/gun.js index 625e272a..da6299c8 100644 --- a/gun.js +++ b/gun.js @@ -424,7 +424,7 @@ return {converge: true, incoming: true}; } } - return {err: "you have not properly handled recursion through your data or filtered it as JSON"}; + return {err: "Invalid CRDT Data: "+ incomingValue +" to "+ currentValue +" at "+ incomingState +" to "+ currentState +"!"}; } if(typeof JSON === 'undefined'){ throw new Error( @@ -834,27 +834,43 @@ at.root = at.root || at.gun; at.graph = at.graph || {}; at.dup = at.dup || new Gun.dup; + at.ask = Gun.on.ask; + at.ack = Gun.on.ack; var gun = at.gun.opt(at.opt); if(!at.once){ - at.on('in', input, at); - at.on('out', output, at); + at.on('in', root, at); + at.on('out', root, at); } at.once = 1; return gun; } - function output(at){ - //console.log("add to.next(at)!"); // TODO: BUG!!!! - var cat = this.as, gun = cat.gun, tmp; - // TODO: BUG! Outgoing `get` to read from in memory!!! - if(at.get && get(at, cat)){ return } - cat.on('in', obj_to(at, {gun: cat.gun})); // TODO: PERF! input now goes to output so it would be nice to reduce the circularity here for perf purposes. - if(at['#']){ + function root(at){ + //console.log("add to.next(at)"); // TODO: BUG!!! + var ev = this, cat = ev.as, coat; + if(!at.gun){ at.gun = cat.gun } + if(!at['#'] && at['@']){ + at['#'] = Gun.text.random(); // TODO: Use what is used other places instead. + // TODO: BUG! For multi-instances, the "ack" system is globally shared, but it shouldn't be. + if(cat.ack(at['@'], at)){ return } // TODO: Consider not returning here, maybe, where this would let the "handshake" on sync occur for Holy Grail? cat.dup.track(at['#']); + Gun.on('out', obj_to(at, {gun: cat.gun})); + return; } - if(!at.gun){ - at = obj_to(at, {gun: gun}); + if(at['#'] && cat.dup.check(at['#'])){ return } + cat.dup.track(at['#']); + if(cat.ack(at['@'], at)){ return } + //cat.ack(at['@'], at); + coat = obj_to(at, {gun: cat.gun}); + if(at.get){ + if(!get(at, cat)){ + Gun.on('get', coat); + } } - Gun.on('out', at); // TODO: BUG! PERF? WARNING!!! A in-memory `put` triggers an out with an existing ID which reflows into IN which at the end also goes Gun OUT, and then this scope/function resumes and it triggers OUT again! + if(at.put){ + Gun.HAM.synth(at, ev, cat.gun); // TODO: Clean up, just make it part of on('put')! + Gun.on('put', coat); + } + Gun.on('out', coat); } function get(at, cat){ var soul = at.get[_soul], node = cat.graph[soul], field = at.get[_field], tmp; @@ -865,50 +881,36 @@ tmp = Gun.obj.put(Gun.node.soul.ify({}, soul), field, node[field]); node = Gun.state.ify(tmp, field, Gun.state.is(node, field)); } - as.on('in', { - put: node, // TODO: BUG! Clone node! - get: as.soul, + //if(at.gun === cat.gun){ + node = Gun.graph.node(node); // TODO: BUG! Clone node? + //} else { + // cat = (at.gun._); + //} + tmp = as.ack; + cat.on('in', { + '@': at['#'], + how: 'mem', + put: node, gun: as.gun }); - if(0 < as.ack){ + if(0 < tmp){ return true; } } - function input(at){ - //console.log("add to.next(at)"); // TODO: BUG!!! - var ev = this, cat = ev.as; - if(!at.gun){ at.gun = cat.gun } - if(!at['#'] && at['@']){ - at['#'] = Gun.text.random(); // TODO: Use what is used other places instead. - if(Gun.on.ack(at['@'], at)){ return } // TODO: Consider not returning here, maybe, where this would let the "handshake" on sync occur for Holy Grail? - cat.dup.track(at['#']); - cat.on('out', at); - return; - } - if(at['#'] && cat.dup.check(at['#'])){ return } - cat.dup.track(at['#']); - if(Gun.on.ack(at['@'], at)){ return } - if(at.put){ - Gun.HAM.synth(at, ev, cat.gun); // TODO: Clean up, just make it part of on('put')! - Gun.on('put', at); - } - if(at.get){ Gun.on('get', at) } - Gun.on('out', at); - } }()); ;(function(){ - var ask = Gun.on.ask = function(cb, as){ + Gun.on.ask = function(cb, as){ + if(!this.on){ return } var id = Gun.text.random(); - if(cb){ ask.on(id, cb, as) } + if(cb){ this.on(id, cb, as) } return id; } - ask.on = Gun.on; Gun.on.ack = function(at, reply){ - if(!at || !reply || !ask.on){ return } + if(!at || !reply || !this.on){ return } var id = at['#'] || at; - if(!ask.tag || !ask.tag[id]){ return } - ask.on(id, reply); + if(!this.tag || !this.tag[id]){ return } + this.on(id, reply); return true; } }()); @@ -954,6 +956,176 @@ module.exports = Gun; })(require, './root'); + ;require(function(module){ + return; + var Gun = require('./root'); + var onto = require('./onto'); + function Chain(back){ + var at = this._ = {back: back, on: onto, $: this, next: {}}; + at.root = back? back.root : at; + at.on('in', input, at); + at.on('out', output, at); + } + var chain = Chain.prototype; + chain.back = function(arg){ var tmp; + if(tmp = this._.back){ + return tmp.$; + } + } + chain.next = function(arg){ + var at = this._, cat; + if(cat = at.next[arg]){ + return cat.$; + } + cat = (new Chain(at)._); + at.next[arg] = cat; + cat.key = arg; + return cat.$; + } + chain.get = function(arg){ + if(typeof arg == 'string'){ + var at = this._, cat; + if(cat = at.next[arg]){ + return cat.$; + } + cat = (this.next(arg)._); + if(at.get || at === at.root){ + cat.get = arg; + } + return cat.$; + } else { + var at = this._; + var out = {'#': Gun.text.random(), get: {}, cap: 1}; + var to = at.root.on(out['#'], get, {next: arg}) + at.on('in', get, to); + at.on('out', out); + } + return this; + } + function get(env){ + var as = this.as; + if(as.next){ + as.next(env, this); + } + } + chain.map = function(cb){ + var at = this._; + var chain = new Chain(at); + var cat = chain._; + var u; + at.on('in', function(env){ var tmp; + if(!env){ return } + var cat = this.as; + var to = this.to; + if(tmp = env.put){ + to.next(env); + Gun.obj.map(tmp, function(data, key){ + if('_' == key){ return } + if(cb){ + data = cb(data, key); + if(u === data){ return } + } + cat.on('in', Gun.obj.to(env, {put: data})); + }); + } + }, cat); + return chain; + } + function input(env){ var tmp; + if(!env){ return } + var cat = this.as; + var to = this.to; + if(tmp = env.put){ + if(tmp && tmp['#'] && (tmp = Gun.val.rel.is(tmp))){ + //input.call(this, Gun.obj.to(env, {put: cat.root.put[tmp]})); + return; + } + cat.put = tmp; + to.next(env); + var next = cat.next; + Gun.obj.map(tmp, function(data, key){ + if(!(key = next[key])){ return } + key.on('in', Gun.obj.to(env, {put: data})) + }); + } + } + function output(env){ var tmp; + var u; + if(!env){ return } + var cat = this.as; + var to = this; + if(!cat.back){ + env.test = true; + env.gun = cat.root.$; + Gun.on('out', env); + return; + } + if(tmp = env.get){ + if(cat.get){ + env = Gun.obj.to(env, {get: {'#': cat.get, '.': tmp}}); + } else + if(cat.key){ + env = Gun.obj.to(env, {get: Gun.obj.put({}, cat.key, tmp)}); + } else { + env = Gun.obj.to(env, {get: {'*': tmp}}) + } + } + cat.back.on('out', env); + } + chain.val = function(cb, opt){ + var at = this._; + if(cb){ + if(opt){ + } else { + if(at.val){ + cb(at.put, at.get, at); + } + } + this.get(function(env, ev){ + cb(env.put, env.get, env); + }); + } + } + + + + + var graph = { + app: {_:{'#':'app'}, + foo: {_:{'#':'foo'}, + bar: {'#': 'asdf'}, + rab: {'#': 'fdsa'} + }/*, + oof: {_:{'#':'oof'}, + bar: {bat: "really"}, + rab: {bat: "nice!"} + }*/ + }, + asdf: {_:{'#': 'asdf'}, baz: "hello world!"}, + fdsa: {_:{'#': 'fdsa'}, baz: "world hello!"} + } + Gun.on('out', function(env){ + if(!env.test){ return } + setTimeout(function(){ + console.log("reply", env.get); + env.gun._.on('in', {'@': env['#'], + put: Gun.graph.node(graph[env.get['#']]) + }); + return; + env.gun._.on('in', {put: graph, '@': env['#']}); + },100); + }); + setTimeout(function(){ + + var c = new Chain(), u; + c.get('app').map().map(x => x.bat? {baz: x.bat} : u).get('baz').val(function(data, key, env){ + console.log("envelope", env); + }); + + },1000); + + })(require, './experiment'); + ;require(function(module){ var Gun = require('./root'); Gun.chain.back = function(n, opt){ var tmp; @@ -1031,7 +1203,7 @@ if(!at.gun._){ return } (at.gun._).on('out', { get: {'#': rel, '.': get}, - '#': Gun.on.ask(Gun.HAM.synth, at.gun), + '#': root._.ask(Gun.HAM.synth, at.gun), gun: at.gun }); return; @@ -1054,7 +1226,7 @@ if(!at.gun._){ return } (at.gun._).on('out', { get: {'#': cat.soul, '.': get}, - '#': Gun.on.ask(Gun.HAM.synth, at.gun), + '#': root._.ask(Gun.HAM.synth, at.gun), gun: at.gun }); return; @@ -1079,7 +1251,7 @@ }); } if(cat.ack){ - if(!obj_has(cat, 'put')){ + if(!obj_has(cat, 'put')){ // u !== cat.put instead? return; } } @@ -1087,7 +1259,8 @@ if(cat.soul){ cat.on('out', { get: {'#': cat.soul}, - '#': Gun.on.ask(Gun.HAM.synth, cat.gun), + '#': root._.ask(Gun.HAM.synth, cat.gun), + gun: cat.gun }); return; } @@ -1107,7 +1280,7 @@ function input(at){ at = at._ || at; var ev = this, cat = this.as, gun = at.gun, coat = gun._, change = at.put, back = cat.back._ || empty, rel, tmp; - if(0 > cat.ack && at.via && !Gun.val.rel.is(change)){ // for better behavior? + if(0 > cat.ack && !Gun.val.rel.is(change)){ // for better behavior? cat.ack = 1; } if(cat.get && at.get !== cat.get){ @@ -1131,6 +1304,7 @@ return; } if(cat.soul){ + if(cat.root._.now){ at = obj_to(at, {put: change = coat.put}) } // TODO: Ugly hack for uncached synchronous maps. ev.to.next(at); echo(cat, at, ev); obj_map(change, map, {at: at, cat: cat}); @@ -1248,7 +1422,7 @@ tmp.ack = tmp.ack || -1; tmp.on('out', { get: {'#': soul}, - '#': Gun.on.ask(Gun.HAM.synth, tmp.gun), + '#': cat.root._.ask(Gun.HAM.synth, tmp.gun), gun: tmp.gun }); return; @@ -1256,7 +1430,7 @@ obj_map(cat.next, function(gun, key){ (gun._).on('out', { get: {'#': soul, '.': key}, - '#': Gun.on.ask(Gun.HAM.synth, tmp.gun), + '#': cat.root._.ask(Gun.HAM.synth, tmp.gun), gun: gun }); }); @@ -1281,9 +1455,9 @@ var gun = this, at = gun._; as = cb || {}; as.use = key; - as.out = as.out || {}; + as.out = as.out || {cap: 1}; as.out.get = as.out.get || {}; - (at.root._).now = true; + '_' != at.get && ((at.root._).now = true); // ugly hack for now. at.on('in', use, as); at.on('out', as.out); (at.root._).now = false; @@ -1338,7 +1512,7 @@ // #soul.field=value>state // ~who#where.where=what>when@was // TODO: BUG! Put probably cannot handle plural chains! - var gun = this, root = (gun._).root, tmp; + var gun = this, at = (gun._), root = at.root, tmp; as = as || {}; as.data = data; as.gun = as.gun || gun; @@ -1347,9 +1521,12 @@ } else { as.ack = cb; } + if(at.soul){ + as.soul = at.soul; + } if(as.soul || root === gun){ if(!obj_is(as.data)){ - (opt.any||noop).call(opt.as || gun, as.out = {err: Gun.log("No field to put", (typeof as.data), '"' + as.data + '" on!')}); + (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; } @@ -1361,12 +1538,17 @@ if(Gun.is(data)){ data.get(function(at,ev){ev.off(); var s = Gun.node.soul(at.put); - if(!s){Gun.log("Can only save a node, not a property.");return} + if(!s){Gun.log("The reference you are saving is a", typeof at.put, '"'+ as.put +'", not a node (object)!');return} gun.put(Gun.val.rel.ify(s), cb, as); }); return gun; } - as.ref = as.ref || (root === (tmp = (gun._).back))? gun : 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('_').get(any, {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. @@ -1382,7 +1564,7 @@ env.soul = as.soul; as.graph = Gun.graph.ify(as.data, env, as); if(env.err){ - (as.ack||noop).call(opt.as || as.gun, as.out = {err: Gun.log(env.err)}); + (as.ack||noop).call(as, as.out = {err: Gun.log(env.err)}); if(as.res){ as.res() } return; } @@ -1393,8 +1575,9 @@ if(!as.graph || obj_map(as.stun, no)){ return } (as.res||iife)(function(){ (as.ref._).on('out', { + cap: 3, gun: as.ref, put: as.out = as.env.graph, opt: as.opt, - '#': Gun.on.ask(function(ack){ this.off(); // One response is good enough for us currently. Later we may want to adjust this. + '#': as.gun.back(-1)._.ask(function(ack){ 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) @@ -1409,7 +1592,7 @@ 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]); // TODO: API change! We won't need 'path: true' anymore. + ref = ref.get(path[i]); } if(as.not || Gun.node.soul(at.obj)){ at.soul(Gun.node.soul(at.obj) || ((as.opt||{}).uuid || as.gun.back('opt.uuid') || Gun.text.random)()); @@ -1440,7 +1623,7 @@ var cat = (at.gun._.back._), data = cat.put, opt = as.opt||{}, root, tmp; ev.off(); if(as.ref !== as.gun){ - tmp = (as.gun._).get; + tmp = (as.gun._).get || cat.get; if(!tmp){ // TODO: Handle console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? return; @@ -1463,10 +1646,10 @@ } if(!as.not && !(as.soul = Gun.node.soul(data))){ if(as.path && obj_is(as.data)){ // Apparently necessary - as.soul = (opt.uuid || as.gun.back('opt.uuid') || Gun.text.random)(); + as.soul = (opt.uuid || cat.root._.opt.uuid || Gun.text.random)(); } else { //as.data = obj_put({}, as.gun._.get, as.data); - as.soul = at.soul; + as.soul = at.soul || cat.soul || (opt.uuid || cat.root._.opt.uuid || Gun.text.random)(); } } as.ref.put(as.data, as.soul, as); @@ -1512,7 +1695,7 @@ if(!val_is(cv) && u !== cv){ return true } // Undefined is okay since a value might not exist on both nodes. // it is true that this is an invalid HAM comparison. var HAM = Gun.HAM(machine, is, cs, iv, cv); if(HAM.err){ - console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", HAM.err); // this error should never happen. + console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", field, HAM.err); // this error should never happen. return; } if(HAM.state || HAM.historical || HAM.current){ // TODO: BUG! Not implemented. @@ -1580,13 +1763,11 @@ } Gun.HAM.synth = function(at, ev, as){ var gun = this.as || as; var cat = gun._, root = cat.root._, put = {}, tmp; - //if(cat.ack){ - // cat.ack = 1; - //} if(!at.put){ //if(obj_has(cat, 'put')){ return } if(cat.put !== u){ return } cat.on('in', { + //root.ack(at['@'], { get: cat.get, put: cat.put = u, gun: gun, @@ -1599,6 +1780,9 @@ put[soul] = Gun.HAM.delta(graph[soul], node, {graph: graph}); // TODO: PERF! SEE IF WE CAN OPTIMIZE THIS BY MERGING UNION INTO DELTA! graph[soul] = Gun.HAM.union(graph[soul], node) || graph[soul]; }, root); + if(at.gun !== root.gun){ + put = at.put; + } // TODO: PERF! Have options to determine if this data should even be in memory on this peer! obj_map(put, function(node, soul){ var root = this, next = root.next || (root.next = {}), gun = next[soul] || (next[soul] = root.gun.get(soul)), coat = (gun._); @@ -1899,15 +2083,15 @@ Gun.chain.val = function(cb, opt){ var gun = this, at = gun._, data = at.put; - if(0 < at.ack && u !== data && cb){ - cb.call(gun, data, at.get); + if(0 < at.ack && u !== data){ + (cb || noop).call(gun, data, at.get); return gun; } if(cb){ (opt = opt || {}).ok = cb; opt.cat = at; gun.get(val, {as: opt}); - opt.async = at.stun? 1 : true; + 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(); @@ -1932,7 +2116,8 @@ data = tmp.put; } if(ev.wait){ clearTimeout(ev.wait) } - if(!to && (!(0 < coat.ack) || ((true === opt.async) && 0 !== opt.wait))){ + //if(!to && (!(0 < coat.ack) || ((true === opt.async) && 0 !== opt.wait))){ + if(!opt.async){ ev.wait = setTimeout(function(){ val.call({as:opt}, at, ev, ev.wait || 1) }, opt.wait || 99); @@ -1973,7 +2158,7 @@ } var obj = Gun.obj, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to; var rel = Gun.val.rel; - var empty = {}, u; + var empty = {}, noop = function(){}, u; })(require, './on'); ;require(function(module){ @@ -2014,7 +2199,7 @@ } function map(at){ if(!at.put || Gun.val.is(at.put)){ return } - if(this.as.val){ this.off() } + if(this.as.val){ this.off() } // TODO: Ugly hack! obj_map(at.put, each, {cat: this.as, gun: at.gun}); this.to.next(at); } @@ -2026,14 +2211,6 @@ var obj_map = Gun.obj.map, noop = function(){}, event = {stun: noop, off: noop}, n_ = Gun.node._, u; })(require, './map'); - ;require(function(module){ - var Gun = require('./core'); - Gun.chain.init = function(){ // TODO: DEPRECATE? - (this._.opt = this._.opt || {}).init = true; - return this.back(-1).put(Gun.node.ify({}, this._.get), null, this._.get); - } - })(require, './init'); - ;require(function(module){ var Gun = require('./core'); Gun.chain.set = function(item, cb, opt){ @@ -2059,24 +2236,48 @@ if(typeof window !== 'undefined'){ root = window } var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop}; + var check = {}, dirty = {}, async = {}, count = 0, max = 10000, wait; + Gun.on('put', function(at){ var err, id, opt, root = at.gun._.root; this.to.next(at); (opt = {}).prefix = (at.opt || opt).prefix || at.gun.back('opt.prefix') || 'gun/'; - Gun.graph.is(at.put, function(node, soul, map){ - var keys = Gun.obj.ify(store.getItem(opt.prefix + soul+'_')||{}); - map(function(val, key){ - keys[key] = 1; - var state = Gun.state.is(node, key); - // #soul.field=val>state - try{store.setItem(opt.prefix + soul+key, JSON.stringify([val,state])); + var graph = root._.graph; + + Gun.obj.map(at.put, function(node, soul){ + async[soul] = graph[soul] || node; + }); + count += 1; + check[at['#']] = root; + function save(){ + clearTimeout(wait); + var ack = check; + var all = async; + count = 0; + wait = false; + check = {}; + async = {}; + Gun.obj.map(all, function(node, soul){ + // Since localStorage only has 5MB, it is better that we keep only + // the data that the user is currently interested in. + node = graph[soul] || all[soul]; + try{store.setItem(opt.prefix + soul, JSON.stringify(node)); }catch(e){ err = e || "localStorage failure" } }); - try{store.setItem(opt.prefix + soul+'_', JSON.stringify(keys)); - }catch(e){ err = e || "localStorage failure" } - }); - if(Gun.obj.empty(at.gun.back('opt.peers'))){ - Gun.on.ack(at, {err: err, ok: 0}); // only ack if there are no peers. + if(!Gun.obj.empty(at.gun.back('opt.peers'))){ return } // only ack if there are no peers. + Gun.obj.map(ack, function(root, id){ + root.on('in', { + '@': id, + err: err, + ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number. + }); + }); } + if(count >= max){ // goal is to do 10K inserts/second. + return save(); + } + if(wait){ return } + clearTimeout(wait); + wait = setTimeout(save, 1000); }); Gun.on('get', function(at){ this.to.next(at); @@ -2084,18 +2285,17 @@ //setTimeout(function(){ (opt = at.opt || {}).prefix = opt.prefix || at.gun.back('opt.prefix') || 'gun/'; if(!lex || !(soul = lex[Gun._.soul])){ return } + //if(0 >= at.cap){ return } var field = lex['.']; - if(field){ - if(data = Gun.obj.ify(store.getItem(opt.prefix + soul+field)||null)||u){ - data = Gun.state.ify(u, field, data[1], data[0], soul); - } - } else { - Gun.obj.map(Gun.obj.ify(store.getItem(opt.prefix + soul+'_')), function(v,field){ - v = Gun.obj.ify(store.getItem(opt.prefix + soul+field)||{}); - data = Gun.state.ify(data, field, v[1], v[0], soul); - }); + + data = Gun.obj.ify(store.getItem(opt.prefix + soul) || null) || async[soul] || u; + if(data && field){ + data = Gun.state.ify(u, field, Gun.state.is(data, field), data[field], soul); } - gun.back(-1).on('in', {'@': at['#'], put: Gun.graph.node(data)}); + if(!data && !Gun.obj.empty(gun.back('opt.peers'))){ // if data not found, don't ack if there are peers. + return; // Hmm, what if we have peers but we are disconnected? + } + gun.on('in', {'@': at['#'], put: Gun.graph.node(data), how: 'lS'}); //},11); }); })(require, './adapters/localStorage'); @@ -2103,7 +2303,6 @@ ;require(function(module){ var Gun = require('./core'); - // Check for stone-age browsers. if (typeof JSON === 'undefined') { throw new Error( 'Gun depends on JSON. Please load it first:\n' + @@ -2111,384 +2310,99 @@ ); } - function Client (url, options, wscOptions ) { - if (!(this instanceof Client)) { - return new Client(url, options, wscOptions); + var WebSocket; + if(typeof window !== 'undefined'){ + WebSocket = window.WebSocket || window.webkitWebSocket || window.mozWebSocket; + } else { + return; + } + var message, count = 0, noop = function(){}, wait; + + Gun.on('out', function(at){ + this.to.next(at); + var cat = at.gun._.root._, wsp = cat.wsp || (cat.wsp = {}); + if(at.wsp && 1 === wsp.count){ return } // if the message came FROM the only peer we are connected to, don't echo it back. + message = JSON.stringify(at); + //if(++count){ console.log("msg OUT:", count, Gun.obj.ify(message)) } + if(cat.udrain){ + cat.udrain.push(message); + return; } - - this.url = Client.formatURL(url); - this.socket = null; - this.queue = []; - this.sid = Gun.text.random(10); - - this.on = Gun.on; - - this.options = options || {}; - this.options.wsc = wscOptions; - this.resetBackoff(); - } - - Client.prototype = { - constructor: Client, - - drainQueue: function () { - var queue = this.queue; - var client = this; - - // Reset the queue. - this.queue = []; - - // Send each message. - queue.forEach(function (msg) { - client.send(msg); - }); - - return queue.length; - }, - - connect: function () { - var client = this; - var socket = new Client.WebSocket(this.url, this.options.wsc.protocols, this.options.wsc ); - this.socket = socket; - - // Forward messages into the emitter. - socket.addEventListener('message', function (msg) { - client.on('message', msg); - }); - - // Reconnect on close events. - socket.addEventListener('close', function () { - client.scheduleReconnect(); - }); - - // Send the messages in the queue. - this.ready(function () { - client.drainQueue(); - }); - - return socket; - }, - - resetBackoff: function () { - var backoff = this.options; - - this.backoff = { - time: backoff.time || 100, - max: backoff.max || 2000, - factor: backoff.factor || 2 - }; - - return this.backoff; - }, - - nextBackoff: function () { - var backoff = this.backoff; - var next = backoff.time * backoff.factor; - var max = backoff.max; - - if (next > max) { - next = max; - } - - return (backoff.time = next); - }, - - // Try to efficiently reconnect. - scheduleReconnect: function () { - var client = this; - var time = this.backoff.time; - this.nextBackoff(); - - setTimeout(function () { - client.connect(); - - client.ready(function () { - client.resetBackoff(); - }); - }, time); - }, - - isClosed: function () { - var socket = this.socket; - - if (!socket) { - return true; - } - - var state = socket.readyState; - - if (state === socket.CLOSING || state === socket.CLOSED) { - return true; - } - - return false; - }, - - ready: function (callback) { - var socket = this.socket; - var state = socket.readyState; - - if (state === socket.OPEN) { - callback(); - return; - } - - if (state === socket.CONNECTING) { - socket.addEventListener('open', callback); - } - }, - - send: function (msg) { - if (this.isClosed()) { - this.queue.push(msg); - - // Will send once connected. - this.connect(); - return false; - } - - var socket = this.socket; - - // Make sure the socket is open. - this.ready(function () { - socket.send(msg); - }); - - return true; - } - }; - - if (typeof window !== 'undefined') { - Client.WebSocket = window.WebSocket || - window.webkitWebSocket || - window.mozWebSocket || - null; - } - - Client.isSupported = !!Client.WebSocket; - - if(!Client.isSupported){ return } // TODO: For now, don't do anything in browsers/servers that don't work. Later, use JSONP fallback and merge with server code? - - // Ensure the protocol is correct. - Client.formatURL = function (url) { - return url.replace(/^http/, 'ws'); - }; - - // Send a message to a group of peers. - Client.broadcast = function (urls, msg) { - var pool = Client.pool; - msg.headers = msg.headers || {}; - - Gun.obj.map(urls, function (options, addr) { - - var url = Client.formatURL(addr); - - var peer = pool[url]; - - var envelope = { - headers: Gun.obj.to(msg.headers, { - 'gun-sid': peer.sid - }), - body: msg.body - }; - - var serialized = Gun.text.ify(envelope); - - peer.send(serialized); - }); - - }; - - // A map of URLs to client instances. - Client.pool = {}; - - // Close all WebSockets when the window closes. - if (typeof window !== 'undefined') { - window.addEventListener('unload', function () { - Gun.obj.map(Client.pool, function (client) { - if (client.isClosed()) { - return; - } - - client.socket.close(); - }); - }); - } - - // Define client instances as gun needs them. - // Sockets will not be opened until absolutely necessary. - Gun.on('opt', function (ctx) { - this.to.next(ctx); - - var gun = ctx.gun; - var peers = gun.back('opt.peers') || {}; - - Gun.obj.map(peers, function (options, addr) { - var url = Client.formatURL(addr); - - // Ignore clients we've seen before. - if (Client.pool.hasOwnProperty(url)) { - return; - } - - var client = new Client(url, options.backoff, gun.back('opt.wsc') || {protocols:null}); - - // Add it to the pool. - Client.pool[url] = client; - - // Listen to incoming messages. - client.on('message', function (msg) { - var data; - - try { - data = Gun.obj.ify(msg.data); - } catch (err) { - // Invalid message, discard it. - return; - } - - if (!data || !data.body) { - return; - } - - gun.on('in', data.body); - }); - }); + cat.udrain = []; + clearTimeout(wait); + wait = setTimeout(function(){ + if(!cat.udrain){ return } + var tmp = cat.udrain; + cat.udrain = null; + message = JSON.stringify(tmp); + Gun.obj.map(cat.opt.peers, send, cat); + },1); + wsp.count = 0; + Gun.obj.map(cat.opt.peers, send, cat); }); - function request (peers, ctx) { - if (Client.isSupported) { - Client.broadcast(peers, ctx); + function send(peer){ + var msg = message, cat = this; + var wire = peer.wire || open(peer, cat); + if(cat.wsp){ cat.wsp.count++ } + if(!wire){ return } + if(wire.readyState === wire.OPEN){ + wire.send(msg); + return; } + (peer.queue = peer.queue || []).push(msg); } - // Broadcast the messages. - Gun.on('out', function (ctx) { - this.to.next(ctx); - var gun = ctx.gun; - var peers = gun.back('opt.peers') || {}; - var headers = gun.back('opt.headers') || {}; - // Validate. - if (Gun.obj.empty(peers)) { + function receive(msg, peer, cat){ + if(!cat || !msg){ return } + try{msg = JSON.parse(msg.data || msg); + }catch(e){} + if(msg instanceof Array){ + var i = 0, m; + while(m = msg[i++]){ + receive(m, peer, cat); + } return; } + //if(++count){ console.log("msg in:", count, msg.body || msg) } + if(cat.wsp && 1 === cat.wsp.count){ (msg.body || msg).wsp = noop } // If there is only 1 client, just use noop since it doesn't matter. + cat.gun.on('in', msg.body || msg); + } - request(peers, {body: ctx, headers: headers}); - }); - - request.jsonp = function (opt, cb) { - request.jsonp.ify(opt, function (url) { - if (!url) { - return; - } - request.jsonp.send(url, function (err, reply) { - cb(err, reply); - request.jsonp.poll(opt, reply); - }, opt.jsonp); - }); - }; - request.jsonp.send = function (url, cb, id) { - var js = document.createElement('script'); - js.src = url; - js.onerror = function () { - (window[js.id] || function () {})(null, { - err: 'JSONP failed!' - }); + function open(peer, as){ + if(!peer || !peer.url){ return } + var url = peer.url.replace('http', 'ws'); + var wire = peer.wire = new WebSocket(url); + wire.onclose = function(){ + reconnect(peer, as); }; - window[js.id = id] = function (res, err) { - cb(err, res); - cb.id = js.id; - js.parentNode.removeChild(js); - delete window[cb.id]; + wire.onerror = function(error){ + reconnect(peer, as); + if(!error){ return } + if(error.code === 'ECONNREFUSED'){ + //reconnect(peer, as); + } }; - js.async = true; - document.getElementsByTagName('head')[0].appendChild(js); - return js; - }; - request.jsonp.poll = function (opt, res) { - if (!opt || !opt.base || !res || !res.headers || !res.headers.poll) { - return; - } - var polls = request.jsonp.poll.s = request.jsonp.poll.s || {}; - polls[opt.base] = polls[opt.base] || setTimeout(function () { - var msg = { - base: opt.base, - headers: { pull: 1 } - }; - - request.each(opt.headers, function (header, name) { - msg.headers[name] = header; + wire.onopen = function(){ + var queue = peer.queue; + peer.queue = []; + Gun.obj.map(queue, function(msg){ + message = msg; + send.call(as, peer); }); + } + wire.onmessage = function(msg){ + receive(msg, peer, as); + }; + return wire; + } - request.jsonp(msg, function (err, reply) { - delete polls[opt.base]; - - var body = reply.body || []; - while (body.length && body.shift) { - var res = reply.body.shift(); - if (res && res.body) { - request.createServer.ing(res, function () { - request(opt.base, null, null, res); - }); - } - } - }); - }, res.headers.poll); - }; - request.jsonp.ify = function (opt, cb) { - var uri = encodeURIComponent, query = '?'; - if (opt.url && opt.url.pathname) { - query = opt.url.pathname + query; - } - query = opt.base + query; - request.each((opt.url || {}).query, function (value, key) { - query += (uri(key) + '=' + uri(value) + '&'); - }); - if (opt.headers) { - query += uri('`') + '=' + uri( - JSON.stringify(opt.headers) - ) + '&'; - } - if (request.jsonp.max < query.length) { - return cb(); - } - var random = Math.floor(Math.random() * (0xffff + 1)); - query += (uri('jsonp') + '=' + uri(opt.jsonp = 'P' + random)); - if (opt.body) { - query += '&'; - var w = opt.body, wls = function (w, l, s) { - return uri('%') + '=' + uri(w+'-'+(l||w)+'/'+(s||w)) + '&' + uri('$') + '='; - } - if (typeof w != 'string') { - w = JSON.stringify(w); - query += uri('^') + '=' + uri('json') + '&'; - } - w = uri(w); - var i = 0, l = w.length - , s = request.jsonp.max - (query.length + wls(l.toString()).length); - if (s < 0){ - return cb(); - } - while (w) { - cb(query + wls(i, (i += s), l) + w.slice(0, i)); - w = w.slice(i); - } - } else { - cb(query); - } - }; - request.jsonp.max = 2000; - request.each = function (obj, cb, as) { - if (!obj || !cb) { - return; - } - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - cb.call(as, obj[key], key); - } - } - }; - module.exports = Client; + function reconnect(peer, as){ + clearTimeout(peer.defer); + peer.defer = setTimeout(function(){ + open(peer, as); + }, 2 * 1000); + } })(require, './polyfill/request'); }()); diff --git a/lib/file.js b/lib/file.js index c6851889..8846f027 100644 --- a/lib/file.js +++ b/lib/file.js @@ -5,30 +5,54 @@ var Gun = require('../gun'), fs = require('fs'); +var files = {}; + Gun.on('put', function(at){ + //console.log("file write", Gun.obj.copy(at), at.gun.back(-1)._.opt.file); this.to.next(at); - var fileOpt = at.gun.back('opt._file') - if(!fileOpt.use){ return } + + var root = at.gun.back(-1); + var f = at.gun.back('opt._file') + if(!f.use){ return } var graph = at.put, opt = at.opt || {}; - var Graph = fileOpt.gun._.graph - + var Graph = f.gun._.graph Gun.obj.map(graph, function(node, soul){ - fileOpt.disk.graph[soul] = Graph[soul] || graph[soul]; + f.disk.graph[soul] = Graph[soul] || graph[soul]; }); - graph = JSON.stringify(fileOpt.disk, null, 2); - // TODO: Allow for a `fs.writeFile` compatible module, that is more reliable/safe, to be passed in through the options. - fs.writeFile(opt.file || fileOpt.file, graph, function(err){ - fileOpt.gun.on('in', { - '@': at['#'], - ok: err? undefined : 1, - err: err + f.count = (f.count || 0) + 1; + if(!at['@']){ // don't ack other acks! + (f.check || (f.check = {}))[at['#']] = root; + } + function save(){ + clearTimeout(f.wait); + var ack = f.check; + f.count = 0; + f.wait = false; + f.check = {}; + graph = JSON.stringify(f.disk, null, 2); + // TODO: Allow for a `fs.writeFile` compatible module, that is more reliable/safe, to be passed in through the options. + fs.writeFile(opt.file || f.file, graph, function(err){ + Gun.obj.map(ack, function(root, id){ + root.on('in', { + '@': id, + ok: err? undefined : 1, + err: err || undefined + }); + }); }); - }); + } + if(f.count >= 10000){ // goal is to do 10K inserts/second. + return save(); + } + if(f.wait){ return } + clearTimeout(f.wait); + f.wait = setTimeout(save, 1000); }); Gun.on('get', function(at){ + var fileOpt = at.gun.back('opt._file'); + //if(at.cap && fileOpt.use){ at.cap-- } this.to.next(at); - var fileOpt = at.gun.back('opt._file') if(!fileOpt.use){ return } var opt = at.opt || {}; var soul = at.get['#']; @@ -39,7 +63,8 @@ Gun.on('get', function(at){ } fileOpt.gun.on('in', { put: Gun.graph.node(node), - '@': at['#'] + '@': at['#'], + how: 'file' }) }); @@ -75,7 +100,7 @@ Gun.on('opt', function(at){ opt._file.use = true; opt._file.file = String(opt.file || opt._file.file || 'data.json'); opt._file.raw = opt._file.raw || ((fs.existsSync || require('path').existsSync)(opt._file.file) ? fs.readFileSync(opt._file.file).toString() : null); - opt._file.disk = opt._file.disk || Gun.obj.ify(opt._file.raw || {graph: {}}); + opt._file.disk = files[opt._file.file] = files[opt._file.file] || opt._file.disk || Gun.obj.ify(opt._file.raw || {graph: {}}); opt._file.disk.graph = opt._file.disk.graph || {}; opt._file.gun = gun; }); diff --git a/lib/s3.js b/lib/s3.js index d0a9a561..f8a12eab 100644 --- a/lib/s3.js +++ b/lib/s3.js @@ -1,4 +1,7 @@ ;(function(){ + + if(!process.env.AWS_S3_BUCKET){ return } + var Gun = require('../gun'); var S3 = require('./aws'); diff --git a/lib/serve.js b/lib/serve.js new file mode 100644 index 00000000..ce6c9757 --- /dev/null +++ b/lib/serve.js @@ -0,0 +1,11 @@ +module.exports = function serve(req, res, next){ + if(!req || !res){ return false } + next = next || serve; + if(!req.url){ return next() } + 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')); + return true; + } + return next(); +} \ No newline at end of file diff --git a/lib/server.js b/lib/server.js index b397bd9f..838f0b34 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,7 +1,9 @@ ;(function(){ var Gun = require('../gun'); + Gun.serve = require('./serve'); require('./s3'); - require('./wsp/server'); + require('./uws'); + //require('./wsp/server'); require('./file'); module.exports = Gun; }()); \ No newline at end of file diff --git a/lib/uws.js b/lib/uws.js index 79b6ae81..d6b9d431 100644 --- a/lib/uws.js +++ b/lib/uws.js @@ -4,25 +4,29 @@ var WebSocket = require('uws'); var url = require('url'); -var con; +console.log("Experimental high performance uWS server is being used."); Gun.on('opt', function(at){ this.to.next(at); if(at.once){ return } var opt = at.opt.uws || at.opt.ws || (at.opt.uws = {}); - var cat = at.gun.back(-1)._; + var cat = (at.gun.back(-1)._); - opt.server = new WebSocket.Server(opt || {port: 8080}); + opt.server = opt.server || at.opt.web; + if(opt.server && opt.server.use){ // if ExpressJS + opt.server.use(Gun.serve); + } + + opt.web = new WebSocket.Server(opt); var peers = cat.opt.peers; - console.log("????", opt.server); - opt.server.on('connection', function(ws){ + opt.web.on('connection', function(ws){ ws.upgradeReq = ws.upgradeReq || {}; ws.url = url.parse(ws.upgradeReq.url||'', true); ws.id = ws.id || Gun.text.random(6); peers[ws.id] = {wire: ws}; ws.on('message', function(msg){ - console.log("MESSAGE", msg); + //console.log("MESSAGE", msg); receive(msg, ws, cat); }); ws.on('close', function(){ @@ -31,22 +35,47 @@ Gun.on('opt', function(at){ }); }); -function receive(msg, wire, cat){ - if(!cat){ return } - try{msg = JSON.parse(msg); - msg.url = wire.url; - }catch(e){} - cat.gun.on('in', msg.body || msg); -} - var message; + Gun.on('out', function(at){ this.to.next(at); var cat = at.gun._.root._; - message = JSON.stringify({body: at, headers: {}}); + message = JSON.stringify(at); + if(cat.udrain){ + cat.udrain.push(message); + return; + } + cat.udrain = []; + setTimeout(function(){ + if(!cat.udrain){ return } + //if(count += cat.udrain.length){ console.log("msg out:", count) } + var tmp = cat.udrain; + cat.udrain = null; + message = JSON.stringify(tmp); + Gun.obj.map(cat.opt.peers, send, cat); + },1); Gun.obj.map(cat.opt.peers, send, cat); }); +var count = 0; +function receive(msg, wire, cat){ + if(!cat){ return } + try{msg = JSON.parse(msg); + }catch(e){} + + if(msg instanceof Array){ + var i = 0, m; while(m = msg[i++]){ + receive(m, wire, cat); + } + return; + } + //if(++count){ console.log("msg in:", count) } + + //msg.url = wire.url; + cat.gun.on('in', msg.body || msg); +} + +// EVERY message taken care of. The "extra" ones are from in-memory not having "asked" for it yet - which we won't want it to do for foreign requests. Likewise, lots of chattyness because the put/ack replies happen before the `get` syncs so everybody now has it in-memory already to reply with. function send(peer){ var msg = message, cat = this; var wire = peer.wire || open(peer, cat); @@ -66,6 +95,7 @@ function open(peer, as){ reconnect(peer, as); }); wire.on('error', function(error){ + if(!error){ return } if(error.code === 'ECONNREFUSED'){ reconnect(peer, as); } diff --git a/lib/wsp/server.js b/lib/wsp/server.js index 6da51795..15740c80 100644 --- a/lib/wsp/server.js +++ b/lib/wsp/server.js @@ -16,18 +16,26 @@ Gun.on('opt', function (at) { gun.__ = at.root._; gun.__.opt.ws = opt.ws = gun.__.opt.ws || opt.ws || {}; + if(gun.__.opt.web){ + setTimeout(function(){ + if(gun.__.opt.web.use){ + gun.__.opt.web.use(Gun.serve); + } + start(gun.__.opt.web); + },1); + } function start (server, port, app) { if (app && app.use) { app.use(gun.wsp.server); } - server = gun.__.opt.ws.server = gun.__.opt.ws.server || opt.ws.server || server; + server = gun.__.opt.ws.server = gun.__.opt.ws.server || gun.__.opt.web || opt.ws.server || server; if (!gun.wsp.ws) { + //console.log("????????", gun.__.opt.ws); gun.wsp.ws = new WSS(gun.__.opt.ws); attach(gun, gun.wsp.ws); } - gun.wsp.ws = gun.wsp.ws || new WSS(gun.__.opt.ws); require('./ws')(gun.wsp.ws, function (req, res) { var ws = this; req.headers['gun-sid'] = ws.sid = ws.sid ? ws.sid : req.headers['gun-sid']; @@ -45,6 +53,7 @@ Gun.on('opt', function (at) { gun.__.opt.ws.port = gun.__.opt.ws.port || opt.ws.port || port || 80; } var wsp = gun.wsp = gun.wsp || function (server) { + console.log("WARNING: gun.wsp(server) should be switched to Gun({web: server}) by v0.7!") if (!server) { return gun; } if (Gun.fns.is(server.address)) { if (server.address()) { diff --git a/package.json b/package.json index 8543ada8..065f83c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gun", - "version": "0.6.3", + "version": "0.6.4", "description": "Graph engine", "main": "index.js", "browser": "gun.min.js", @@ -50,7 +50,7 @@ "dependencies": { "aws-sdk": "~>2.15.0", "formidable": "~>1.0.15", - "ws": "~>1.0.1" + "uws": "~>0.13.0" }, "devDependencies": { "express": "~>4.13.4", diff --git a/test/common.js b/test/common.js index 1892c98d..d2e09b04 100644 --- a/test/common.js +++ b/test/common.js @@ -1755,6 +1755,7 @@ describe('Gun', function(){ //expect(count['Alice']).to.be(1); //expect(count['Bob']).to.be(1); //expect(count['Alice Zzxyz']).to.be(1); + if(done.c){ return } done.c = 1; done(); },200); } @@ -3132,6 +3133,7 @@ describe('Gun', function(){ gun.path('alias').get(function(at){ done.alias = done.alias || at.put.mark; }).path('mark').get(function(at){ + //console.log("************", at.put); setTimeout(function(){ done.mark = done.mark || at.put.pub; expect(Gun.val.rel.is(done.mark)).to.be('pub'); @@ -3159,7 +3161,6 @@ describe('Gun', function(){ setTimeout(function(){ var gun2 = Gun(); gun2.get('stef').path('address').val(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" - //console.log("******", data); done.a = true; expect(data.country).to.be('Netherlands'); expect(data.zip).to.be('999999'); @@ -3168,7 +3169,6 @@ describe('Gun', function(){ done(); }); gun2.get('stef').val(function(data){ //Object {_: Object, address: Object} "stef" - //console.log("*****************", data); done.s = true; expect(data.name).to.be('Stef'); expect(data.address).to.be.ok(); @@ -3328,8 +3328,8 @@ describe('Gun', function(){ var app = gun.get('mult/times'); - app.path('alias').path('mark').set(gun.get('asdf').put({ - pub: 'asdf', + app.path('alias').path('mark').set(gun.get('ASDF').put({ + pub: 'ASDF', alias: 'mark', born: 1 })); @@ -3343,7 +3343,7 @@ describe('Gun', function(){ app.path('alias').map().map().path('alias').on(function(data){ done.two = data; //console.log("alias 2!", data); - expect(done.one).to.be("asdf"); + expect(done.one).to.be("ASDF"); expect(done.two).to.be("mark"); if(done.c){ return } done.c = 1; done(); @@ -3358,7 +3358,7 @@ describe('Gun', function(){ Gun.on('put', {gun: gun, put: Gun.graph.ify({ alias: { mark: { - pub: {_:{'#':'pub'}, + pub: {_:{'#':'PUB'}, pub: 'asdf', alias: 'mark', born: 1 @@ -3386,6 +3386,7 @@ describe('Gun', function(){ }); it('map with map function', function(done){ + console.debug.i=0; var gun = Gun(), s = 'map/mapfunc', u; var app = gun.get(s); var list = app.get('list'); @@ -3399,8 +3400,7 @@ describe('Gun', function(){ done(); } }); - - list.set({name: 'alice', age: 27}); + list.set({name: 'alice', age: 27}); // on put, table-scan flag doesn't get set, but is needed for initial!?? list.set({name: 'bob', age: 27}); list.set({name: 'carl', age: 29}); list.set({name: 'dave', age: 25}); @@ -3455,7 +3455,42 @@ describe('Gun', function(){ list.path('message').put('hello world'); // outputs "message: hello world" list.path('message').put(null); // throws Uncaught TypeError: Cannot read property '#' of null }); - + + it('Check multi instance message passing', function(done){ + try{ require('fs').unlinkSync('bdata') }catch(e){} + try{ require('fs').unlinkSync('ddata') }catch(e){} + Gun.on('out', function(msg){ + //console.log("oye", msg); + this.to.next(msg); + var onGun = msg.gun.back(-1); + if(onGun === b) { + if(d){ + //console.log("b can send to d....", Gun.obj.copy(msg)); + d.on("in", msg); + } + } else if(onGun === d){ + //console.log("d sends to b....", Gun.obj.copy(msg)); + b.on("in", msg); + } + }) + + var b = Gun({file: "bdata"}); + var d = null; + + var bb = b.get("key"); + bb.put({msg: "hello"}); + + d = Gun({file: "ddata"}); + var db = d.get("key"); + db.map().on(function(val,field){ + //console.log("d key got val:", field, val) + expect(val).to.be('hello'); + if(done.c){ return } done.c = 1; + setTimeout(function(){ + done(); + },1700); + }); + }); return; it.only('Custom extensions are chainable', function(done){ Gun.chain.filter = function(filter){ @@ -3503,6 +3538,20 @@ describe('Gun', function(){ .val(function(yes){console.log("YES!", yes)}) }); + it.only('Check that events are called with multiple instances', function(done){ + var gunA = Gun( { file : "A.json" } ); + var gunB = Gun( { file : "B.json" }); + var gunC = Gun( { file : "C.json" }); + + gunA.get( "some path A" ).map( (v,f)=>{ console.log( "event on A: ", f, v ) } ); + gunB.get( "some path B" ).map( (v,f)=>{ console.log( "event on B: ", f, v ) } ); + gunC.get( "some path C" ).map( (v,f)=>{ console.log( "event on C: ", f, v ) } ); + + gunA.get( "some path A" ).put( { simple:"message" } ); + gunB.get( "some path B" ).put( { simple:"message" } ); + gunC.get( "some path C" ).put( { simple:"message" } ); + }); + it.only('Make sure circular contexts are not copied', function(done){ /* let's define an appropriate deep default database... */ var dfltSansUsers = { 1: { name : "org1", sites : { 1: {name : "site1"} } } }; diff --git a/test/panic/b2s2s2b.js b/test/panic/b2s2s2b.js new file mode 100644 index 00000000..41d110f2 --- /dev/null +++ b/test/panic/b2s2s2b.js @@ -0,0 +1,176 @@ +var config = { + IP: require('ip').address(), + port: 8080, + servers: 2, + browsers: 2, + each: 12000, + burst: 1000, + wait: 1, + 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 browsers = clients.excluding(servers); + +describe("Load test "+ config.browsers +" browser(s) across "+ config.servers +" server(s)!", function(){ + //this.timeout(5 * 60 * 1000); + this.timeout(10 * 60 * 1000); + + it("Servers have joined!", function(){ + return servers.atLeast(config.servers); + }); + + it("GUN has spawned!", 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){} + 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)!"); + browsers.atLeast(1).then(function(){ + browsers.run(function(test){ + var env = test.props; + $('body').prepend(""); + window.allopen = function(i){ + if(env.config.browsers <= i){ return } + i = i || 1; + var win = window.open(location, '_blank'); + win.focus(); + setTimeout(function(){allopen(i+1)},0); + } + }, {config: config}); + }); + return browsers.atLeast(config.browsers); + }); + + it("Data was saved and synced across all browsers!", function(){ + var tests = [], ids = {}, i = 0; + browsers.each(function(client, id){ + ids[id] = 1; + }); + browsers.each(function(client, id){ + tests.push(client.run(function(test){ + localStorage.clear(); + var env = test.props; + test.async(); + var peers = [], i = env.i; + //while(i--){ + peers.push('http://'+ env.config.IP + ':' + (env.config.port + (i)) + '/gun'); + //} + console.log("Connect to", peers); + var gun = Gun(peers); + var num = 0, total = 0, check = Gun.obj.map(env.ids, function(v,id,t){ + var i = env.config.each; + while(i--){ + t(id + (i + 1), 1); + total += 1; + } + }); + var report = $("