From 880709598426e692fa57a1586549b46a7a7a0b7c Mon Sep 17 00:00:00 2001 From: mhelander Date: Tue, 29 Aug 2017 23:57:39 +0300 Subject: [PATCH 01/29] SEA User.auth test cases done & bug fixed --- sea.js | 6 +- test/common.js | 16682 ++++++++++++++++++++++++----------------------- 2 files changed, 8375 insertions(+), 8313 deletions(-) diff --git a/sea.js b/sea.js index bd03cff9..face5334 100644 --- a/sea.js +++ b/sea.js @@ -140,7 +140,7 @@ root.get(key).get(function(at, ev){ key = key.slice(4); ev.off(); - if(!at.put){ return reject({err: 'Public key does not exist!'}) } + if(!at.put){return} // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) SEA.read(at.put.salt, key).then(function(salt){ return SEA.proof(pass, salt); @@ -198,10 +198,14 @@ return; } // Or else we failed to log in... + }).catch(function(e){ Gun.log('Failed to sign in!'); reject({err: 'Attempt failed'}); }); }); + // if (!found) { + // reject({err: 'Public key does not exist!'}) + // } }); }); }; diff --git a/test/common.js b/test/common.js index 417a77d1..5e972897 100644 --- a/test/common.js +++ b/test/common.js @@ -1,8313 +1,8371 @@ -var root; -(function(env){ - root = env.window ? env.window : global; - env.window && root.localStorage && root.localStorage.clear(); - try{ require('fs').unlinkSync('data.json') }catch(e){} - //root.Gun = root.Gun || require('../gun'); - if(root.Gun){ - root.Gun = root.Gun; - } else { - root.Gun = require('../gun'); - Gun.SEA = require('../sea'); // TODO: breaks original deep tests! - Gun.serve = require('../lib/serve'); - //require('./s3'); - //require('./uws'); - //require('./wsp/server'); - require('../lib/file'); - } -}(this)); -//Gun.log.squelch = true; -var gleak = {globals: {}, check: function(){ // via tobyho - var leaked = [] - for (var key in gleak.globe){ if (!(key in gleak.globals)){ leaked.push(key)} } - if (leaked.length > 0){ console.log("GLOBAL LEAK!", leaked); return leaked } -}}; -(function(env){ - for (var key in (gleak.globe = env)){ gleak.globals[key] = true } -}(this)); - -describe('Performance', function(){ return; // performance tests - var console = root.console || {log: function(){}}; - function perf(fn, i){ - i = i || 1000; - while(--i){ - fn(i); - } - } - perf.now = this.performance? function(){ return performance.now() } : function(){ return Gun.time.now()/1000 }; - (function(){ - var t1 = perf.now(); - var obj = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i'}; - Object.keys && perf(function(){ - var l = Object.keys(obj), ll = l.length, i = 0, s = ''; - for(; i < ll; i++){ - var v = l[i]; - s += v; - } - }); - console.log('map: native', (t1 = (perf.now() - t1)/1000) + 's'); - - var t2 = perf.now(); - var obj = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i'}; - perf(function(){ - var s = ''; - Gun.obj.map(obj, function(v){ - s += v; - }) - }); - console.log('map: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); - }()); - (function(){ - if(!Gun.store){ - var tab = Gun().tab; - if(!tab){ return } - Gun.store = tab.store; - } - root.localStorage && root.localStorage.clear(); - var it = 1000; - var t1 = perf.now(); - perf(function(i){ - var obj = {'i': i, 'v': Gun.text.random(100)}; - Gun.store.put('test/native/' + i, obj); - }, it); - console.log('store: native', (t1 = (perf.now() - t1)/1000) + 's'); - - root.localStorage && root.localStorage.clear(); - var gun = Gun({wire: {get:function(l,cb){cb()},put:function(g,cb){ - Gun.is.graph(g, function(node, soul){ - Gun.store.put(soul, node); - }); - cb(null); - }}}); - var t2 = perf.now(); - perf(function(i){ - var obj = {'i': i, 'v': Gun.text.random(100)}; - gun.put(obj); - }, it); - console.log('store: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); - root.localStorage && root.localStorage.clear(); - }()); - (function(){ // setTimeout - if(!Gun.store){ - var tab = Gun().tab; - if(!tab){ return } - Gun.store = tab.store; - } - root.localStorage && root.localStorage.clear(); - var t1 = perf.now(); - i = i || 1000; - while(--i){ - var obj = {'i': i, 'v': Gun.text.random(100)}; - Gun.store.put('test/native/' + i, obj); - } - console.log('store: native', (t1 = (perf.now() - t1)/1000) + 's'); - - root.localStorage && root.localStorage.clear(); - var gun = Gun({wire: {get:function(l,cb){cb()},put:function(g,cb){ - Gun.is.graph(g, function(node, soul){ - Gun.store.put(soul, node); - }); - cb(null); - }}}); - var t2 = perf.now(); - perf(function(i){ - var obj = {'i': i, 'v': Gun.text.random(100)}; - gun.put(obj); - }, it); - console.log('store: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); - root.localStorage && root.localStorage.clear(); - }()); - (function(){ - var t1 = perf.now(); - var on = Gun.on.create(), c = 0, o = []; - perf(function(i){ - o.push(function(n){ - c += 1; - }); - var ii = 0, l = o.length; - for(; ii < l; ii++){ - o[ii](i); - } - }); - console.log('on: native', (t1 = (perf.now() - t1)/1000) + 's'); - - var on = Gun.on.create(), c = 0; - var t2 = perf.now(); - perf(function(i){ - on('change').event(function(n){ - c += 1; - }); - on('change').emit(i); - }); - console.log('on: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); - }());return; - (function(){ // always do this last! - var t1 = perf.now(); - perf(function(i){ - setTimeout(function(){ - if(i === 1){ - cb1(); - } - },0); - }); var cb1 = function(){ - console.log('setTimeout: native', (t1 = (perf.now() - t1)/1000) + 's', (t1 / t2).toFixed(1)+'x', 'slower.'); - } - var t2 = perf.now(); - perf(function(i){ - setImmediate(function(){ - if(i === 1){ - cb2(); - } - }); - }); var cb2 = function(){ - console.log('setImmediate: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); - } - }()); -}); - -describe('Gun', function(){ - var t = {}; - describe('Utility', function(){ - var u; - /* // causes logger to no longer log. - it('verbose console.log debugging', function(done) { - - var gun = Gun(); - var log = root.console.log, counter = 1; - root.console.log = function(a,b,c){ - --counter; - //log(a,b,c); - } - Gun.log.verbose = true; - gun.put('bar', function(err, yay){ // intentionally trigger an error that will get logged. - expect(counter).to.be(0); - - Gun.log.verbose = false; - gun.put('bar', function(err, yay){ // intentionally trigger an error that will get logged. - expect(counter).to.be(0); - - root.console.log = log; - done(); - }); - }); - } ); - */ - - describe('Type Check', function(){ - it('binary', function(){ - expect(Gun.bi.is(false)).to.be(true); - expect(Gun.bi.is(true)).to.be(true); - expect(Gun.bi.is(u)).to.be(false); - expect(Gun.bi.is(null)).to.be(false); - expect(Gun.bi.is('')).to.be(false); - expect(Gun.bi.is('a')).to.be(false); - expect(Gun.bi.is(0)).to.be(false); - expect(Gun.bi.is(1)).to.be(false); - expect(Gun.bi.is([])).to.be(false); - expect(Gun.bi.is([1])).to.be(false); - expect(Gun.bi.is({})).to.be(false); - expect(Gun.bi.is({a:1})).to.be(false); - expect(Gun.bi.is(function(){})).to.be(false); - }); - it('number',function(){ - expect(Gun.num.is(0)).to.be(true); - expect(Gun.num.is(1)).to.be(true); - expect(Gun.num.is(Infinity)).to.be(true); - expect(Gun.num.is(u)).to.be(false); - expect(Gun.num.is(null)).to.be(false); - expect(Gun.num.is(NaN)).to.be(false); - expect(Gun.num.is('')).to.be(false); - expect(Gun.num.is('a')).to.be(false); - expect(Gun.num.is([])).to.be(false); - expect(Gun.num.is([1])).to.be(false); - expect(Gun.num.is({})).to.be(false); - expect(Gun.num.is({a:1})).to.be(false); - expect(Gun.num.is(false)).to.be(false); - expect(Gun.num.is(true)).to.be(false); - expect(Gun.num.is(function(){})).to.be(false); - }); - it('text',function(){ - expect(Gun.text.is('')).to.be(true); - expect(Gun.text.is('a')).to.be(true); - expect(Gun.text.is(u)).to.be(false); - expect(Gun.text.is(null)).to.be(false); - expect(Gun.text.is(false)).to.be(false); - expect(Gun.text.is(true)).to.be(false); - expect(Gun.text.is(0)).to.be(false); - expect(Gun.text.is(1)).to.be(false); - expect(Gun.text.is([])).to.be(false); - expect(Gun.text.is([1])).to.be(false); - expect(Gun.text.is({})).to.be(false); - expect(Gun.text.is({a:1})).to.be(false); - expect(Gun.text.is(function(){})).to.be(false); - }); - it('list',function(){ - expect(Gun.list.is([])).to.be(true); - expect(Gun.list.is([1])).to.be(true); - expect(Gun.list.is(u)).to.be(false); - expect(Gun.list.is(null)).to.be(false); - expect(Gun.list.is(0)).to.be(false); - expect(Gun.list.is(1)).to.be(false); - expect(Gun.list.is('')).to.be(false); - expect(Gun.list.is('a')).to.be(false); - expect(Gun.list.is({})).to.be(false); - expect(Gun.list.is({a:1})).to.be(false); - expect(Gun.list.is(false)).to.be(false); - expect(Gun.list.is(true)).to.be(false); - expect(Gun.list.is(function(){})).to.be(false); - }); - it('obj',function(){ - expect(Gun.obj.is({})).to.be(true); - expect(Gun.obj.is({a:1})).to.be(true); - expect(Gun.obj.is(u)).to.be(false); - expect(Gun.obj.is()).to.be(false); - expect(Gun.obj.is(undefined)).to.be(false); - expect(Gun.obj.is(null)).to.be(false); - expect(Gun.obj.is(NaN)).to.be(false); - expect(Gun.obj.is(0)).to.be(false); - expect(Gun.obj.is(1)).to.be(false); - expect(Gun.obj.is('')).to.be(false); - expect(Gun.obj.is('a')).to.be(false); - expect(Gun.obj.is([])).to.be(false); - expect(Gun.obj.is([1])).to.be(false); - expect(Gun.obj.is(false)).to.be(false); - expect(Gun.obj.is(true)).to.be(false); - expect(Gun.obj.is(function(){})).to.be(false); - expect(Gun.obj.is(new Date())).to.be(false); - expect(Gun.obj.is(/regex/)).to.be(false); - this.document && expect(Gun.obj.is(document.createElement('div'))).to.be(false); - expect(Gun.obj.is(new (function Class(){ this.x = 1; this.y = 2 })())).to.be(true); - }); - it('fns',function(){ - expect(Gun.fns.is(function(){})).to.be(true); - expect(Gun.fns.is(u)).to.be(false); - expect(Gun.fns.is(null)).to.be(false); - expect(Gun.fns.is('')).to.be(false); - expect(Gun.fns.is('a')).to.be(false); - expect(Gun.fns.is(0)).to.be(false); - expect(Gun.fns.is(1)).to.be(false); - expect(Gun.fns.is([])).to.be(false); - expect(Gun.fns.is([1])).to.be(false); - expect(Gun.fns.is({})).to.be(false); - expect(Gun.fns.is({a:1})).to.be(false); - expect(Gun.fns.is(false)).to.be(false); - expect(Gun.fns.is(true)).to.be(false); - }); - it('time',function(){ - t.ts = Gun.time.is(); - expect(13 <= t.ts.toString().length).to.be.ok(); - expect(Gun.num.is(t.ts)).to.be.ok(); - expect(Gun.time.is(new Date())).to.be.ok(); - }); - }); - describe('Text', function(){ - it('ify',function(){ - expect(Gun.text.ify(0)).to.be('0'); - expect(Gun.text.ify(22)).to.be('22'); - expect(Gun.text.ify([true,33,'yay'])).to.be('[true,33,"yay"]'); - expect(Gun.text.ify({a:0,b:'1',c:[0,'1'],d:{e:'f'}})).to.be('{"a":0,"b":"1","c":[0,"1"],"d":{"e":"f"}}'); - expect(Gun.text.ify(false)).to.be('false'); - expect(Gun.text.ify(true)).to.be('true'); - }); - it('random',function(){ - expect(Gun.text.random().length).to.be(24); - expect(Gun.text.random(11).length).to.be(11); - expect(Gun.text.random(4).length).to.be(4); - t.tr = Gun.text.random(2,'as'); expect((t.tr=='as'||t.tr=='aa'||t.tr=='sa'||t.tr=='ss')).to.be.ok(); - }); - 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', '<': '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(); - expect(Gun.text.match("user/mad/person", {'*': 'user/', '-': 'mad'})).to.not.be.ok(); - expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '-': ['mark', 'nadal']})).to.not.be.ok(); - expect(Gun.text.match("user/amber/rachel/cazzell", {'*': 'user/', '-': ['mark', 'nadal']})).to.be.ok(); - expect(Gun.text.match("user/mark/nadal", {'*': 'user/', '+': 'ark'})).to.be.ok(); - expect(Gun.text.match("user/mad/person", {'*': 'user/', '+': 'ark'})).to.not.be.ok(); - expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '+': ['mark', 'nadal']})).to.be.ok(); - expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '+': ['nadal', 'mark']})).to.be.ok(); - expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '+': ['mark', 'amber']})).to.not.be.ok(); - expect(Gun.text.match("user/mark/rachel/nadal/cazzell", {'*': 'user/', '+': ['mark', 'cazzell'], '-': ['amber', 'timothy']})).to.be.ok(); - 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(); - }); - }); - describe('List', function(){ - it('slit',function(){ - (function(){ - expect(Gun.list.slit.call(arguments, 0)).to.eql([1,2,3,'a','b','c']); - }(1,2,3,'a','b','c')); - }); - it('sort',function(){ - expect([{i:9},{i:4},{i:1},{i:-3},{i:0}].sort(Gun.list.sort('i'))).to.eql([{i:-3},{i:0},{i:1},{i:4},{i:9}]); - }); - it('map',function(){ - expect(Gun.list.map([1,2,3,4,5],function(v,i,t){ t(v+=this.d); this.d=v; },{d:0})).to.eql([1,3,6,10,15]); - expect(Gun.list.map([2,3,0,4],function(v,i,t){ if(!v){ return } t(v*=this.d); this.d=v; },{d:1})).to.eql([2,6,24]); - expect(Gun.list.map([true,false,NaN,Infinity,'',9],function(v,i,t){ if(i===3){ return 0 }})).to.be(0); - }); - }); - describe('Object', function(){ - it('del',function(){ - var obj = {a:1,b:2}; - Gun.obj.del(obj,'a'); - expect(obj).to.eql({b:2}); - }); - it('has',function(){ - var obj = {a:1,b:2}; - expect(Gun.obj.has(obj,'a')).to.be.ok(); - }); - it('empty',function(){ - expect(Gun.obj.empty()).to.be(true); - expect(Gun.obj.empty({a:false})).to.be(false); - expect(Gun.obj.empty({a:false},'a')).to.be(true); - expect(Gun.obj.empty({a:false},{a:1})).to.be(true); - expect(Gun.obj.empty({a:false,b:1},'a')).to.be(false); - expect(Gun.obj.empty({a:false,b:1},{a:1})).to.be(false); - expect(Gun.obj.empty({a:false,b:1},{a:1,b:1})).to.be(true); - expect(Gun.obj.empty({a:false,b:1,c:3},{a:1,b:1})).to.be(false); - expect(Gun.obj.empty({1:1},'danger')).to.be(false); - }); - it('copy',function(){ - var obj = {"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}; - var copy = Gun.obj.copy(obj); - expect(copy).to.eql(obj); - expect(copy).to.not.be(obj); - }); - it('ify',function(){ - expect(Gun.obj.ify('[0,1]')).to.eql([0,1]); - expect(Gun.obj.ify('{"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}')).to.eql({"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}); - }); - it('map',function(){ - expect(Gun.obj.map({a:'z',b:'y',c:'x'},function(v,i,t){ t(v,i) })).to.eql({x:'c',y:'b',z:'a'}); - expect(Gun.obj.map({a:'z',b:false,c:'x'},function(v,i,t){ if(!v){ return } t(i,v) })).to.eql({a:'z',c:'x'}); - expect(Gun.obj.map({a:'z',b:3,c:'x'},function(v,i,t){ if(v===3){ return 0 }})).to.be(0); - }); - }); - describe('Functions', function(){ - /* - it.skip('sum',function(done){ // deprecate? - var obj = {a:2, b:2, c:3, d: 9}; - Gun.obj.map(obj, function(num, key){ - setTimeout(this.add(function(){ - this.done(null, num * num); - }, key), parseInt((""+Math.random()).substring(2,5))); - }, Gun.fns.sum(function(err, val){ - expect(val.a).to.eql(4); - expect(val.b).to.eql(4); - expect(val.c).to.eql(9); - expect(val.d).to.eql(81); - done(); - })); - }); - */ - }); - describe('On', function(){ - it('subscribe', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a){ - done.first = true; - expect(a).to.be(1); - this.to.next(a); - }); - e.on('foo', function(a){ - expect(a).to.be(1); - expect(done.first).to.be.ok(); - done(); - }); - e.on('foo', 1); - }); - it('unsubscribe', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a){ - this.off(); - done.first = a; - expect(a).to.be(1); - this.to.next(a); - }); - e.on('foo', function(a){ - var to = this; - expect(a).to.be(done.second? 2 : 1); - expect(done.first).to.be(1); - done.second = true; - if(a === 2){ - setTimeout(function(){ - expect(e.tag.foo.to === to).to.be.ok(); - done(); - }, 10); - } - }); - e.on('foo', 1); - e.on('foo', 2); - }); - it('stun', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - if(2 === a){ - done.first2 = true; - this.to.next(a); - return; - } - setTimeout(function(){ - expect(done.second).to.not.be.ok(); - expect(done.second2).to.be.ok(); - expect(done.first2).to.be.ok(); - done(); - },10); - }); - e.on('foo', function(a, ev){ - if(2 === a){ - done.second2 = true; - } else { - done.second = true; - } - }); - e.on('foo', 1); - e.on('foo', 2); - }); - it('resume', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - var to = this.to; - setTimeout(function(){ - expect(done.second).to.not.be.ok(); - to.next(a); - },10); - }); - e.on('foo', function(a){ - done.second = true; - expect(a).to.be(1); - done(); - }); - e.on('foo', 1); - }); - it('double resume', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - var to = this.to; - setTimeout(function(){ - if(1 === a){ - done.first1 = true; - expect(done.second).to.not.be.ok(); - } - if(2 === a){ - done.first2 = true; - } - to.next(a); - },10); - }); - e.on('foo', function(a, ev){ - done.second = true; - if(1 === a){ - expect(done.first2).to.not.be.ok(); - done.second1 = true; - } - if(2 === a){ - expect(done.first2).to.be.ok(); - if(done.second1){ - done(); - } - } - }); - e.on('foo', 1); - e.on('foo', 2); - }); - it('double resume different event', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - var to = this.to; - setTimeout(function(){ - done.first1 = true; - to.next(a); - },10); - }); - e.on('foo', function(a){ - if(1 === a){ - expect(done.first1).to.be.ok(); - done(); - } - }); - e.on('foo', 1); - e.on('bar', 2); - }); - it('resume params', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - var to = this.to; - setTimeout(function(){ - expect(done.second).to.not.be.ok(); - to.next(0); - },10); - }); - e.on('foo', function(a){ - done.second = true; - expect(a).to.be(0); - done(); - }); - e.on('foo', 1); - }); - it('map', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - var to = this.to; - Gun.obj.map(a.it, function(v,f){ - setTimeout(function(){ - var emit = {field: 'where', soul: f}; - to.next(emit); - },10); - }) - }); - e.on('foo', function(a, ev){ - var to = this.to; - setTimeout(function(){ - to.next({node: a.soul}); - },100); - }); - e.on('foo', function(a){ - if('a' == a.node){ - done.a = true; - } else { - expect(done.a).to.be.ok(); - done(); - } - }); - e.on('foo', {field: 'where', it: {a: 1, b: 2}}); - }); - it('map synchronous', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a, ev){ - var to = this.to; - Gun.obj.map(a.node, function(v,f){ - //setTimeout(function(){ - var emit = {field: 'where', soul: f}; - to.next(emit); - //},10); - }) - }); - e.on('foo', function(a, ev){ - var to = this.to; - setTimeout(function(){ - to.next({node: a.soul}); - },100); - }); - e.on('foo', function(a){ - expect(this.as.hi).to.be(1); - if('a' == a.node){ - done.a = true; - } else { - expect(done.a).to.be.ok(); - done(); - } - }, {hi: 1}).on.on('foo', {field: 'where', node: {a: 1, b: 2}}); - }); - it('synchronous async', function(done){ - var e = {on: Gun.on}; - e.on('foo', function(a){ - expect(a.b).to.be(5); - done.first = true; - this.to.next(a); - }); - e.on('foo', function(a, ev){ - expect(a.b).to.be(5); - done.second = true; - var to = this.to; - setTimeout(function(){ - to.next({c: 9, again: a.again}); - },100); - }); - e.on('foo', function(a){ - this.off(); - expect(a.again).to.not.be.ok(); - expect(a.c).to.be(9); - expect(done.first).to.be.ok(); - expect(done.second).to.be.ok(); - done(); - }).on.on('foo', {b: 5}).on.on('foo', {b:5, again: true}); - }); - }); - describe('flow', function(){ - var i = 0; - function flow(){ - var f = function(arg){ - var cb = f.cb? f.cb.fn : f.fn; - if(cb){ - f.cb = cb; - var ff = flow(); - ff.f = f; - cb(ff); - return; - } - if(f.f){ - f.f(arg); - f.cb = 0; - return; - } - }, cb; - f.flow = function(fn){ - cb = (cb || f).fn = fn; - return f; - }; - return f; - } - it('intermittent interruption', function(done){ - var f = flow(); - //var f = {flow: flow} - f.flow(function(f){ - //console.log(1); - f.flow(function(f){ - //console.log(2); - f({yes: 'please'}); - }); - setTimeout(function(){ - f.flow(function(f){ - //console.log(2.1); - f({forever: 'there'}); - }); - f({strange: 'places'}); - //console.log("-----"); - f({earlier: 'location'}); - },100); - }); - f.flow(function(f){ - //console.log(3); - f({ok: 'now'}); - }); - f.flow(function(f){ - //console.log(4); - done(); - }); - setTimeout(function(){ - f({hello: 'world'}); - }, 100); - }); - var i = 0; - ;(function(exports){ - function next(arg){ var n = this; - if(arg instanceof Function){ - if(!n.fn){ return n.fn = arg, n } - var f = {next: next, fn: arg, first: n.first || n}; - n.last = (n.last || n).to = f; - return n; - } - if(n.fn){ - var sub = {next: next, from: n.to || (n.first || {}).from}; - n.fn(sub); - return; - } - if(n.from){ - n.from.next(arg); - return; - } - } - exports.next = next; - }(Gun)); - it('intermittent interruptions', function(done){ - //var f = flow(); - var f = {next: Gun.next}; // for now - f.next(function(f){ - //console.log(1, f); - f.next(function(f){ - //console.log(2, f); - f.next({yes: 'please'}); - }); - setTimeout(function(){ - f.next(function(f){ - //console.log(2.1, f); - f.next({forever: 'there'}); - }); - f.next({strange: 'places'}); - //console.log("-----"); - f.next({earlier: 'location'}); - },100); - }); - f.next(function(f){ - //console.log(3); - f.next({ok: 'now'}); - }); - f.next(function(f){ - //console.log(4); - if(!done.a){ return done.a = true } - done(); - }); - setTimeout(function(){ - f.next({hello: 'world'}); - }, 100); - }); - }); - 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); - expect(Gun.is(true)).to.be(false); - expect(Gun.is(false)).to.be(false); - expect(Gun.is(0)).to.be(false); - expect(Gun.is(1)).to.be(false); - expect(Gun.is('')).to.be(false); - expect(Gun.is('a')).to.be(false); - expect(Gun.is(Infinity)).to.be(false); - expect(Gun.is(NaN)).to.be(false); - expect(Gun.is([])).to.be(false); - expect(Gun.is([1])).to.be(false); - expect(Gun.is({})).to.be(false); - expect(Gun.is({a:1})).to.be(false); - expect(Gun.is(function(){})).to.be(false); - }); - it('is value',function(){ - expect(Gun.val.is(false)).to.be(true); - expect(Gun.val.is(true)).to.be(true); - expect(Gun.val.is(0)).to.be(true); - expect(Gun.val.is(1)).to.be(true); - expect(Gun.val.is('')).to.be(true); - expect(Gun.val.is('a')).to.be(true); - expect(Gun.val.is({'#':'somesoulidhere'})).to.be('somesoulidhere'); - expect(Gun.val.is({'#':'somesoulidhere', and: 'nope'})).to.be(false); - expect(Gun.val.is(Infinity)).to.be(false); // boohoo :( - expect(Gun.val.is(NaN)).to.be(false); - expect(Gun.val.is([])).to.be(false); - expect(Gun.val.is([1])).to.be(false); - expect(Gun.val.is({})).to.be(false); - expect(Gun.val.is({a:1})).to.be(false); - expect(Gun.val.is(function(){})).to.be(false); - }); - it('is rel',function(){ - expect(Gun.val.rel.is({'#':'somesoulidhere'})).to.be('somesoulidhere'); - expect(Gun.val.rel.is({'#':'somethingelsehere'})).to.be('somethingelsehere'); - expect(Gun.val.rel.is({'#':'somesoulidhere', and: 'nope'})).to.be(false); - expect(Gun.val.rel.is({or: 'nope', '#':'somesoulidhere'})).to.be(false); - expect(Gun.val.rel.is(false)).to.be(false); - expect(Gun.val.rel.is(true)).to.be(false); - expect(Gun.val.rel.is('')).to.be(false); - expect(Gun.val.rel.is('a')).to.be(false); - expect(Gun.val.rel.is(0)).to.be(false); - expect(Gun.val.rel.is(1)).to.be(false); - expect(Gun.val.rel.is(Infinity)).to.be(false); // boohoo :( - expect(Gun.val.rel.is(NaN)).to.be(false); - expect(Gun.val.rel.is([])).to.be(false); - expect(Gun.val.rel.is([1])).to.be(false); - expect(Gun.val.rel.is({})).to.be(false); - expect(Gun.val.rel.is({a:1})).to.be(false); - expect(Gun.val.rel.is(function(){})).to.be(false); - }); - it.skip('is lex',function(){ - expect(Gun.is.lex({'#': 'soul'})).to.eql({soul: 'soul'}); - expect(Gun.is.lex({'.': 'field'})).to.eql({field: 'field'}); - expect(Gun.is.lex({'=': 'value'})).to.eql({value: 'value'}); - expect(Gun.is.lex({'>': 'state'})).to.eql({state: 'state'}); - expect(Gun.is.lex({'#': {'=': 'soul'}})).to.eql({soul: {'=': 'soul'}}); - expect(Gun.is.lex({'#': {'=': 'soul'}, '.': []})).to.be(false); - expect(Gun.is.lex({'#': {'=': 'soul'}, 'asdf': 'oye'})).to.be(false); - expect(Gun.is.lex()).to.be(false); - expect(Gun.is.lex('')).to.be(false); - }); - it.skip('is lex ify',function(){ - expect(Gun.is.lex.ify({'#': 'soul', '.': 'field', soul: 'foo', field: 'field', state: 0})).to.eql({'#': 'soul', '.': 'field', '>': 0}); - }); - it('is node',function(){ - var n; - expect(Gun.node.is({_:{'#':'somesoulidhere'}})).to.be(true); - expect(Gun.node.is(n = {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}})).to.be(true); - expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}, g: Infinity})).to.be(false); - expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, z: NaN, c: '', d: 'e'})).to.be(false); - expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, y: {_: 'cool'}, c: '', d: 'e'})).to.be(false); - expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, x: [], c: '', d: 'e'})).to.be(false); - expect(Gun.node.is({})).to.be(false); - expect(Gun.node.is({a:1})).to.be(false); - expect(Gun.node.is({_:{}})).to.be(false); - expect(Gun.node.is({_:{}, a:1})).to.be(false); - expect(Gun.node.is({'#':'somesoulidhere'})).to.be(false); - Gun.node.is(n, function(v,f){ - //console.log("v/f", v,f); - }); - }); - it('is graph',function(){ - var g; - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}}})).to.be(true); - expect(Gun.graph.is(g = {'somesoulidhere': {_:{'#':'somesoulidhere'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(true); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(true); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}})).to.be(true); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: 1, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: {}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: {_:{'#':'FOO'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: {_:{}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: Infinity, c: '', d: 'e', f: {'#':'somethingelsehere'}}})).to.be(false); - expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: Infinity, c: '', d: 'e', f: {'#':'somethingelsehere'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); - expect(Gun.graph.is({_:{'#':'somesoulidhere'}})).to.be(false); - expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}})).to.be(false); - expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}, g: Infinity})).to.be(false); - expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, z: NaN, c: '', d: 'e'})).to.be(false); - expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, y: {_: 'cool'}, c: '', d: 'e'})).to.be(false); - expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, x: [], c: '', d: 'e'})).to.be(false); - expect(Gun.graph.is({})).to.be(false); // Empty graph is not a graph :( - expect(Gun.graph.is({a:1})).to.be(false); - expect(Gun.graph.is({_:{}})).to.be(false); - expect(Gun.graph.is({_:{}, a:1})).to.be(false); - expect(Gun.graph.is({'#':'somesoulidhere'})).to.be(false); - Gun.graph.is(g, function(n,s){ - //console.log("node/soul", n,s); - }); - }); - it('graph ify', function(done){ - function map(v,f,n){ - done.m = true; - } - var graph = Gun.graph.ify({ - _: {'#': 'yay'}, - a: 1 - }, map); - expect(graph).to.eql({ - yay: { - _: {'#': 'yay'}, - a: 1 - } - }); - expect(done.m).to.be.ok(); - var graph = Gun.graph.ify({ - _: {'#': 'yay', '>': {a: 9}}, - a: 1 - }, map); - expect(graph).to.eql({ - yay: { - _: {'#': 'yay', '>': {a: 9}}, - a: 1 - } - }); - var map = Gun.state.map(map, 9); - var graph = Gun.graph.ify({ - _: {'#': 'yay', '>': {a: 1}}, - a: 1 - }, map); - expect(graph).to.eql({ - yay: { - _: {'#': 'yay', '>': {a: 9}}, - a: 1 - } - }); - var graph = Gun.graph.ify({a:1}); - Gun.obj.map(graph, function(node){ - expect(node._['#']).to.be.ok(); - }); - - var alice = {_:{'#':'ASDF'}, age: 27, name: "Alice"}; - var bob = {_:{'#':'DASF'}, age: 29, name: "Bob"}; - var cat = {_:{'#':'FDSA'}, name: "Fluffy", species: "kitty"}; - alice.spouse = bob; - bob.spouse = alice; - alice.pet = cat; - cat.slave = bob; - cat.master = alice; - var graph = Gun.graph.ify(bob); - expect(graph).to.eql({ - 'ASDF': {_:{'#':'ASDF'}, - age: 27, - name: "Alice", - spouse: {'#':'DASF'}, - pet: {'#':'FDSA'} - }, - 'DASF': {_:{'#':'DASF'}, - age: 29, - name: 'Bob', - spouse: {'#':'ASDF'}, - }, - 'FDSA': {_:{'#':'FDSA'}, - name: "Fluffy", - species: "kitty", - slave: {'#':'DASF'}, - master: {'#':'ASDF'} - } - }); - - done(); - }); - }); - }); - describe('ify', function(){ - console.log("TODO: BUG! Upgrade IFY tests to new internal API!"); - return; - - var test, gun = Gun(); - - it('null', function(done){ - Gun.ify(null, function(err, ctx){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('basic', function(done){ - var data = {a: false, b: true, c: 0, d: 1, e: '', f: 'g', h: null}; - Gun.ify(data, function(err, ctx){ - expect(err).to.not.be.ok(); - expect(ctx.err).to.not.be.ok(); - expect(ctx.root).to.eql(data); - expect(ctx.root === data).to.not.ok(); - done(); - }, {pure: true}); - }); - - it('basic soul', function(done){ - var data = {_: {'#': 'SOUL'}, a: false, b: true, c: 0, d: 1, e: '', f: 'g', h: null}; - Gun.ify(data, function(err, ctx){ - expect(err).to.not.be.ok(); - expect(ctx.err).to.not.be.ok(); - - expect(ctx.root).to.eql(data); - expect(ctx.root === data).to.not.be.ok(); - expect(Gun.node.soul(ctx.root) === Gun.node.soul(data)); - done(); - }, {pure: true}); - }); - - it('arrays', function(done){ - var data = {before: {path: 'kill'}, one: {two: {lol: 'troll', three: [9, 8, 7, 6, 5]}}}; - Gun.ify(data, function(err, ctx){ - expect(err).to.be.ok(); - expect((err.err || err).indexOf("one.two.three")).to.not.be(-1); - done(); - }); - }); - - it('undefined', function(done){ - var data = {z: undefined, x: 'bye'}; - Gun.ify(data, function(err, ctx){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('NaN', function(done){ - var data = {a: NaN, b: 2}; - Gun.ify(data, function(err, ctx){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('Infinity', function(done){ // SAD DAY PANDA BEAR :( :( :(... Mark wants Infinity. JSON won't allow. - var data = {a: 1, b: Infinity}; - Gun.ify(data, function(err, ctx){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('function', function(done){ - var data = {c: function(){}, d: 'hi'}; - Gun.ify(data, function(err, ctx){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('extraneous', function(done){ - var data = {_: {'#': 'shhh', meta: {yay: 1}}, sneak: true}; - Gun.ify(data, function(err, ctx){ - expect(err).to.not.be.ok(); // extraneous metadata needs to be stored, but it can't be used for data. - done(); - }); - }); - - it('document', function(done){ - var data = {users: {1: {where: {lat: Math.random(), lng: Math.random(), i: 1}}}}; - Gun.ify(data, function(err, ctx){ - var soul, node; - expect(soul = Gun.val.rel.is(ctx.root.users)).to.be.ok(); - node = ctx.graph[soul]; - expect(soul = Gun.val.rel.is(node[1])).to.be.ok(); - node = ctx.graph[soul]; - expect(soul = Gun.val.rel.is(node.where)).to.be.ok(); - node = ctx.graph[soul]; - expect(node.lat).to.be.ok(); - expect(node.lng).to.be.ok(); - expect(node.i).to.be(1); - done(); - }); - }); - - return; // TODO! Fix GUN to handle this! - data = {}; - data.sneak = false; - data.both = {inside: 'meta data'}; - data._ = {'#': 'shhh', data: {yay: 1}, spin: data.both}; - test = Gun.ify(data); - expect(test.err.meta).to.be.ok(); // TODO: Fail: this passes, somehow? Fix ify code! - }); - - describe('Schedule', function(){ - console.log("TODO: BUG! Upgrade SCHEDULE tests to new internal API!"); - return; - it('one', function(done){ - Gun.schedule(Gun.time.is(), function(){ - expect(true).to.be(true); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('many', function(done){ - Gun.schedule(Gun.time.is() + 50, function(){ - done.first = true; - }); - Gun.schedule(Gun.time.is() + 100, function(){ - done.second = true; - }); - Gun.schedule(Gun.time.is() + 200, function(){ - done.third = true; - expect(done.first).to.be(true); - expect(done.second).to.be(true); - expect(done.third).to.be(true); - done(); //setTimeout(function(){ done() },1); - }); - }); - }); - - describe('Union', function(){ - console.log("TODO: BUG! Upgrade UNION tests to new internal API!"); - return; - var gun = Gun(); - - it('fail', function(){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - a: 'cheating' - }}, - a: 0 - } - } - - expect(gun.__.graph['asdf']).to.not.be.ok(); - var ctx = Gun.HAM.graph(gun, prime); - expect(ctx).to.not.be.ok(); - });return; - - it('basic', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - a: Gun.time.is() - }}, - a: 0 - } - } - - expect(gun.__.graph['asdf']).to.not.be.ok(); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['asdf'].a).to.be(0); - done(); - }); - }); - - it('disjoint', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - b: Gun.time.is() - }}, - b: 'c' - } - } - - expect(gun.__.graph['asdf'].a).to.be(0); - expect(gun.__.graph['asdf'].b).to.not.be.ok(); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['asdf'].a).to.be(0); - expect(gun.__.graph['asdf'].b).to.be('c'); - done(); - }); - }); - - it('mutate', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - b: Gun.time.is() - }}, - b: 'd' - } - } - - expect(gun.__.graph['asdf'].b).to.be('c'); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['asdf'].b).to.be('d'); - done(); - }); - }); - - it('disjoint past', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - x: 0 // beginning of time! - }}, - x: 'hi' - } - } - expect(gun.__.graph['asdf'].x).to.not.be.ok(); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['asdf'].x).to.be('hi'); - done(); - }); - }); - - it('past', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - x: Gun.time.is() - (60 * 1000) // above lower boundary, below now or upper boundary. - }}, - x: 'hello' - } - } - - expect(gun.__.graph['asdf'].x).to.be('hi'); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['asdf'].x).to.be('hello'); - done(); - }); - }); - - it('future', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - x: Gun.time.is() + (200) // above now or upper boundary, aka future. - }}, - x: 'how are you?' - } - } - - expect(gun.__.graph['asdf'].x).to.be('hello'); - var now = Gun.time.is(); - var ctx = Gun.union(gun, prime, function(){ - expect(Gun.time.is() - now).to.be.above(100); - expect(gun.__.graph['asdf'].x).to.be('how are you?'); - done(); - }); - }); - var to = 5000; - it('disjoint future', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - y: Gun.time.is() + (200) // above now or upper boundary, aka future. - }}, - y: 'goodbye' - } - } - expect(gun.__.graph['asdf'].y).to.not.be.ok(); - var now = Gun.time.is(); - var ctx = Gun.union(gun, prime, function(){ - expect(Gun.time.is() - now).to.be.above(100); - expect(gun.__.graph['asdf'].y).to.be('goodbye'); - done(); - }); - }); - - it('disjoint future max', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - y: Gun.time.is() + (2), // above now or upper boundary, aka future. - z: Gun.time.is() + (200) // above now or upper boundary, aka future. - }}, - y: 'bye', - z: 'who' - } - } - - expect(gun.__.graph['asdf'].y).to.be('goodbye'); - expect(gun.__.graph['asdf'].z).to.not.be.ok(); - var now = Gun.time.is(); - var ctx = Gun.union(gun, prime, function(){ - expect(Gun.time.is() - now).to.be.above(100); - expect(gun.__.graph['asdf'].y).to.be('bye'); - expect(gun.__.graph['asdf'].z).to.be('who'); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('future max', function(done){ - var prime = { - 'asdf': { - _: {'#': 'asdf', '>':{ - w: Gun.time.is() + (2), // above now or upper boundary, aka future. - x: Gun.time.is() - (60 * 1000), // above now or upper boundary, aka future. - y: Gun.time.is() + (200), // above now or upper boundary, aka future. - z: Gun.time.is() + (50) // above now or upper boundary, aka future. - }}, - w: true, - x: 'nothing', - y: 'farewell', - z: 'doctor who' - } - } - - expect(gun.__.graph['asdf'].w).to.not.be.ok(); - expect(gun.__.graph['asdf'].x).to.be('how are you?'); - expect(gun.__.graph['asdf'].y).to.be('bye'); - expect(gun.__.graph['asdf'].z).to.be('who'); - var now = Gun.time.is(); - var ctx = Gun.union(gun, prime, function(){ - expect(Gun.time.is() - now).to.be.above(100); - expect(gun.__.graph['asdf'].w).to.be(true); - expect(gun.__.graph['asdf'].x).to.be('how are you?'); - expect(gun.__.graph['asdf'].y).to.be('farewell'); - expect(gun.__.graph['asdf'].z).to.be('doctor who'); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('two nodes', function(done){ // chat app problem where disk dropped the last data, turns out it was a union problem! - var state = Gun.time.is(); - var prime = { - 'sadf': { - _: {'#': 'sadf', '>':{ - 1: state - }}, - 1: {'#': 'fdsa'} - }, - 'fdsa': { - _: {'#': 'fdsa', '>':{ - msg: state - }}, - msg: "Let's chat!" - } - } - - expect(gun.__.graph['sadf']).to.not.be.ok(); - expect(gun.__.graph['fdsa']).to.not.be.ok(); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['sadf'][1]).to.be.ok(); - expect(gun.__.graph['fdsa'].msg).to.be("Let's chat!"); - done(); - }); - }); - - it('append third node', function(done){ // chat app problem where disk dropped the last data, turns out it was a union problem! - var state = Gun.time.is(); - var prime = { - 'sadf': { - _: {'#': 'sadf', '>':{ - 2: state - }}, - 2: {'#': 'fads'} - }, - 'fads': { - _: {'#': 'fads', '>':{ - msg: state - }}, - msg: "hi" - } - } - - expect(gun.__.graph['sadf']).to.be.ok(); - expect(gun.__.graph['fdsa']).to.be.ok(); - var ctx = Gun.union(gun, prime, function(){ - expect(gun.__.graph['sadf'][1]).to.be.ok(); - expect(gun.__.graph['sadf'][2]).to.be.ok(); - expect(gun.__.graph['fads'].msg).to.be("hi"); - done(); - }); - }); - - it('ify null', function(){ - var node = Gun.union.ify(null, 'pseudo'); - expect(Gun.node.soul(node)).to.be('pseudo'); - }); - - it('ify node', function(){ - - var graph = { - 'asdf': { - _: {'#': 'asdf', '>': { - x: Gun.time.is(), - y: Gun.time.is() - }}, - x: 1, - y: 2 - }, - 'soul': { - _: {'#': 'soul', '~': 1, '>': { - 'asdf': Gun.time.is() - }}, - 'asdf': {'#': 'asdf'} - } - } - var node = Gun.union.ify(graph, 'soul'); - expect(Gun.node.soul(node)).to.be('soul'); - expect(node.x).to.be(1); - expect(node.y).to.be(2); - }); - - it('ify graph', function(){ - var graph = { - 'asdf': { - _: {'#': 'asdf', '>': { - a: Gun.time.is() - 2, - z: Gun.time.is() - 2 - }}, - a: 1, - z: 1 - }, - 'fdsa': { - _: {'#': 'fdsa', '>': { - b: Gun.time.is() - 1, - z: Gun.time.is() - 1 - }}, - b: 2, - z: 2 - }, - 'sadf': { - _: {'#': 'sadf', '>': { - c: Gun.time.is(), - z: Gun.time.is() - 100 - }}, - c: 3, - z: 3 - }, - 'soul': { - _: {'#': 'soul', '~': 1, '>': { - 'asdf': Gun.time.is(), - 'fdsa': Gun.time.is(), - 'sadf': Gun.time.is() - }}, - 'asdf': {'#': 'asdf'}, - 'fdsa': {'#': 'fdsa'}, - 'sadf': {'#': 'sadf'} - } - } - var node = Gun.union.ify(graph, 'soul'); - expect(Gun.node.soul(node)).to.be('soul'); - expect(node.a).to.be(1); - expect(node.b).to.be(2); - expect(node.c).to.be(3); - expect(node.z).to.be(2); - }); - }); - - !Gun.SEA && describe('API', function(){ - var gopt = {wire:{put:function(n,cb){cb()},get:function(k,cb){cb()}}}; - var gun = Gun(); - - it.skip('gun chain separation', function(done){ // TODO: UNDO! - var gun = Gun(); - - var c1 = gun.put({hello: 'world'}); - - var c2 = gun.put({hi: 'earth'}); - - c1.on(function(val){ - expect(val.hi).to.not.be.ok(); - }); - - c2.on(function(val){ - expect(val.hello).to.not.be.ok(); - if(done.c){ return } - done(); done.c = 1; - }); - }); - - describe.skip('timeywimey', function(){ // TODO: UNDO! - - it('kitty', function(done){ - var g1 = gun.put({hey: 'kitty'}).key('timeywimey/kitty'); - - var g2 = gun.get('timeywimey/kitty').on(function(val){ - delete val._; - //console.log("kitty?", val); - expect(val.hey).to.be('kitty'); - expect(val.hi).to.not.be.ok(); - expect(val.hello).to.not.be.ok(); - expect(val.foo).to.not.be.ok(); - if(done.c){ return } - done(); done.c = 1; - }); - }); - - it('kitty puppy', function(done){ - var g3 = gun.put({hey: 'kitty'}).key('timeywimey/kitty/puppy'); - - var g4 = gun.put({hi: 'puppy'}).key('timeywimey/kitty/puppy'); - - var g5 = gun.get('timeywimey/kitty/puppy').on(function(val){ - //delete val._; - //console.log("puppy?", val); - expect(val.hey).to.be('kitty'); - expect(val.hi).to.be('puppy'); - if(done.c){ return } - done(); done.c = 1; - }); - }); - - it('hello', function(done){ - gun.get('timeywimey/hello').on(function(val){ - //delete val._; - //console.log("hello?", val); - expect(val.hello).to.be('world'); - if(done.c){ return } - done(); done.c = 1; - }); - - gun.put({hello: 'world'}).key('timeywimey/hello'); - }); - - it('hello foo', function(done){ - gun.get('timeywimey/hello/foo').on(function(val){ - //delete val._; - expect(val.hello).to.be('world'); - if(val.foo){ - expect(val.foo).to.be('bar'); - if(done.c){ return } - done(); done.c = 1; - } - }); - - gun.put({hello: 'world'}).key('timeywimey/hello/foo'); - - gun.put({foo: 'bar'}).key('timeywimey/hello/foo'); - }); - - it('all', function(done){ - gun.put({hey: 'kitty'}).key('timeywimey/all'); - - gun.put({hi: 'puppy'}).key('timeywimey/all'); - - gun.get('timeywimey/all').on(function(val){ - // console.log('all', done.c, val); - expect(val.hey).to.be('kitty'); - expect(val.hi).to.be('puppy'); - if(val.hello){ - expect(val.hello).to.be('world'); - done.hello = true; - } - if(val.foo){ - expect(val.foo).to.be('bar'); - if(done.c || !done.hello){ return } - done(); done.c = 1; - } - }); - - gun.put({hello: 'world'}).key('timeywimey/all'); - - gun.put({foo: 'bar'}).key('timeywimey/all'); - }); - - }); - - describe('plural chains', function(){ - this.timeout(5000); - it('uncached synchronous map on', function(done){ - /* - Biggest challenges so far: - - Unsubscribe individual mapped next. ! - - Performance deduplication on asking relation's next. ! - - Replying immediately to parent cached contexts. - - Performant read lock on write contexts. - - Proxying event across maps. - */ - var s = Gun.state.map();s.soul = 'u/m'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 26, - name: "Alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "Bob!", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m').map().on(function(v,f){ - check[f] = v; - count[f] = (count[f] || 0) + 1; - //console.log("***********", f, v); - if(check.alice && check.bob){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice.age).to.be(26); - expect(check.alice.name).to.be('Alice'); - expect(Gun.val.rel.is(check.alice.pet)).to.be.ok(); - //expect(count.alice).to.be(1); - expect(check.bob.age).to.be(29); - expect(check.bob.name).to.be('Bob!'); - expect(Gun.val.rel.is(check.bob.pet)).to.be.ok(); - //expect(count.bob).to.be(1); - done(); - },10); - } - }); - }); - - it('uncached synchronous map get on', function(done){ - var s = Gun.state.map();s.soul = 'u/m/p'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 26, - name: "alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/p').map().get('name').on(function(v,f){ - //console.log("*****************", f, v); - check[v] = f; - count[v] = (count[v] || 0) + 1; - if(check.alice && check.bob){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice).to.be('name'); - expect(check.bob).to.be('name'); - //expect(count.alice).to.be(1); - //expect(count.bob).to.be(1); - done(); - },10); - } - }); - }); - - it('uncached synchronous map get on node', function(done){ - var s = Gun.state.map();s.soul = 'u/m/p/n'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 26, - name: "alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/p/n').map().get('pet').on(function(v,f){ - //console.log("********************", f,v); - check[v.name] = v; - count[v.name] = (count[v.name] || 0) + 1; - if(check.Fluffy && check.Frisky){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.Fluffy.a).to.be(1); - expect(check.Frisky.b).to.be(2); - //expect(count.Fluffy).to.be(1); - //expect(count.Frisky).to.be(1); - //expect(count['undefined']).to.not.be.ok(); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - }); - - it('uncached synchronous map get on node get', function(done){ - var gun = Gun(); - var s = Gun.state.map();s.soul = 'u/m/p/n/p'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 26, - name: "alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - //console.debug.i=1;console.log('-------------------'); - gun.get('u/m/p/n/p').map().get('pet').get('name').on(function(v,f){ - check[v] = f; - count[v] = (count[v] || 0) + 1; - //console.log("*****************", f, v); - if(check.Fluffy && check.Frisky){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.Fluffy).to.be('name'); - expect(check.Frisky).to.be('name'); - Gun.obj.map(gun._.graph, function(n,s){ - if('u/m/p/n/p' === s){ return } - var a = Gun.obj.map(n, function(v,f,t){t(v)}); - expect(a.length).to.be(2); // make sure that ONLY the selected properties were loaded, not the whole node. - }); - //expect(count.Fluffy).to.be(1); - //expect(count.Frisky).to.be(1); - done(); - },10); - } - }); - }); - - it('uncached synchronous map on mutate', function(done){ - var s = Gun.state.map();s.soul = 'u/m/mutate'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 26, - name: "Alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "Bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/mutate').map().get('name').get(function(at,ev){ - var e = at.err, v = at.put, f = at.get; - //console.log("****************", f,v); - check[v] = f; - count[v] = (count[v] || 0) + 1; - if(check.Alice && check.Bob && check['undefined']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - //expect(count.Alice).to.be(1); - //expect(count.Bob).to.be(1); - //expect(count['undefined']).to.be(1); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('u/m/mutate').get('alice').put(7); - }, 300); - }); - - it('uncached synchronous map on mutate node', function(done){ - var s = Gun.state.map();s.soul = 'u/m/mutate/n'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: {_:{'#':'umaliceo'}, - age: 26, - name: "Alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "Bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/mutate/n').map().get('name').get(function(at,ev){ - var e = at.err, v = at.put, f = at.get; - check[v] = f; - count[v] = (count[v] || 0) + 1; - //console.log("************", f,v); - if(check.Alice && check.Bob && check['undefined'] && check['Alice Zzxyz']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(done.last).to.be.ok(); - expect(check['Alice Aabca']).to.not.be.ok(); - //expect(count.Alice).to.be(1); - //expect(count.Bob).to.be(1); - //expect(count['undefined']).to.be(1); - //expect(count['Alice Zzxyz']).to.be(1); - done(); - },200); - } - }); - setTimeout(function(){ - //console.debug.i=1;console.log("-----------------------"); - gun.get('u/m/mutate/n').get('alice').put({ - _:{'#':'u/m/m/n/soul'}, - name: 'Alice Zzxyz' - }); - setTimeout(function(){ - gun.get('umaliceo').put({ - name: 'Alice Aabca' - }); - done.last = true; - }, 10); - }, 300); - }); - - it('uncached synchronous map on mutate node uncached', function(done){ - var s = Gun.state.map();s.soul = 'u/m/mutate/n/u'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: {_:{'#':'umaliceo1'}, - age: 26, - name: "Alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "Bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/mutate/n/u').map().on(function(v,f){ - check[v.name] = f; - count[v.name] = (count[v.name] || 0) + 1; - //console.log("*****************", f,v); - if(check.Alice && check.Bob && check['Alice Zzxyz']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(done.last).to.be.ok(); - //expect(check['Alice Aabca']).to.not.be.ok(); - //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); - } - }); - setTimeout(function(){ - var s = Gun.state.map();s.soul = 'u/m/m/n/u/soul'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - name: 'Alice Zzxyz' - }, s)}); - //console.debug.i=1;console.log("---------------"); - gun.get('u/m/mutate/n/u').put({ - alice: {'#':'u/m/m/n/u/soul'}, - }); - /* - { - users: {_:#users - alice: {#newalice} - } - } - */ - setTimeout(function(){ - gun.get('umaliceo1').put({ - name: 'Alice Aabca' - }); - done.last = true; - }, 10); - }, 300); - }); - - it('uncached synchronous map on get mutate node uncached', function(done){ - var s = Gun.state.map();s.soul = 'u/m/p/mutate/n/u'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: {_:{'#':'umaliceo2'}, - age: 26, - name: "Alice", - pet: {a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "Bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/p/mutate/n/u').map().get('name').on(function(v,f){ - check[v] = f; - count[v] = (count[v] || 0) + 1; - //console.log("*************", f,v); - if(check.Alice && check.Bob && check['Alice Zzxyz']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - var a = Gun.obj.map(gun._.graph['u/m/p/m/n/u/soul'], function(v,f,t){t(v)}); - expect(a.length).to.be(2); - expect(done.last).to.be.ok(); - expect(check['Alice Aabca']).to.not.be.ok(); - //expect(count.Alice).to.be(1); - //expect(count.Bob).to.be(1); - //expect(count['Alice Zzxyz']).to.be(1); - done(); - },200); - } - }); - setTimeout(function(){ - var s = Gun.state.map();s.soul = 'u/m/p/m/n/u/soul'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - name: 'Alice Zzxyz', age: 34 - }, s)}); - gun.get('u/m/p/mutate/n/u').put({ - alice: {'#':'u/m/p/m/n/u/soul'}, - }); - setTimeout(function(){ - gun.get('umaliceo2').put({ - name: 'Alice Aabca' - }); - done.last = true; - }, 10); - }, 300); - }); - - it('uncached synchronous map on get node mutate node uncached', function(done){ - var s = Gun.state.map();s.soul = 'u/m/p/n/mutate/n/u'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: {_:{'#':'umaliceo3'}, - age: 26, - name: "Alice", - pet: {_:{'#':'sflufso'},a:1, name: "Fluffy"} - }, - bob: { - age: 29, - name: "Bob", - pet: {b:2, name: "Frisky"} - } - }, s)}); - var check = {}, count = {}; - gun.get('u/m/p/n/mutate/n/u').map().get('pet').on(function(v,f){ - check[v.name] = f; - count[v.name] = (count[v.name] || 0) + 1; - //console.log("*****************", f,v); - if(check.Fluffy && check.Frisky && check.Fuzzball){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(done.last).to.be.ok(); - expect(check['Fluffs']).to.not.be.ok(); - //expect(count.Fluffy).to.be(1); - //expect(count.Frisky).to.be(1); - //expect(count.Fuzzball).to.be(1); - done(); - },200); - } - }); - setTimeout(function(){ - var s = Gun.state.map();s.soul = 'alice/fuzz/soul'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - name: 'Alice Zzxyz', age: 34, - pet: {c:3, name: "Fuzzball"} - }, s)}); - gun.get('u/m/p/n/mutate/n/u').put({ - alice: {'#':'alice/fuzz/soul'}, - }); - setTimeout(function(){ - gun.get('sflufso').put({ - name: 'Fluffs' - }); - done.last = true; - }, 10); - }, 300); - }); - - it("get before put in memory", function(done){ - var gun = Gun(); - var check = {}; - var count = {}; - gun.get('g/n/m/f/l/n/r').map().on(function(v,f){ - //console.log("***********", f,v); - check[f] = v; - count[f] = (count[f] || 0) + 1; - if(check.alice && check.bob && check.alice.PhD){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice.age).to.be(24); - expect(check.bob.age).to.be(26); - expect(check.alice.PhD).to.be(true); - //expect(count.alice).to.be(2); - //expect(count.bob).to.be(1); - if(done.c){return} - done();done.c=1; - },50); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/r'}, - alice: {_:{'#':'GALICE1'}, - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - } - }); - setTimeout(function(){ - gun.get('GALICE1').put({PhD: true}); - },300); - }); - - it("in memory get after", function(done){ - var gun = Gun(); - gun.put({_:{'#':'g/n/m/f/l/n'}, - alice: {_:{'#':'GALICE2'}, - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - } - }); - var check = {}; - //gun.get('g/n/m/f/l/n').get('bob.spouse.work').on(function(v,f){ console.log("!!!!!!!!!", f, v);});return; - gun.get('g/n/m/f/l/n').map().on(function(v,f){ - check[f] = v; - //console.log("*******************", f, v); - if(check.alice && check.bob && check.alice.PhD){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice.age).to.be(24); - expect(check.bob.age).to.be(26); - expect(check.alice.PhD).to.be(true); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('GALICE2').put({PhD: true}); - },300); - }); - - it("in memory get before map get", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/p').map().get('name').on(function(v,f){ - check[v] = f; - //console.log("****************", f,v); - if(check.alice && check.bob && check.Alice){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice).to.be('name'); - expect(check.bob).to.be('name'); - expect(check.Alice).to.be('name'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/b/p'}, - alice: {_:{'#':'GALICE3'}, - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - } - }); - setTimeout(function(){ - gun.get('GALICE3').put({name: 'Alice'}); - },300); - }); - - it("in memory get after map get", function(done){ - var gun = Gun(); - gun.put({_:{'#':'g/n/m/f/l/n/m/p'}, - alice: {_:{'#':'GALICE4'}, - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - } - }); - var check = {}; - gun.get('g/n/m/f/l/n/m/p').map().get('name').on(function(v,f){ - check[v] = f; - //console.log("*****************", f,v); - if(check.alice && check.bob && check.Alice){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice).to.be('name'); - expect(check.bob).to.be('name'); - expect(check.Alice).to.be('name'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('GALICE4').put({name: 'Alice'}); - },300); - }); - - it("in memory get before map get get", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/p/p/p').map().get('spouse').get('work').on(function(v,f){ - check[v.name] = f; - if(check['GUN INC'] && check['ACME INC'] && check['ACME INC.']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check['GUN INC']).to.be('work'); - expect(check['ACME INC']).to.be('work'); - expect(check['ACME INC.']).to.be('work'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/b/p/p/p'}, - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: {_:{'#':'CCINEMA1'}, - name: "ACME INC" - } - } - } - }); - setTimeout(function(){ - gun.get('CCINEMA1').put({name: 'ACME INC.'}); - },300); - }); - - it("in memory get after map get get", function(done){ - var gun = Gun(); - gun.put({_:{'#':'g/n/m/f/l/n/b/p/p/p/a'}, - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: {_:{'#':'CCINEMA2'}, - name: "ACME INC" - } - } - } - }); - var check = {}; - gun.get('g/n/m/f/l/n/b/p/p/p/a').map().get('spouse').get('work').on(function(v,f){ - check[v.name] = f; - if(check['GUN INC'] && check['ACME INC'] && check['ACME INC.']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check['GUN INC']).to.be('work'); - expect(check['ACME INC']).to.be('work'); - expect(check['ACME INC.']).to.be('work'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('CCINEMA2').put({name: 'ACME INC.'}); - },300); - }); - - it("in memory get before map map", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/a/m/m').map().map().on(function(v,f){ - check[f] = v; - //console.log("****************", f,v); - if(check.alice && check.bob && check.GUN && check.ACME && check.ACME.corp){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice.name).to.be('alice'); - expect(check.alice.age).to.be(24); - expect(Gun.val.rel.is(check.alice.spouse)).to.be.ok(); - expect(check.bob.name).to.be('bob'); - expect(check.bob.age).to.be(26); - expect(Gun.val.rel.is(check.bob.spouse)).to.be.ok(); - expect(check.GUN.name).to.be('GUN'); - expect(check.ACME.name).to.be('ACME'); - expect(check.ACME.corp).to.be('C'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - //console.debug.i=1;console.log("------------------------"); - gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m'}, - users: { - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN" - }, - ACME: {_:{'#':"CCINEMA3"}, - name: "ACME" - } - } - }); - setTimeout(function(){ - gun.get('CCINEMA3').put({corp: "C"}); - },300); - }); - - it("in memory get after map map", function(done){ - var gun = Gun(); - gun.put({_:{'#':'g/n/m/f/l/n/b/m/m'}, - users: { - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN" - }, - ACME: {_:{'#':"CCINEMA4"}, - name: "ACME" - } - } - }); - var check = {}; - gun.get('g/n/m/f/l/n/b/m/m').map().map().on(function(v,f){ - check[f] = v; - //console.log("***************", f,v); - if(check.alice && check.bob && check.GUN && check.ACME && check.ACME.corp){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice.name).to.be('alice'); - expect(check.alice.age).to.be(24); - expect(Gun.val.rel.is(check.alice.spouse)).to.be.ok(); - expect(check.bob.name).to.be('bob'); - expect(check.bob.age).to.be(26); - expect(Gun.val.rel.is(check.bob.spouse)).to.be.ok(); - expect(check.GUN.name).to.be('GUN'); - expect(check.ACME.name).to.be('ACME'); - expect(check.ACME.corp).to.be('C'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('CCINEMA4').put({corp: "C"}); - },300); - }); - - it("in memory get before map map get", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/m/m/p').map().map().get('name').on(function(v,f){ - check[v] = f; - //console.log("***********", f,v); - if(check.alice && check.bob && check.GUN && check.ACME && check.ACMEINC){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice).to.be('name'); - expect(check.bob).to.be('name'); - expect(check.GUN).to.be('name'); - expect(check.ACME).to.be('name'); - expect(check.ACMEINC).to.be('name'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p'}, - users: { - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN" - }, - ACME: {_:{'#':"CCINEMA5"}, - name: "ACME" - } - } - }); - setTimeout(function(){ - gun.get('CCINEMA5').put({name: "ACMEINC"}); - },300); - }); - - it("in memory get after map map get", function(done){ - var gun = Gun(); - var check = {}; - gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p'}, - users: { - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN" - }, - ACME: {_:{'#':'CCINEMA6'}, - name: "ACME" - } - } - }); - gun.get('g/n/m/f/l/n/b/a/m/m/p').map().map().get('name').on(function(v,f){ - check[v] = f; - //console.log("************", f,v); - if(check.alice && check.bob && check.GUN && check.ACME && check.ACMEINC){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.alice).to.be('name'); - expect(check.bob).to.be('name'); - expect(check.GUN).to.be('name'); - expect(check.ACME).to.be('name'); - expect(check.ACMEINC).to.be('name'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('CCINEMA6').put({name: "ACMEINC"}); - },300); - }); - - it("in memory get before map map get get", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/m/m/p/p').map().map().get('address').get('state').on(function(v,f){ - check[v] = f; - if(check.QR && check.NY && check.CA && check.TX && check.MA){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.QR).to.be('state'); - expect(check.NY).to.be('state'); - expect(check.CA).to.be('state'); - expect(check.TX).to.be('state'); - expect(check.MA).to.be('state'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p/p'}, - users: { - alice: { - name: "alice", - age: 24, - address: {_:{'#':'QUANGO'}, - state: "MA" - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: "TX" - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: "CA" - } - }, - ACME: { - name: "ACME", - address: { - state: "NY" - } - } - } - }); - setTimeout(function(){ - gun.get('QUANGO').put({state: 'QR'}); - },300); - }); - - it("in memory get after map map get get", function(done){ - var gun = Gun(); - gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p'}, - users: { - alice: { - name: "alice", - age: 24, - address: {_:{'#':'QUANGO1'}, - state: "MA" - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: "TX" - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: "CA" - } - }, - ACME: { - name: "ACME", - address: { - state: "NY" - } - } - } - }); - var check = {}; - gun.get('g/n/m/f/l/n/b/a/m/m/p/p').map().map().get('address').get('state').on(function(v,f){ - check[v] = f; - if(check.QR && check.NY && check.CA && check.TX && check.MA){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.QR).to.be('state'); - expect(check.NY).to.be('state'); - expect(check.CA).to.be('state'); - expect(check.TX).to.be('state'); - expect(check.MA).to.be('state'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('QUANGO1').put({state: 'QR'}); - },300); - }); - - it("in memory get before map map get get get", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/m/m/p/p/p').map().map().get('address').get('state') - .get('code') - .on(function(v,f){ - check[v] = f; - if(check.QR && check.NY && check.CA && check.TX && check.MA){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.QR).to.be('code'); - expect(check.NY).to.be('code'); - expect(check.CA).to.be('code'); - expect(check.TX).to.be('code'); - expect(check.MA).to.be('code'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p/p/p'}, - users: { - alice: { - name: "alice", - age: 24, - address: { - state: {_:{'#':'HIPPOM'}, - code: "MA", - county: { - MA1: "First" - } - } - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: { - code: "TX", - county: { - TX1: "First" - } - } - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: { - code: "CA", - county: { - CA1: "First" - } - } - } - }, - ACME: { - name: "ACME", - address: { - state: { - code: "NY", - county: { - NY1: "First" - } - } - } - } - } - }); - setTimeout(function(){ - gun.get('HIPPOM').put({code: 'QR'}); - },300); - }); - - it("in memory get before after map map get get get", function(done){ - var gun = Gun(); - var check = {}; - gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p/p'}, - users: { - alice: { - name: "alice", - age: 24, - address: { - state: {_:{'#':'HIPPOM1'}, - code: "MA", - county: { - MA1: "First" - } - } - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: { - code: "TX", - county: { - TX1: "First" - } - } - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: { - code: "CA", - county: { - CA1: "First" - } - } - } - }, - ACME: { - name: "ACME", - address: { - state: { - code: "NY", - county: { - NY1: "First" - } - } - } - } - } - }); - gun.get('g/n/m/f/l/n/b/a/m/m/p/p/p').map().map().get('address').get('state') - .get('code') - .on(function(v,f){ - check[v] = f; - //console.log("***********", f,v); - if(check.QR && check.NY && check.CA && check.TX && check.MA){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.QR).to.be('code'); - expect(check.NY).to.be('code'); - expect(check.CA).to.be('code'); - expect(check.TX).to.be('code'); - expect(check.MA).to.be('code'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('HIPPOM1').put({code: 'QR'}); - },300); - }); - - it("in memory get before map map get get node", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f/l/n/b/m/m/p/p/n').map().map().get('address').get('state').on(function(v,f){ - check[v.code] = f; - //console.log("************", f, v); - if(check.QR && check.NY && check.CA && check.TX && check.MA){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.QR).to.be('state'); - expect(check.NY).to.be('state'); - expect(check.CA).to.be('state'); - expect(check.TX).to.be('state'); - expect(check.MA).to.be('state'); - if(done.c){return}done.c=1; - done(); - },10); - } - }); - gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p/p/n'}, - users: { - alice: { - name: "alice", - age: 24, - address: { - state: {_:{'#':'HIPPOM3'}, - code: "MA", - county: { - MA1: "First" - } - } - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: { - code: "TX", - county: { - TX1: "First" - } - } - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: { - code: "CA", - county: { - CA1: "First" - } - } - } - }, - ACME: { - name: "ACME", - address: { - state: { - code: "NY", - county: { - NY1: "First" - } - } - } - } - } - }); - setTimeout(function(){ - gun.get('HIPPOM3').put({code: 'QR'}); - },300); - }); - - it("in memory get before after map map get get node", function(done){ - var gun = Gun(); - var check = {}; - gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p/n'}, - users: { - alice: { - name: "alice", - age: 24, - address: { - state: {_:{'#':'HIPPOM4'}, - code: "MA", - county: { - MA1: "First" - } - } - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: { - code: "TX", - county: { - TX1: "First" - } - } - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: { - code: "CA", - county: { - CA1: "First" - } - } - } - }, - ACME: { - name: "ACME", - address: { - state: { - code: "NY", - county: { - NY1: "First" - } - } - } - } - } - }); - gun.get('g/n/m/f/l/n/b/a/m/m/p/p/n').map().map().get('address').get('state').on(function(v,f){ - check[v.code] = f; - //console.log("**********", f, v); - if(check.QR && check.NY && check.CA && check.TX && check.MA){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.QR).to.be('state'); - expect(check.NY).to.be('state'); - expect(check.CA).to.be('state'); - expect(check.TX).to.be('state'); - expect(check.MA).to.be('state'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('HIPPOM4').put({code: 'QR'}); - },300); - }); - - it("in memory get after map map get get get map", function(done){ - var gun = Gun(); - var check = {}; - gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p/p/n'}, - users: { - alice: { - name: "alice", - age: 24, - address: { - state: { - code: "MA", - county: { - MA1: "First" - ,MA2: "Second" - } - } - }, - spouse: { - name: "carl", - age: 25 - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - address: { - state: { - code: "TX", - county: { - TX1: "First" - ,TX2: "Second" - } - } - }, - spouse: { - name: "diana", - age: 27 - } - } - }, - companies: { - GUN: { - name: "GUN", - address: { - state: { - code: "CA", - county: { - CA1: "First" - ,CA2: "Second" - } - } - } - }, - ACME: { - name: "ACME", - address: { - state: { - code: "NY", - county: {_:{'#':'NYCOUNT'}, - NY1: "First" - ,NY2: "Second" - } - } - } - } - } - }); - gun.get('g/n/m/f/l/n/b/a/m/m/p/p/p/n').map().map().get('address').get('state').get('county').map().on(function(v,f){ - check[f] = v; - //console.log("****************", f,v); - if(check.MA1 && check.MA2 && check.TX1 && check.TX2 && check.CA1 && check.CA2 && check.NY1 && check.NY2 && check.NY3){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check.MA1).to.be('First'); - expect(check.TX1).to.be('First'); - expect(check.CA1).to.be('First'); - expect(check.NY1).to.be('First'); - expect(check.MA2).to.be('Second'); - expect(check.TX2).to.be('Second'); - expect(check.CA2).to.be('Second'); - expect(check.NY2).to.be('Second'); - expect(check.NY3).to.be('Third'); - done(); - },10); - } - }); - setTimeout(function(){ - gun.get('NYCOUNT').put({NY3: "Third"}); - },300); - }); - }); - - it('get node after recursive field', function(done){ - var s = Gun.state.map();s.soul = 'node/circle'; - var bob = {age: 29, name: "Bob!"}; - var cat = {name: "Fluffy", species: "kitty"}; - var user = {bob: bob}; - bob.pet = cat; - cat.slave = bob; - gun.on('put', {gun: gun, put: Gun.graph.ify(user, s)}); - gun.get(s.soul).get('bob').get('pet').get('slave').val(function(data){ - //clearTimeout(done.to); - //setTimeout(function(){ - //console.log("*****************", data); - expect(data.age).to.be(29); - expect(data.name).to.be("Bob!"); - expect(Gun.val.rel.is(data.pet)).to.ok(); - done(); - //},300); - }); - }); - - it('recursive put', function(done){ - //localStorage.clear(); - var gun = Gun(); - - var parent = gun.get('parent'); - var child = gun.get('child'); - - child.put({ - way: 'down' - }); - - parent.get('sub').put(child); - - parent.get('sub').on(function(data){ - //console.log("sub", data); - done.sub = data; - }); - child.on(function(data){ - done.child = data; - //console.log("child", data); - }); - parent.on(function(data){ - done.parent = data; - //console.log("parent", data); - if(done.c){ return } done.c = 1; - done(); // TODO: Add more meaningful checks! - }); - }); - - it('empty val followed', function(done){ - var gun = Gun(); - - gun.get('val/follow').val(function(data){ - //console.log("val", data); - }).get(function(at){ - if(done.c){ return } done.c = 1; - done(); - }); - - }); - - it('map val get put', function(done){ - - var gun = Gun().get('chat/asdf'); - - var check = {}, count = {}; - gun.map().val(function(v,f){ - check[f] = v; - count[f] = (count[f] || 0) + 1; - if(check['1_1'] && check['2_2']){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(check['1_1'].what).to.be('hi'); - expect(check['2_2'].what).to.be('you.'); - expect(count['1_1']).to.be(1); - expect(count['2_2']).to.be(1); - done(); - },50); - } - }); - - setTimeout(function(){ - gun.get('1_1').put({what: "hi"}); - setTimeout(function(){ - gun.get('2_2').put({what: "you."}); - },40); - },40); - }); - - it('get list set map val', function(done){ - - var gun = Gun(); - - var list = gun.get('list'); - - list.set(gun.get('alice').put({name: "Alice", group: "awesome", married: true})); - list.set(gun.get('bob').put({name: "Bob", group: "cool", married: true})); - list.set(gun.get('carl').put({name: "Carl", group: "cool", married: false})); - list.set(gun.get('dave').put({name: "Dave", group: "awesome", married: true})); - - var check = {}, count = {}; - list.map().val(function(data, id){ - //console.log("***************", id, data); - check[id] = data; - count[id] = (count[id] || 0) + 1; - if(check.alice && check.bob && check.carl && check.dave){ - clearTimeout(done.to); - done.to = setTimeout(function(){ - expect(count.alice).to.be(1); - expect(check.alice.name).to.be('Alice'); - expect(check.alice.group).to.be('awesome'); - expect(check.alice.married).to.be(true); - expect(count.bob).to.be(1); - expect(check.bob.name).to.be('Bob'); - expect(check.bob.group).to.be('cool'); - expect(check.bob.married).to.be(true); - expect(count.carl).to.be(1); - expect(check.carl.name).to.be('Carl'); - expect(check.carl.group).to.be('cool'); - expect(check.carl.married).to.be(false); - expect(count.dave).to.be(1); - expect(check.dave.name).to.be('Dave'); - expect(check.dave.group).to.be('awesome'); - expect(check.dave.married).to.be(true); - done(); - },50); - } - }); - /* - Have we asked for this yet? No. - Do we have it cached? No. - Is its parent cached? Yes. - Reply immediately with that cache for map to process. - */ - - /* - chain has a root // all - an ID // all - a back // all - inputs // all - and outputs // all - acks // any - echo // any - next // any - cache or map of many ones // only a one can have a cache, only a map can have many, and they must be ones. However any chain might have neither. By default a chain is a many, unless it is designated as a one. - - gun.get('alice').also('bob').path('name').on(cb); - gun.get('users').map().path('friends').map().on(cb); - - friends is a map, it has an echo - {name: "alice", friends: []} - {name: "xavier"} - {name: "yara"} - {name: "zack"} - {name: "bob", friends: []} - {name: "xavier"} - {name: "yara"} - {name: "zack"} - {name: "carl", friends: []} - {name: "xavier"} - {name: "yara"} - {name: "zack"} - */ - }); - - it('get get get set root get put', function(done){ - var gun = Gun().get('app'); - gun.get('alias').get('mark').set( - gun.back(-1).get('pub').put({ - alias: 'mark', - auth: 'encrypt', // oops - born: 1, - pub: 'pub', - salt: 'random' - }) - ); - setTimeout(function(){ - gun.get(function(at){ - //console.log('*', at.put); - done.app = done.app || at.put.alias; - }); - gun.back(-1).get('pub').get(function(at){ - //console.log("**", at.put); - done.pub = done.pub || at.put.auth; - }); - gun.get('alias').get(function(at){ - //console.log("***", at.put); - done.alias = done.alias || at.put.mark; - }).get('mark').get(function(at){ - //console.log("************", at.put);return; - setTimeout(function(){ - done.mark = done.mark || at.put.pub; - expect(Gun.val.rel.is(done.mark)).to.be('pub'); - expect(done.app).to.be.ok(); - expect(done.pub).to.be.ok(); - expect(done.alias).to.be.ok(); - if(done.c){ return } done.c = 1; - done(); - },100); - }) - },100); - }); - - it('get put get get put reload get get then get', function(done){ - var gun = Gun(); - - gun.get('stef').put({name:'Stef'}); - var address = { - country: 'Netherlands', - zip:'999999' - }; - gun.get('stef').get('address').put(address); - - // reload - setTimeout(function(){ - var gun2 = Gun(); - gun2.get('stef').get('address').val(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" - done.a = true; - expect(data.country).to.be('Netherlands'); - expect(data.zip).to.be('999999'); - if(!done.s){ return } - if(done.c){ return } done.c = 1; - done(); - }); - gun2.get('stef').val(function(data){ //Object {_: Object, address: Object} "stef" - done.s = true; - expect(data.name).to.be('Stef'); - expect(data.address).to.be.ok(); - if(!done.a){ return } - if(done.c){ return } done.c = 1; - done(); - }); - },300); - }); - - it('get get get any parallel', function(done){ - var s = Gun.state.map();s.soul = 'parallel'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!" - } - }, s)}); - gun.get('parallel').get('bob').get('age').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("***** age", data, at.gun._.ack);return; - expect(data).to.be(29); - expect(field).to.be('age'); - done.age = true; - }); - gun.get('parallel').get('bob').get('name').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("*********** name", data, at.gun._.ack);return; - expect(data).to.be('Bob!'); - expect(field).to.be('name'); - done.name = true; - expect(done.age).to.be.ok(); - if(done.c){ return } done.c = 1; - done(); - }); - }); - - it('get get get any later', function(done){ - var s = Gun.state.map();s.soul = 'parallel/later'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: {_:{'#':'ddfsa'}, - age: 29, - name: "Bob!" - } - }, s)}); - gun.get('parallel/later').get('bob').get('age').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("***** age", data); - expect(data).to.be(29); - expect(field).to.be('age'); - done.age = true; - }); - setTimeout(function(){ - gun.get('parallel/later').get('bob').get('name').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("*********** name", data); - expect(data).to.be('Bob!'); - expect(field).to.be('name'); - done.name = true; - expect(done.age).to.be.ok(); - if(done.c){ return } done.c = 1; - done(); - }); - },400); - }); - - it('get get get any not', function(done){ - gun.get('parallel/not').get('bob').get('age').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("***** age", data); - expect(data).to.be(undefined); - expect(field).to.be('age'); - done.age = true; - }); - gun.get('parallel/not').get('bob').get('name').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("*********** name", data); - expect(data).to.be(undefined); - expect(field).to.be('name'); - done.name = true; - expect(done.age).to.be.ok(); - if(done.c){return}done.c=1; - done(); - }); - }); - - it('get get get any not later', function(done){ - gun.get('parallel/not/later').get('bob').get('age').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("***** age", data); - expect(data).to.be(undefined); - expect(field).to.be('age'); - done.age = true; - }); - setTimeout(function(){ - gun.get('parallel/not/later').get('bob').get('name').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("*********** name", field, data); - expect(data).to.be(undefined); - expect(field).to.be('name'); - done.name = true; - expect(done.age).to.be.ok(); - if(done.c){ return } done.c = 1; - done(); - }); - },400); - }); - - it('get any any', function(done){ - var s = Gun.state.map();s.soul = 'full'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - hello: 'world', - goodbye: 'mars' - }, s)}); - gun.get('full').get(function(at, ev){ - var err = at.err, data = at.gun._.put || at.put, field = at.get; - //console.log("*****1", data); - expect(data.hello).to.be('world'); - expect(data.goodbye).to.be('mars'); - }); - gun.get('full').get(function(at, ev){ - var err = at.err, data = at.gun._.put || at.put, field = at.get; - //console.log("*****1", data); - expect(data.hello).to.be('world'); - expect(data.goodbye).to.be('mars'); - if(done.c){ return } done.c = 1; - done(); - }); - }); - - it('get any any later', function(done){ - var s = Gun.state.map();s.soul = 'full/later'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - hello: 'world', - goodbye: 'mars' - }, s)}); - gun.get('full/later').get(function(at, ev){ - var err = at.err, data = at.gun._.put || at.put, field = at.get; - //console.log("*****", data); - expect(data.hello).to.be('world'); - expect(data.goodbye).to.be('mars'); - }); - setTimeout(function(){ - gun.get('full/later').get(function(at, ev){ - var err = at.err, data = at.gun._.put || at.put, field = at.get; - //console.log("*****2", field, data); - expect(data.hello).to.be('world'); - expect(data.goodbye).to.be('mars'); - if(done.c){ return } done.c = 1; - done(); - }); - },400); - }); - - it('multiple times', function(done){ - var gun = Gun(); - - var app = gun.get('mult/times'); - - app.get('alias').get('mark').set(gun.get('ASDF').put({ - pub: 'ASDF', - alias: 'mark', - born: 1 - })); - - app.get('alias').map().map().get('pub').on(function(data){ - done.one = data; - //console.log("pub 1!", data); - }); - - setTimeout(function(){ - app.get('alias').map().map().get('alias').on(function(data){ - done.two = data; - //console.log("alias 2!", data); - expect(done.one).to.be("ASDF"); - expect(done.two).to.be("mark"); - if(done.c){ return } done.c = 1; - done(); - }); - },100); - }); - - it('multiple times partial', function(done){ - var gun = Gun(); - - var s = Gun.state.map();s.soul = 'mult/times/part'; - gun.on('put', {gun: gun, put: Gun.graph.ify({ - alias: { - mark: { - pub: {_:{'#':'PUB'}, - pub: 'asdf', - alias: 'mark', - born: 1 - } - } - } - }, s)}); - - var app = gun.get(s.soul); - - app.get('alias').get('mark').map().val(function(alias){ - done.alias = alias; - }); - - setTimeout(function(){ - app.get('alias').map().map().get('born').on(function(data){ - expect(data).to.be(1); - expect(done.alias.pub).to.be("asdf"); - expect(done.alias.alias).to.be("mark"); - expect(done.alias.born).to.be(1); - if(done.c){ return } done.c = 1; - done(); - }); - },300); - }); - - 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'); - - var check = {}; - list.map(function(user){ return user.age === 27? user.name + "thezombie" : u }).on(function(data){ - //console.log('data:', data); - check[data] = true; - if(check.alicethezombie && check.bobthezombie){ - if(done.c){return}done.c=1; - done(); - } - }); - 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}); - }); - - it('val and then map', function(done){ - var gun = Gun(), s = 'val/then/map', u; - var list = gun.get(s); - - list.set(gun.get('alice').put({name: 'alice', age: 27})); - list.set(gun.get('bob').put({name: 'bob', age: 27})); - list.set(gun.get('carl').put({name: 'carl', age: 29})); - list.set(gun.get('dave').put({name: 'dave', age: 25})); - - var check = {}; - list.val().map().on(function(data, key){ - check[key] = data; - clearTimeout(done.to); - done.to = setTimeout(function(){ - if(check.alice && check.bob && check.carl && check.dave && done.last){ - expect(check.alice.age).to.be(27); - expect(check.bob.age).to.be(28); - expect(check.carl.age).to.be(29); - expect(check.dave.age).to.be(25); - expect(check.eve).to.not.be.ok(); - if(done.c){return}done.c=1; - done(); - } - },600); - }); - setTimeout(function(){ - list.set(gun.get('eve').put({name: 'eve', age: 30})); - gun.get('bob').get('age').put(28); - done.last = true; - },300); - }); - - it('check null on map', function(done){ - var list = gun.get('myList'); - list.map(function(value, id){ - if("hello world" === value){ - done.one = true; - } - if(null === value){ - done.two = true; - } - if(done.one && done.two){ - if(done.c){ return } done.c = 1; - done(); - } - }); - list.get('message').put('hello world'); // outputs "message: hello world" - list.get('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('opt', function(ctx){ - ctx.on('out', function(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){ - expect(val).to.be('hello'); - if(done.c){ return } done.c = 1; - setTimeout(function(){ - done(); - },1700); - }); - }); - - it('val should now get called if no data is found', function(done){ - var gun = Gun(); - - gun.get('nv/foo').get('bar').get('baz').val(function(val, key){ - //console.log('*******', key, val); - expect(val).to.be(undefined); - done.fbb = true; - }); - - gun.get('nv/totesnothing').val(function(val, key){ - //console.log('***********', key, val); - expect(val).to.be(undefined); - done.t = true; - }); - - gun.get('nv/bz').get('lul').val(function(val, key){ - //console.log('*****************', key, val); - expect(val).to.be(undefined); - done.bzl = true; - if(done.fbb && done.t && done.bzl){ - if(done.c){ return } done.c = 1; - done(); - } - }); - }); - - it('Callbacks should have database safe data copies', function(done){ - var gun = Gun(); - - gun.get('ds/safe').put({a: 1}); - - gun.get('ds/safe').on(function(data){ - data.b = 2; - }); - - gun.get('ds/safe').val(function(data){ - expect(gun._.root._.graph['ds/safe'].b).to.not.be.ok(); - if(done.c){ return } done.c = 1; - done(); - }); - }); - return; - it.only('Memory management', function(done){ - this.timeout(9999999); - var gun = Gun(), c = 100000, big = "big"; - while(--c){big += "big"} - c = 0; - setInterval(function(){ - var key = Gun.text.random(5); - gun.get(key).put({data: big}); - setTimeout(function(){ - gun.get(key).off(); - },10); - if(typeof process === 'undefined'){ return } - var mem = process.memoryUsage(); - console.log(((mem.heapUsed / mem.heapTotal) * 100).toFixed(0) + '% memory'); - console.log(Object.keys(gun._.graph).length, 'item in memory graph:', Object.keys(gun._.graph)); - },25); - }); - - return; - it.only('Custom extensions are chainable', function(done){ - Gun.chain.filter = function(filter){ - var chain = this.chain(); - var context = this; - var _tags; - context.val(function(obj, key){ - if(!obj.tags){ - console.warn('Not tagged to anything!'); - context._.valid = false; - chain._.on('in', {get: key, gun: this}); - return false; - } else { - _tags = Gun.obj.ify(obj.tags); - if(Array.isArray(filter)){ - context._.valid = filter.every(function(f){ return ( _tags[f] && _tags[f]==1) }); - if(context._.valid){ - chain._.on('in', {get: key, put: obj, gun: this}); - return context; - } else { - console.log("that was wrong"); - chain._.on('in', {get: key, put: undefined, gun: this}); - } - return false; - } else { - console.warn('filter should be an Array'); - return false; - } - } - }); - return chain; - } - - var gun = Gun(); - - var fake1 = gun.get('fake1').put({name:'faker1',tags:JSON.stringify({a:1,b:0,c:1})}); - var fake2 = gun.get('fake2').put({name:'faker2',tags:JSON.stringify({a:1,b:1,c:1})}); - var list = gun.get('list'); - list.set(fake1); - list.set(fake2); - - gun.get('fake1')//.map() - .filter(['a','b']) // Gun.chain.filter = function(tags){ .... } - .get(function(no){console.log("NO!", no)}) - .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(function(v,f){ console.log( "event on A: ", f, v ) } ); - gunB.get( "some path B" ).map(function(v,f){ console.log( "event on B: ", f, v ) } ); - gunC.get( "some path C" ).map(function(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"} } } }; - - var alice = {name: "alice" } - - var gun = Gun(); - - var root = gun.get( "root" ); - root.put( dfltSansUsers ); - - var alice = gun.get( "alice" ).put( { name: "alice" } ); - console.log( "Failed after this" ); - root.path( "1.sites.1.users" ).put( { 1: alice } ); - console.log( "Failed before this" ); - }); - - it.only('get any any none', function(done){ - gun.get('full/none').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - console.log("*****", data); - expect(data).to.be(undefined); - }); - gun.get('full/none').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - console.log("*****2", data); - expect(data).to.be(undefined); - done(); - }); - }); - - it('get any any none later', function(done){ - gun.get('full/none/later').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("*****", data); - expect(data).to.be(undefined); - }); - setTimeout(function(){ - gun.get('full/none/later').get(function(at, ev){ - var err = at.err, data = at.put, field = at.get; - //console.log("*****2", data); - expect(data).to.be(undefined); - done(); - }); - },400); - });return; - - it('get get any parallel', function(done){ - var s = Gun.state.map();s.soul = 'parallel/get/get'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!" - } - }, s)}); - gun.get('parallel/get/get').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 1", data); - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - }); - gun.get('parallel/get/get').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 2", data); - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - done(); - }); - }); - - it('get get any parallel later', function(done){ - var s = Gun.state.map();s.soul = 'parallel/get/get/later'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!" - } - }, s)}); - gun.get('parallel/get/get/later').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 1", data); - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - }); - setTimeout(function(){ - gun.get('parallel/get/get/later').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 2", data); - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - done(); - }); - },400); - }); - - it('get get any none', function(done){ - var s = Gun.state.map();s.soul = 'get/get/none'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 31, - name: "alice" - } - }, s)}); - var c = 0, s = 0; - gun.get('get/get/none').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 1", data); - c++; - s = 0; - expect(data).to.be(undefined); - }); - s = 1; - gun.get('get/get/none').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 2", data); - c++; - //if(s){ c++ } // TODO: Talk to Jesse about this. - expect(data).to.be(undefined); - if(2 === c){ // We want 2 replies for each `any`, once from LS replying with the soul (but not the field), and once from WSP replying that the soul couldn't be found. - // Wrong! I think we've changed this, such that lS handles it alone and not WSP. - done(); - } - }); - }); - - it('get get any none later', function(done){ - var s = Gun.state.map();s.soul = 'get/get/none/later'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - alice: { - age: 31, - name: "alice" - } - }, s)}); - var c = 0; - gun.get('get/get/none/later').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 1", data); - c++; - expect(data).to.be(undefined); - }); - setTimeout(function(){ - gun.get('get/get/none/later').path('bob').any(function(err, data, field, at, ev){ - //console.log("***** 2", data); - c++; - expect(data).to.be(undefined); - //if(3 === c){ // Because we already have active listeners cached waiting for data to pipe in, BUT we have already received multiple responses that the data isn't found, the "not found" is cached and so we get an immediate response just from cache. If later data does get piped in, this will still get called. - done(); - //} - }); - },400); - }); - - it('get get primitive get any', function(done){ - var s = Gun.state.map();s.soul = 'get/get/prim'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: "is awesome" - }, s)}); - gun.get('get/get/prim').path('bob').path('age').any(function(err, data, field, at, ev){ - //console.log("***** 1", data); - expect(data).to.be(undefined); - }); - gun.get('get/get/prim').path('bob').path('age').any(function(err, data, field, at, ev){ - //console.log("***** 2", data); - expect(data).to.be(undefined); - done(); - }); - }); - - it('get put any', function(done){ - var s = Gun.state.map();s.soul = 'get/put/any'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - here: "we go" - }, s)}); - console.debug.i=1;console.log("---------------"); - gun.get('get/put/any') - .put({}) - .any(function(err, data, field, at, ev){ - console.log("***** 1", data); - done(); - }); - }); - return; - it('get any, get put any', function(done){ - var s = Gun.state.map();s.soul = 'get/any/get/put/any'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - here: "we go" - }, s)}); - gun.get('get/any/get/put/any') - .any(function(err, data, field, at, ev){ - if(done.first){ return } // it is okay for `any` to get called multiple times. - //console.log("***** 1", data); - expect(data.here).to.be('we go'); - if(done.first){ - expect(data.yes).to.be('please'); - } - done.first=1; - }); - setTimeout(function(){ - gun.get('get/any/get/put/any') - .put({yes: 'please'}) - .any(function(err, data, field, at, ev){ - if(done.second){ return } // it is okay for `any` to get called multiple times. - //console.log("***** 2", data); - expect(data.here).to.be('we go'); - expect(data.yes).to.be('please'); - done(); - done.second = 1; - }); - },400); - }); - - it('mutate pointer to primitive deep on', function(done){ - var s = Gun.state.map();s.soul = 'change/pointer'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - gun.get('change/pointer').path('bob').path('pet').any(function(err, data, f, at, ev){ - //console.log("***", data);return setTimeout(function(){asdf},500); - if(done.c){ - expect(data).to.be(undefined); - return; - } - expect(data.name).to.be('Fluffy'); - expect(data.species).to.be('kitty'); - done.c = 1; - }); - setTimeout(function(){ - gun.get('change/pointer').put({ - bob: null - }); - setTimeout(function(){ - gun.get('change/pointer').put({ - bob: "hello!" - }); - },400); - },400); - gun.get('change/pointer').any(function(err, data){ - //console.log("****************", data); - if(2 <= done.e && data.bob){ - expect(data.bob).to.be('hello!'); - done(); - return; - } - if(1 <= done.e){ - expect(data.bob).to.be(null); - done.e = 2; - return; - } - expect(Gun.val.rel.is(data.bob)).to.be.ok(); - done.e = 1; - }); - }); - - it('get only soul', function(done){ - var s = Gun.state.map();s.soul = 'only/soul'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - gun.get('only/soul')/*.path('bob')*/.any(function(err, data){ - expect(Gun.obj.empty(data, '_')).to.be.ok(); - done(); - }, {'.': null}); - }); - - it('get path only soul', function(done){ - var s = Gun.state.map();s.soul = 'only/p/soul'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - gun.get('only/p/soul').path('bob').any(function(err, data){ - //console.log("*********", err, data); - expect(Gun.val.rel.is(data)).to.be.ok(); - //expect(Gun.obj.empty(data, '_')).to.be.ok(); - done(); - }, {'.': null}); - }); - - it('mutate pointer to self', function(done){ - var s = Gun.state.map();s.soul = 'change/pointer/point'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - gun.get('change/pointer/point').path('bob').any(function(err, data){ - if(done.c){ - expect(data.age).to.be(30); - expect(data.name).to.be('Bob!'); - expect(Gun.val.rel.is(data.pet)).to.be.ok(); - expect(done.c).to.be(1); - done(); - done.c = 2; - return; - } - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - expect(Gun.val.rel.is(data.pet)).to.be.ok(); - done.c=1; - }); - setTimeout(function(){ - gun.get('change/pointer/point').path('bob').put({age: 30}); - },400); - }); - it('mutate pointer to self deep', function(done){ - var s = Gun.state.map();s.soul = 'change/pointer/point/deep'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - gun.get('change/pointer/point/deep').path('bob').any(function(err, data){ - //console.log("***", data); - if(done.c){ - expect(data.age).to.be(30); - expect(data.name).to.be('Bob!'); - expect(Gun.val.rel.is(data.pet)).to.be.ok(); - done(); - return; - } - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - expect(Gun.val.rel.is(data.pet)).to.be.ok(); - done.c=1; - }); - setTimeout(function(){ - gun.get('change/pointer/point/deep').path('bob').path('age').put(30); - },400); - }); - - it('mutate pointer to primitive after any', function(done){ - var s = Gun.state.map();s.soul = 'change/pointer/to/prime'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: {_: {'#': 'asdffdsa'}, - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - var bob = gun.get('asdffdsa').any(function(err, data){ - //console.log("***", data); - }); - gun.get('change/pointer/to/prime').path('bob').any(function(err, data, f, at){ - //console.log("***********", data); - if(!Gun.obj.is(data)){ - expect(data).to.be(3); - if(done.c){return} - done();done.c=1; - return; - } - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - expect(Gun.val.rel.is(data.pet)).to.be.ok(); - }); - setTimeout(function(){ - gun.get('change/pointer/to/prime').path('bob').put(3); - setTimeout(function(){ - bob.put({age: 30}); - },100); - },400); - }); - - it('mutate pointer to primitive after any deep', function(done){ - var s = Gun.state.map();s.soul = 'change/pointer/to/prime/deep'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: { - age: 29, - name: "Bob!", - pet: {_: {'#': 'sadffads'}, - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - var cat = gun.get('sadffads').any(function(err, data){ - //console.log("***", data); - }); - gun.get('change/pointer/to/prime/deep').path('bob').path('pet').any(function(err, data){ - //console.log("*********", data); - if(!Gun.obj.is(data)){ - expect(data).to.be(undefined); - if(done.c){return} - done();done.c=1; - return; - } - expect(data.species).to.be('kitty'); - expect(data.name).to.be('Fluffy'); - }); - setTimeout(function(){ - gun.get('change/pointer/to/prime/deep').path('bob').put(3); - setTimeout(function(){ - cat.put({laser_eyes: true}); - },100); - },400); - }); - return; - it.only('mutate pointer to another pointer after any', function(done){ - var s = Gun.state.map();s.soul = 'change/pointer/to/pointer'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({ - bob: {_: {'#': 'dafssfad'}, - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }, s)}); - var bob = gun.get('dafssfad').any(function(err, data){ - console.log("***", data); - }); - console.debug.i=1;console.log("--------------------"); - gun.get('change/pointer/to/pointer').path('bob').any(function(err, data){ - console.log("*********", data);return; - if(done.soul && done.soul !== Gun.node.soul(data)){ - expect(Gun.node.soul(data)).to.be('fsdaadsf'); - expect(data.cat).to.be(true); - expect(data.age).to.not.be.ok(); - expect(data.name).to.not.be.ok(); - expect(data.pet).to.not.be.ok(); - if(done.c){return} - done();done.c=1; - return; - } - expect(done.soul = Gun.node.soul(data)).to.be('dafssfad'); - expect(data.age).to.be(29); - expect(data.name).to.be('Bob!'); - expect(Gun.val.rel.is(data.pet)).to.be.ok(); - }); - return; - setTimeout(function(){ - gun.get('change/pointer/to/pointer').path('bob').put(Gun.node.ify({cat: true}, 'fsdaadsf')); - setTimeout(function(){ - bob.put({age: 30}); - },100); - },400); - }); - return; - it.only('deep freeze put', function(done){ - gun.get('deep/freeze').put({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }); - gun.get('deep/freeze').path('bob').path('pet').any(function(err, data){ - console.log("********************", data); - expect(data.name).to.be('Fluffy'); - expect(data.species).to.be('kitty'); - setTimeout(function(){ - done(); - },100); - }); - }); - - it('deep freezing put', function(done){ - gun.get('deep/freeze/ing').put({ - bob: { - age: 29, - name: "Bob!", - pet: { - name: "Fluffy", - species: "kitty" - } - } - }); - gun.get('deep/freeze/ing').path('bob').path('pet').any(function(err, data){ - //console.log("******** bob's pet", data); - if(done.c){ - expect(data).to.be(undefined); - return; - } - expect(data.name).to.be('Fluffy'); - expect(data.species).to.be('kitty'); - done.c=1; - }); - gun.get('deep/freeze/ing').put({bob: 'lol'}); - gun.get('deep/freeze/ing').path('bob').path('pet').any(function(err, data){ - //console.log("********* 2 bob's pet", data);return; - expect(data).to.be(undefined); - done(); - }); - }); - return; - it('put put put put', function(){ - var gun = Gun(); - var get = gun.get('put/put/put/put'); - get.put({}); - get.put({ - all: { - the: { - way: 'down' - } - } - }); - get.put({foo: 'bar'}); - get.any(function(err,data){ - //console.log("data", data); - expect(Gun.val.rel.is(data.all)).to.be.ok(); - expect(data.foo).to.be('bar'); - }); - }); - - it('perf put', function(done){ - var gun = Gun(); - var hey = gun.get('heylo'); - hey.put({hello: "world"}); - hey.any(function(err, data){ - expect(data.hello).to.be('world'); - done(); - }); - }); - - it('put', function(done){ - gun.put("hello", function(err, ok){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('put NaN', function(done){ - gun.put({num: NaN}, function(err, ok){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('put date', function(done){ - gun.put({date: new Date()}, function(err, ok){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('put regex', function(done){ - gun.put({reg: /regex/i}, function(err, ok){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('put node', function(done){ - gun.put({hello: "world"}, function(err, ok){ - expect(err).to.not.be.ok(); - done(); - }); - }); - - it('put node then value', function(done){ - var ref = gun.put({hello: "world"}); - //console.log("---------"); - ref.put('hello', function(err, ok){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('put node then put', function(done){ - gun.put({hello: "world"}).put({goodbye: "world"}, function(err, ok){ - expect(err).to.not.be.ok(); - done(); - }); - }); - - it('put node with soul get soul', function(done){ - gun.put({_: {'#': 'foo'}, hello: 'world'}) - .get({'#': 'foo'}, function(err, node){ - expect(err).to.not.be.ok(); - expect(Gun.node.soul(node)).to.be('foo'); - expect(node.hello).to.be('world'); - if(done.c){ return } - done(); done.c = 1; - }) - }); - - it('put node with soul get soul tweak', function(done){ - Gun().put({_: {'#': 'foo'}, hello: 'world'}); - setTimeout(function(){ - var gun = Gun(); - gun.put({_: {'#': 'foo'}, boo: 'bear'}) - .get({'#': 'foo'}, function(err, node){ - if(done.c >= 1){ return } - //console.log("**********", err, node); - expect(Gun.node.soul(node)).to.be('foo'); - expect(err).to.not.be.ok(); - expect(node.boo).to.be('bear'); - //if(!done.c){ return done.c = 1 } done.c = 2; - //expect(node.hello).to.be('world'); - done(); done.c = 2; - }) - },100); - }); - - it('put node key get', function(done){ - gun.put({hello: "key"}).key('yes/key', function(err, ok){ - //console.log("***", err, ok); - expect(err).to.not.be.ok(); - done.w = 1; if(done.c){ return } if(done.r){ done(); done.c = 1 }; - }).get('yes/key', function(err, node){ - //console.log("*******", err, node); - expect(err).to.not.be.ok(); - expect(Gun.node.soul(node)).to.be('yes/key'); - expect(node.hello).to.be('key'); - done.r = 1; if(done.c){ return } if(done.w){ done(); done.c = 1 }; - }); - }); - - it('put node key gun get', function(done){ - gun.put({hello: "a key"}).key('yes/a/key', function(err, ok){ - expect(err).to.not.be.ok(); - }); - gun.get('yes/a/key', function(err, node){ - expect(err).to.not.be.ok(); - expect(node.hello).to.be('a key'); - if(done.c){ return } - done(); done.c = 1; - }); - }); - - it('gun key', function(){ // Revisit this behavior? - try{ gun.key('fail/key') } - catch(err){ - expect(err).to.be.ok(); - } - }); - - it('get key no override', function(done){ - var gun = Gun(); - gun.put({cream: 'pie'}).key('cream/pie').get('cream/pie', function(err, node){ - expect(Gun.node.soul(node)).to.be('cream/pie'); - if(done.c){ return } - if(node.cream && node.pie){ - expect(node.cream).to.be('pie'); - expect(node.pie).to.be('cream'); - done(); done.c = 1; - } return; - if(done.c >= 2){ return } - if(done.c){ done(); done.c = 2; return; } done.c = 1; - }); - gun.get('cream/pie').key('pie/cream'); - gun.get('pie/cream').put({pie: 'cream'}); - }); - - it('get key', function(done){ - gun.get('yes/key', function(err, node){ - expect(err).to.not.be.ok(); - expect(node.hello).to.be('key'); - }).key('hello/key', function(err, ok){ - expect(err).to.not.be.ok(); - done.key = true; - if(!done.c && done.yes){ done();done.c=1; } - }).key('yes/hello', function(err, ok){ - expect(err).to.not.be.ok(); - done.yes = true; - if(!done.c && done.key){ done();done.c=1; } - }); - }); - - it('get key null', function(done){ - gun.get('yes/key').key('', function(err, ok){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('key node has no key relations', function(done){ - var gun = Gun(); - gun.put({hello: 'world'}).key('hello/earth'); - gun.put({continent: 'africa'}).key('hello/earth'); - gun.put({place: 'asia'}).key('hello/earth'); - gun.put({north: 'america'}).key('hello/galaxy'); - gun.put({south: 'pole'}).key('hello/galaxy'); - gun.get('hello/earth').key('hello/galaxy', function(err, ok){ - expect(err).to.not.be.ok(); - }); - var node = gun.Back(-1)._.graph['hello/earth'] || {}; // TODO: IS THIS CORRECT? - expect(node['hello/galaxy']).to.not.be.ok(); - gun.get('hello/earth', function(err, pseudo){ - expect(pseudo.hello).to.be('world'); - expect(pseudo.continent).to.be('africa'); - expect(pseudo.place).to.be('asia'); - expect(pseudo.north).to.not.be.ok(); - }); - var galaxy = gun.Back(-1)._.graph['hello/galaxy'] || {}; // TODO: IS THIS CORRECT? - expect(galaxy['hello/earth']).to.not.be.ok(); - gun.get('hello/galaxy', function(err, pseudo){ - if(done.c || !pseudo.hello || !pseudo.south || !pseudo.place || !pseudo.continent || !pseudo.north){ return } - expect(pseudo.hello).to.be('world'); - expect(pseudo.south).to.be('pole'); - expect(pseudo.place).to.be('asia'); - expect(pseudo.continent).to.be('africa'); - expect(pseudo.north).to.be('america'); - expect(pseudo['hello/earth']).to.not.be.ok(); - expect(pseudo['#hello/earth#']).to.not.be.ok(); - done(); done.c = 1; - }); - }); - - function soulnode(gun, kn, r){ // TODO: WARNING! Key implementation has changed significantly. Tests are somewhat hardcoded, sad day. - r = r || []; - kn = Gun.obj.copy(kn); - delete kn._; - expect(Gun.obj.empty(kn, '##')).to.be.ok(); - kn = gun.Back(-1)._.graph[Gun.val.rel.is(kn['##'])]; - Gun.node.is(kn, function(node, s){ - var n = gun.Back(-1)._.graph[s]; - if(Gun.obj.has(n, '##')){ - soulnode(gun, n, r); - return; - } - r.push(s); - }); - return r; - } - - it('get node put node merge', function(done){ - gun.get('hello/key', function(err, node){ - if(done.soul){ return } - expect(err).to.not.be.ok(); - expect(node.hello).to.be('key'); - done.soul = Gun.node.soul(node); - }).put({hi: 'you'}, function(err, ok){ - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph[done.soul], soul; - expect(keynode.hi).to.not.be.ok(); - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - var node = gun.Back(-1)._.graph[soul]; - expect(node.hello).to.be('key'); - expect(node.hi).to.be('you'); - }).on(function(node){ - if(done.c){ return } - //expect(done.soul).to.be(Gun.node.soul(node)); // TODO: DISCUSSION! This has changed? - expect(node.hi).to.be('you'); - expect(node.hello).to.be('key'); - done(); done.c = 1; - }); - }); - - it('get null put node never', function(done){ // TODO: GET returns nothing, and then doing a PUT? - gun.get(null, function(err, ok){ - expect(err).to.be.ok(); - done.err = true; - }).put({hi: 'you'}, function(err, ok){ - done.flag = true; - }); - setTimeout(function(){ - expect(done.err).to.be.ok(); - expect(done.flag).to.not.be.ok(); - done(); - }, 500); - }); - - it('get key no data put', function(done){ - var gun = Gun({init: true}); - gun.get('this/key/definitely/does/not/exist', function(err, data){ - done.gcb = true; - done.err = err; - expect(err).to.not.be.ok(); - expect(data).to.not.be.ok(); - }).put({testing: 'stuff'}, function(err, ok){ - done.flag = true; - }); - setTimeout(function(){ - expect(done.gcb).to.be.ok(); - expect(done.err).to.not.be.ok(); - expect(done.flag).to.not.be.ok(); - done(); - }, 500); - }); - - it('get node put node merge conflict', function(done){ - gun.get('hello/key', function(err, node){ - if(done.soul){ return } - expect(err).to.not.be.ok(); - expect(node.hello).to.be('key'); - expect(node.hi).to.be('you'); - done.soul = Gun.node.soul(node); - }).put({hi: 'overwritten'}, function(err, ok){ - if(done.c){ return } - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph[done.soul], soul; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - var node = gun.Back(-1)._.graph[soul]; - expect(node.hello).to.be('key'); - expect(node.hi).to.be('overwritten'); - done.w = 1; if(done.r){ done(); done.c = 1 }; - }).on(function(node){ - if(done.c){ return } - //expect(done.soul).to.be(Gun.node.soul(node)); // since put has changed chains, do we keep the pseudomerge key context? - expect(node.hello).to.be('key'); - expect(node.hi).to.be('overwritten'); - done.r = 1; if(done.w){ done(); done.c = 1 }; - }); - }); - - it('get key path put', function(done){ - var gun = Gun().put({foo:'lol', extra: 'yes'}).key('key/path/put'); - var data = gun.get('key/path/put'); - data.path('foo').put('epic'); - data.val(function(val, field){ - expect(val.foo).to.be('epic'); - expect(Gun.node.soul(val)).to.be('key/path/put'); - done(); - }); - }); - - it('put node path', function(done){ - var gun = Gun(); - gun.put({hello: 'world'}).path('hello', function(err, val, field){ - if(done.end){ return } // it is okay for path's callback to be called multiple times. - expect(err).to.not.be.ok(); - expect(field).to.be('hello'); - expect(val).to.be('world'); - done(); done.end = true; - }); - }); - - it('put node path path', function(done){ - var gun = Gun(); - //console.debug.i=1;console.log("-----------------"); - var g = gun.put({hello: {little: 'world'}}).path('hello').path('little', function(err, val, field, cat){ - if(done.end){ return } // it is okay for path's callback to be called multiple times. - expect(err).to.not.be.ok(); - expect(field).to.be('little'); - expect(val).to.be('world'); - done(); done.end = true; - }); - }); - - it('put node path rel', function(done){ - gun.put({foo: {bar: 'lol'}}).path('foo', function(err, val, field){ - //console.log("*****", err, val, field); - if(done.end){ return } // it is okay for path's callback to be called multiple times. - expect(err).to.not.be.ok(); - expect(field).to.be('foo'); - expect(val.bar).to.be('lol'); - done(); done.end = true; - }); - }); - - it('get node path', function(done){ - gun.get('hello/key').path('hi', function(err, val, field){ - if(done.end){ return } // it is okay for path's callback to be called multiple times. - expect(err).to.not.be.ok(); - expect(field).to.be('hi'); - expect(val).to.be('overwritten'); - done(); done.end = true; - }); - }); - - it('put node get field', function(done){ // future feature. - var gun = Gun(); - gun.put({_:{'#': 'soul/field'}, hi: 'lol', foo: 'bar'});//.key('key/field'); - gun.get({'#': 'soul/field', '.': 'hi'}, function(err, val){ - //expect(val.hi).to.be('lol'); // TODO: REVISE API? - expect(val).to.be('lol'); - //expect(Gun.obj.has(val,'foo')).to.not.be.ok(); - done(); - }) - }); - - it('get node path put value', function(done){ - gun.get('hello/key', function(err, node){ - expect(err).to.not.be.ok(); - if(done.soul){ return } - expect(node.hi).to.be('overwritten'); - done.soul = Gun.node.soul(node); - }).path('hi').put('again', function(err, ok){ - if(done.c){ return } - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph[done.soul], soul; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - var node = gun.Back(-1)._.graph[done.sub = soul]; - expect(node.hello).to.be('key'); - expect(node.hi).to.be('again'); - done.w = 1; if(done.r){ done(); done.c = 1 }; - }).on(function(val, field){ - if(done.c){ return } - expect(val).to.be('again'); - expect(field).to.be('hi'); - done.r = 1; if(done.w){ done(); done.c = 1 }; - }); - }); - - it('get node path put object', function(done){ - var foo = gun.get('hello/key', function(err, node){ - if(done.soul){ return } - expect(err).to.not.be.ok(); - expect(node.hi).to.be('again'); - expect(node.hello).to.be('key'); - done.soul = Gun.node.soul(node); - }).path('hi').put({yay: "value"}, function(err, ok){ - if(done.c){ return } - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph[done.soul], soul; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - var root = gun.Back(-1)._.graph[soul]; - expect(root.hello).to.be('key'); - expect(root.yay).to.not.be.ok(); - expect(Gun.val.rel.is(root.hi)).to.be.ok(); - expect(Gun.val.rel.is(root.hi)).to.not.be(soul); - var node = gun.Back(-1)._.graph[Gun.val.rel.is(root.hi)]; - expect(node.yay).to.be('value'); - if(done.sub){ expect(done.sub).to.be(Gun.val.rel.is(root.hi)) } - else { done.sub = Gun.val.rel.is(root.hi) } - done.w = 1; if(done.r){ done(); done.c = 1 }; - }).on(function(node, field){ - if(done.c){ return } - expect(field).to.be('hi'); - expect(node.yay).to.be('value'); - if(done.sub){ expect(done.sub).to.be(Gun.node.soul(node)) } - else { done.sub = Gun.node.soul(node) } - done.r = 1; if(done.w){ done(); done.c = 1 }; - }); - }); - - it('get path wire', function(done){ - var gun = Gun(); - var get = gun.get('shallow/path'); - var path = get.path('one'); - var put = path.put('good'); - put.val(function(val, field){ - expect(val).to.be('good'); - expect(field).to.be('one'); - done(); - }); - }); - - it('get path wire shallow', function(done){ - var gun = Gun(); - var get = gun.get('slightly/shallow/path'); - var path = get.path('one'); - var put = path.put({you: 'are', here: 1}); - put.val(function(val, field){ - //console.log('***********', field, val); - expect(val.you).to.be('are'); - expect(val.here).to.be(1); - expect(field).to.be('one'); - done(); - }); - }); - - it('get put, Gun get path', function(done){ // For testing lazy eval that it works on cb style. - var gun = Gun(); - gun.get('test').put({you: {are: 'cool'}}); - // TODO: BUG!? Occasionally has a stack overflow???? :/ - setTimeout(function(){ - var g = Gun(); // TODO: NOTE! This will not work for in-memory only. This means it might not be viable as a test for core. - g.get('test').path('you', function(e,d){ - if(!d || done.c){ return } - expect(d.are).to.be('cool'); - done.c = true; - setTimeout(function(){ - done(); - },10); - }); - },250); - }); - - it('get put, Gun get path to path', function(done){ // For testing lazy eval that it works on cb style. - var gun = Gun(); - gun.get('test1').put({you: {are: 'cool'}}); - setTimeout(function(){ - var g = Gun(); // TODO: NOTE! This will not work for in-memory only. This means it might not be viable as a test for core. - var p = g.get('test1').path('you'); - setTimeout(function(){ - p.path('are', function(e,d){ - if(!d || done.c){ return } - expect(d).to.be('cool'); - done();done.c = true; - }); - },100); - - },100) - }); - - it('get put, Gun get path path', function(done){ // For testing lazy eval that it works on cb style. - var gun = Gun(); - gun.get('test2').put({you: {are: 'cool'}}); - setTimeout(function(){ - var g = Gun(); // TODO: NOTE! This will not work for in-memory only. This means it might not be viable as a test for core. - var p = g.get('test2').path('you').path('are', function(e,d){ - if(!d || done.c){ return } - expect(d).to.be('cool'); - done();done.c=true; - }); - },100); - }); - - it('any any not', function(done){ - var s = Gun.state.map(); - s.soul = 'a'; - Gun.on('put', {gun: gun, put: Gun.graph.ify({b: 1, c: 2}, s)}); - function cb(e,d,f,a){ - if('b' === f && 1 === d){ - done.b = true; - } - if('c' === f && 2 === d){ - done.c = true; - } - if('d' === f && !d){ - done.d = true; - } - if(done.done){ return } - if(done.b && done.c && done.d){ - done.done = true; - done(); - } - } - gun.get('a').path('b').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('a').path('c').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('a').path('d').any(cb);//.err(cb).not(cb).on(cb).val(cb); - }); - - it('any not any not any not', function(done){ - function cb(e,d,f,a){ - if('b' === f && !d){ - done.b = true; - } - if('c' === f && !d){ - done.c = true; - } - if('d' === f && !d){ - done.d = true; - } - if(done.b && done.c && done.d){ - done(); - } - } - gun.get('x').path('b').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('x').path('c').any(cb);//.err(cb).not(cb).on(cb).val(cb); - gun.get('x').path('d').any(cb);//.err(cb).not(cb).on(cb).val(cb); - }); - - it('get put, put deep', function(done){ - var gun = Gun(); - var get = gun.get('put/deep/ish'); - get.put({}); - get.val(function(data){ // TODO: API CHANGE! Empty objects should react. - //console.log("...1", data); - expect(Gun.obj.empty(data, '_')).to.be.ok(); // API CHANGED, - //expect(Gun.val.rel.is(data.very)).to.be.ok(); - });//, {wait: 10000}); - setTimeout(function(){ - var put = get.put({ - very: { - deep: { - ly: { - oriented: true - } - } - } - }); - get.val(function(data){ - //console.log("...2", data); - expect(Gun.val.rel.is(data.very)).to.be.ok(); - }); - setTimeout(function(){ - put.val(function(data){ - //console.log("...3", data); - expect(Gun.val.rel.is(data.very)).to.be.ok(); - done.val = true; - }); - var p = put.path('very'); - p.put({we: 'have gone!'}); - setTimeout(function(){ - p.val(function(data){ - //console.log("...4", data); - expect(data.we).to.be('have gone!'); - expect(Gun.val.rel.is(data.deep)).to.be.ok(); - }); - p.put('EXPLODE'); - setTimeout(function(){ - expect(done.val).to.be.ok(); - done(); - },5); - },150); - },250); - },110); - }); - - it('get path wire shallow swoop', function(done){ - var gun = Gun(); - var get = gun.get('slightly/shallow/path/swoop'); - var path = get.path('one.two'); - var put = path.put({oh: 'okay'}); - put.val(function(val, field){ - //console.log("****", field, val); - expect(val.oh).to.be('okay'); - expect(field).to.be('two'); - done(); - }); - }); - - it('get path wiring', function(done){ - var gun = Gun(); - var get = gun.get('deep/path'); - var path = get.path('one.two'); - var path3 = path.path('three'); - var put = path3.put({you: 'found', the: 'bottom!'}); - put.val(function(val, field){ - //console.log("********1********", field, val); - expect(val.you).to.be('found'); - expect(val.the).to.be('bottom!'); - expect(field).to.be('three'); - }); - gun.get('deep/path').path('one.two.three.you').put('are').val(function(val, field){ - //console.log("********2*********", field, val);return; - expect(val).to.be('are'); - expect(field).to.be('you'); - done(); - }); - }); - - it('get node path put object merge isolated', function(done){ - // MORAL OF THE STORY: in KEY ON.GET check for change as NODE = AT.CHANGE || GUN.__.GRAPH[AT.soul] && Gun.node.soul(NODE, 'KEY') === 1; BAM! - var gun = Gun(); - var put = gun.put({hello: 'key'}).key('hello/key/iso') - var get = gun.get('hello/key/iso'); - var puthi = get.put({hi: 'you'}); - puthi.on(function(node){ - if(done.hi){ return } - //console.log(1, node); - expect(node.hello).to.be('key'); - expect(node.hi).to.be('you'); - done.hi = 1; - }); - setTimeout(function(){ - var get2 = gun.get('hello/key/iso'); - var path2 = get2.path('hi'); - path2._.id = 'path2'; - var putyay = path2.put({yay: "value"}); - putyay.on(function(node, field){ - if(done.yay){ return } - expect(field).to.be('hi'); - expect(node.yay).to.be('value'); - done.yay = true; - }); - setTimeout(function(){ - var get3 = gun.get('hello/key/iso'); - var path3 = get3.path('hi'); - path3._.id = 'path3'; - var puthappy = path3.put({happy: "faces"}); - puthappy.on(function(node, field){ - //console.log(3, field, node); - expect(field).to.be('hi'); - expect(node.happy).to.be('faces'); - expect(node.yay).to.be('value'); - setTimeout(function(){ - console.log("******************************"); - done(); - },200); - }); - },100); - },100); - }); - - it('get node path put object merge', function(done){ - var g = gun.get('hello/key', function(err, node){ - if(done.soul){ return } - expect(err).to.not.be.ok(); - expect(done.ref = Gun.val.rel.is(node.hi)).to.be.ok(); - done.soul = Gun.node.soul(node); - }); - g.path('hi').put({happy: "faces"}, function(err, ok){ - if(done.c){ return } - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph[done.soul], soul; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - var root = gun.Back(-1)._.graph[soul]; - var sub = gun.Back(-1)._.graph[done.ref]; - expect(root.hello).to.be('key'); - expect(root.yay).to.not.be.ok(); - expect(Gun.node.soul(sub)).to.be(done.ref); - expect(sub.yay).to.be('value'); - expect(sub.happy).to.be('faces'); - if(done.sub){ expect(done.sub).to.be(done.ref) } - else { done.sub = done.ref } - done.w = 1; if(done.r){ done(); done.c = 1 }; - }).on(function(node, field){ - if(done.c){ return } - expect(field).to.be('hi'); - expect(node.happy).to.be('faces'); - expect(node.yay).to.be('value'); - if(done.sub){ expect(done.sub).to.be(Gun.node.soul(node)) } - else { done.sub = Gun.node.soul(node) } - done.r = 1; if(done.w){ done(); done.c = 1 }; - }); - }); - - it('get node path put value conflict relation', function(done){ - gun.get('hello/key', function(err, node){ - if(done.soul){ return } - expect(err).to.not.be.ok(); - expect(done.ref = Gun.val.rel.is(node.hi)).to.be.ok(); - done.soul = Gun.node.soul(node); - }).path('hi').put('crushed', function(err, ok){ - if(done.c){ return } - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph[done.soul], soul; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - var root = gun.Back(-1)._.graph[soul]; - var sub = gun.Back(-1)._.graph[done.ref]; - expect(root.hello).to.be('key'); - expect(root.yay).to.not.be.ok(); - expect(Gun.node.soul(sub)).to.be(done.ref); - expect(sub.yay).to.be('value'); - expect(sub.happy).to.be('faces'); - expect(root.hi).to.be('crushed'); - done.w = 1; if(done.r){ done(); done.c = 1 }; - }).on(function(val, field){ - if(done.c){ return } - expect(field).to.be('hi'); - expect(val).to.be('crushed'); - done.r = 1; if(done.w){ done(); done.c = 1 }; - }); - }); - - it.skip('put gun node', function(done){ - var mark = gun.put({age: 23, name: "Mark Nadal"}); - var amber = gun.put({age: 23, name: "Amber Nadal"}); - mark.path('wife').put(amber, function(err){ - expect(err).to.not.be.ok(); - }); - mark.path('wife.name').val(function(val){ - expect(val).to.be("Amber Nadal"); - }); - }); - - it('put val', function(done){ - gun.put({hello: "world"}).val(function(val){ - expect(val.hello).to.be('world'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - it('put key val', function(done){ - gun.put({hello: "world"}).key('hello/world').val(function(val, field){ - if(done.c){ return } - expect(val.hello).to.be('world'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - it('get val', function(done){ - gun.get('hello/world').val(function(val, field){ - expect(val.hello).to.be('world'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - it('get path', function(done){ - gun.get('hello/world').path('hello').val(function(val){ - //console.log("**************", val); - expect(val).to.be('world'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 1900); - }); - - it('get put path', function(done){ - gun.get('hello/world').put({hello: 'Mark'}).path('hello').val(function(val, field){ - expect(val).to.be('Mark'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - it('get path put', function(done){ - gun.get('hello/world').path('hello').put('World').val(function(val){ - expect(val).to.be('World'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - it('get empty put', function(done){ - var gun = Gun({init: true}); - gun.get('nothing/here').put({far: "wide"}, function(err, ok){ - done.put = true; - }); - gun.get({'#': 'asdfoobar'}).put({far: "wide"}, function(err, ok){ - done.put2 = true; - }); - setTimeout(function(){ - expect(done.put).to.not.be.ok(); - expect(done.put2).to.not.be.ok(); - done(); - }, 100) - }); - - it('get path empty put val', function(done){ - var gun = Gun({init: true}).put({hello: "Mark"}).key('hello/world/not'); - gun.get('hello/world/not').path('earth').put('mars').val(function(val){ - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.not.be.ok(); // CHANGELOG: API 0.3 BREAKING CHANGE, .put is suppose to be dependent on the previous chain, which means it SHOULD NOT PUT on an empty path. - done(); - }, 100); - }); - - it('get empty put val implicit', function(done){ - var gun = Gun(); - var get = gun.get('hello/imp/world'); - var put = get.put({planet: 'the earth'}); - put.val(function(val){ - expect(val.planet).to.be('the earth'); - done(); - }); - }); - - it('get empty path put val implicit split', function(done){ - var gun = Gun(); - var get = gun.get('hello/imp/where'); - var path = get.path('where'); - var put = path.put('the mars'); - var val = put.val(function(val, field){ - expect(field).to.be('where'); - expect(val).to.be('the mars'); - done(); - }); - }); - - it('get path empty put val implicit', function(done){ - gun.get('hello/world').path('earth').put('mars').val(function(val, field){ - expect(val).to.be('mars'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - it('get path val', function(done){ - var gun = Gun({init: true}).put({hello: "Mark"}).key('hello/world/not'); - gun.get('hello/world').path('earth').put('mars'); - gun.get('hello/world/not').path('earth').val(function(val){ - expect(val).to.be('mars'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.not.be.ok(); - done(); - }, 100); - }); - - it('get path val implicit', function(done){ - gun.get('hello/world').path('earth').val(function(val){ - expect(val).to.be('mars'); - expect(done.c).to.not.be.ok(); - done.c = 1; - }); - setTimeout(function(){ - expect(done.c).to.be.ok(); - done(); - }, 100); - }); - - describe('some nots', function(){ - it('get not kick val', function(done){ - gun.get("some/empty/thing").not(function(key, kick){ // that if you call not first - this.put({now: 'exists'}).key(key); // you can put stuff - }).val(function(val){ // and THEN still retrieve it. - expect(val.now).to.be('exists'); - done(); - }); - }); - - it('get not kick val when it already exists', function(done){ - var foo; - foo = gun.get("some/empty/thing"); - foo.not(function(key, kick){ - done.not = true; - this.put({now: 'THIS SHOULD NOT HAPPEN'}).key(key); - }).val(function(val){ - expect(val.now).to.be('exists'); - expect(done.not).to.not.be.ok(); - done(); - }); - }); - }); - - it('put path val sub', function(done){ - gun.put({last: {some: 'object'}}).path('last').val(function(val){ - expect(val.some).to.be('object'); - done(); - }); - }); - //return; - it('chain ordering', function(done){ - var sec = gun.get('order/second'); - var res = Gun.on.stun(sec); - gun.get('order/first', function(){ // this has a race condition against the third get. However if it fulfills first... - //console.log('callback', 0); - done.zero = true; - expect(done.one).to.not.be.ok(); - expect(done.two).to.not.be.ok(); - res(function(){ - sec.any(function(){ // then this guy should be run before the third get, since it is queued first relative to this soul. - //console.log('callback', 1); - done.one = true; - expect(done.zero).to.be.ok(); - expect(done.one).to.be.ok(); - expect(done.two).to.not.be.ok(); - res(); - }); - }); - }); - - gun.get('order/second', function(){ - //console.log('callback', 2); - done.two = true; - expect(done.zero).to.be.ok(); - expect(done.one).to.be.ok(); - expect(done.two).to.be.ok(); - done(); - }); - }); - - it('get put null', function(done){ - gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ - //console.log("**", field, val); - expect(field).to.be('last'); - expect(val.some).to.be('object'); - }).put(null).val(function(val, field){ - //console.log("******", field, val); - expect(field).to.be('last'); - expect(val).to.be(null); - done(); - }); - }); - - it('Gun get put null', function(done){ // flip flop bug - var gun = Gun(); - gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ - //console.log("**", field, val); - done.some = true; - expect(val.some).to.be('object'); - }).put(null).val(function(val, field){ - //console.log("********", field, val); - expect(val).to.be(null); - expect(done.some).to.be.ok(); - done(); - }); - }); - - it('var put key path', function(done){ // contexts should be able to be saved to a variable - var foo = gun.put({foo: 'bar'}).key('foo/bar'); - foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original - setTimeout(function(){ - foo.path('foo').val(function(val){ // and then the original should be able to be reused later - expect(val).to.be('bar'); // this should work - done(); - }); - }, 500); - }); - - it('var get path', function(done){ // contexts should be able to be saved to a variable - var foo = gun.get('foo/bar'); - foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original - setTimeout(function(){ - foo.path('foo').val(function(val){ // and then the original should be able to be reused later - expect(val).to.be('bar'); // this should work - done(); - }); - }, 500); - }); - - it('get not put val path val', function(done){ - var todos = gun.get("examples/list/foobar").not(function(key){ - this.put({ - id: 'foobar', - title: 'awesome title', - todos: {} - }).key(key); - }).val(function(data){ - expect(data.id).to.be('foobar'); - //}).path('todos').val(function(todos, field){ - }).path('todos').val(function(todos, field){ - expect(field).to.be('todos'); - expect(todos).to.not.have.property('id'); - done(); - }, {empty: true}); // API CHANGED! .val fires on empty by default now. - }); - - it('put circular ref', function(done){ - var data = {}; - data[0] = "DATA!"; - data.a = {c: 'd', e: 1, f: true}; - data.b = {x: 2, y: 'z'}; - data.a.kid = data.b; - data.b.parent = data.a; - gun.put(data, function(err, ok){ - expect(err).to.not.be.ok(); - }).val(function(val){ - setTimeout(function(){ // TODO: Is this cheating? I don't think so cause we are using things outside of the API! - var a = gun.Back(-1)._.graph[Gun.val.rel.is(val.a)]; - var b = gun.Back(-1)._.graph[Gun.val.rel.is(val.b)]; - expect(Gun.val.rel.is(val.a)).to.be(Gun.node.soul(a)); - expect(Gun.val.rel.is(val.b)).to.be(Gun.node.soul(b)); - expect(Gun.val.rel.is(a.kid)).to.be(Gun.node.soul(b)); - expect(Gun.val.rel.is(b.parent)).to.be(Gun.node.soul(a)); - done(); - },10); - }); - }); - - it('gun put path and some changes node', function(done){ done.c = 0; - var ref = gun.put({ - foo: {bar: 'lol'} - }); - var sub = ref.path('foo').on(function(val){ - done.c++; - if(val){ - expect(val.extra).to.not.be.ok(); - } - if(done.c === 1){ - expect(val.bar).to.be('lol'); - ref.put({foo: 'hi'}); - return; - } - if(done.c === 2){ - expect(val).to.be('hi'); - done(); - } - }); - }); - - it('gun put two nodes, link one, path and detach', function(done){ done.c = 0; - // this test is not written yet! - var ref = gun.put({ - foo: {bar: 'lol'} - }); - var sub = ref.path('foo').on(function(val){ - done.c++; - if(val){ - expect(val.extra).to.not.be.ok(); - } - if(done.c === 1){ - expect(val.bar).to.be('lol'); - ref.put({foo: 'hi'}); - return; - } - if(done.c === 2){ - expect(val).to.be('hi'); - done(); - } - }); - // ref.put({foo: {extra: 'field'}}); - }); - - it('gun put path deep primitive', function(done){ - gun.put({ - foo: { - bar: { - lol: true - } - } - }).path('foo.bar.lol').val(function(val){ - expect(val).to.be(true); - done(); - }); - }); - - it('gun put path deep node', function(done){ - gun.put({ - foo: { - bar: { - lol: {ok: true} - } - } - }).path('foo.bar.lol').val(function(val){ - expect(val.ok).to.be(true); - done(); - }); - }); - - it('put circular deep', function(done){ - var mark = { - age: 23, - name: "Mark Nadal" - } - var amber = { - age: 23, - name: "Amber Nadal", - phd: true - } - mark.wife = amber; - amber.husband = mark; - var cat = { - age: 3, - name: "Hobbes" - } - mark.pet = cat; - amber.pet = cat; - cat.owner = mark; - cat.master = amber; - //console.debug.i=1;console.log("------------"); - gun.put(mark, function(err, ok){ - expect(err).to.not.be.ok(); - }).val(function(val){ - expect(val.age).to.be(23); - expect(val.name).to.be("Mark Nadal"); - expect(Gun.val.rel.is(val.wife)).to.be.ok(); - expect(Gun.val.rel.is(val.pet)).to.be.ok(); - }).path('wife.pet.name').val(function(val){ - //console.debug(1, "*****************", val); - expect(val).to.be('Hobbes'); - }).back.path('pet.master').val(function(val){ - //console.log("*****************", val); - expect(val.name).to.be("Amber Nadal"); - expect(val.phd).to.be.ok(); - expect(val.age).to.be(23); - expect(Gun.val.rel.is(val.pet)).to.be.ok(); - done(); - }); - }); - - it('key get', function(done){ - var gun = Gun(); - gun.get('key/get').put({yay: 'something'}).key('index/yay'); - gun.get('index/yay', function(err, node){ - expect(node.yay).to.be('something'); - if(done.c){return} - done();done.c=1; - }); - }); - - it('put partial sub merge', function(done){ - var gun = Gun(); - var mark = gun.put({name: "Mark", wife: { name: "Amber" }}).key('person/mark').val(function(mark){ - //console.log("VAL1", mark); - done.marksoul = Gun.node.soul(mark); - expect(mark.name).to.be("Mark"); - }); - mark.put({age: 23, wife: {age: 23}}); - setTimeout(function(){ - mark.put({citizen: "USA", wife: {citizen: "USA"}}).val(function(mark){ - //console.log("VAL2", mark, gun); - expect(mark.name).to.be("Mark"); - expect(mark.age).to.be(23); - expect(mark.citizen).to.be("USA"); - this.path('wife').on(function(Amber){ // TODO: turn this .on back into a .val - //console.log("VAL3", Amber); - if(done.c){ return } - expect(done.c).to.not.be.ok(); // RELATED TO BELOW #"CONTEXT NO DOUBLE EMIT". - expect(Amber.name).to.be("Amber"); - expect(Amber.age).to.be(23); - expect(Amber.citizen).to.be("USA"); - done();done.c=1; - }); - }); - }, 500); - }); - - it('path path', function(done){ - var deep = gun.put({some: {deeply: {nested: 'value'}}}); - deep.path('some.deeply.nested').val(function(val){ - expect(val).to.be('value'); - }); - deep.path('some').path('deeply').path('nested').val(function(val){ - expect(val).to.be('value'); - done(); - }); - }); - - it('context null put value val error', function(done){ - gun.put("oh yes", function(err){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('context no double emit', function(done){ // annoying problem where somehow the gun.path in a put starts subscribing and firing to its context if we let get handle emitting for the chain in put. - var c = 0; - var gun = Gun(); - var fo = gun.put({fo: 'bar'}); - Gun.log.ba = 1; - fo.put({ba: {}}).val(function(obj, field){ - c += 1; - expect(c).to.be(1); - done(); - }); - Gun.log.ba = 0; - var ba = fo.path('ba'); - ba.put({co: 'do'}); - }); - - describe('random', function(){ - var foo; - it('context null put node', function(done){ - foo = gun.put({foo: 'bar'}).val(function(obj){ - expect(obj.foo).to.be('bar'); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('context node put val', function(done){ - // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - foo.put('banana', function(err){ - expect(err).to.be.ok(); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('context node put node', function(done){ - // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - foo.put({bar: {zoo: 'who'}}).val(function(obj, field){ - //console.log("terribly terrilby unpleasant", field, obj); - expect(obj.foo).to.be('bar'); - expect(Gun.val.rel.is(obj.bar)).to.ok(); - done(); //setTimeout(function(){ done() },1); - }); - }); - - var bar; - it('context node and field of relation put node', function(done){ - // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - bar = foo.path('bar'); - expect(gleak.check()).to.not.be.ok(); - bar.put({combo: 'double'}).val(function(obj, field){ - //expect(obj.zoo).to.be('who'); - expect(obj.combo).to.be('double'); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('context node and field put value', function(done){ - // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - var tar = foo.path('tar'); - tar.put('zebra').val(function(val){ - expect(val).to.be('zebra'); - done(); //setTimeout(function(){ done() },1); - }); - }); - - it('context node and field, put node', function(done){ - // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! - bar.path('combo').put({another: 'node'}).val(function(obj){ - expect(obj.another).to.be('node'); - // double .vals here also RELATED to the #"context no double emit" but because of a faulty .not or .init system. - bar.val(function(node){ - expect(Gun.val.rel.is(node.combo)).to.be.ok(); - expect(Gun.val.rel.is(node.combo)).to.be(Gun.node.soul(obj)); - done(); //setTimeout(function(){ done() },1); - }); - }); - }); - }); - - it('val path put val', function(done){ - var gun = Gun(); - - var al = gun.put({gender:'m', age:30, name:'alfred'}).key('user/alfred'); - var beth = gun.put({gender:'f', age:22, name:'beth'}).key('user/beth'); - - al.val(function(a){ - beth.put({friend: a}, function(err, ok){ - expect(err).to.not.be.ok(); - }).path('friend').val(function(aa){ - expect(Gun.node.soul(a)).to.be(Gun.node.soul(aa)); - done(); - }); - }); - - }); - // TODO: Write a test that tests for keysoul has a key meta indicator. - // TODO: A soulsoul does not have a key meta indicator. - // TODO: Souls match their graph. - it('val path put val key', function(done){ // bug discovered from Jose's visualizer - var gun = Gun(), s = Gun.time.is(), n = function(){ return Gun.time.is() } - this.timeout(5000); - - gun.put({gender:'m', age:30, name:'alfred'}).key('user/alfred'); - gun.put({gender:'f', age:22, name:'beth' }).key('user/beth'); - //gun.get('user/beth').path('friend').put(gun.get('user/alfred')); // ideal format which we have a future test for. - gun.get('user/alfred').val(function(a){ - //console.log("*****", a); - //expect(a[Gun._.meta]['key']).to.be.ok(); - gun.get('user/beth').put({friend: a}, function(err, ok){ // b - friend_of -> a - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph['user/alfred']; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - }); - gun.get('user/beth').val(function(b){ - //console.log("beth", b); - gun.get('user/alfred').put({friend: b}).val(function(al){ // a - friend_of -> b - //console.log("al again", al); - gun.get('user/beth').put({cat: {name: "fluffy", age: 3, coat: "tabby"}}).val(function(bet){ - gun.get('user/alfred').path('friend.cat').key('the/cat'); - gun.get('the/cat').val(function(c){ - //console.log("cat!!!", c); - expect(c.name).to.be('fluffy'); - expect(c.age).to.be(3); - expect(c.coat).to.be('tabby'); - done(); - }); - }); - }); - }); - }); - }); - - it('map', function(done){ - var c = 0, set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); - set.map(function(obj, field){ - c++; - if(field === 'a'){ - expect(obj.here).to.be('you'); - } - if(field === 'b'){ - expect(obj.go).to.be('dear'); - } - if(field === 'c'){ - expect(obj.sir).to.be('!'); - } - if(c === 3){ - done(); - } - }) - }); - - it.skip('key soul', function(done){ // TODO: Deprecated? Maybe. - var gun = Gun(); - gun.key('me', function(err, ok){ - expect(err).to.not.be.ok(); - var keynode = gun.Back(-1)._.graph['me']; - var c = soulnode(gun, keynode), soul = c[0]; - expect(c.length).to.be(1); - - expect(soul).to.be('qwertyasdfzxcv'); - done(); - }, 'qwertyasdfzxcv'); - }); - - it.skip('no false positive null emit', function(done){ // TODO: THE API HAS CHANGED! REDO TEHSE! - var gun = Gun({wire: {get: function(key, cb){ - var g = {}; - g[soul] = {_: {'#': soul, '>': {'a': 0}}, - 'a': 'b' - }; - cb(null, g); - g = {}; - g[soul] = {_: {'#': soul, '>': {'c': 0}}, - 'c': 'd' - }; - cb(null, g); - g = {}; - g[soul] = {_: {'#': soul }}; - cb(null, g); - cb(); // false trigger! - }}}), soul = Gun.text.random(); - gun.get(soul).not(function(err, ok){ - done.fail = true; - }).val(function(val){ - setTimeout(function(){ - expect(val.a).to.be('b'); - expect(val.c).to.be('d'); - expect(done.fail).to.not.be.ok(); - done(); - },5); - }); - }); - - it.skip('unique val on stream', function(done){ // TODO: THE API HAS CHANGED! REDO TEHSE! - var gun = Gun({wire: {get: function(key, cb){ - if(Gun.obj.has(key, Gun._.soul)){ - key = key[Gun._.soul]; - var node = tmp.graph[key]; - cb(null, node); - cb(null, Gun.is.node.ify({}, key)); - cb(null, {}); - } - }}}), tmp = {graph: {}}; - tmp.graph[tmp.soul = Gun.text.random()] = tmp.node = {a: 'b', c: 'd'}; - Gun.is.node.ify(tmp.node, tmp.soul); - - tmp.graph['me'] = tmp.keynode = {}; - Gun.obj.put(tmp.rel = {}, Gun._.soul, tmp.soul); - tmp.keynode[tmp.soul] = tmp.rel; - Gun.is.node.ify(tmp.keynode, 'me'); - tmp.keynode[Gun._.meta]['key'] = 1; - - gun.get('me', function(err, data){ - - }).val(function(val){ - done.count = (done.count || 0) + 1; - setTimeout(function(){ - expect(val.a).to.be('b'); - expect(val.c).to.be('d'); - expect(done.count).to.be(1); - done(); - },5); - }); - }); - - it.skip('unique path val on stream', function(done){ // TODO: THE API HAS CHANGED! REDO TEHSE! - var gun = Gun({wire: {get: function(key, cb){ - var n = {}; - n = {_: {'#': soul, '>': {'a': 0}}, - 'a': 'a' - }; - cb(null, n); - n = {}; - n = {_: {'#': soul, '>': {'a': 1}}, - 'a': 'b' - }; - cb(null, n); - n = {}; - n = {_: {'#': soul }}; - cb(null, n); - }}}), soul = Gun.text.random(); - - gun.get(soul).path('a').val(function(val){ - done.count = (done.count || 0) + 1; - setTimeout(function(){ - expect(val).to.be('b'); - expect(done.count).to.be(1); - done(); - },5); - }); - }); - - it('double not', function(done){ // from the thought tutorial - var gun = Gun(gopt).get('thoughts').not(function(key){ - this.put({}).key(key); - }); - - setTimeout(function(){ - gun.not(function(){ - done.not = true; - }).val(function(){ - expect(done.not).to.not.be.ok(); - done(); - }, {empty: true}); - }, 10); - }); - - it('node path node path node path', function(done){ - var gun = Gun(/*gopt*/); - var data = gun.get('data'); - gun.put({ - a: 1, - b: 2, - c: 3 - }).key('data'); - data.path('a', function(e, v, f){ - //console.log("FIRST", e,v,f); - expect(done.D).to.not.be.ok(); - if(done.a){return} - expect(done.a).to.not.be.ok(); - expect(v).to.be(1); - done.a = true; - }); - data.path('b', function(e, v, f){ - //console.log("SECOND", e,v,f); - expect(done.D).to.not.be.ok(); - if(done.b){return} - expect(done.b).to.not.be.ok(); - expect(v).to.be(2); - done.b = true; - }); - data.path('c', function(e, v, f){ - //console.log("THIRD", e,v,f); - expect(done.D).to.not.be.ok(); - if(done.c){return} - expect(done.c).to.not.be.ok(); - expect(v).to.be(3); - done.c = true; - }); - setTimeout(function(){//return; - done.D=true; - data.put({d: 4}); - expect(done.a).to.be.ok(); - expect(done.b).to.be.ok(); - expect(done.c).to.be.ok(); - done(); - },250); - }); - - it('node path obj node path obj node path obj', function(done){ - var gun = Gun(); - var data = gun.get('data1'); - gun.put({ - a: {v: 1}, - b: {v: 2}, - c: {v: 3} - }).key('data1'); - data.path('a', function(e, v, f){ - //console.log("FIRST", f,v); - expect(done.D).to.not.be.ok(); - if(done.a){return} - expect(done.a).to.not.be.ok(); - expect(v.v).to.be(1); - done.a = true; - }); - data.path('b', function(e, v, f){ - //console.log("SECOND", f,v); - expect(done.D).to.not.be.ok(); - if(done.b){return} - expect(done.b).to.not.be.ok(); - expect(v.v).to.be(2); - done.b = true; - }); - data.path('c', function(e, v, f){ - //console.log("THIRD", f,v); - expect(done.D).to.not.be.ok(); - if(done.c){return} - expect(done.c).to.not.be.ok(); - expect(v.v).to.be(3); - done.c = true; - }); - setTimeout(function(){ - done.D = true; - //data.put({d: {v: 4}}); - expect(done.a).to.be.ok(); - expect(done.b).to.be.ok(); - expect(done.c).to.be.ok(); - done(); - },100); - }); - - describe('prototype crash', function(){ - it('instance.key', function(done){ - Gun().key('oye', function(err){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('instance.on', function(done){ - Gun().on(); - done(); - }); - - it('instance.path', function(done){ - Gun().path('oye', function(err){ - expect(err).to.be.ok(); - done(); - }); - }); - - it('instance.map', function(done){ - Gun().map(); - done(); - }); - - it('instance.not', function(done){ - Gun().not(); - done(); - }); - - it('instance.val', function(done){ - Gun().val(); - done(); - }); - }); - - it('implicit put on empty get', function(done){ - var gun = Gun().get('init'); - gun.on(function(val){ - expect(val.not).to.be(true); - if(done.c){ return } done(); done.c = 1; - }); - gun.put({not: true}); - }); - - it.skip('implicit put on empty get explicit not', function(done){ // HUH? This seems like wrong behavior. - var gun = Gun().get('init/not').not(); - gun.on(function(val){ - console.log("??", val); - done.c = 1; - }); - gun.put({not: true}); - setTimeout(function(){ - expect(done.c).to.not.be.ok(); - done(); - },1); - }); - - it('no implicit put on empty get', function(done){ - var gun = Gun({init: true}).get('not/init'); - gun.on(function(val){ - console.log("hmmm???", val); - expect(val.not).to.be(true); - if(done.c){ return } done.c = 1; - }); - gun.put({not: true}); - setTimeout(function(){ - expect(done.c).to.not.be.ok(); - done(); - },1); - }); - - it('no implicit put on empty get explicit init', function(done){ - var gun = Gun({init: true}).get('not/init/init').init(); - gun.on(function(val){ - if(val.not){ - expect(val.not).to.be(true); - if(done.c){return} - done();done.c=1; - return; - } - expect(Gun.obj.empty(val, '_')).to.be.ok(); - }); - gun.put({not: true}) - }); - - it('init', function(done){ - var gun = Gun().get('init/todo').init(); - gun.on(function(val){ - console.log("*******", val); - if(done.c){return} - if(val.data){ - expect(val.data).to.be('initialized!'); - done();done.c=1; - return; - } - expect(Gun.obj.empty(val, '_')).to.be.ok(); - }); - gun.put({data: 'initialized!'}); - }); - - describe('map able', function(){ - - it('map chain', function(done){ - Gun().put({a:1, b:2}).map().on(function(v,f){ - done[f] = v; - if(done.a && done.b){ - done(); - } - }); - }); - - it('map chain after', function(done){ - var g = Gun().get('m/c/a'); - g.map().on(function(v,f){ - done[f] = v; - if(done.a && done.b){ - done(); - } - }); - g.put({a:1, b:2}); - }); - - it('map chain map to', function(done){ - var g = Gun().get('m/c/m/to'); - var obj = { - a: {x: 1, y: 2, z: 3}, - b: {u: 4, v: 5, w: 6} - }, check = {x:1,y:1,z:1,u:1,v:1,w:1}; - g.map().map().on(function(v,f){ - check[f] = 0; - if(Gun.obj.map(check, function(v,f){if(v){return true}})){return} - done(); - }); - setTimeout(function(){ - g.put(obj); - },110); - }); - - it('map chain map', function(done){ - var g = Gun().get('m/c/m'); - var obj = { - a: {x: 1, y: 2, z: 3}, - b: {u: 4, v: 5, w: 6} - }, check = {x:1,y:1,z:1,u:1,v:1,w:1}; - g.map().map().on(function(v,f){ - check[f] = 0; - if(Gun.obj.map(check, function(v,f){if(v){return true}})){return} - done(); - }); - g.put(obj); - }); - - it('map chain map before', function(done){ - var g = Gun().get('m/c/m/b'); - var obj = { - a: {x: 1, y: 2, z: 3}, - b: {u: 4, v: 5, w: 6} - }, check = {x:1,y:1,z:1,u:1,v:1,w:1}; - g.put(obj); - g.map().map().on(function(v,f){ - check[f] = 0; - if(Gun.obj.map(check, function(v,f){if(v){return true}})){return} - done(); - }); - }); - }); - - it('init todo', function(done){ - var gun = Gun(), todo = gun.get('init/todo/early'); - todo.path('random1').put('eat chocolate'); - todo.map().on(function(val, field){ - expect(val).to.be('eat chocolate'); - expect(field).to.be('random1'); - if(done.c){ return } done(); done.c = 1; - }); - }); - - it('init todo defer', function(done){ - var gun = Gun(), todo = gun.get('init/todo/defer'); - todo.map().on(function(val, field){ - expect(val).to.be('eat chocolate'); - expect(field).to.be('random1'); - if(done.c){ return } done(); done.c = 1; - }); - setTimeout(function(){ - todo.path('random1').put('eat chocolate'); - }, 100); - }); - - /* // CHANGELOG: API 0.3 BREAKING CHANGE, .set has been deprecated! - it('set', function(done){ - done.c = 0; - var u, gun = Gun(); - gun.get('set').set().set().val(function(val){ - var keynode = gun.__.graph['set']; - expect(Gun.node.soul.ify(keynode, Gun._.key)).to.be.ok(); - Gun.is.node(keynode, function(rel, soul){ - rel = gun.__.by(soul).node; - expect(Gun.obj.empty(rel, Gun._.meta)).to.be.ok(); - }); - done.c += 1; - setTimeout(function(){ - expect(done.c).to.be(1); - done() - },10); - }); - }); - - it('root set', function(done){ - var gun = Gun().set(); - gun.on(function(val, field){ - expect(Gun.obj.empty(val, Gun._.meta)).to.be.ok(); - if(done.c){return} done(); done.c = 1; - }); - }); - - // TODO: BUG! We need 2 more tests... without .set()... and multiple paths on the same node. - it('set multiple', function(done){ // kinda related to flip flop? - var gun = Gun().get('sets').set(), i = 0; - gun.val(function(val){ - console.log("TEST 1", val); - expect(Gun.obj.empty(val, Gun._.meta)).to.be.ok(); - expect(Gun.node.soul(val)).to.be('sets'); - var keynode = gun.__.graph['sets']; - expect(Gun.obj.empty(keynode, Gun._.meta)).to.not.be.ok(); - }); - gun.set(1); //.set(2).set(3).set(4); // if you set an object you'd have to do a `.back` - gun.map(function(val, field){ - //gun.map().val(function(val, field){ // TODO: SEAN! DON'T LET ME FORGET! - console.log("\n TEST 2+", field, val); - return; - i += 1; - expect(val).to.be(i); - if(i % 4 === 0){ - setTimeout(function(){ - done.i = 0; - Gun.obj.map(gun.__.graph, function(){ done.i++ }); - expect(done.i).to.be(1); // make sure there isn't double. - Gun.log.verbose = false; - done() - },10); - } - }); - gun.set(2); - }); - */ - - it('val should not print relation', function(done){ // #132 - var users = Gun().get('example').path('users'); - users.path(Gun.text.random()).put('bob'); - users.path(Gun.text.random()).put('sam'); - setTimeout(function(){ - users.val(function(v){ - expect(Gun.val.rel.is(v)).to.not.be.ok(); - expect(Object.keys(v).length).to.be(3); - done(); - }); - },100); - }); - - it('peer 1 get key, peer 2 put key, peer 1 val', function(done){ - var hooks = {get: function(key, cb, opt){ - cb(); - }, put: function(nodes, cb, opt){ - Gun.union(gun1, nodes); - cb(); - }}, - gun1 = Gun({wire: {get: hooks.get}}).get('race') - , gun2 = Gun({wire: hooks}); //.get('race'); - - setTimeout(function(){ - gun2.put({the: 'data'}).key('race'); - setTimeout(function(){ - gun1.on(function(val){ - expect(val.the).to.be('data'); - if(done.c){ return } done(); done.c = 1; - }); - },10); - },10); - }); - - it('get pseudo merge', function(done){ - var gun = Gun(); - - gun.put({a: 1, z: -1}).key('pseudo'); - gun.put({b: 2, z: 0}).key('pseudo'); - - gun.get('pseudo').val(function(val){ - expect(val.a).to.be(1); - expect(val.b).to.be(2); - expect(val.z).to.be(0); - done(); - }); - }); - - it('get pseudo merge on', function(done){ - var gun = Gun(); - - gun.put({a: 1, z: -1}).key('pseudon'); - gun.put({b: 2, z: 0}).key('pseudon'); - - gun.get('pseudon').on(function(val){ - if(done.val){ return } // TODO: Maybe prevent repeat ons where there is no diff? (may not happen to after 1.0.0) - done.val = val; - expect(val.a).to.be(1); - expect(val.b).to.be(2); - expect(val.z).to.be(0); - done(); - }); - }); - - it.skip('get pseudo merge across peers', function(done){ // TODO: These tests should be replaced with PANIC tests! - // ctx.halt - var acb, bcb, ag, bg; - Gun.on('opt').event(function(gun, o){ - if(connect){ return } - gun.__.opt.wire = {get: function(key, cb, opt){ - key = key[Gun._.soul]; - if(o.alice){ acb = cb; ag = gun.__.graph; } else { bcb = cb; bg = gun.__.graph; } - var other = (o.alice? gun2 : gun1); - if(connect){ - var node = other.__.graph[key]; - cb(null, node); - } else { - cb(); - } - }, put: function(nodes, cb, opt){ - var other = (o.alice? gun2 : gun1); - if(connect){ - Gun.union(other, nodes); - } - cb(); - }} - }); - function pushAtoB(key){ - var node = ag[key]; - bcb(null, node); - } - function pushBtoA(key){ - var node = bg[key]; - acb(null, node); - } - var connect, gun1 = Gun({alice: true}).get('pseudo/merge').put({hello: 'world!'})/*.not(function(key){ - this.put({hello: "world!"}).key(key); - })*/, gun2; - gun1.val(function(val){ - expect(val.hello).to.be('world!'); - }); - setTimeout(function(){ - gun2 = Gun({bob: true}).get('pseudo/merge').put({hi: 'mars!'})/*.not(function(key){ - this.put({hi: "mars!"}).key(key); - });*/ - gun2.val(function(val){ - expect(val.hi).to.be('mars!'); - }); - setTimeout(function(){ - // CONNECT THE TWO PEERS - connect = true; - pushBtoA('pseudo/merge'); - pushAtoB('pseudo/merge'); - //gun1.get('pseudo/merge', null, {force: true}); // fake a browser refersh, in real world we should auto-reconnect - //gun2.get('pseudo/merge', null, {force: true}); // fake a browser refersh, in real world we should auto-reconnect - setTimeout(function(){ - gun1.val(function(val){ - expect(val.hello).to.be('world!'); - expect(val.hi).to.be('mars!'); - done.gun1 = true; - }); - //return; - gun2.val(function(val){ - expect(val.hello).to.be('world!'); - expect(val.hi).to.be('mars!'); - expect(done.gun1).to.be.ok(); - Gun({}); - done(); - }); - },10); - },10); - },10); - }); - - it("get map val -> map val", function(done){ // Terje's bug - var gun = Gun(); // we can test GUN locally. - var passengers = gun.get('passengers').not(function(key){ - this.put({'randombob': { - name: "Bob", - location: {'lat': '37.6159', 'lng': '-128.5'}, - direction: '128.2' - }, 'randomfred': { - name: "Fred", - location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, - direction: 'f128.2' - }}).key(key); - }); // this is now a list of passengers that we will map over. - var ctx = {n: 0, d: 0, l: 0}; - passengers.map().val(function(passenger, id){ - this.map().val(function(change, field){ - if('name' == field){ expect(change).to.be(passenger.name); ctx.n++ } - if('direction' == field){ expect(change).to.be(passenger.direction); ctx.d++ } - if('location' == field){ - delete change._; ctx.l++; - if('Bob' == passenger.name){ - expect(change).to.eql({'lat': '37.6159', 'lng': '-128.5'}); - } else { - expect(change).to.eql({'lat': 'f37.6159', 'lng': 'f-128.5'}); - } - } - if(ctx.n == 2 && ctx.d == 2 && ctx.l == 2){ done() } - }); - }); - }); - - it("put map update sub", function(done){ - var g = Gun(); - var list = gun.get('map/sub'); - list.put({a: {x:1}, b: {y: 1}}); - var check = {}; - list.map().on(function(v,f){ - check[f] = v; - if(done.c){return} - if(check.a && check.b && check.a.w){ - expect(check.a.x).to.be(1); - expect(check.b.y).to.be(1); - expect(check.a.w).to.be(2); - done.c=1; - done(); - } - }); - list.path('a').path('w').put(2); - }); - - it("put map update sub val", function(done){ - var g = Gun(); - var list = gun.get('map/sub/val'); - list.put({a: {x:1}, b: {y: 1}}); - list.path('a').path('w').put(2); - var check = {}; - list.map().val(function(v,f){ - check[f] = v; - console.log("*************************", f,v); - if(check.a && check.b){ - expect(check.a.w).to.be(2); - expect(check.a.x).to.be(1); - expect(check.b.y).to.be(1); - done(); - } - }, {wait: 400}); - }); - - it("put map update sub val after", function(done){ - var g = Gun(); - var list = gun.get('map/sub/val/after'); - var check = {}; - list.map().val(function(v,f){ - check[f] = v; - if(check.a && check.b){ - setTimeout(function(){ - expect(check.a.x).to.be(1); - expect(check.b.y).to.be(1); - expect(check.a.w).to.not.be.ok(); - expect(done.c).to.not.be.ok(); - done();done.c=1; - },400); - } - }); - list.put({a: {x:1}, b: {y: 1}}); - setTimeout(function(){ - list.path('a').path('w').put(2); - },300); - }); - - it("put map update sub val after to", function(done){ - var g = Gun(); - var list = gun.get('map/sub/val/after/to'); - var check = {}; - list.map().val(function(v,f){ - //console.log("*************", f,v);return; - check[f] = v; - if(check.a && check.b){ - setTimeout(function(){ - expect(check.a.x).to.be(1); - expect(check.b.y).to.be(1); - expect(check.a.w).to.be(2); - expect(done.c).to.not.be.ok(); - done();done.c=1; - },200); - } - }); - list.put({a: {x:1}, b: {y: 1}}); - list.path('a').path('w').put(2); - }); - - it("put map simple after", function(done){ - var g = Gun(); - var list = gun.get('map/simple/after'); - var check = {}; - list.map().val(function(v,f){ - check[f] = v; - if(check.a && check.b){ - setTimeout(function(){ - expect(check.a).to.be(2); - expect(check.b).to.be(1); - expect(done.c).to.not.be.ok(); - done();done.c=1; - },200); - } - }); - list.put({a: 1, b: 1}); - list.path('a').put(2); - }); - - it("put map simple after to", function(done){ - var g = Gun(); - var list = gun.get('map/simple/after/to'); - var check = {}; - list.map().val(function(v,f){ - check[f] = v; - if(check.a && check.b){ - setTimeout(function(){ - expect(check.a).to.be(1); - expect(check.b).to.be(1); - expect(done.c).to.not.be.ok(); - done();done.c=1; - },200); - } - }); - list.put({a: 1, b: 1}); - setTimeout(function(){ - list.path('a').put(2); - },300); - }); - - it("put map", function(done){ - var gun = Gun(); - var get = gun.get('map/that'); - var put = gun.put({a: 1, b: 2, c: 3}).key('map/that'); - get.map(function(v,f){ - if(1 === v){ done.a = true } - if(2 === v){ done.b = true } - if(3 === v){ done.c = true } - if(done.a && done.b && done.c){ - if(done.done){ return } - done(); done.done = 1; - } - }); - }); - - it("put map before", function(done){ - var gun = Gun(); - var get = gun.get('map/that/before'); - get.map(function(v,f){ - if(1 === v){ done.a = true } - if(2 === v){ done.b = true } - if(3 === v){ done.c = true } - if(done.a && done.b && done.c){ - if(done.done){ return } - done(); done.done = 1; - } - }); - var put = get.put({a: 1, b: 2, c: 3}); - }); - - it("get map map val", function(done){ // Terje's bug - var gun = Gun(/*{init: true}*/); // we can test GUN locally. - var passengers = gun.get('passengers/map').not(function(key){ - gun.put({randombob: { - name: "Bob", - location: {'lat': '37.6159', 'lng': '-128.5'}, - direction: '128.2' - }}).key(key); - }); // this is now a list of passengers that we will map over. - var ctx = {n: 0, d: 0, l: 0}; - passengers.map().map().val(function(val, field){ - if('name' == field){ expect(val).to.be(!ctx.n? 'Bob' : 'Fred'); ctx.n++ } - if('direction' == field){ expect(val).to.be(!ctx.d? '128.2' : 'f128.2'); ctx.d++ } - if('location' == field){ - delete val._; - if(!ctx.l){ - expect(val).to.eql({'lat': '37.6159', 'lng': '-128.5'}); - } else { - expect(val).to.eql({'lat': 'f37.6159', 'lng': 'f-128.5'}); - } - ctx.l++; - } - if(ctx.n == 2 && ctx.d == 2 && ctx.l == 2){ done() } - }); - setTimeout(function(){ - passengers.put({randomfred: { - name: "Fred", - location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, - direction: 'f128.2' - }}); - },400); - }); - - it("not before map deep after conflict", function(done){ - var gun = Gun(); - var g = gun.get('n/b/l/a/c').not(function(k){ - console.log("not", k); - gun.put({ - a: { - x:1, - y:1 - } - }).key('n/b/l/a/c'); - }); - var check = {a:{},b:{}}, F = 'a'; - g.map().map().val(function(v,f){ - var c = check[F]; - c[f] = v; - if(check.b && check.b.x && check.b.y){ - expect(check.a.x).to.be(1); - expect(check.a.y).to.be(1); - expect(check.b.x).to.be(1); - expect(check.b.y).to.be(1); - done(); - } - }); - setTimeout(function(){ - F = 'b'; - g.put({b: {x:1,y:1}}); - },400); - }); - - it("not before map deep after", function(done){ - var gun = Gun(); - var g = gun.get('n/b/l/a').not(function(k){ - console.log("not", k); - gun.put({ - a: { - x:1, - y:1 - } - }).key('n/b/l/a'); - }); - var check = {}; - g.map().map().val(function(v,f){ - check[f] = v; - if(check.x && check.y && check.w && check.u){ - expect(check.x).to.be(1); - expect(check.y).to.be(1); - expect(check.w).to.be(1); - expect(check.u.deep).to.be(true); - done(); - } - }); - setTimeout(function(){ - g.put({b: {w:1,u:{deep:true}}}); - },400); - }); - - it("before map after", function(done){ - var gun = Gun(); - var g = gun.get('b/l/a'); - g.put({a: {x:1,y:1}}); - var check = {}; - g.map().map().val(function(v,f){ - check[f] = v; - if(check.x && check.y && check.w && check.u && check.z){ - expect(check.x).to.be(1); - expect(check.w).to.be(1); - expect(check.u).to.be(1); - expect(check.y).to.be(2); - expect(check.z).to.be(1); - done(); - } - }); - setTimeout(function(){ - g.put({b: {w:1,u:1,y:2,z:1}}); - },150); - }); - - it("before map deep after", function(done){ - var gun = Gun(); - var g = gun.get('b/d/l/a'); - g.put({a: {x:1,y:1}}); - var check = {}; - g.map().map().val(function(v,f){ - check[f] = v; - if(check.x && check.y && check.w && check.u){ - expect(check.x).to.be(1); - expect(check.y).to.be(1); - expect(check.w).to.be(1); - expect(check.u.deep).to.be(true); - done(); - } - }); - setTimeout(function(){ - g.put({b: {w:1,u:{deep:true}}}); - },150); - }); - - it("get map map map map", function(done){ - var gun = Gun(); - var g = gun.get('m/m/m/m'); - console.log(" // TODO: BUG!!! If you make them have the same fields, they do not both iterate."); - g.put({ - a: { - b: { - c: { - d: 1, - e: 2, - f: 3 - } - } - }, - u: { - v: { - w: { - d: 1, - e: 2, - f: 3 - } - } - } - }); - var check = {}; - g.map().map().map().map().val(function(v,f){ - check[f] = (check[f] || 0) + 1; - if(check.d === 2 && check.e === 2 && check.f === 2){ - done(); - } - }); - }); - - it("get users map path path any", function(done){ - var gun = Gun(); - var check = {}; - gun.get('g/n/m/f').map().path('spouse').path('work').any(function(e,v,f){ - console.log("********", f,v, this); - check[v.name] = true; - if(check["ACME INC"] && check["GUN INC"]){ - done(); - } - }); - gun.put({_:{'#':'g/n/m/f'}, - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - }, - }); - }); - - it("get users map path path val after", function(done){ - var gun = Gun(); - gun.put({_:{'#':'g/n/m/f/a'}, - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - }, - }); - setTimeout(function(){ - //console.debug.i=1;console.log("----------------------"); - var check = {}; - gun.get('g/n/m/f/a').map().path('spouse').path('work').any(function(e,v,f){ - console.log("********", f,v, this); - check[v.name] = true; - if(check["ACME INC"] && check["GUN INC"]){ - done(); - } - }); - },100); - }); - - it("get users map path path any later", function(done){ - var gun = Gun(); - gun.get('g/n/m/f/l').map().path('spouse').path('work'); - gun.put({_:{'#':'g/n/m/f/l'}, - alice: { - name: "alice", - age: 24, - spouse: { - name: "carl", - age: 25, - work: { - name: "GUN INC" - } - }, - bout: {huh:1} - }, - bob: { - name: "bob", - age: 26, - spouse: { - name: "diana", - age: 27, - work: { - name: "ACME INC" - } - } - }, - }); - setTimeout(function(){ - var work = {}; - console.log("..........", gun); - return; - gun.get('g/n/m/f').map().path('spouse').path('work').any(function(e,v,f){ - console.log("********", f,v, this); - return; - check[v.name] = true; - if(check["ACME INC"] && check["GUN INC"]){ - done(); - } - }); - },100); - }); - - it("get graph node field ref", function(done){ - var gun = Gun(); - gun.put({data: {a: 1, b: 2}}, null, 'g/n/f') - console.debug.i=1;console.log("-----------------"); - gun.get('g/n/f').path('data').path('a').any(function(b,a){ - console.log(":D", a,b); - expect(a).to.be(1); - done(); - }); - }); - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - return; - it("get map path val", function(done){ // Terje's bug - var gun = Gun(); - var ctx = {l: -1, d: 0}; - var passengers = gun.get('passengers/path').not(function(key){ - this.put({randombob: { - name: "Bob", - location: {'lat': '37.6159', 'lng': '-128.5'}, - direction: '128.2' - }}).key(key); - }); - passengers.map().path('location.lng').val(function(val, field){ - //passengers.map().path('location.lng').on(function(val, field){ - console.log("******", field, val); - expect(field).to.be('lng'); - if(ctx.l){ - expect(val).to.be('-128.5'); - } else { - expect(val).to.eql('f-128.5'); - } - ctx.l++; - if(ctx.l){ done() } - }); - setTimeout(function(){ - console.debug.i=1;console.log("-------------------------------------"); - passengers.put({randomfred: { - name: "Fred", - location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, - direction: 'f128.2' - }}); - },300); - }); - - it("FILT ER FILTER", function(done){ - var g = Gun(); - var a = gun.put({}); - var b = gun.put({age: 19, name: "bob"}); - - console.debug.i=1;console.log("~~~~~~~~~~~~~~~~~~~~~~~~~"); - (window.ALICE = a.filter()).path('spouse.name').on(function(a,b){ - console.log("1", b,a); - }); - - a.put({age: 24, name: "alice", spouse: {name: "carl"}}); - return; - b.filter().on(function(a,b){ - console.log("2", b,a); - }); - }); - - it("map path before", function(done){ - var gun = Gun(); - var g = gun.put({a: {x:1}, b: {x:2}, c: {x:3}}); - var c = 0; - var m = g.map().path('x').on(function(v,f){ - console.log("*********************", f,v); - if(3 === ++c && 3 === v){ - done(); - } - }); - }); - - it("map path", function(done){ - var gun = Gun(); - var g = gun.get('map/path/ing'); - var c = 0; - var m = g.map().path('x').on(function(v,f){ - if(3 === ++c && 3 === v){ - done(); - } - }); - g.put({a: {x:1}, b: {x:2}, c: {x:3}}); - }); - - it("map path path", function(done){ - var gun = Gun(); - var g = gun.get('map/path/path/ing'); - var c = 0; - var m = g.map().path('x.y').on(function(v,f){ - //console.log("Hmmmm", f,v); - if(3 === ++c && 3 === v){ - done(); - } - }); - g.put({a: {x:{y:1}}, b: {x:{y:2}}, c: {x:{y:3}}}); - }); - - it("put path deep val -> path val", function(done){ // Terje's bug - var gun = Gun(); - gun.put({you: {have: {got: {to: {be: {kidding: "me!"}}}}}}).path('you.have.got.to.be').val(function(val, field){ - expect(val.kidding).to.be('me!'); - this.path('kidding').val(function(val){ - expect(val).to.be('me!'); - done(); - }); - }); - }); - - it("get set path put, map path val -> path val", function(done){ // Terje's bug - var gun = Gun(); - var ctx = {l: -1, d: 0}; - var passengers = gun; //.get('passengers/set/path'); - passengers = passengers.put({randombob: {name: 'Bob', direction: {}}}); - passengers.path('randombob.direction', function(err, ok, field){ - }).put({lol: {just: 'kidding', dude: '!'}}); - passengers.map().path('direction.lol').val(function(val){ - this.path('just').val(function(val){ - expect(val).to.be('kidding'); - }).back.path('dude').val(function(val){ - expect(val).to.be('!'); - done(); - }); - }) - }); - - it('path should not slowdown', function(done){ - this.timeout(5000); - var gun = Gun().put({ - history: {} - }); - //console.log("---------- setup data done -----------"); - var prev, diff, max = 25, total = 100, largest = -1, gone = {}; - //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.path('history').map(function(time, index){ - diff = Gun.time.is() - time; - expect(gone[index]).to.not.be.ok(); - gone[index] = diff; - largest = (largest < diff)? diff : largest; - //console.log(turns, index, 'largest', largest, diff, '???', diff > max, diff, max); - expect(diff > max).to.not.be.ok(); - }); - var turns = 0; - var many = setInterval(function(){ - if(turns > total || (diff || 0) > (max + 5)){ - clearTimeout(many); - expect(Gun.num.is(diff)).to.be.ok(); - if(done.c){ return } done(); done.c = 1; - return; - } - prev = Gun.time.is(); - var put = {}; put[turns += 1] = prev; - //console.log("put", put); - gun.put({history: put}); - }, 1); - }); - - it('path rel should not slowdown', function(done){ - this.timeout(5000); - var gun = Gun(/*gopt*/).put({ - history: {} - }); - var prev, diff, max = 100, total = 20, largest = -1, gone = {}; - var run = 0; - gun.path('history').map(function(entry, index){ - //if(!entry){ return } // TODO: BUG! KNOWN BUG!!!!!!! FIX!!!!! - ++run; - var i = run+''; - expect(i).to.be(index+''); - expect(i).to.be(entry.x+''); - expect(i).to.be(entry.y+''); - expect(i).to.be(entry.direction+''); - expect(entry.axis).to.be.ok(); - expect(entry.direction).to.be.ok(); - if(run > total){ - if(done.c){ return } - setTimeout(function(){ - done(); - done.c=true; - },20); - } - return; - //console.log("THE GRAPH\n", gun.__.graph); - //expect(gone[index]).to.not.be.ok(); - gone[index] = diff; - diff = Gun.time.is() - (entry.time || prev); - largest = (largest < diff)? diff : largest; - //console.log('turn', turns, 'index', index, 'diff', diff, 'largest', largest); - expect(diff > max).to.not.be.ok(); - }); - - var turns = 0; - var many = setInterval(function(){ - if(turns > total || diff > (max + 5)){ - //console.log("was it", turns > total, 'or', diff > (max + 5)); - clearTimeout(many); - return; - expect(Gun.num.is(diff)).to.be.ok(); - if(done.c){ return } done(); done.c = 1; - return; - } - prev = Gun.time.is(); - turns += 1; - var val = { - x: turns, - y: turns, - axis: 'y', - direction: turns, - time: prev - } - var put = {}; put[turns] = val; - gun.put({history: put}); - //gun.path(['history', turns += 1]).put({ - },1); - }); - - it.skip('paths rel should not slowdown', function(done){ // TODO: NEED TO ADD THIS NEW TEST! - this.timeout(5000); - //this.timeout(60000); - - //Gun.log.debug = 1; console.log("~~~~~ START ~~~~~~"); - var gun = Gun(gopt).put({ - history: {} - }); - //console.log("-------- DATA SET UP -----------"); - var prev, diff, max = 100, total = 100, largest = -1, gone = {}; - gun.path('history').map(function(entry, index){ - //if(!entry){ return } // TODO: BUG! KNOWN BUG!!!!!!! FIX!!!!! - //console.log("WAT", index, entry); - //console.log("THE GRAPH\n", gun.__.graph); - //expect(gone[index]).to.not.be.ok(); - gone[index] = diff; - diff = Gun.time.is() - (entry.time || prev); - largest = (largest < diff)? diff : largest; - console.log('turn', turns, 'index', index, 'diff', diff, 'largest', largest); - expect(diff > max).to.not.be.ok(); - }); - - var turns = 0; - //console.log("------------ PATH MAP SET UP --------------"); - var many = setInterval(function(){ - if(turns > total || diff > (max + 5)){ - clearTimeout(many); - expect(Gun.num.is(diff)).to.be.ok(); - if(done.c){ return } done(); done.c = 1; - return; - } - prev = Gun.time.is(); - Gun.log.base = Gun.log.ref = Gun.log.fer = prev; - //if(turns === 0){ Gun.log.debug = 1; console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); } - //console.log("-------------- ", turns + 1, "-----------------"); - var val = { - TURN: turns + 1, - x: 1, - y: 1, - axis: 'y', - direction: 1, - time: prev - } - //var put = {}; put[turns += 1] = val; - //gun.put({history: put}); - gun.path(['history', turns += 1]).put(val); - },1); - }); - - it("gun get on, later gun put key", function(done){ - var gun = Gun(); - - var keyC = gun.get('keyC').on(function(val){ - expect(val.hello).to.be('world'); - if(done.done){ return } - done.done = true; - done(); - }); - - setTimeout(function(){ - gun.put({hello: 'world'}).key('keyC'); - }, 100); - }); - - it('gun get put, sub path put, original val', function(done){ // bug from Jesse working on Trace // - var gun = Gun(gopt).get('players'); - - gun.put({ - taken: true, - history: {0: {}, 1: {}} - }); - - gun - .path('history') - .put(null) - .back - .path('taken') - .put(false) - - // TODO: BUG! There is a variation of this, where we just do `.val` rather than `gun.val` and `.val` by itself (chained off of the sub-paths) doesn't even get called. :( - gun.on(function(players){ // this val is subscribed to the original put and therefore does not get any of the sub-path listeners, therefore it gets called EARLY with the original/old data rather than waiting for the sub-path data to "finish" and then get called. - expect(players.history).to.be(null); - expect(players.taken).to.be(false); - if(done.c){ return } done(); done.c = 1; - }); - }); - - it("gun put recursive path slowdown", function(done){ - this.timeout(5000); - var gun = Gun(); - gun.__.opt.wire.put = null; - function put(num, t) { - var now = new Date().getTime(); - var cb; - for (var i = 1; i <= num; i++) { - if (i === num) { - cb = function (err, ok) { - console.log(num + 'ops: ' + (new Date().getTime() - now)/1000 + 's'); - } - } - Gun.ify({ //hello: 'world'}, cb); - deeply: { - nested: i - } - })(cb); - } - return new Date().getTime() - now; - } - /* - put(1); - put(2); - put(10); - put(50); - put(100); - put(1000); - put(5000);*/ - put(1000, true); - - var gun2 = Gun(); - gun2.__.opt.wire.put = null; - function put2(num, t) { - var now = new Date().getTime(); - var cb; - for (var i = 1; i <= num; i++) { - if (i === num) { - cb = function () { - console.log(num + ' API ops: ' + (new Date().getTime() - now)/1000 + 's'); - t && done(); - } - } - gun2.put({ //hello: 'world'}, cb); - deeply: { - nested: i - } - }, cb); - } - return new Date().getTime() - now; - } - Gun.log.start = Gun.time.is(); - put2(1); - put2(1000); // TODO: BUG! Interesting! If you add another 0 it causes a stack overflow! If I make Gun.time.now() not recurse then it runs but takes 4x as long. Even on the 10k ops there seems to be about a 4x overhead with the API versus raw serializer. - put2(1, true); - //put2(2); - //put2(10); - //put2(50); - //put2(100, true); - //put2(5000, true); - } ); - - it('choke time.now by using a while loop', function(){ - var i = 10; //100000; // causes an overflow. - while(--i){ - Gun.time.now(); - } - }); - /* // TODO: These tests should be deleted. - it("test timeout", function(done){ return done(); - var i = 1000, start = Date.now(); - while(i--){ - setTimeout(function(){ - console.log("ended in", (Date.now() - start)/1000); - },0); - } - return; - Gun.schedule(start, function(){ - console.log("ended in", (Date.now() - start)/1000); - }); - setImmediate(function(){ - console.log("ended in", (Date.now() - start)/1000); - }); - process.nextTick(function(){ - console.log("ended in", (Date.now() - start)/1000); - }); - }); - it("test assignment", function(done){ - var env = {graph: {}}; - function speed(other){ - var i = 10000; - while(i--){ - var $ = {soul: Gun.text.random()}; - var at = {node: {_: {}}}; - var obj = { - deeply: { - nested: 'lol' - } - } - env.graph[at.node._[Gun._.soul] = at.soul = $.soul] = at.node - } - } - var start = Date.now(); - speed(); - console.log('wat', (Date.now() - start)/1000); - }); - it("test fn call", function(done){ - function speed(i, cb){ - var r = 0; - while(i--){ - if(cb){ - cb(i); - } else { - r += i; - } - } - } - var start = Date.now(); - speed(100000000); - console.log('no fn', (Date.now() - start)/1000); - var start = Date.now(), r = 0; - speed(100000000, function(i){ r += i }); - console.log('w/ fn', (Date.now() - start)/1000); - var start = Date.now(), r = 0; - function foo(i){ r += i } - speed(100000000, foo); - console.log('w/ named fn', (Date.now() - start)/1000); - }); - it("gun put recursive path slowdown MUTANT TEST", function(done){ - this.timeout(30000); - - Gun.chain.put = function(val, cb, opt){ - var gun = this.chain(), obj; - var drift = Gun.time.now(), call = {}; - cb = cb || function(){}; - gun._.at('soul').event( - //( - function($){ - var chain = $.gun || gun; - var ctx = {}, obj = val, $ = Gun.obj.copy($); - var hash = $.field? $.soul + $.field : ($.from? $.from + ($.at || '') : $.soul); - if(call[hash]){ return } - gun.__.meta($.soul).put = true; - call[hash] = true; - if(Gun.is.val(obj)){ - if($.from && $.at){ - $.soul = $.from; - $.field = $.at; - } // no else! - if(!$.field){ - return cb.call(gun, {err: Gun.log("No field exists for " + (typeof obj) + "!")}); - } else - if(gun.__.graph[$.soul]){ - ctx.tmp = {}; - ctx.tmp[ctx.field = $.field] = obj; - obj = ctx.tmp; - } else { - return cb.call(gun, {err: Gun.log("No node exists to put " + (typeof obj) + " in!")}); - } - } - if(Gun.obj.is(obj)){ - if($.field && !ctx.field){ - ctx.tmp = {}; - ctx.tmp[ctx.field = $.field] = obj; - obj = ctx.tmp; - } - Gun.ify(obj || val, function(env, cb){ - var at; - if(!env || !(at = env.at) || !env.at.node){ return } - if(!at.node._){ - at.node._ = {}; - } - if(!Gun.node.soul(at.node)){ - if(obj === at.obj){ - env.graph[at.node._[Gun._.soul] = at.soul = $.soul] = at.node; - cb(at, at.soul); - } else { - function path(err, data){ - if(at.soul){ return } - at.soul = Gun.node.soul(data) || Gun.node.soul(at.obj) || Gun.roulette.call(gun); // TODO: refactor Gun.roulette! - env.graph[at.node._[Gun._.soul] = at.soul] = at.node; - //var start = performance.now(); - cb(at, at.soul); - //first = performance.now() - start;(first > .05) && console.log('here'); - }; - ($.empty && !$.field)? path() : chain.back.path(at.path || [], path, {once: true, end: true}); // TODO: clean this up. - } - //var diff1 = (first - start), diff2 = (second - first), diff3 = (third - second); - //(diff1 || diff2 || diff3) && console.log(diff1, ' ', diff2, ' ', diff3); - } - if(!at.node._[Gun._.state]){ - at.node._[Gun._.state] = {}; - } - if(!at.field){ return } - at.node._[Gun._.state][at.field] = drift; - })(function(err, ify){ - //console.log("chain.put PUT <----", ify.graph, '\n'); - if(err || ify.err){ return cb.call(gun, err || ify.err) } - if(err = Gun.union(gun, ify.graph).err){ return cb.call(gun, err) } - if($.from = Gun.val.rel.is(ify.root[$.field])){ $.soul = $.from; $.field = null } - Gun.obj.map(ify.graph, function(node, soul){ Gun.union(gun, Gun.union.pseudo(soul)) }); - gun._.at('soul').emit({soul: $.soul, field: $.field, key: $.key, PUT: 'SOUL', WAS: 'ON'}); // WAS ON - //return cb(null, true); - if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.put)){ - ctx.hook(ify.graph, function(err, data){ // now iterate through those nodes to a persistence layer and get a callback once all are saved - if(err){ return cb.call(gun, err) } - return cb.call(gun, null, data); - }, opt); - } else { - //console.Log("Warning! You have no persistence layer to save to!"); - cb.call(gun, null); // This is in memory success, hardly "success" at all. - } - }); - } - }) - gun._.at('soul').emit({soul: Gun.roulette.call(gun), field: null, empty: true}); - return gun; - } - - var gun = Gun(); //.get('bug').put({}); - gun.__.opt.hooks.put = null; - function put(num, t) { - var now = new Date().getTime(); - var cb; - for (var i = 1; i <= num; i++) { - if (i === num) { - cb = function (err, ok) { - console.log(num + 'MUTANT ops: ' + (new Date().getTime() - now)/1000 + 's'); - t && done(); - } - } - gun.put({ //hello: 'world'}, cb); - deeply: { - nested: i - } - }, cb); - } - return new Date().getTime() - now; - } - - //put(1, true); - //put(2); - //put(10); - //put(50); - //put(100); - //put(1000); - //put(5000); - put(10000, true); - }); - */ - it("gun get empty set, path not -> this put", function(done){ // Issue #99 #101, bug in survey and trace game. - var test = {c: 0}, u; - var gun = Gun(); - var game = gun.get('some/not/yet/set/put/thing').not(function(key){ - gun.put({alias: {}}).key(key); - });//.set(); - var me = game.path('alias').on(function(val){ - if(!done.put){ return } - expect(val).to.not.be(u); - expect(val.a).to.be('b'); - var meid = Gun.node.soul(val); - var self = this; - /* - expect(self === game).to.not.be.ok(); - expect(self === me).to.be.ok(); - */ - if(done.c){ return } done(); done.c = 1; - }); - setTimeout(function(){ - done.put = true; - me.put({a: 'b'}); - },100); - }); - - it("gun get empty set path empty later path put multi", function(done){ // Issue #99 #101, bug in survey and trace game. // ctx.halt - done.c = 0; - var gun = Gun(); - var data = gun.get('some/not/yet/set/put/thing/2'); - var path = data.path('sub'); - function put(d, t, f){ - setTimeout(function(){ - path.put(d, function(err, ok){ - expect(err).to.not.be.ok(); - done.c++; - if(f && done.c >= 3){ - done(); - } - }); - },t || 10); - }; - put({on: 'bus', not: 'transparent'}); - put({on: null, not: 'torrent'}, 200); - put({on: 'sub', not: 'parent'}, 250, true); - }); - - it("ToDo", function(done){ // Simulate ToDo app! - var gun = Gun().get('example/todo/data'); - gun.on(function renderToDo(val){ - if(done.done){ return } - if(done.clear){ - done.done = true; - expect(val[done.id]).to.not.be.ok(); - return done(); - } - delete val._; - Gun.obj.map(val, function(val, field){ return done.id = field; }); - expect(val[done.id]).to.be('groceries'); - }); - setTimeout(function(){ // form submit - gun.path('random1').put("groceries"); - setTimeout(function(){ // clear off element - done.clear = true; - gun.path(done.id).put(null); - },100); - },200); - }); - - it("gun put null path on put sub object", function(done){ // consensus4's bug - done.c = 1; - var gun = Gun(); - //Gun.log.verbose = true; - var game = gun.put({board: null, teamA: null, teamB: null, turn: null}).key('the/game'); - game.path('board').on(function(board, field){ - expect(field).to.be('board'); - if(done.c === 1){ - expect(board).to.not.be.ok(); - } - if(done.c === 2){ - if(!board[11] || !board[22] || !board[33]){ return } - done.c++; - delete board._; - expect(board).to.be.eql({11: ' ', 22: ' ', 33: 'A'}); - done(); - } - }); - setTimeout(function(){ - done.c++; - game.put({board: {11: ' ', 22: ' ', 33: 'A'}}); - },100); - }); - - it("get init put map -> put, foreach gun path map", function(done){ // replicate Jesse's Trace game bug - done.c = 0; - var gun = Gun(gopt).opt({init: true}) - .get('players').init() - .put({ - 0: { - num: 0 - }, - 1: { - num: 1 - }, - 2: { - num: 2 - }, - 3: { - num: 3 - } - }, function(err,ok){ - expect(done.c++).to.be(0); - }).val(function(p){ - done.p = Gun.node.soul(p); - done.m = Gun.val.rel.is(p[0]); - expect(Gun.val.rel.is(p[0])).to.be.ok(); - expect(Gun.val.rel.is(p[1])).to.be.ok(); - expect(Gun.val.rel.is(p[2])).to.be.ok(); - expect(Gun.val.rel.is(p[3])).to.be.ok(); - }) - - var players = [], me; - gun.map(function (player, number) { - players[number] = player; - players[number].history = []; - if (!player.taken && !me) { - this.put({ - taken: true, - history: { - 0: {x: 1, y: 2} - } - }, function(err,ok){}); - me = number; - } - }); - - Gun.list.map([0, 1, 2, 3], function (player, number) { - number = number - 1; - gun - .path(number + '.history') - .map(function (entry, logNum) { - done.c++; - players[number].history[logNum] = entry; - expect(entry.x).to.be(1); - expect(entry.y).to.be(2); - setTimeout(function(){ - expect(done.c).to.be(2); - done(); - },100); - }); - }); - }); - - it("gun get path empty val", function(done){ // flip flop bug - done.c = 0; - var u; - var gun = Gun(gopt); - var game = gun.get('game1/players'); - var me = game.path('player1').val(function(val){ - if(!done.c){ done.fail = true } - expect(val).to.not.be(u); - expect(val.x).to.be(0); - expect(val.y).to.be(0); - expect(done.fail).to.not.be.ok(); - done(); - }); - setTimeout(function(){ - done.c++; - expect(done.fail).to.not.be.ok(); - me.put({x: 0, y: 0}); - },10); - }); - - it("gun get path empty on", function(done){ - done.c = 0; - var u; - var gun = Gun(gopt); - var game = gun.get('game2/players'); - var me = game.path('player2').on(function(val){ - if(!done.c){ done.fail = true } - expect(done.fail).to.not.be.ok(); - expect(val).to.not.be(u); - if(done.done || !val.x || !val.y){ return } // it is okay if ON gets called many times, this protects against that. - // TODO: although it would be nice if we could minimize the amount of duplications. (may not happen to after 1.0.0) - expect(val.x).to.be(1); - expect(val.y).to.be(1); - done.done = true; - done(); - }); - setTimeout(function(){ - done.c++; - expect(done.fail).to.not.be.ok(); - me.put({x: 1, y: 1}); - },10); - }); - - it("gun get path empty not", function(done){ - var u; - var gun = Gun(gopt).opt({init: true}) - var game = gun.get('game3/players').init(); - var me = game.path('player3').not(function(field){ - expect(field).to.be('player3'); - done(); - }); - }); - - it("gun get path empty init", function(done){ - var u; - var gun = Gun(gopt).opt({init: true}); - var game = gun.get('game4/players').init(); - var me = game.path('player4').init().path('alias').init().put({oh: 'awesome'}).val(function(val, field){ - expect(val.oh).to.be('awesome'); - expect(field).to.be('alias'); - done(); - }) - }); - - it("no invalid graph", function(done){ - var gun = Gun({wire:{ - put: function(graph){ - expect(Gun.is.graph(graph)).to.be.ok(); - if(done.c){ return } if(done.on){ done(); done.c = 1 } - } - }}).get('example/todo/data/graph'); - gun.on(function renderToDo(val){ - done.on = true; - }); - setTimeout(function(){ - gun.path(Gun.text.random()).put('hoorah'); - },100) - }); - - it("no undefined field", function(done){ - var gun = Gun(); - var chat = gun.get('example/chat/data/graph/field').not(function(key){ - gun.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); - }); - chat.map().val(function renderToDo(val, field){ - expect(field).to.be.ok(); - expect(val.who).to.be.ok(); - expect(val.when).to.be.ok(); - expect(val.what).to.be.ok(); - if(done.c >= 2){ return } - if(done.c === 1){ done() } - done.c = done.c || 0; - done.c += 1; - }); - setTimeout(function(){ - var msg = {}; - msg.when = Gun.time.is(); - msg.what = "lol!"; - msg.who = "Alice"; - chat.path(msg.when + '_' + Gun.text.random(4)).put(msg); - },100); - }); - /* // This test didn't work for what I was wanting to test :(, will either remove it or modify it if I think of a clever solution to test what I want to test. - it("simulate json app", function(done){ - var peers = {}; - peers.server = Gun(); - function wipeServer(){ - peers.server = Gun(); - } - var gopt = {wire:{ - put: function(graph, cb){ - Gun.union(peers.server, graph); - cb(null); - } - ,get: function(lex, cb){ - setTimeout(function(){ - var soul = lex[Gun._.soul]; - if(peers.localStorage){ - var g = peers.localStorage; - console.log("VIA LOCALSTORAGE!", lex, g[soul]); - if(g[soul]){ - var n = g[soul]; - cb(null, n); - cb(null, Gun.is.node.ify({}, soul)); - cb(null, {}); - } - } - setTimeout(function(){ - var graph = peers.server.__.graph; - console.log("VIA the SERVER!!", lex, graph[soul]); - if(!graph[soul]){ - cb(null); - cb(null, {}); - return; - } - var node = graph[soul]; - cb(null, node); - cb(null, Gun.is.node.ify({}, soul)); - cb(null, {}); - },5); - },5); - } - }} - peers.gun = Gun(gopt); - function reload(){ - peers.localStorage = Gun.obj.copy(peers.gun.__.graph); - peers.gun2 = Gun(gopt); - } - var ref = peers.gun.get('example/json/data/test'); - setTimeout(function(){ - ref.path('hello').put("value"); - setTimeout(function(){ - wipeServer(); - reload(); - setTimeout(function(){ - Gun.log.debug = 1; console.log("~~~~~~~~~~~~~~~~~~~"); - var ref = peers.gun2.get('example/json/data/test'); - ref.on(function(data){ - console.log("on!", data); - }); - },100); - },100); - },100); - }); - */ - it("simulate chat app", function(done){ - var server = Gun(); - var gopt = {wire:{ - put: function(graph, cb){ - Gun.union(server, graph); - cb(null); - } - ,get: function(lex, cb){ - setTimeout(function(){ - var soul = lex[Gun._.soul]; - var graph = server.__.graph; - //console.log('server replying', soul, graph); - if(!graph[soul]){ - //console.log("replying to Alice...", null); - cb(null); - cb(null, {}); - return; - } - var node = graph[soul]; - //console.log("replying to Bob...", node); - cb(null, node); - cb(null, Gun.is.node.ify({}, soul)); - cb(null, {}); - },5); - } - }} - var gun = Gun(gopt); - var chat = gun.get('example/chat/data/graph/field').not(function(key){ - gun.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); - }); - chat.map().val(function renderToDo(val, field){ - //console.log("ALICE", field, val); - expect(field).to.be.ok(); - expect(val.who).to.be.ok(); - expect(val.when).to.be.ok(); - expect(val.what).to.be.ok(); - }); - setTimeout(function(){ - var gun2 = Gun(gopt); - //Gun.log.debug =1; console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - var chat2 = gun2.get('example/chat/data/graph/field').not(function(key){ - //console.log("BOB's key", key); - gun2.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); - }); - chat2.map().val(function renderToDo(val, field){ - //console.log("BOB", field, val); - expect(field).to.be.ok(); - expect(val.who).to.be.ok(); - expect(val.when).to.be.ok(); - expect(val.what).to.be.ok(); - done(); - }); - },100); - }); - - it.skip("gun path via gun path", function(done){ // TODO: Future feature? - var gun = Gun(); - var book = gun.put({ name: 'Potato Cooking' }); - var author = gun.put({ name: 'Bob Bobson' }); - author.path(book.path('name')).put(book); - }); - - it("gun set", function(done){ - var gun = Gun(); - var users = gun.get('users'); - var alice = gun.put({name: 'alice', birth: Math.random()}).key('person/alice'); - var bob = gun.put({name: 'bob', birth: Math.random()}).key('person/bob'); - var carl = gun.put({name: 'carl', birth: Math.random()}).key('person/carl'); - var dave = gun.put({name: 'dave', birth: Math.random()}).key('person/dave'); - - // Test set with new object - var alan = users.set({name: 'alan', birth: Math.random()}).key('person/alan'); - alan.val(function(alan) { - // Test set with node - dave.path('friends').set(alan); - }); - - users.set(alice); - users.set(bob); - users.set(carl); - users.set(dave); - - alice.path('friends').set(bob).back.set(carl); - bob.path('friends').set(alice); - dave.path('friends').set(alice).back.set(carl); - - var team = gun.get('team/lions').put({name: "Lions"}); - team.path('members').set(alice); - team.path('members').set(bob); - team.path('members').set(alan); // Test set with set - - alice.path('team').put(team); - bob.path('team').put(team); - - dave.path('friends').map().path('team.members').map().val(function(member){ - //console.log("Dave's friend is on a team that has", member.name, "on it."); - if('alice' === member.name){ - done.alice = true; - } else - if('bob' === member.name){ - done.bob = true; - } else - if('alan' === member.name){ - done.alan = true; - } else - { - expect(member).to.not.be.ok(); - } - if(done.alice && done.bob && done.alan){ - setTimeout(function(){ - done(); - },10); - } - }); - }); - - it("localStorage", function(done){ - var localStorage = localStorage || {clear:function(){}}; - localStorage.clear(); - var gun = Gun(); - - - var text = Gun.text.random(1024 * 1024 * 6); - gun.put({i: text}, function(err, ok){ - if(done.c){ return } - if(!err){ return done() } - var text = "If you are seeing this message, it means the localStorage error was caught successfully rather than it crashing and stopping replication to peers. Also, the error is now reported back to you via the put callback. Here it is!"; - localStorage.clear(); - done(); done.c = 1; - }); - }); - - it("get context", function(done){ // TODO: HUH?????? This was randomly causing errors? - var gun = Gun(); - var ref = gun.get('ctx/lol').get('ctx/foo').put({hello: 'world'}); - gun.get('ctx/lol').val(function(implicit){ - done.fail = true; - expect(implicit).to.not.be.ok(); - }); - gun.get('ctx/lol').not(function(){ - done.please = true; - }); - gun.get('ctx/foo').val(function(data){ - expect(data.hello).to.be('world'); - expect(done.fail).to.not.be.ok(); - expect(done.please).to.be.ok(); - done(); - }); - }); - - it.skip("chaining val", function(done){ // Not implemented yet! - var gun = Gun(); - gun.get('users').set(gun.put({name: 'alice'})); - gun.get('users').set(gun.put({name: 'bob'}));; - gun.get('users').val().map(function(person){ - if(person.name === 'alice'){ - done.alice = true; - } - if(person.name === 'bob'){ - done.bob = true; - } - if(person.name === 'carl'){ - done.carl = true; - } - }); - gun.get('users').set(gun.put({name: 'carl'})); - setTimeout(function(){ - console.log('wha?', done.alice, done.bob, done.carl); - expect(done.alice).to.be.ok(); - expect(done.bob).to.be.ok(); - expect(done.carl).to.not.be.ok(); - done(); - },10); - }); - - it.skip('Deep async change not updating', function (done) { // Issue #167 TODO: NEEDS TO BE ADDED TO 0.5 BRANCH! - // object nested three layers deep - // must be at least three layers - var obj = { 1: { 2: { data: false } } } - - // define gun and place the deep object - gun = Gun().get('deep change').put(obj) - - // listen for changes - Gun.log.debug = 1; console.log("------------------"); - gun.path('1.2.data').on(function (data) { - console.log("??????", data); - if (data) { - // gun will never receive the "true" update - done(); - } - }) - - // asynchronously set data - // synchronous deviations will succeed - setTimeout(function () { - obj[1][2].data = true - gun.put(obj); - }, 50) - }); - - it('should allow more than 2 items depthwise', function (done) { // Issue #186 - var gun = Gun(); - var list = gun.get('list'); - // create a list two layers deep - list.put({ - depth: 1, - next: { - depth: 2 - } - }); - - //Gun.log.verbose=true;Gun.log.debug=1;console.log("----------------------"); - // append a third item - list.path('next').put({ - to: { - depth: 3 - } - }); - setTimeout(function(){ - - //list.path('next').val('wat'); - - //console.log("!!!!!!", gun.__.graph); - - // try to read the third item - list.path('next.to').val(function () { // TODO: BUG! If this is 'next.next' as with the data, then it fails. - done(); - }); - },100); - }); - - it("Batch put status update not save", function(done){ // TODO: ADD TO 0.5 BRANCH. Stefdv's bug. - var obj = { - a: 1, - b: 2, - c: 3, - d: 4, - e: 5, - f: 6, - g: 7, - h: 8, - i: 9, - j: 10, - k: 11, - l: 12, - m: 13, - n: 14, - o: 15, - p: 16, - q: 17, - r: 18, - s: 19, - t: 20 - } - - var bsmi = { - group1: { - item1: { - 10: Gun.obj.copy(obj) - } - }/*, - group2: { - item2: { - 10: Gun.obj.copy(obj) - } - }*/ - } - - var gun = Gun(); - var BSMI = gun.get('bsmi').put(bsmi); - - // path is - //BSMI is a set holding all items - //var allPaths = ["1116.1116-A7001.10","1354.1354-E1930.10"] - var allPaths = ["group1.item1.10"];//,"group2.item2.10"] - allPaths.forEach(function(path) { - BSMI.path(path).put({status:false}); - }); - setTimeout(function(){ - BSMI.path(allPaths[0]).val(function(a,b,c){ - expect(a.a).to.be(1); - expect(a.b).to.be(2); - expect(a.c).to.be(3); - expect(a.d).to.be(4); - expect(a.e).to.be(5); - expect(a.f).to.be(6); - expect(a.g).to.be(7); - expect(a.h).to.be(8); - expect(a.i).to.be(9); - expect(a.j).to.be(10); - expect(a.k).to.be(11); - expect(a.l).to.be(12); - expect(a.m).to.be(13); - expect(a.n).to.be(14); - expect(a.o).to.be(15); - expect(a.p).to.be(16); - expect(a.q).to.be(17); - expect(a.r).to.be(18); - expect(a.s).to.be(19); - expect(a.t).to.be(20); - expect(a.status).to.be(false); - done(); - }); - },100); - }); - - it("Don't put on parents", function(done){ // TODO: ADD TO 0.5 BRANCH! // Another Stefdv find. - var test = gun.get('test'); - test.path('try.this.at.lvl4').put({msg:'hoi'}) - test.val(function(node,b){ - delete node._; - expect(Gun.obj.empty(node, 'try')).to.be.ok(); - node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.try)]); - - delete node._; - expect(Gun.obj.empty(node, 'this')).to.be.ok(); - node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.this)]); - - delete node._; - expect(Gun.obj.empty(node, 'at')).to.be.ok(); - node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.at)]); - - delete node._; - expect(Gun.obj.empty(node, 'lvl4')).to.be.ok(); - node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.lvl4)]); - - delete node._; - expect(Gun.obj.empty(node, 'msg')).to.be.ok(); - expect(node.msg).to.be('hoi'); - done(); - }); - }); - - it("Deep not fails to fire", function(done){ // @d3x0r's bug! - var gun = Gun().get("org.d3x0r.voxelarium.local." + Gun.text.random()); - - var player = gun.path( "player" ); - - player.path("id").not(function(){ - done.not = true; - //console.log("Not is run!"); - var id = 'fluffy'; - var world = 0; - player.path("id").put(id); - player.path("world_id").put(world); - }).val(function(data){ - //console.log("we have value!", data); - expect(done.not).to.be.ok(); - expect(data).to.be('fluffy'); - done(); - }); - - }); - /* - depp.on(log).path('spouse').on(log).path('pet').on(log); - // 0) Depp & Heide & dog - // 1) dog - // 2) cat - // 3) cat - // 4) Julie & cat - - depp.path('spouse.pet.name').on(log).put('pearls'); - depp.path('spouse.pet.name').put('paws').on(log); - depp.path('spouse.pet.name').on(log).not(log); - // 0: fluffy - // 1: fluff - // 3: bacon - // 9: `.not` - - depp.path('spouse.pet.name').val().on(log); - // 0: fluffy - // 1: fluff - */ - }); +var root; +(function(env){ + root = env.window ? env.window : global; + env.window && root.localStorage && root.localStorage.clear(); + try{ require('fs').unlinkSync('data.json') }catch(e){} + //root.Gun = root.Gun || require('../gun'); + if(root.Gun){ + root.Gun = root.Gun; + } else { + root.Gun = require('../gun'); + Gun.SEA = require('../sea'); // TODO: breaks original deep tests! + Gun.serve = require('../lib/serve'); + //require('./s3'); + //require('./uws'); + //require('./wsp/server'); + require('../lib/file'); + } +}(this)); +//Gun.log.squelch = true; +var gleak = {globals: {}, check: function(){ // via tobyho + var leaked = [] + for (var key in gleak.globe){ if (!(key in gleak.globals)){ leaked.push(key)} } + if (leaked.length > 0){ console.log("GLOBAL LEAK!", leaked); return leaked } +}}; +(function(env){ + for (var key in (gleak.globe = env)){ gleak.globals[key] = true } +}(this)); - Gun.SEA && describe('SEA', function(){ - console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!'); - var alias = 'dude'; - var pass = 'my secret password'; - var userKeys = ['pub', 'priv']; - var clearText = 'My precious secret!'; - var encKeys = ['ct', 'iv', 's']; - - ['callback', 'Promise'].forEach(function(type){ - describe(type, function(){ - it('proof', function(done){ - var check = function(proof){ - expect(proof).to.not.be(undefined); - expect(proof).to.not.be(''); - done(); - } - // proof - generates PBKDF2 hash from user's alias and password - // which is then used to decrypt user's auth record - if(type === 'callback'){ - Gun.SEA.proof(alias, pass, check); - } else { - Gun.SEA.proof(alias, pass).then(check).catch(done); - } - }); - - it('pair', function(done){ - var check = function(key){ - expect(key).to.not.be(undefined); - expect(key).to.not.be(''); - expect(key).to.have.keys(userKeys); - userKeys.map(function(fld){ - expect(key[fld]).to.not.be(undefined); - expect(key[fld]).to.not.be(''); - }); - done(); - }; - // pair - generates ECDH key pair (for new user when created) - if(type === 'callback'){ - Gun.SEA.pair(check); - } else { - Gun.SEA.pair().then(check).catch(done);; - } - }); - - it('en', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(jsonSecret){ - expect(jsonSecret).to.not.be(undefined); - expect(jsonSecret).to.not.be(''); - expect(jsonSecret).to.not.eql(clearText); - expect(jsonSecret).to.not.eql(JSON.stringify(clearText)); - var objSecret = JSON.parse(jsonSecret); - expect(objSecret).to.have.keys(encKeys); - encKeys.map(function(key){ - expect(objSecret[key]).to.not.be(undefined); - expect(objSecret[key]).to.not.be(''); - }); - done(); - }; - // en - encrypts JSON data using user's private or derived ECDH key - if(type === 'callback'){ - Gun.SEA.en(JSON.stringify(clearText), key.priv, check); - } else { - Gun.SEA.en(JSON.stringify(clearText), key.priv).then(check); - } - }).catch(function(e){done(e)}); - }); - - it('sign', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(signature){ - expect(signature).to.not.be(undefined); - expect(signature).to.not.be(''); - expect(signature).to.not.eql(key.pub); - done(); - }; - // sign - calculates signature for data using user's private ECDH key - if(type === 'callback'){ - Gun.SEA.sign(key.pub, key.priv, check); - } else { - Gun.SEA.sign(key.pub, key.priv).then(check); - } - }).catch(function(e){done(e)}); - }); - - it('verify', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(ok){ - expect(ok).to.not.be(undefined); - expect(ok).to.not.be(''); - expect(ok).to.be(true); - done(); - }; - // sign - calculates signature for data using user's private ECDH key - Gun.SEA.sign(key.pub, key.priv).then(function(signature){ - if(type === 'callback'){ - Gun.SEA.verify(key.pub, key.pub, signature, check); - } else { - Gun.SEA.verify(key.pub, key.pub, signature).then(check); - } - }); - }).catch(function(e){done(e)}); - }); - - it('de', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(jsonText){ - expect(jsonText).to.not.be(undefined); - expect(jsonText).to.not.be(''); - expect(jsonText).to.not.eql(clearText); - var decryptedSecret = JSON.parse(jsonText); - expect(decryptedSecret).to.not.be(undefined); - expect(decryptedSecret).to.not.be(''); - expect(decryptedSecret).to.be.eql(clearText); - done(); - }; - Gun.SEA.en(JSON.stringify(clearText), key.priv).then(function(jsonSecret){ - // de - decrypts JSON data using user's private or derived ECDH key - if(type === 'callback'){ - Gun.SEA.de(jsonSecret, key.priv, check); - } else { - Gun.SEA.de(jsonSecret, key.priv).then(check); - } - }); - }).catch(function(e){done(e)}); - }); - - it('derive', function(done){ - Gun.SEA.pair().then(function(txKey){ - return Gun.SEA.pair().then(function(rxKey){ - return { tx: txKey, rx: rxKey }; - }); - }).then(function(keys){ - var check = function(shared){ - expect(shared).to.not.be(undefined); - expect(shared).to.not.be(''); - [keys.rx.pub, keys.rx.priv, keys.tx.pub, keys.tx.priv] - .map(function(val){ - expect(shared).to.not.eql(val); - }); - done(); - }; - // derive - provides shared secret for both receiver and sender - // which can be used to encrypt or sign data - if(type === 'callback'){ - Gun.SEA.derive(keys.rx.pub, keys.tx.priv, check); - } else { - Gun.SEA.derive(keys.rx.pub, keys.tx.priv).then(check); - } - }).catch(function(e){done(e)}); - }); - - it('write', function(done){ - Gun.SEA.pair().then(function(key){ - Gun.SEA.sign(key.pub, key.priv).then(function(signature){ - var check = function(result){ - expect(result).to.not.be(undefined); - expect(result).to.not.be(''); - expect(result.slice(0, 4)).to.eql('SEA['); - var parts = JSON.parse(result.slice(3)); - expect(parts).to.not.be(undefined); - expect(parts[0]).to.be.eql(key.pub); - expect(parts[1]).to.be.eql(signature); - done(); - }; - // write - wraps data to 'SEA["data","signature"]' - if(type === 'callback'){ - Gun.SEA.write(key.pub, key.priv, check); - } else { - Gun.SEA.write(key.pub, key.priv).then(check); - } - }); - }).catch(function(e){done(e)}); - }); - - it('read', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(result){ - expect(result).to.not.be(undefined); - expect(result).to.not.be(''); - expect(result).to.be.equal(key.pub); - done(); - }; - Gun.SEA.sign(key.pub, key.priv).then(function(signature){ - Gun.SEA.write(key.pub, key.priv).then(function(signed){ - // read - unwraps data from 'SEA["data","signature"]' - if(type === 'callback'){ - Gun.SEA.read(signed, key.pub, check); - } else { - Gun.SEA.read(signed, key.pub).then(check); - } - }); - }); - }).catch(function(e){done(e)}); - }); - }); - }); - }); - - Gun().user && describe('User', function(){ - console.log('TODO: User! THIS IS AN EARLY ALPHA!!!'); - var alias = 'dude'; - var pass = 'my secret password'; - var user = Gun().user(); - - ['callback', 'Promise'].forEach(function(type){ - describe(type, function(){ - describe('create', function(){ - it('new', function(done){ - var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - done(); - }; - // Gun.user.create - creates new user - if(type === 'callback'){ - user.create(alias+type, pass, check); - } else { - user.create(alias+type, pass).then(check).catch(done); - } - }); - it('conflict', function(done){ - var gunLog = Gun.log; // Temporarily removing logging - Gun.log = function(){}; - var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).not.to.be(undefined); - expect(ack.err).not.to.be(''); - done(); - }; - // Gun.user.create - fails to create existing user - if(type === 'callback'){ - user.create(alias+type, pass, check); - } else { - user.create(alias+type, pass).then(function(ack){ - done('Failed to decline creating existing user!'); - }).catch(check); - } - Gun.log = gunLog; - }); - }); - - describe('auth', function(){ - it('login', function(done){ - var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - done(); - }; - var props = {alias: alias+'-'+type, pass: pass}; - user.create(props.alias, props.pass).catch(function(){}) - .then(function(){ - // Gun.user.create - creates new user - if(type === 'callback'){ - user.auth(props, check); - } else { - user.auth(props).then(check).catch(done); - } - }); - }); - - it.skip('failed login', function(done){ - done(); - }); - - it.skip('new password', function(done){ - done(); - }); - - it.skip('failed new password', function(done){ - done(); - }); - }); - - describe('remember', function(){ - it.skip('TBD', function(done){ - done(); - }); - }); - }); - }); - }); - - describe('Streams', function(){ - console.log("TODO: BUG! Upgrade UNION tests to new internal API!"); - return; - var gun = Gun(), g = function(){ - return Gun({wire: {get: ctx.get}}); - }, ctx = {gen: 9, extra: 100, network: 2}; - - it('prep hook', function(done){ - this.timeout(ctx.gen * ctx.extra); - var peer = Gun(), ref; - ctx.get = function(key, cb){ - var c = 0; - cb = cb || function(){}; - key = key[Gun._.soul]; - if('big' !== key){ return cb(null) } - setTimeout(function badNetwork(){ - c += 1; - var soul = Gun.node.soul(ref); - var graph = {}; - var data = /*graph[soul] = */ {_: {'#': soul, '>': {}}}; - if(!ref['f' + c]){ - return cb(null, data), cb(null, {}); - } - data._[Gun._.state]['f' + c] = ref._[Gun._.state]['f' + c]; - data['f' + c] = ref['f' + c]; - cb(null, data); - setTimeout(badNetwork, ctx.network); - },ctx.network); - } - ctx.get.fake = {}; - for(var i = 1; i < (ctx.gen) + 1; i++){ - ctx.get.fake['f'+i] = i; - ctx.length = i; - } - ctx.get.fake = Gun.is.node.ify(ctx.get.fake, 'big'); - var big = peer.put(ctx.get.fake).val(function(val){ - ref = val; - ctx.get({'#': 'big'}, function(err, graph){ - if(Gun.obj.empty(graph)){ done() } - }); - gun.opt({wire: {get: ctx.get}}); - }); - }); - - it('map chain', function(done){ - var set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); - set.map().val(function(obj, field){ - if(obj.here){ - done.a = obj.here; - expect(obj.here).to.be('you'); - } - if(obj.go){ - done.b = obj.go; - expect(obj.go).to.be('dear'); - } - if(obj.sir){ - done.c = obj.sir; - expect(obj.sir).to.be('!'); - } - if(done.a && done.b && done.c){ - done(); - } - }); - }); - - it('map chain path', function(done){ - var set = gun.put({ - a: {name: "Mark", - pet: {coat: "tabby", name: "Hobbes"} - }, b: {name: "Alice", - pet: {coat: "calico", name: "Cali"} - }, c: {name: "Bob", - pet: {coat: "tux", name: "Casper"} - } - }); - set.map().path('pet').val(function(obj, field){ - if(obj.name === 'Hobbes'){ - done.hobbes = obj.name; - expect(obj.name).to.be('Hobbes'); - expect(obj.coat).to.be('tabby'); - } - if(obj.name === 'Cali'){ - done.cali = obj.name; - expect(obj.name).to.be('Cali'); - expect(obj.coat).to.be('calico'); - } - if(obj.name === 'Casper'){ - done.casper = obj.name; - expect(obj.name).to.be('Casper'); - expect(obj.coat).to.be('tux'); - } - if(done.hobbes && done.cali && done.casper){ - done(); - } - }); - }); - - it('get big on', function(done){ - this.timeout(ctx.gen * ctx.extra); - var test = {c: 0, last: 0}; - g().get('big').on(function(val){ - if(test.done){ return console.log("hey yo! you got duplication on your ons!"); } - delete val._; - if(val['f' + (test.last + 1)]){ - test.c += 1; - test.last += 1; - } - var obj = {}; - for(var i = 1; i < test.c + 1; i++){ - obj['f'+i] = i; - } - expect(val).to.eql(obj); - if(test.c === ctx.length){ - test.done = true; - done(); - } - }); - }); - - it('get big on delta', function(done){ - this.timeout(ctx.gen * ctx.extra); - var test = {c: 0, seen: {}}; - g().get('big').on(function(val){ - delete val._; - if(test.seen['f' + test.c]){ return } - test.seen['f' + test.c] = true; - test.c += 1; - var obj = {}; - obj['f' + test.c] = test.c; - expect(val).to.eql(obj); - if(test.c === ctx.length){ - done(); - } - }, true); - }); - - it('get val', function(done){ - this.timeout(ctx.gen * ctx.extra); - g().get('big').val(function(obj){ - delete obj._; - expect(obj.f1).to.be(1); - expect(obj['f' + ctx.length]).to.be(ctx.length); - var raw = Gun.obj.copy(ctx.get.fake); - delete raw._; - expect(obj).to.be.eql(raw); - Gun.log.debug = 0; - done(); - }); - }); - - it('get big map val', function(done){ - this.timeout(ctx.gen * ctx.extra); - var test = {c: 0, seen: {}}; - g().get('big').map().val(function(val, field){ - if(test.seen[field]){ return } - test.seen[field] = true; - delete val._; - expect(field).to.be('f' + (test.c += 1)); - expect(val).to.be(test.c); - if(test.c === ctx.length){ - done(); - } - }); - }); - - it('val emits all data', function(done){ // bug in chat app - var chat = Gun().get('example/chat/data').not(function(){ - this.put({1: {who: 'Welcome', what: "to the chat app!", when: 0}}).key('example/chat/data'); - }); - chat.put({random1: {who: 'mark', what: "1", when: 1}}); - chat.put({random2: {who: 'mark', what: "2", when: 2}}); - chat.put({random3: {who: 'mark', what: "3", when: 3}}); - chat.put({random4: {who: 'mark', what: "4", when: 4}}); - chat.put({random5: {who: 'mark', what: "5", when: 5}}); - var seen = {1: false, 2: false, 3: false, 4: false, 5: false} - setTimeout(function(){ - chat.map(function(m){ }).val(function(msg, field){ - var msg = Gun.obj.copy(msg); - if(msg.what){ - expect(msg.what).to.be.ok(); - seen[msg.when] = true; - } - if(!Gun.obj.map(seen, function(boo){ if(!boo){ return true } })){ - done(); - } - }); - }, 100); - }); - }); -}); +describe('Performance', function(){ return; // performance tests + var console = root.console || {log: function(){}}; + function perf(fn, i){ + i = i || 1000; + while(--i){ + fn(i); + } + } + perf.now = this.performance? function(){ return performance.now() } : function(){ return Gun.time.now()/1000 }; + (function(){ + var t1 = perf.now(); + var obj = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i'}; + Object.keys && perf(function(){ + var l = Object.keys(obj), ll = l.length, i = 0, s = ''; + for(; i < ll; i++){ + var v = l[i]; + s += v; + } + }); + console.log('map: native', (t1 = (perf.now() - t1)/1000) + 's'); + + var t2 = perf.now(); + var obj = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i'}; + perf(function(){ + var s = ''; + Gun.obj.map(obj, function(v){ + s += v; + }) + }); + console.log('map: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); + }()); + (function(){ + if(!Gun.store){ + var tab = Gun().tab; + if(!tab){ return } + Gun.store = tab.store; + } + root.localStorage && root.localStorage.clear(); + var it = 1000; + var t1 = perf.now(); + perf(function(i){ + var obj = {'i': i, 'v': Gun.text.random(100)}; + Gun.store.put('test/native/' + i, obj); + }, it); + console.log('store: native', (t1 = (perf.now() - t1)/1000) + 's'); + + root.localStorage && root.localStorage.clear(); + var gun = Gun({wire: {get:function(l,cb){cb()},put:function(g,cb){ + Gun.is.graph(g, function(node, soul){ + Gun.store.put(soul, node); + }); + cb(null); + }}}); + var t2 = perf.now(); + perf(function(i){ + var obj = {'i': i, 'v': Gun.text.random(100)}; + gun.put(obj); + }, it); + console.log('store: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); + root.localStorage && root.localStorage.clear(); + }()); + (function(){ // setTimeout + if(!Gun.store){ + var tab = Gun().tab; + if(!tab){ return } + Gun.store = tab.store; + } + root.localStorage && root.localStorage.clear(); + var t1 = perf.now(); + i = i || 1000; + while(--i){ + var obj = {'i': i, 'v': Gun.text.random(100)}; + Gun.store.put('test/native/' + i, obj); + } + console.log('store: native', (t1 = (perf.now() - t1)/1000) + 's'); + + root.localStorage && root.localStorage.clear(); + var gun = Gun({wire: {get:function(l,cb){cb()},put:function(g,cb){ + Gun.is.graph(g, function(node, soul){ + Gun.store.put(soul, node); + }); + cb(null); + }}}); + var t2 = perf.now(); + perf(function(i){ + var obj = {'i': i, 'v': Gun.text.random(100)}; + gun.put(obj); + }, it); + console.log('store: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); + root.localStorage && root.localStorage.clear(); + }()); + (function(){ + var t1 = perf.now(); + var on = Gun.on.create(), c = 0, o = []; + perf(function(i){ + o.push(function(n){ + c += 1; + }); + var ii = 0, l = o.length; + for(; ii < l; ii++){ + o[ii](i); + } + }); + console.log('on: native', (t1 = (perf.now() - t1)/1000) + 's'); + + var on = Gun.on.create(), c = 0; + var t2 = perf.now(); + perf(function(i){ + on('change').event(function(n){ + c += 1; + }); + on('change').emit(i); + }); + console.log('on: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); + }());return; + (function(){ // always do this last! + var t1 = perf.now(); + perf(function(i){ + setTimeout(function(){ + if(i === 1){ + cb1(); + } + },0); + }); var cb1 = function(){ + console.log('setTimeout: native', (t1 = (perf.now() - t1)/1000) + 's', (t1 / t2).toFixed(1)+'x', 'slower.'); + } + var t2 = perf.now(); + perf(function(i){ + setImmediate(function(){ + if(i === 1){ + cb2(); + } + }); + }); var cb2 = function(){ + console.log('setImmediate: gun', (t2 = (perf.now() - t2)/1000) + 's', (t2 / t1).toFixed(1)+'x', 'slower.'); + } + }()); +}); + +describe('Gun', function(){ + var t = {}; + describe('Utility', function(){ + var u; + /* // causes logger to no longer log. + it('verbose console.log debugging', function(done) { + + var gun = Gun(); + var log = root.console.log, counter = 1; + root.console.log = function(a,b,c){ + --counter; + //log(a,b,c); + } + Gun.log.verbose = true; + gun.put('bar', function(err, yay){ // intentionally trigger an error that will get logged. + expect(counter).to.be(0); + + Gun.log.verbose = false; + gun.put('bar', function(err, yay){ // intentionally trigger an error that will get logged. + expect(counter).to.be(0); + + root.console.log = log; + done(); + }); + }); + } ); + */ + + describe('Type Check', function(){ + it('binary', function(){ + expect(Gun.bi.is(false)).to.be(true); + expect(Gun.bi.is(true)).to.be(true); + expect(Gun.bi.is(u)).to.be(false); + expect(Gun.bi.is(null)).to.be(false); + expect(Gun.bi.is('')).to.be(false); + expect(Gun.bi.is('a')).to.be(false); + expect(Gun.bi.is(0)).to.be(false); + expect(Gun.bi.is(1)).to.be(false); + expect(Gun.bi.is([])).to.be(false); + expect(Gun.bi.is([1])).to.be(false); + expect(Gun.bi.is({})).to.be(false); + expect(Gun.bi.is({a:1})).to.be(false); + expect(Gun.bi.is(function(){})).to.be(false); + }); + it('number',function(){ + expect(Gun.num.is(0)).to.be(true); + expect(Gun.num.is(1)).to.be(true); + expect(Gun.num.is(Infinity)).to.be(true); + expect(Gun.num.is(u)).to.be(false); + expect(Gun.num.is(null)).to.be(false); + expect(Gun.num.is(NaN)).to.be(false); + expect(Gun.num.is('')).to.be(false); + expect(Gun.num.is('a')).to.be(false); + expect(Gun.num.is([])).to.be(false); + expect(Gun.num.is([1])).to.be(false); + expect(Gun.num.is({})).to.be(false); + expect(Gun.num.is({a:1})).to.be(false); + expect(Gun.num.is(false)).to.be(false); + expect(Gun.num.is(true)).to.be(false); + expect(Gun.num.is(function(){})).to.be(false); + }); + it('text',function(){ + expect(Gun.text.is('')).to.be(true); + expect(Gun.text.is('a')).to.be(true); + expect(Gun.text.is(u)).to.be(false); + expect(Gun.text.is(null)).to.be(false); + expect(Gun.text.is(false)).to.be(false); + expect(Gun.text.is(true)).to.be(false); + expect(Gun.text.is(0)).to.be(false); + expect(Gun.text.is(1)).to.be(false); + expect(Gun.text.is([])).to.be(false); + expect(Gun.text.is([1])).to.be(false); + expect(Gun.text.is({})).to.be(false); + expect(Gun.text.is({a:1})).to.be(false); + expect(Gun.text.is(function(){})).to.be(false); + }); + it('list',function(){ + expect(Gun.list.is([])).to.be(true); + expect(Gun.list.is([1])).to.be(true); + expect(Gun.list.is(u)).to.be(false); + expect(Gun.list.is(null)).to.be(false); + expect(Gun.list.is(0)).to.be(false); + expect(Gun.list.is(1)).to.be(false); + expect(Gun.list.is('')).to.be(false); + expect(Gun.list.is('a')).to.be(false); + expect(Gun.list.is({})).to.be(false); + expect(Gun.list.is({a:1})).to.be(false); + expect(Gun.list.is(false)).to.be(false); + expect(Gun.list.is(true)).to.be(false); + expect(Gun.list.is(function(){})).to.be(false); + }); + it('obj',function(){ + expect(Gun.obj.is({})).to.be(true); + expect(Gun.obj.is({a:1})).to.be(true); + expect(Gun.obj.is(u)).to.be(false); + expect(Gun.obj.is()).to.be(false); + expect(Gun.obj.is(undefined)).to.be(false); + expect(Gun.obj.is(null)).to.be(false); + expect(Gun.obj.is(NaN)).to.be(false); + expect(Gun.obj.is(0)).to.be(false); + expect(Gun.obj.is(1)).to.be(false); + expect(Gun.obj.is('')).to.be(false); + expect(Gun.obj.is('a')).to.be(false); + expect(Gun.obj.is([])).to.be(false); + expect(Gun.obj.is([1])).to.be(false); + expect(Gun.obj.is(false)).to.be(false); + expect(Gun.obj.is(true)).to.be(false); + expect(Gun.obj.is(function(){})).to.be(false); + expect(Gun.obj.is(new Date())).to.be(false); + expect(Gun.obj.is(/regex/)).to.be(false); + this.document && expect(Gun.obj.is(document.createElement('div'))).to.be(false); + expect(Gun.obj.is(new (function Class(){ this.x = 1; this.y = 2 })())).to.be(true); + }); + it('fns',function(){ + expect(Gun.fns.is(function(){})).to.be(true); + expect(Gun.fns.is(u)).to.be(false); + expect(Gun.fns.is(null)).to.be(false); + expect(Gun.fns.is('')).to.be(false); + expect(Gun.fns.is('a')).to.be(false); + expect(Gun.fns.is(0)).to.be(false); + expect(Gun.fns.is(1)).to.be(false); + expect(Gun.fns.is([])).to.be(false); + expect(Gun.fns.is([1])).to.be(false); + expect(Gun.fns.is({})).to.be(false); + expect(Gun.fns.is({a:1})).to.be(false); + expect(Gun.fns.is(false)).to.be(false); + expect(Gun.fns.is(true)).to.be(false); + }); + it('time',function(){ + t.ts = Gun.time.is(); + expect(13 <= t.ts.toString().length).to.be.ok(); + expect(Gun.num.is(t.ts)).to.be.ok(); + expect(Gun.time.is(new Date())).to.be.ok(); + }); + }); + describe('Text', function(){ + it('ify',function(){ + expect(Gun.text.ify(0)).to.be('0'); + expect(Gun.text.ify(22)).to.be('22'); + expect(Gun.text.ify([true,33,'yay'])).to.be('[true,33,"yay"]'); + expect(Gun.text.ify({a:0,b:'1',c:[0,'1'],d:{e:'f'}})).to.be('{"a":0,"b":"1","c":[0,"1"],"d":{"e":"f"}}'); + expect(Gun.text.ify(false)).to.be('false'); + expect(Gun.text.ify(true)).to.be('true'); + }); + it('random',function(){ + expect(Gun.text.random().length).to.be(24); + expect(Gun.text.random(11).length).to.be(11); + expect(Gun.text.random(4).length).to.be(4); + t.tr = Gun.text.random(2,'as'); expect((t.tr=='as'||t.tr=='aa'||t.tr=='sa'||t.tr=='ss')).to.be.ok(); + }); + 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', '<': '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(); + expect(Gun.text.match("user/mad/person", {'*': 'user/', '-': 'mad'})).to.not.be.ok(); + expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '-': ['mark', 'nadal']})).to.not.be.ok(); + expect(Gun.text.match("user/amber/rachel/cazzell", {'*': 'user/', '-': ['mark', 'nadal']})).to.be.ok(); + expect(Gun.text.match("user/mark/nadal", {'*': 'user/', '+': 'ark'})).to.be.ok(); + expect(Gun.text.match("user/mad/person", {'*': 'user/', '+': 'ark'})).to.not.be.ok(); + expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '+': ['mark', 'nadal']})).to.be.ok(); + expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '+': ['nadal', 'mark']})).to.be.ok(); + expect(Gun.text.match("user/mark/timothy/nadal", {'*': 'user/', '+': ['mark', 'amber']})).to.not.be.ok(); + expect(Gun.text.match("user/mark/rachel/nadal/cazzell", {'*': 'user/', '+': ['mark', 'cazzell'], '-': ['amber', 'timothy']})).to.be.ok(); + 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(); + }); + }); + describe('List', function(){ + it('slit',function(){ + (function(){ + expect(Gun.list.slit.call(arguments, 0)).to.eql([1,2,3,'a','b','c']); + }(1,2,3,'a','b','c')); + }); + it('sort',function(){ + expect([{i:9},{i:4},{i:1},{i:-3},{i:0}].sort(Gun.list.sort('i'))).to.eql([{i:-3},{i:0},{i:1},{i:4},{i:9}]); + }); + it('map',function(){ + expect(Gun.list.map([1,2,3,4,5],function(v,i,t){ t(v+=this.d); this.d=v; },{d:0})).to.eql([1,3,6,10,15]); + expect(Gun.list.map([2,3,0,4],function(v,i,t){ if(!v){ return } t(v*=this.d); this.d=v; },{d:1})).to.eql([2,6,24]); + expect(Gun.list.map([true,false,NaN,Infinity,'',9],function(v,i,t){ if(i===3){ return 0 }})).to.be(0); + }); + }); + describe('Object', function(){ + it('del',function(){ + var obj = {a:1,b:2}; + Gun.obj.del(obj,'a'); + expect(obj).to.eql({b:2}); + }); + it('has',function(){ + var obj = {a:1,b:2}; + expect(Gun.obj.has(obj,'a')).to.be.ok(); + }); + it('empty',function(){ + expect(Gun.obj.empty()).to.be(true); + expect(Gun.obj.empty({a:false})).to.be(false); + expect(Gun.obj.empty({a:false},'a')).to.be(true); + expect(Gun.obj.empty({a:false},{a:1})).to.be(true); + expect(Gun.obj.empty({a:false,b:1},'a')).to.be(false); + expect(Gun.obj.empty({a:false,b:1},{a:1})).to.be(false); + expect(Gun.obj.empty({a:false,b:1},{a:1,b:1})).to.be(true); + expect(Gun.obj.empty({a:false,b:1,c:3},{a:1,b:1})).to.be(false); + expect(Gun.obj.empty({1:1},'danger')).to.be(false); + }); + it('copy',function(){ + var obj = {"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}; + var copy = Gun.obj.copy(obj); + expect(copy).to.eql(obj); + expect(copy).to.not.be(obj); + }); + it('ify',function(){ + expect(Gun.obj.ify('[0,1]')).to.eql([0,1]); + expect(Gun.obj.ify('{"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}')).to.eql({"a":false,"b":1,"c":"d","e":[0,1],"f":{"g":"h"}}); + }); + it('map',function(){ + expect(Gun.obj.map({a:'z',b:'y',c:'x'},function(v,i,t){ t(v,i) })).to.eql({x:'c',y:'b',z:'a'}); + expect(Gun.obj.map({a:'z',b:false,c:'x'},function(v,i,t){ if(!v){ return } t(i,v) })).to.eql({a:'z',c:'x'}); + expect(Gun.obj.map({a:'z',b:3,c:'x'},function(v,i,t){ if(v===3){ return 0 }})).to.be(0); + }); + }); + describe('Functions', function(){ + /* + it.skip('sum',function(done){ // deprecate? + var obj = {a:2, b:2, c:3, d: 9}; + Gun.obj.map(obj, function(num, key){ + setTimeout(this.add(function(){ + this.done(null, num * num); + }, key), parseInt((""+Math.random()).substring(2,5))); + }, Gun.fns.sum(function(err, val){ + expect(val.a).to.eql(4); + expect(val.b).to.eql(4); + expect(val.c).to.eql(9); + expect(val.d).to.eql(81); + done(); + })); + }); + */ + }); + describe('On', function(){ + it('subscribe', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a){ + done.first = true; + expect(a).to.be(1); + this.to.next(a); + }); + e.on('foo', function(a){ + expect(a).to.be(1); + expect(done.first).to.be.ok(); + done(); + }); + e.on('foo', 1); + }); + it('unsubscribe', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a){ + this.off(); + done.first = a; + expect(a).to.be(1); + this.to.next(a); + }); + e.on('foo', function(a){ + var to = this; + expect(a).to.be(done.second? 2 : 1); + expect(done.first).to.be(1); + done.second = true; + if(a === 2){ + setTimeout(function(){ + expect(e.tag.foo.to === to).to.be.ok(); + done(); + }, 10); + } + }); + e.on('foo', 1); + e.on('foo', 2); + }); + it('stun', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + if(2 === a){ + done.first2 = true; + this.to.next(a); + return; + } + setTimeout(function(){ + expect(done.second).to.not.be.ok(); + expect(done.second2).to.be.ok(); + expect(done.first2).to.be.ok(); + done(); + },10); + }); + e.on('foo', function(a, ev){ + if(2 === a){ + done.second2 = true; + } else { + done.second = true; + } + }); + e.on('foo', 1); + e.on('foo', 2); + }); + it('resume', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + var to = this.to; + setTimeout(function(){ + expect(done.second).to.not.be.ok(); + to.next(a); + },10); + }); + e.on('foo', function(a){ + done.second = true; + expect(a).to.be(1); + done(); + }); + e.on('foo', 1); + }); + it('double resume', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + var to = this.to; + setTimeout(function(){ + if(1 === a){ + done.first1 = true; + expect(done.second).to.not.be.ok(); + } + if(2 === a){ + done.first2 = true; + } + to.next(a); + },10); + }); + e.on('foo', function(a, ev){ + done.second = true; + if(1 === a){ + expect(done.first2).to.not.be.ok(); + done.second1 = true; + } + if(2 === a){ + expect(done.first2).to.be.ok(); + if(done.second1){ + done(); + } + } + }); + e.on('foo', 1); + e.on('foo', 2); + }); + it('double resume different event', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + var to = this.to; + setTimeout(function(){ + done.first1 = true; + to.next(a); + },10); + }); + e.on('foo', function(a){ + if(1 === a){ + expect(done.first1).to.be.ok(); + done(); + } + }); + e.on('foo', 1); + e.on('bar', 2); + }); + it('resume params', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + var to = this.to; + setTimeout(function(){ + expect(done.second).to.not.be.ok(); + to.next(0); + },10); + }); + e.on('foo', function(a){ + done.second = true; + expect(a).to.be(0); + done(); + }); + e.on('foo', 1); + }); + it('map', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + var to = this.to; + Gun.obj.map(a.it, function(v,f){ + setTimeout(function(){ + var emit = {field: 'where', soul: f}; + to.next(emit); + },10); + }) + }); + e.on('foo', function(a, ev){ + var to = this.to; + setTimeout(function(){ + to.next({node: a.soul}); + },100); + }); + e.on('foo', function(a){ + if('a' == a.node){ + done.a = true; + } else { + expect(done.a).to.be.ok(); + done(); + } + }); + e.on('foo', {field: 'where', it: {a: 1, b: 2}}); + }); + it('map synchronous', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a, ev){ + var to = this.to; + Gun.obj.map(a.node, function(v,f){ + //setTimeout(function(){ + var emit = {field: 'where', soul: f}; + to.next(emit); + //},10); + }) + }); + e.on('foo', function(a, ev){ + var to = this.to; + setTimeout(function(){ + to.next({node: a.soul}); + },100); + }); + e.on('foo', function(a){ + expect(this.as.hi).to.be(1); + if('a' == a.node){ + done.a = true; + } else { + expect(done.a).to.be.ok(); + done(); + } + }, {hi: 1}).on.on('foo', {field: 'where', node: {a: 1, b: 2}}); + }); + it('synchronous async', function(done){ + var e = {on: Gun.on}; + e.on('foo', function(a){ + expect(a.b).to.be(5); + done.first = true; + this.to.next(a); + }); + e.on('foo', function(a, ev){ + expect(a.b).to.be(5); + done.second = true; + var to = this.to; + setTimeout(function(){ + to.next({c: 9, again: a.again}); + },100); + }); + e.on('foo', function(a){ + this.off(); + expect(a.again).to.not.be.ok(); + expect(a.c).to.be(9); + expect(done.first).to.be.ok(); + expect(done.second).to.be.ok(); + done(); + }).on.on('foo', {b: 5}).on.on('foo', {b:5, again: true}); + }); + }); + describe('flow', function(){ + var i = 0; + function flow(){ + var f = function(arg){ + var cb = f.cb? f.cb.fn : f.fn; + if(cb){ + f.cb = cb; + var ff = flow(); + ff.f = f; + cb(ff); + return; + } + if(f.f){ + f.f(arg); + f.cb = 0; + return; + } + }, cb; + f.flow = function(fn){ + cb = (cb || f).fn = fn; + return f; + }; + return f; + } + it('intermittent interruption', function(done){ + var f = flow(); + //var f = {flow: flow} + f.flow(function(f){ + //console.log(1); + f.flow(function(f){ + //console.log(2); + f({yes: 'please'}); + }); + setTimeout(function(){ + f.flow(function(f){ + //console.log(2.1); + f({forever: 'there'}); + }); + f({strange: 'places'}); + //console.log("-----"); + f({earlier: 'location'}); + },100); + }); + f.flow(function(f){ + //console.log(3); + f({ok: 'now'}); + }); + f.flow(function(f){ + //console.log(4); + done(); + }); + setTimeout(function(){ + f({hello: 'world'}); + }, 100); + }); + var i = 0; + ;(function(exports){ + function next(arg){ var n = this; + if(arg instanceof Function){ + if(!n.fn){ return n.fn = arg, n } + var f = {next: next, fn: arg, first: n.first || n}; + n.last = (n.last || n).to = f; + return n; + } + if(n.fn){ + var sub = {next: next, from: n.to || (n.first || {}).from}; + n.fn(sub); + return; + } + if(n.from){ + n.from.next(arg); + return; + } + } + exports.next = next; + }(Gun)); + it('intermittent interruptions', function(done){ + //var f = flow(); + var f = {next: Gun.next}; // for now + f.next(function(f){ + //console.log(1, f); + f.next(function(f){ + //console.log(2, f); + f.next({yes: 'please'}); + }); + setTimeout(function(){ + f.next(function(f){ + //console.log(2.1, f); + f.next({forever: 'there'}); + }); + f.next({strange: 'places'}); + //console.log("-----"); + f.next({earlier: 'location'}); + },100); + }); + f.next(function(f){ + //console.log(3); + f.next({ok: 'now'}); + }); + f.next(function(f){ + //console.log(4); + if(!done.a){ return done.a = true } + done(); + }); + setTimeout(function(){ + f.next({hello: 'world'}); + }, 100); + }); + }); + 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); + expect(Gun.is(true)).to.be(false); + expect(Gun.is(false)).to.be(false); + expect(Gun.is(0)).to.be(false); + expect(Gun.is(1)).to.be(false); + expect(Gun.is('')).to.be(false); + expect(Gun.is('a')).to.be(false); + expect(Gun.is(Infinity)).to.be(false); + expect(Gun.is(NaN)).to.be(false); + expect(Gun.is([])).to.be(false); + expect(Gun.is([1])).to.be(false); + expect(Gun.is({})).to.be(false); + expect(Gun.is({a:1})).to.be(false); + expect(Gun.is(function(){})).to.be(false); + }); + it('is value',function(){ + expect(Gun.val.is(false)).to.be(true); + expect(Gun.val.is(true)).to.be(true); + expect(Gun.val.is(0)).to.be(true); + expect(Gun.val.is(1)).to.be(true); + expect(Gun.val.is('')).to.be(true); + expect(Gun.val.is('a')).to.be(true); + expect(Gun.val.is({'#':'somesoulidhere'})).to.be('somesoulidhere'); + expect(Gun.val.is({'#':'somesoulidhere', and: 'nope'})).to.be(false); + expect(Gun.val.is(Infinity)).to.be(false); // boohoo :( + expect(Gun.val.is(NaN)).to.be(false); + expect(Gun.val.is([])).to.be(false); + expect(Gun.val.is([1])).to.be(false); + expect(Gun.val.is({})).to.be(false); + expect(Gun.val.is({a:1})).to.be(false); + expect(Gun.val.is(function(){})).to.be(false); + }); + it('is rel',function(){ + expect(Gun.val.rel.is({'#':'somesoulidhere'})).to.be('somesoulidhere'); + expect(Gun.val.rel.is({'#':'somethingelsehere'})).to.be('somethingelsehere'); + expect(Gun.val.rel.is({'#':'somesoulidhere', and: 'nope'})).to.be(false); + expect(Gun.val.rel.is({or: 'nope', '#':'somesoulidhere'})).to.be(false); + expect(Gun.val.rel.is(false)).to.be(false); + expect(Gun.val.rel.is(true)).to.be(false); + expect(Gun.val.rel.is('')).to.be(false); + expect(Gun.val.rel.is('a')).to.be(false); + expect(Gun.val.rel.is(0)).to.be(false); + expect(Gun.val.rel.is(1)).to.be(false); + expect(Gun.val.rel.is(Infinity)).to.be(false); // boohoo :( + expect(Gun.val.rel.is(NaN)).to.be(false); + expect(Gun.val.rel.is([])).to.be(false); + expect(Gun.val.rel.is([1])).to.be(false); + expect(Gun.val.rel.is({})).to.be(false); + expect(Gun.val.rel.is({a:1})).to.be(false); + expect(Gun.val.rel.is(function(){})).to.be(false); + }); + it.skip('is lex',function(){ + expect(Gun.is.lex({'#': 'soul'})).to.eql({soul: 'soul'}); + expect(Gun.is.lex({'.': 'field'})).to.eql({field: 'field'}); + expect(Gun.is.lex({'=': 'value'})).to.eql({value: 'value'}); + expect(Gun.is.lex({'>': 'state'})).to.eql({state: 'state'}); + expect(Gun.is.lex({'#': {'=': 'soul'}})).to.eql({soul: {'=': 'soul'}}); + expect(Gun.is.lex({'#': {'=': 'soul'}, '.': []})).to.be(false); + expect(Gun.is.lex({'#': {'=': 'soul'}, 'asdf': 'oye'})).to.be(false); + expect(Gun.is.lex()).to.be(false); + expect(Gun.is.lex('')).to.be(false); + }); + it.skip('is lex ify',function(){ + expect(Gun.is.lex.ify({'#': 'soul', '.': 'field', soul: 'foo', field: 'field', state: 0})).to.eql({'#': 'soul', '.': 'field', '>': 0}); + }); + it('is node',function(){ + var n; + expect(Gun.node.is({_:{'#':'somesoulidhere'}})).to.be(true); + expect(Gun.node.is(n = {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}})).to.be(true); + expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}, g: Infinity})).to.be(false); + expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, z: NaN, c: '', d: 'e'})).to.be(false); + expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, y: {_: 'cool'}, c: '', d: 'e'})).to.be(false); + expect(Gun.node.is({_:{'#':'somesoulidhere'}, a:0, b: 1, x: [], c: '', d: 'e'})).to.be(false); + expect(Gun.node.is({})).to.be(false); + expect(Gun.node.is({a:1})).to.be(false); + expect(Gun.node.is({_:{}})).to.be(false); + expect(Gun.node.is({_:{}, a:1})).to.be(false); + expect(Gun.node.is({'#':'somesoulidhere'})).to.be(false); + Gun.node.is(n, function(v,f){ + //console.log("v/f", v,f); + }); + }); + it('is graph',function(){ + var g; + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}}})).to.be(true); + expect(Gun.graph.is(g = {'somesoulidhere': {_:{'#':'somesoulidhere'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(true); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(true); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}})).to.be(true); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: 1, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: {}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: {_:{'#':'FOO'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}}, foo: {_:{}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: Infinity, c: '', d: 'e', f: {'#':'somethingelsehere'}}})).to.be(false); + expect(Gun.graph.is({'somesoulidhere': {_:{'#':'somesoulidhere'}, a:0, b: Infinity, c: '', d: 'e', f: {'#':'somethingelsehere'}}, 'somethingelsehere': {_:{'#':'somethingelsehere'}}})).to.be(false); + expect(Gun.graph.is({_:{'#':'somesoulidhere'}})).to.be(false); + expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}})).to.be(false); + expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, c: '', d: 'e', f: {'#':'somethingelsehere'}, g: Infinity})).to.be(false); + expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, z: NaN, c: '', d: 'e'})).to.be(false); + expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, y: {_: 'cool'}, c: '', d: 'e'})).to.be(false); + expect(Gun.graph.is({_:{'#':'somesoulidhere'}, a:0, b: 1, x: [], c: '', d: 'e'})).to.be(false); + expect(Gun.graph.is({})).to.be(false); // Empty graph is not a graph :( + expect(Gun.graph.is({a:1})).to.be(false); + expect(Gun.graph.is({_:{}})).to.be(false); + expect(Gun.graph.is({_:{}, a:1})).to.be(false); + expect(Gun.graph.is({'#':'somesoulidhere'})).to.be(false); + Gun.graph.is(g, function(n,s){ + //console.log("node/soul", n,s); + }); + }); + it('graph ify', function(done){ + function map(v,f,n){ + done.m = true; + } + var graph = Gun.graph.ify({ + _: {'#': 'yay'}, + a: 1 + }, map); + expect(graph).to.eql({ + yay: { + _: {'#': 'yay'}, + a: 1 + } + }); + expect(done.m).to.be.ok(); + var graph = Gun.graph.ify({ + _: {'#': 'yay', '>': {a: 9}}, + a: 1 + }, map); + expect(graph).to.eql({ + yay: { + _: {'#': 'yay', '>': {a: 9}}, + a: 1 + } + }); + var map = Gun.state.map(map, 9); + var graph = Gun.graph.ify({ + _: {'#': 'yay', '>': {a: 1}}, + a: 1 + }, map); + expect(graph).to.eql({ + yay: { + _: {'#': 'yay', '>': {a: 9}}, + a: 1 + } + }); + var graph = Gun.graph.ify({a:1}); + Gun.obj.map(graph, function(node){ + expect(node._['#']).to.be.ok(); + }); + + var alice = {_:{'#':'ASDF'}, age: 27, name: "Alice"}; + var bob = {_:{'#':'DASF'}, age: 29, name: "Bob"}; + var cat = {_:{'#':'FDSA'}, name: "Fluffy", species: "kitty"}; + alice.spouse = bob; + bob.spouse = alice; + alice.pet = cat; + cat.slave = bob; + cat.master = alice; + var graph = Gun.graph.ify(bob); + expect(graph).to.eql({ + 'ASDF': {_:{'#':'ASDF'}, + age: 27, + name: "Alice", + spouse: {'#':'DASF'}, + pet: {'#':'FDSA'} + }, + 'DASF': {_:{'#':'DASF'}, + age: 29, + name: 'Bob', + spouse: {'#':'ASDF'}, + }, + 'FDSA': {_:{'#':'FDSA'}, + name: "Fluffy", + species: "kitty", + slave: {'#':'DASF'}, + master: {'#':'ASDF'} + } + }); + + done(); + }); + }); + }); + describe('ify', function(){ + console.log("TODO: BUG! Upgrade IFY tests to new internal API!"); + return; + + var test, gun = Gun(); + + it('null', function(done){ + Gun.ify(null, function(err, ctx){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('basic', function(done){ + var data = {a: false, b: true, c: 0, d: 1, e: '', f: 'g', h: null}; + Gun.ify(data, function(err, ctx){ + expect(err).to.not.be.ok(); + expect(ctx.err).to.not.be.ok(); + expect(ctx.root).to.eql(data); + expect(ctx.root === data).to.not.ok(); + done(); + }, {pure: true}); + }); + + it('basic soul', function(done){ + var data = {_: {'#': 'SOUL'}, a: false, b: true, c: 0, d: 1, e: '', f: 'g', h: null}; + Gun.ify(data, function(err, ctx){ + expect(err).to.not.be.ok(); + expect(ctx.err).to.not.be.ok(); + + expect(ctx.root).to.eql(data); + expect(ctx.root === data).to.not.be.ok(); + expect(Gun.node.soul(ctx.root) === Gun.node.soul(data)); + done(); + }, {pure: true}); + }); + + it('arrays', function(done){ + var data = {before: {path: 'kill'}, one: {two: {lol: 'troll', three: [9, 8, 7, 6, 5]}}}; + Gun.ify(data, function(err, ctx){ + expect(err).to.be.ok(); + expect((err.err || err).indexOf("one.two.three")).to.not.be(-1); + done(); + }); + }); + + it('undefined', function(done){ + var data = {z: undefined, x: 'bye'}; + Gun.ify(data, function(err, ctx){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('NaN', function(done){ + var data = {a: NaN, b: 2}; + Gun.ify(data, function(err, ctx){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('Infinity', function(done){ // SAD DAY PANDA BEAR :( :( :(... Mark wants Infinity. JSON won't allow. + var data = {a: 1, b: Infinity}; + Gun.ify(data, function(err, ctx){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('function', function(done){ + var data = {c: function(){}, d: 'hi'}; + Gun.ify(data, function(err, ctx){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('extraneous', function(done){ + var data = {_: {'#': 'shhh', meta: {yay: 1}}, sneak: true}; + Gun.ify(data, function(err, ctx){ + expect(err).to.not.be.ok(); // extraneous metadata needs to be stored, but it can't be used for data. + done(); + }); + }); + + it('document', function(done){ + var data = {users: {1: {where: {lat: Math.random(), lng: Math.random(), i: 1}}}}; + Gun.ify(data, function(err, ctx){ + var soul, node; + expect(soul = Gun.val.rel.is(ctx.root.users)).to.be.ok(); + node = ctx.graph[soul]; + expect(soul = Gun.val.rel.is(node[1])).to.be.ok(); + node = ctx.graph[soul]; + expect(soul = Gun.val.rel.is(node.where)).to.be.ok(); + node = ctx.graph[soul]; + expect(node.lat).to.be.ok(); + expect(node.lng).to.be.ok(); + expect(node.i).to.be(1); + done(); + }); + }); + + return; // TODO! Fix GUN to handle this! + data = {}; + data.sneak = false; + data.both = {inside: 'meta data'}; + data._ = {'#': 'shhh', data: {yay: 1}, spin: data.both}; + test = Gun.ify(data); + expect(test.err.meta).to.be.ok(); // TODO: Fail: this passes, somehow? Fix ify code! + }); + + describe('Schedule', function(){ + console.log("TODO: BUG! Upgrade SCHEDULE tests to new internal API!"); + return; + it('one', function(done){ + Gun.schedule(Gun.time.is(), function(){ + expect(true).to.be(true); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('many', function(done){ + Gun.schedule(Gun.time.is() + 50, function(){ + done.first = true; + }); + Gun.schedule(Gun.time.is() + 100, function(){ + done.second = true; + }); + Gun.schedule(Gun.time.is() + 200, function(){ + done.third = true; + expect(done.first).to.be(true); + expect(done.second).to.be(true); + expect(done.third).to.be(true); + done(); //setTimeout(function(){ done() },1); + }); + }); + }); + + describe('Union', function(){ + console.log("TODO: BUG! Upgrade UNION tests to new internal API!"); + return; + var gun = Gun(); + + it('fail', function(){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + a: 'cheating' + }}, + a: 0 + } + } + + expect(gun.__.graph['asdf']).to.not.be.ok(); + var ctx = Gun.HAM.graph(gun, prime); + expect(ctx).to.not.be.ok(); + });return; + + it('basic', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + a: Gun.time.is() + }}, + a: 0 + } + } + + expect(gun.__.graph['asdf']).to.not.be.ok(); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['asdf'].a).to.be(0); + done(); + }); + }); + + it('disjoint', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + b: Gun.time.is() + }}, + b: 'c' + } + } + + expect(gun.__.graph['asdf'].a).to.be(0); + expect(gun.__.graph['asdf'].b).to.not.be.ok(); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['asdf'].a).to.be(0); + expect(gun.__.graph['asdf'].b).to.be('c'); + done(); + }); + }); + + it('mutate', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + b: Gun.time.is() + }}, + b: 'd' + } + } + + expect(gun.__.graph['asdf'].b).to.be('c'); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['asdf'].b).to.be('d'); + done(); + }); + }); + + it('disjoint past', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + x: 0 // beginning of time! + }}, + x: 'hi' + } + } + expect(gun.__.graph['asdf'].x).to.not.be.ok(); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['asdf'].x).to.be('hi'); + done(); + }); + }); + + it('past', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + x: Gun.time.is() - (60 * 1000) // above lower boundary, below now or upper boundary. + }}, + x: 'hello' + } + } + + expect(gun.__.graph['asdf'].x).to.be('hi'); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['asdf'].x).to.be('hello'); + done(); + }); + }); + + it('future', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + x: Gun.time.is() + (200) // above now or upper boundary, aka future. + }}, + x: 'how are you?' + } + } + + expect(gun.__.graph['asdf'].x).to.be('hello'); + var now = Gun.time.is(); + var ctx = Gun.union(gun, prime, function(){ + expect(Gun.time.is() - now).to.be.above(100); + expect(gun.__.graph['asdf'].x).to.be('how are you?'); + done(); + }); + }); + var to = 5000; + it('disjoint future', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + y: Gun.time.is() + (200) // above now or upper boundary, aka future. + }}, + y: 'goodbye' + } + } + expect(gun.__.graph['asdf'].y).to.not.be.ok(); + var now = Gun.time.is(); + var ctx = Gun.union(gun, prime, function(){ + expect(Gun.time.is() - now).to.be.above(100); + expect(gun.__.graph['asdf'].y).to.be('goodbye'); + done(); + }); + }); + + it('disjoint future max', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + y: Gun.time.is() + (2), // above now or upper boundary, aka future. + z: Gun.time.is() + (200) // above now or upper boundary, aka future. + }}, + y: 'bye', + z: 'who' + } + } + + expect(gun.__.graph['asdf'].y).to.be('goodbye'); + expect(gun.__.graph['asdf'].z).to.not.be.ok(); + var now = Gun.time.is(); + var ctx = Gun.union(gun, prime, function(){ + expect(Gun.time.is() - now).to.be.above(100); + expect(gun.__.graph['asdf'].y).to.be('bye'); + expect(gun.__.graph['asdf'].z).to.be('who'); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('future max', function(done){ + var prime = { + 'asdf': { + _: {'#': 'asdf', '>':{ + w: Gun.time.is() + (2), // above now or upper boundary, aka future. + x: Gun.time.is() - (60 * 1000), // above now or upper boundary, aka future. + y: Gun.time.is() + (200), // above now or upper boundary, aka future. + z: Gun.time.is() + (50) // above now or upper boundary, aka future. + }}, + w: true, + x: 'nothing', + y: 'farewell', + z: 'doctor who' + } + } + + expect(gun.__.graph['asdf'].w).to.not.be.ok(); + expect(gun.__.graph['asdf'].x).to.be('how are you?'); + expect(gun.__.graph['asdf'].y).to.be('bye'); + expect(gun.__.graph['asdf'].z).to.be('who'); + var now = Gun.time.is(); + var ctx = Gun.union(gun, prime, function(){ + expect(Gun.time.is() - now).to.be.above(100); + expect(gun.__.graph['asdf'].w).to.be(true); + expect(gun.__.graph['asdf'].x).to.be('how are you?'); + expect(gun.__.graph['asdf'].y).to.be('farewell'); + expect(gun.__.graph['asdf'].z).to.be('doctor who'); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('two nodes', function(done){ // chat app problem where disk dropped the last data, turns out it was a union problem! + var state = Gun.time.is(); + var prime = { + 'sadf': { + _: {'#': 'sadf', '>':{ + 1: state + }}, + 1: {'#': 'fdsa'} + }, + 'fdsa': { + _: {'#': 'fdsa', '>':{ + msg: state + }}, + msg: "Let's chat!" + } + } + + expect(gun.__.graph['sadf']).to.not.be.ok(); + expect(gun.__.graph['fdsa']).to.not.be.ok(); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['sadf'][1]).to.be.ok(); + expect(gun.__.graph['fdsa'].msg).to.be("Let's chat!"); + done(); + }); + }); + + it('append third node', function(done){ // chat app problem where disk dropped the last data, turns out it was a union problem! + var state = Gun.time.is(); + var prime = { + 'sadf': { + _: {'#': 'sadf', '>':{ + 2: state + }}, + 2: {'#': 'fads'} + }, + 'fads': { + _: {'#': 'fads', '>':{ + msg: state + }}, + msg: "hi" + } + } + + expect(gun.__.graph['sadf']).to.be.ok(); + expect(gun.__.graph['fdsa']).to.be.ok(); + var ctx = Gun.union(gun, prime, function(){ + expect(gun.__.graph['sadf'][1]).to.be.ok(); + expect(gun.__.graph['sadf'][2]).to.be.ok(); + expect(gun.__.graph['fads'].msg).to.be("hi"); + done(); + }); + }); + + it('ify null', function(){ + var node = Gun.union.ify(null, 'pseudo'); + expect(Gun.node.soul(node)).to.be('pseudo'); + }); + + it('ify node', function(){ + + var graph = { + 'asdf': { + _: {'#': 'asdf', '>': { + x: Gun.time.is(), + y: Gun.time.is() + }}, + x: 1, + y: 2 + }, + 'soul': { + _: {'#': 'soul', '~': 1, '>': { + 'asdf': Gun.time.is() + }}, + 'asdf': {'#': 'asdf'} + } + } + var node = Gun.union.ify(graph, 'soul'); + expect(Gun.node.soul(node)).to.be('soul'); + expect(node.x).to.be(1); + expect(node.y).to.be(2); + }); + + it('ify graph', function(){ + var graph = { + 'asdf': { + _: {'#': 'asdf', '>': { + a: Gun.time.is() - 2, + z: Gun.time.is() - 2 + }}, + a: 1, + z: 1 + }, + 'fdsa': { + _: {'#': 'fdsa', '>': { + b: Gun.time.is() - 1, + z: Gun.time.is() - 1 + }}, + b: 2, + z: 2 + }, + 'sadf': { + _: {'#': 'sadf', '>': { + c: Gun.time.is(), + z: Gun.time.is() - 100 + }}, + c: 3, + z: 3 + }, + 'soul': { + _: {'#': 'soul', '~': 1, '>': { + 'asdf': Gun.time.is(), + 'fdsa': Gun.time.is(), + 'sadf': Gun.time.is() + }}, + 'asdf': {'#': 'asdf'}, + 'fdsa': {'#': 'fdsa'}, + 'sadf': {'#': 'sadf'} + } + } + var node = Gun.union.ify(graph, 'soul'); + expect(Gun.node.soul(node)).to.be('soul'); + expect(node.a).to.be(1); + expect(node.b).to.be(2); + expect(node.c).to.be(3); + expect(node.z).to.be(2); + }); + }); + + !Gun.SEA && describe('API', function(){ + var gopt = {wire:{put:function(n,cb){cb()},get:function(k,cb){cb()}}}; + var gun = Gun(); + + it.skip('gun chain separation', function(done){ // TODO: UNDO! + var gun = Gun(); + + var c1 = gun.put({hello: 'world'}); + + var c2 = gun.put({hi: 'earth'}); + + c1.on(function(val){ + expect(val.hi).to.not.be.ok(); + }); + + c2.on(function(val){ + expect(val.hello).to.not.be.ok(); + if(done.c){ return } + done(); done.c = 1; + }); + }); + + describe.skip('timeywimey', function(){ // TODO: UNDO! + + it('kitty', function(done){ + var g1 = gun.put({hey: 'kitty'}).key('timeywimey/kitty'); + + var g2 = gun.get('timeywimey/kitty').on(function(val){ + delete val._; + //console.log("kitty?", val); + expect(val.hey).to.be('kitty'); + expect(val.hi).to.not.be.ok(); + expect(val.hello).to.not.be.ok(); + expect(val.foo).to.not.be.ok(); + if(done.c){ return } + done(); done.c = 1; + }); + }); + + it('kitty puppy', function(done){ + var g3 = gun.put({hey: 'kitty'}).key('timeywimey/kitty/puppy'); + + var g4 = gun.put({hi: 'puppy'}).key('timeywimey/kitty/puppy'); + + var g5 = gun.get('timeywimey/kitty/puppy').on(function(val){ + //delete val._; + //console.log("puppy?", val); + expect(val.hey).to.be('kitty'); + expect(val.hi).to.be('puppy'); + if(done.c){ return } + done(); done.c = 1; + }); + }); + + it('hello', function(done){ + gun.get('timeywimey/hello').on(function(val){ + //delete val._; + //console.log("hello?", val); + expect(val.hello).to.be('world'); + if(done.c){ return } + done(); done.c = 1; + }); + + gun.put({hello: 'world'}).key('timeywimey/hello'); + }); + + it('hello foo', function(done){ + gun.get('timeywimey/hello/foo').on(function(val){ + //delete val._; + expect(val.hello).to.be('world'); + if(val.foo){ + expect(val.foo).to.be('bar'); + if(done.c){ return } + done(); done.c = 1; + } + }); + + gun.put({hello: 'world'}).key('timeywimey/hello/foo'); + + gun.put({foo: 'bar'}).key('timeywimey/hello/foo'); + }); + + it('all', function(done){ + gun.put({hey: 'kitty'}).key('timeywimey/all'); + + gun.put({hi: 'puppy'}).key('timeywimey/all'); + + gun.get('timeywimey/all').on(function(val){ + // console.log('all', done.c, val); + expect(val.hey).to.be('kitty'); + expect(val.hi).to.be('puppy'); + if(val.hello){ + expect(val.hello).to.be('world'); + done.hello = true; + } + if(val.foo){ + expect(val.foo).to.be('bar'); + if(done.c || !done.hello){ return } + done(); done.c = 1; + } + }); + + gun.put({hello: 'world'}).key('timeywimey/all'); + + gun.put({foo: 'bar'}).key('timeywimey/all'); + }); + + }); + + describe('plural chains', function(){ + this.timeout(5000); + it('uncached synchronous map on', function(done){ + /* + Biggest challenges so far: + - Unsubscribe individual mapped next. ! + - Performance deduplication on asking relation's next. ! + - Replying immediately to parent cached contexts. + - Performant read lock on write contexts. + - Proxying event across maps. + */ + var s = Gun.state.map();s.soul = 'u/m'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 26, + name: "Alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "Bob!", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m').map().on(function(v,f){ + check[f] = v; + count[f] = (count[f] || 0) + 1; + //console.log("***********", f, v); + if(check.alice && check.bob){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice.age).to.be(26); + expect(check.alice.name).to.be('Alice'); + expect(Gun.val.rel.is(check.alice.pet)).to.be.ok(); + //expect(count.alice).to.be(1); + expect(check.bob.age).to.be(29); + expect(check.bob.name).to.be('Bob!'); + expect(Gun.val.rel.is(check.bob.pet)).to.be.ok(); + //expect(count.bob).to.be(1); + done(); + },10); + } + }); + }); + + it('uncached synchronous map get on', function(done){ + var s = Gun.state.map();s.soul = 'u/m/p'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 26, + name: "alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/p').map().get('name').on(function(v,f){ + //console.log("*****************", f, v); + check[v] = f; + count[v] = (count[v] || 0) + 1; + if(check.alice && check.bob){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice).to.be('name'); + expect(check.bob).to.be('name'); + //expect(count.alice).to.be(1); + //expect(count.bob).to.be(1); + done(); + },10); + } + }); + }); + + it('uncached synchronous map get on node', function(done){ + var s = Gun.state.map();s.soul = 'u/m/p/n'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 26, + name: "alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/p/n').map().get('pet').on(function(v,f){ + //console.log("********************", f,v); + check[v.name] = v; + count[v.name] = (count[v.name] || 0) + 1; + if(check.Fluffy && check.Frisky){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.Fluffy.a).to.be(1); + expect(check.Frisky.b).to.be(2); + //expect(count.Fluffy).to.be(1); + //expect(count.Frisky).to.be(1); + //expect(count['undefined']).to.not.be.ok(); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + }); + + it('uncached synchronous map get on node get', function(done){ + var gun = Gun(); + var s = Gun.state.map();s.soul = 'u/m/p/n/p'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 26, + name: "alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + //console.debug.i=1;console.log('-------------------'); + gun.get('u/m/p/n/p').map().get('pet').get('name').on(function(v,f){ + check[v] = f; + count[v] = (count[v] || 0) + 1; + //console.log("*****************", f, v); + if(check.Fluffy && check.Frisky){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.Fluffy).to.be('name'); + expect(check.Frisky).to.be('name'); + Gun.obj.map(gun._.graph, function(n,s){ + if('u/m/p/n/p' === s){ return } + var a = Gun.obj.map(n, function(v,f,t){t(v)}); + expect(a.length).to.be(2); // make sure that ONLY the selected properties were loaded, not the whole node. + }); + //expect(count.Fluffy).to.be(1); + //expect(count.Frisky).to.be(1); + done(); + },10); + } + }); + }); + + it('uncached synchronous map on mutate', function(done){ + var s = Gun.state.map();s.soul = 'u/m/mutate'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 26, + name: "Alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "Bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/mutate').map().get('name').get(function(at,ev){ + var e = at.err, v = at.put, f = at.get; + //console.log("****************", f,v); + check[v] = f; + count[v] = (count[v] || 0) + 1; + if(check.Alice && check.Bob && check['undefined']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + //expect(count.Alice).to.be(1); + //expect(count.Bob).to.be(1); + //expect(count['undefined']).to.be(1); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('u/m/mutate').get('alice').put(7); + }, 300); + }); + + it('uncached synchronous map on mutate node', function(done){ + var s = Gun.state.map();s.soul = 'u/m/mutate/n'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: {_:{'#':'umaliceo'}, + age: 26, + name: "Alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "Bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/mutate/n').map().get('name').get(function(at,ev){ + var e = at.err, v = at.put, f = at.get; + check[v] = f; + count[v] = (count[v] || 0) + 1; + //console.log("************", f,v); + if(check.Alice && check.Bob && check['undefined'] && check['Alice Zzxyz']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(done.last).to.be.ok(); + expect(check['Alice Aabca']).to.not.be.ok(); + //expect(count.Alice).to.be(1); + //expect(count.Bob).to.be(1); + //expect(count['undefined']).to.be(1); + //expect(count['Alice Zzxyz']).to.be(1); + done(); + },200); + } + }); + setTimeout(function(){ + //console.debug.i=1;console.log("-----------------------"); + gun.get('u/m/mutate/n').get('alice').put({ + _:{'#':'u/m/m/n/soul'}, + name: 'Alice Zzxyz' + }); + setTimeout(function(){ + gun.get('umaliceo').put({ + name: 'Alice Aabca' + }); + done.last = true; + }, 10); + }, 300); + }); + + it('uncached synchronous map on mutate node uncached', function(done){ + var s = Gun.state.map();s.soul = 'u/m/mutate/n/u'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: {_:{'#':'umaliceo1'}, + age: 26, + name: "Alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "Bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/mutate/n/u').map().on(function(v,f){ + check[v.name] = f; + count[v.name] = (count[v.name] || 0) + 1; + //console.log("*****************", f,v); + if(check.Alice && check.Bob && check['Alice Zzxyz']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(done.last).to.be.ok(); + //expect(check['Alice Aabca']).to.not.be.ok(); + //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); + } + }); + setTimeout(function(){ + var s = Gun.state.map();s.soul = 'u/m/m/n/u/soul'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + name: 'Alice Zzxyz' + }, s)}); + //console.debug.i=1;console.log("---------------"); + gun.get('u/m/mutate/n/u').put({ + alice: {'#':'u/m/m/n/u/soul'}, + }); + /* + { + users: {_:#users + alice: {#newalice} + } + } + */ + setTimeout(function(){ + gun.get('umaliceo1').put({ + name: 'Alice Aabca' + }); + done.last = true; + }, 10); + }, 300); + }); + + it('uncached synchronous map on get mutate node uncached', function(done){ + var s = Gun.state.map();s.soul = 'u/m/p/mutate/n/u'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: {_:{'#':'umaliceo2'}, + age: 26, + name: "Alice", + pet: {a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "Bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/p/mutate/n/u').map().get('name').on(function(v,f){ + check[v] = f; + count[v] = (count[v] || 0) + 1; + //console.log("*************", f,v); + if(check.Alice && check.Bob && check['Alice Zzxyz']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + var a = Gun.obj.map(gun._.graph['u/m/p/m/n/u/soul'], function(v,f,t){t(v)}); + expect(a.length).to.be(2); + expect(done.last).to.be.ok(); + expect(check['Alice Aabca']).to.not.be.ok(); + //expect(count.Alice).to.be(1); + //expect(count.Bob).to.be(1); + //expect(count['Alice Zzxyz']).to.be(1); + done(); + },200); + } + }); + setTimeout(function(){ + var s = Gun.state.map();s.soul = 'u/m/p/m/n/u/soul'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + name: 'Alice Zzxyz', age: 34 + }, s)}); + gun.get('u/m/p/mutate/n/u').put({ + alice: {'#':'u/m/p/m/n/u/soul'}, + }); + setTimeout(function(){ + gun.get('umaliceo2').put({ + name: 'Alice Aabca' + }); + done.last = true; + }, 10); + }, 300); + }); + + it('uncached synchronous map on get node mutate node uncached', function(done){ + var s = Gun.state.map();s.soul = 'u/m/p/n/mutate/n/u'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: {_:{'#':'umaliceo3'}, + age: 26, + name: "Alice", + pet: {_:{'#':'sflufso'},a:1, name: "Fluffy"} + }, + bob: { + age: 29, + name: "Bob", + pet: {b:2, name: "Frisky"} + } + }, s)}); + var check = {}, count = {}; + gun.get('u/m/p/n/mutate/n/u').map().get('pet').on(function(v,f){ + check[v.name] = f; + count[v.name] = (count[v.name] || 0) + 1; + //console.log("*****************", f,v); + if(check.Fluffy && check.Frisky && check.Fuzzball){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(done.last).to.be.ok(); + expect(check['Fluffs']).to.not.be.ok(); + //expect(count.Fluffy).to.be(1); + //expect(count.Frisky).to.be(1); + //expect(count.Fuzzball).to.be(1); + done(); + },200); + } + }); + setTimeout(function(){ + var s = Gun.state.map();s.soul = 'alice/fuzz/soul'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + name: 'Alice Zzxyz', age: 34, + pet: {c:3, name: "Fuzzball"} + }, s)}); + gun.get('u/m/p/n/mutate/n/u').put({ + alice: {'#':'alice/fuzz/soul'}, + }); + setTimeout(function(){ + gun.get('sflufso').put({ + name: 'Fluffs' + }); + done.last = true; + }, 10); + }, 300); + }); + + it("get before put in memory", function(done){ + var gun = Gun(); + var check = {}; + var count = {}; + gun.get('g/n/m/f/l/n/r').map().on(function(v,f){ + //console.log("***********", f,v); + check[f] = v; + count[f] = (count[f] || 0) + 1; + if(check.alice && check.bob && check.alice.PhD){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice.age).to.be(24); + expect(check.bob.age).to.be(26); + expect(check.alice.PhD).to.be(true); + //expect(count.alice).to.be(2); + //expect(count.bob).to.be(1); + if(done.c){return} + done();done.c=1; + },50); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/r'}, + alice: {_:{'#':'GALICE1'}, + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + } + }); + setTimeout(function(){ + gun.get('GALICE1').put({PhD: true}); + },300); + }); + + it("in memory get after", function(done){ + var gun = Gun(); + gun.put({_:{'#':'g/n/m/f/l/n'}, + alice: {_:{'#':'GALICE2'}, + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + } + }); + var check = {}; + //gun.get('g/n/m/f/l/n').get('bob.spouse.work').on(function(v,f){ console.log("!!!!!!!!!", f, v);});return; + gun.get('g/n/m/f/l/n').map().on(function(v,f){ + check[f] = v; + //console.log("*******************", f, v); + if(check.alice && check.bob && check.alice.PhD){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice.age).to.be(24); + expect(check.bob.age).to.be(26); + expect(check.alice.PhD).to.be(true); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('GALICE2').put({PhD: true}); + },300); + }); + + it("in memory get before map get", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/p').map().get('name').on(function(v,f){ + check[v] = f; + //console.log("****************", f,v); + if(check.alice && check.bob && check.Alice){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice).to.be('name'); + expect(check.bob).to.be('name'); + expect(check.Alice).to.be('name'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/b/p'}, + alice: {_:{'#':'GALICE3'}, + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + } + }); + setTimeout(function(){ + gun.get('GALICE3').put({name: 'Alice'}); + },300); + }); + + it("in memory get after map get", function(done){ + var gun = Gun(); + gun.put({_:{'#':'g/n/m/f/l/n/m/p'}, + alice: {_:{'#':'GALICE4'}, + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + } + }); + var check = {}; + gun.get('g/n/m/f/l/n/m/p').map().get('name').on(function(v,f){ + check[v] = f; + //console.log("*****************", f,v); + if(check.alice && check.bob && check.Alice){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice).to.be('name'); + expect(check.bob).to.be('name'); + expect(check.Alice).to.be('name'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('GALICE4').put({name: 'Alice'}); + },300); + }); + + it("in memory get before map get get", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/p/p/p').map().get('spouse').get('work').on(function(v,f){ + check[v.name] = f; + if(check['GUN INC'] && check['ACME INC'] && check['ACME INC.']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check['GUN INC']).to.be('work'); + expect(check['ACME INC']).to.be('work'); + expect(check['ACME INC.']).to.be('work'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/b/p/p/p'}, + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: {_:{'#':'CCINEMA1'}, + name: "ACME INC" + } + } + } + }); + setTimeout(function(){ + gun.get('CCINEMA1').put({name: 'ACME INC.'}); + },300); + }); + + it("in memory get after map get get", function(done){ + var gun = Gun(); + gun.put({_:{'#':'g/n/m/f/l/n/b/p/p/p/a'}, + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: {_:{'#':'CCINEMA2'}, + name: "ACME INC" + } + } + } + }); + var check = {}; + gun.get('g/n/m/f/l/n/b/p/p/p/a').map().get('spouse').get('work').on(function(v,f){ + check[v.name] = f; + if(check['GUN INC'] && check['ACME INC'] && check['ACME INC.']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check['GUN INC']).to.be('work'); + expect(check['ACME INC']).to.be('work'); + expect(check['ACME INC.']).to.be('work'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('CCINEMA2').put({name: 'ACME INC.'}); + },300); + }); + + it("in memory get before map map", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/a/m/m').map().map().on(function(v,f){ + check[f] = v; + //console.log("****************", f,v); + if(check.alice && check.bob && check.GUN && check.ACME && check.ACME.corp){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice.name).to.be('alice'); + expect(check.alice.age).to.be(24); + expect(Gun.val.rel.is(check.alice.spouse)).to.be.ok(); + expect(check.bob.name).to.be('bob'); + expect(check.bob.age).to.be(26); + expect(Gun.val.rel.is(check.bob.spouse)).to.be.ok(); + expect(check.GUN.name).to.be('GUN'); + expect(check.ACME.name).to.be('ACME'); + expect(check.ACME.corp).to.be('C'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + //console.debug.i=1;console.log("------------------------"); + gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m'}, + users: { + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN" + }, + ACME: {_:{'#':"CCINEMA3"}, + name: "ACME" + } + } + }); + setTimeout(function(){ + gun.get('CCINEMA3').put({corp: "C"}); + },300); + }); + + it("in memory get after map map", function(done){ + var gun = Gun(); + gun.put({_:{'#':'g/n/m/f/l/n/b/m/m'}, + users: { + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN" + }, + ACME: {_:{'#':"CCINEMA4"}, + name: "ACME" + } + } + }); + var check = {}; + gun.get('g/n/m/f/l/n/b/m/m').map().map().on(function(v,f){ + check[f] = v; + //console.log("***************", f,v); + if(check.alice && check.bob && check.GUN && check.ACME && check.ACME.corp){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice.name).to.be('alice'); + expect(check.alice.age).to.be(24); + expect(Gun.val.rel.is(check.alice.spouse)).to.be.ok(); + expect(check.bob.name).to.be('bob'); + expect(check.bob.age).to.be(26); + expect(Gun.val.rel.is(check.bob.spouse)).to.be.ok(); + expect(check.GUN.name).to.be('GUN'); + expect(check.ACME.name).to.be('ACME'); + expect(check.ACME.corp).to.be('C'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('CCINEMA4').put({corp: "C"}); + },300); + }); + + it("in memory get before map map get", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/m/m/p').map().map().get('name').on(function(v,f){ + check[v] = f; + //console.log("***********", f,v); + if(check.alice && check.bob && check.GUN && check.ACME && check.ACMEINC){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice).to.be('name'); + expect(check.bob).to.be('name'); + expect(check.GUN).to.be('name'); + expect(check.ACME).to.be('name'); + expect(check.ACMEINC).to.be('name'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p'}, + users: { + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN" + }, + ACME: {_:{'#':"CCINEMA5"}, + name: "ACME" + } + } + }); + setTimeout(function(){ + gun.get('CCINEMA5').put({name: "ACMEINC"}); + },300); + }); + + it("in memory get after map map get", function(done){ + var gun = Gun(); + var check = {}; + gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p'}, + users: { + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN" + }, + ACME: {_:{'#':'CCINEMA6'}, + name: "ACME" + } + } + }); + gun.get('g/n/m/f/l/n/b/a/m/m/p').map().map().get('name').on(function(v,f){ + check[v] = f; + //console.log("************", f,v); + if(check.alice && check.bob && check.GUN && check.ACME && check.ACMEINC){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.alice).to.be('name'); + expect(check.bob).to.be('name'); + expect(check.GUN).to.be('name'); + expect(check.ACME).to.be('name'); + expect(check.ACMEINC).to.be('name'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('CCINEMA6').put({name: "ACMEINC"}); + },300); + }); + + it("in memory get before map map get get", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/m/m/p/p').map().map().get('address').get('state').on(function(v,f){ + check[v] = f; + if(check.QR && check.NY && check.CA && check.TX && check.MA){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.QR).to.be('state'); + expect(check.NY).to.be('state'); + expect(check.CA).to.be('state'); + expect(check.TX).to.be('state'); + expect(check.MA).to.be('state'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p/p'}, + users: { + alice: { + name: "alice", + age: 24, + address: {_:{'#':'QUANGO'}, + state: "MA" + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: "TX" + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: "CA" + } + }, + ACME: { + name: "ACME", + address: { + state: "NY" + } + } + } + }); + setTimeout(function(){ + gun.get('QUANGO').put({state: 'QR'}); + },300); + }); + + it("in memory get after map map get get", function(done){ + var gun = Gun(); + gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p'}, + users: { + alice: { + name: "alice", + age: 24, + address: {_:{'#':'QUANGO1'}, + state: "MA" + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: "TX" + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: "CA" + } + }, + ACME: { + name: "ACME", + address: { + state: "NY" + } + } + } + }); + var check = {}; + gun.get('g/n/m/f/l/n/b/a/m/m/p/p').map().map().get('address').get('state').on(function(v,f){ + check[v] = f; + if(check.QR && check.NY && check.CA && check.TX && check.MA){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.QR).to.be('state'); + expect(check.NY).to.be('state'); + expect(check.CA).to.be('state'); + expect(check.TX).to.be('state'); + expect(check.MA).to.be('state'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('QUANGO1').put({state: 'QR'}); + },300); + }); + + it("in memory get before map map get get get", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/m/m/p/p/p').map().map().get('address').get('state') + .get('code') + .on(function(v,f){ + check[v] = f; + if(check.QR && check.NY && check.CA && check.TX && check.MA){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.QR).to.be('code'); + expect(check.NY).to.be('code'); + expect(check.CA).to.be('code'); + expect(check.TX).to.be('code'); + expect(check.MA).to.be('code'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p/p/p'}, + users: { + alice: { + name: "alice", + age: 24, + address: { + state: {_:{'#':'HIPPOM'}, + code: "MA", + county: { + MA1: "First" + } + } + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: { + code: "TX", + county: { + TX1: "First" + } + } + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: { + code: "CA", + county: { + CA1: "First" + } + } + } + }, + ACME: { + name: "ACME", + address: { + state: { + code: "NY", + county: { + NY1: "First" + } + } + } + } + } + }); + setTimeout(function(){ + gun.get('HIPPOM').put({code: 'QR'}); + },300); + }); + + it("in memory get before after map map get get get", function(done){ + var gun = Gun(); + var check = {}; + gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p/p'}, + users: { + alice: { + name: "alice", + age: 24, + address: { + state: {_:{'#':'HIPPOM1'}, + code: "MA", + county: { + MA1: "First" + } + } + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: { + code: "TX", + county: { + TX1: "First" + } + } + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: { + code: "CA", + county: { + CA1: "First" + } + } + } + }, + ACME: { + name: "ACME", + address: { + state: { + code: "NY", + county: { + NY1: "First" + } + } + } + } + } + }); + gun.get('g/n/m/f/l/n/b/a/m/m/p/p/p').map().map().get('address').get('state') + .get('code') + .on(function(v,f){ + check[v] = f; + //console.log("***********", f,v); + if(check.QR && check.NY && check.CA && check.TX && check.MA){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.QR).to.be('code'); + expect(check.NY).to.be('code'); + expect(check.CA).to.be('code'); + expect(check.TX).to.be('code'); + expect(check.MA).to.be('code'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('HIPPOM1').put({code: 'QR'}); + },300); + }); + + it("in memory get before map map get get node", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f/l/n/b/m/m/p/p/n').map().map().get('address').get('state').on(function(v,f){ + check[v.code] = f; + //console.log("************", f, v); + if(check.QR && check.NY && check.CA && check.TX && check.MA){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.QR).to.be('state'); + expect(check.NY).to.be('state'); + expect(check.CA).to.be('state'); + expect(check.TX).to.be('state'); + expect(check.MA).to.be('state'); + if(done.c){return}done.c=1; + done(); + },10); + } + }); + gun.put({_:{'#':'g/n/m/f/l/n/b/m/m/p/p/n'}, + users: { + alice: { + name: "alice", + age: 24, + address: { + state: {_:{'#':'HIPPOM3'}, + code: "MA", + county: { + MA1: "First" + } + } + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: { + code: "TX", + county: { + TX1: "First" + } + } + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: { + code: "CA", + county: { + CA1: "First" + } + } + } + }, + ACME: { + name: "ACME", + address: { + state: { + code: "NY", + county: { + NY1: "First" + } + } + } + } + } + }); + setTimeout(function(){ + gun.get('HIPPOM3').put({code: 'QR'}); + },300); + }); + + it("in memory get before after map map get get node", function(done){ + var gun = Gun(); + var check = {}; + gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p/n'}, + users: { + alice: { + name: "alice", + age: 24, + address: { + state: {_:{'#':'HIPPOM4'}, + code: "MA", + county: { + MA1: "First" + } + } + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: { + code: "TX", + county: { + TX1: "First" + } + } + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: { + code: "CA", + county: { + CA1: "First" + } + } + } + }, + ACME: { + name: "ACME", + address: { + state: { + code: "NY", + county: { + NY1: "First" + } + } + } + } + } + }); + gun.get('g/n/m/f/l/n/b/a/m/m/p/p/n').map().map().get('address').get('state').on(function(v,f){ + check[v.code] = f; + //console.log("**********", f, v); + if(check.QR && check.NY && check.CA && check.TX && check.MA){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.QR).to.be('state'); + expect(check.NY).to.be('state'); + expect(check.CA).to.be('state'); + expect(check.TX).to.be('state'); + expect(check.MA).to.be('state'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('HIPPOM4').put({code: 'QR'}); + },300); + }); + + it("in memory get after map map get get get map", function(done){ + var gun = Gun(); + var check = {}; + gun.put({_:{'#':'g/n/m/f/l/n/b/a/m/m/p/p/p/n'}, + users: { + alice: { + name: "alice", + age: 24, + address: { + state: { + code: "MA", + county: { + MA1: "First" + ,MA2: "Second" + } + } + }, + spouse: { + name: "carl", + age: 25 + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + address: { + state: { + code: "TX", + county: { + TX1: "First" + ,TX2: "Second" + } + } + }, + spouse: { + name: "diana", + age: 27 + } + } + }, + companies: { + GUN: { + name: "GUN", + address: { + state: { + code: "CA", + county: { + CA1: "First" + ,CA2: "Second" + } + } + } + }, + ACME: { + name: "ACME", + address: { + state: { + code: "NY", + county: {_:{'#':'NYCOUNT'}, + NY1: "First" + ,NY2: "Second" + } + } + } + } + } + }); + gun.get('g/n/m/f/l/n/b/a/m/m/p/p/p/n').map().map().get('address').get('state').get('county').map().on(function(v,f){ + check[f] = v; + //console.log("****************", f,v); + if(check.MA1 && check.MA2 && check.TX1 && check.TX2 && check.CA1 && check.CA2 && check.NY1 && check.NY2 && check.NY3){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check.MA1).to.be('First'); + expect(check.TX1).to.be('First'); + expect(check.CA1).to.be('First'); + expect(check.NY1).to.be('First'); + expect(check.MA2).to.be('Second'); + expect(check.TX2).to.be('Second'); + expect(check.CA2).to.be('Second'); + expect(check.NY2).to.be('Second'); + expect(check.NY3).to.be('Third'); + done(); + },10); + } + }); + setTimeout(function(){ + gun.get('NYCOUNT').put({NY3: "Third"}); + },300); + }); + }); + + it('get node after recursive field', function(done){ + var s = Gun.state.map();s.soul = 'node/circle'; + var bob = {age: 29, name: "Bob!"}; + var cat = {name: "Fluffy", species: "kitty"}; + var user = {bob: bob}; + bob.pet = cat; + cat.slave = bob; + gun.on('put', {gun: gun, put: Gun.graph.ify(user, s)}); + gun.get(s.soul).get('bob').get('pet').get('slave').val(function(data){ + //clearTimeout(done.to); + //setTimeout(function(){ + //console.log("*****************", data); + expect(data.age).to.be(29); + expect(data.name).to.be("Bob!"); + expect(Gun.val.rel.is(data.pet)).to.ok(); + done(); + //},300); + }); + }); + + it('recursive put', function(done){ + //localStorage.clear(); + var gun = Gun(); + + var parent = gun.get('parent'); + var child = gun.get('child'); + + child.put({ + way: 'down' + }); + + parent.get('sub').put(child); + + parent.get('sub').on(function(data){ + //console.log("sub", data); + done.sub = data; + }); + child.on(function(data){ + done.child = data; + //console.log("child", data); + }); + parent.on(function(data){ + done.parent = data; + //console.log("parent", data); + if(done.c){ return } done.c = 1; + done(); // TODO: Add more meaningful checks! + }); + }); + + it('empty val followed', function(done){ + var gun = Gun(); + + gun.get('val/follow').val(function(data){ + //console.log("val", data); + }).get(function(at){ + if(done.c){ return } done.c = 1; + done(); + }); + + }); + + it('map val get put', function(done){ + + var gun = Gun().get('chat/asdf'); + + var check = {}, count = {}; + gun.map().val(function(v,f){ + check[f] = v; + count[f] = (count[f] || 0) + 1; + if(check['1_1'] && check['2_2']){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(check['1_1'].what).to.be('hi'); + expect(check['2_2'].what).to.be('you.'); + expect(count['1_1']).to.be(1); + expect(count['2_2']).to.be(1); + done(); + },50); + } + }); + + setTimeout(function(){ + gun.get('1_1').put({what: "hi"}); + setTimeout(function(){ + gun.get('2_2').put({what: "you."}); + },40); + },40); + }); + + it('get list set map val', function(done){ + + var gun = Gun(); + + var list = gun.get('list'); + + list.set(gun.get('alice').put({name: "Alice", group: "awesome", married: true})); + list.set(gun.get('bob').put({name: "Bob", group: "cool", married: true})); + list.set(gun.get('carl').put({name: "Carl", group: "cool", married: false})); + list.set(gun.get('dave').put({name: "Dave", group: "awesome", married: true})); + + var check = {}, count = {}; + list.map().val(function(data, id){ + //console.log("***************", id, data); + check[id] = data; + count[id] = (count[id] || 0) + 1; + if(check.alice && check.bob && check.carl && check.dave){ + clearTimeout(done.to); + done.to = setTimeout(function(){ + expect(count.alice).to.be(1); + expect(check.alice.name).to.be('Alice'); + expect(check.alice.group).to.be('awesome'); + expect(check.alice.married).to.be(true); + expect(count.bob).to.be(1); + expect(check.bob.name).to.be('Bob'); + expect(check.bob.group).to.be('cool'); + expect(check.bob.married).to.be(true); + expect(count.carl).to.be(1); + expect(check.carl.name).to.be('Carl'); + expect(check.carl.group).to.be('cool'); + expect(check.carl.married).to.be(false); + expect(count.dave).to.be(1); + expect(check.dave.name).to.be('Dave'); + expect(check.dave.group).to.be('awesome'); + expect(check.dave.married).to.be(true); + done(); + },50); + } + }); + /* + Have we asked for this yet? No. + Do we have it cached? No. + Is its parent cached? Yes. + Reply immediately with that cache for map to process. + */ + + /* + chain has a root // all + an ID // all + a back // all + inputs // all + and outputs // all + acks // any + echo // any + next // any + cache or map of many ones // only a one can have a cache, only a map can have many, and they must be ones. However any chain might have neither. By default a chain is a many, unless it is designated as a one. + + gun.get('alice').also('bob').path('name').on(cb); + gun.get('users').map().path('friends').map().on(cb); + + friends is a map, it has an echo + {name: "alice", friends: []} + {name: "xavier"} + {name: "yara"} + {name: "zack"} + {name: "bob", friends: []} + {name: "xavier"} + {name: "yara"} + {name: "zack"} + {name: "carl", friends: []} + {name: "xavier"} + {name: "yara"} + {name: "zack"} + */ + }); + + it('get get get set root get put', function(done){ + var gun = Gun().get('app'); + gun.get('alias').get('mark').set( + gun.back(-1).get('pub').put({ + alias: 'mark', + auth: 'encrypt', // oops + born: 1, + pub: 'pub', + salt: 'random' + }) + ); + setTimeout(function(){ + gun.get(function(at){ + //console.log('*', at.put); + done.app = done.app || at.put.alias; + }); + gun.back(-1).get('pub').get(function(at){ + //console.log("**", at.put); + done.pub = done.pub || at.put.auth; + }); + gun.get('alias').get(function(at){ + //console.log("***", at.put); + done.alias = done.alias || at.put.mark; + }).get('mark').get(function(at){ + //console.log("************", at.put);return; + setTimeout(function(){ + done.mark = done.mark || at.put.pub; + expect(Gun.val.rel.is(done.mark)).to.be('pub'); + expect(done.app).to.be.ok(); + expect(done.pub).to.be.ok(); + expect(done.alias).to.be.ok(); + if(done.c){ return } done.c = 1; + done(); + },100); + }) + },100); + }); + + it('get put get get put reload get get then get', function(done){ + var gun = Gun(); + + gun.get('stef').put({name:'Stef'}); + var address = { + country: 'Netherlands', + zip:'999999' + }; + gun.get('stef').get('address').put(address); + + // reload + setTimeout(function(){ + var gun2 = Gun(); + gun2.get('stef').get('address').val(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" + done.a = true; + expect(data.country).to.be('Netherlands'); + expect(data.zip).to.be('999999'); + if(!done.s){ return } + if(done.c){ return } done.c = 1; + done(); + }); + gun2.get('stef').val(function(data){ //Object {_: Object, address: Object} "stef" + done.s = true; + expect(data.name).to.be('Stef'); + expect(data.address).to.be.ok(); + if(!done.a){ return } + if(done.c){ return } done.c = 1; + done(); + }); + },300); + }); + + it('get get get any parallel', function(done){ + var s = Gun.state.map();s.soul = 'parallel'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!" + } + }, s)}); + gun.get('parallel').get('bob').get('age').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("***** age", data, at.gun._.ack);return; + expect(data).to.be(29); + expect(field).to.be('age'); + done.age = true; + }); + gun.get('parallel').get('bob').get('name').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("*********** name", data, at.gun._.ack);return; + expect(data).to.be('Bob!'); + expect(field).to.be('name'); + done.name = true; + expect(done.age).to.be.ok(); + if(done.c){ return } done.c = 1; + done(); + }); + }); + + it('get get get any later', function(done){ + var s = Gun.state.map();s.soul = 'parallel/later'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: {_:{'#':'ddfsa'}, + age: 29, + name: "Bob!" + } + }, s)}); + gun.get('parallel/later').get('bob').get('age').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("***** age", data); + expect(data).to.be(29); + expect(field).to.be('age'); + done.age = true; + }); + setTimeout(function(){ + gun.get('parallel/later').get('bob').get('name').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("*********** name", data); + expect(data).to.be('Bob!'); + expect(field).to.be('name'); + done.name = true; + expect(done.age).to.be.ok(); + if(done.c){ return } done.c = 1; + done(); + }); + },400); + }); + + it('get get get any not', function(done){ + gun.get('parallel/not').get('bob').get('age').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("***** age", data); + expect(data).to.be(undefined); + expect(field).to.be('age'); + done.age = true; + }); + gun.get('parallel/not').get('bob').get('name').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("*********** name", data); + expect(data).to.be(undefined); + expect(field).to.be('name'); + done.name = true; + expect(done.age).to.be.ok(); + if(done.c){return}done.c=1; + done(); + }); + }); + + it('get get get any not later', function(done){ + gun.get('parallel/not/later').get('bob').get('age').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("***** age", data); + expect(data).to.be(undefined); + expect(field).to.be('age'); + done.age = true; + }); + setTimeout(function(){ + gun.get('parallel/not/later').get('bob').get('name').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("*********** name", field, data); + expect(data).to.be(undefined); + expect(field).to.be('name'); + done.name = true; + expect(done.age).to.be.ok(); + if(done.c){ return } done.c = 1; + done(); + }); + },400); + }); + + it('get any any', function(done){ + var s = Gun.state.map();s.soul = 'full'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + hello: 'world', + goodbye: 'mars' + }, s)}); + gun.get('full').get(function(at, ev){ + var err = at.err, data = at.gun._.put || at.put, field = at.get; + //console.log("*****1", data); + expect(data.hello).to.be('world'); + expect(data.goodbye).to.be('mars'); + }); + gun.get('full').get(function(at, ev){ + var err = at.err, data = at.gun._.put || at.put, field = at.get; + //console.log("*****1", data); + expect(data.hello).to.be('world'); + expect(data.goodbye).to.be('mars'); + if(done.c){ return } done.c = 1; + done(); + }); + }); + + it('get any any later', function(done){ + var s = Gun.state.map();s.soul = 'full/later'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + hello: 'world', + goodbye: 'mars' + }, s)}); + gun.get('full/later').get(function(at, ev){ + var err = at.err, data = at.gun._.put || at.put, field = at.get; + //console.log("*****", data); + expect(data.hello).to.be('world'); + expect(data.goodbye).to.be('mars'); + }); + setTimeout(function(){ + gun.get('full/later').get(function(at, ev){ + var err = at.err, data = at.gun._.put || at.put, field = at.get; + //console.log("*****2", field, data); + expect(data.hello).to.be('world'); + expect(data.goodbye).to.be('mars'); + if(done.c){ return } done.c = 1; + done(); + }); + },400); + }); + + it('multiple times', function(done){ + var gun = Gun(); + + var app = gun.get('mult/times'); + + app.get('alias').get('mark').set(gun.get('ASDF').put({ + pub: 'ASDF', + alias: 'mark', + born: 1 + })); + + app.get('alias').map().map().get('pub').on(function(data){ + done.one = data; + //console.log("pub 1!", data); + }); + + setTimeout(function(){ + app.get('alias').map().map().get('alias').on(function(data){ + done.two = data; + //console.log("alias 2!", data); + expect(done.one).to.be("ASDF"); + expect(done.two).to.be("mark"); + if(done.c){ return } done.c = 1; + done(); + }); + },100); + }); + + it('multiple times partial', function(done){ + var gun = Gun(); + + var s = Gun.state.map();s.soul = 'mult/times/part'; + gun.on('put', {gun: gun, put: Gun.graph.ify({ + alias: { + mark: { + pub: {_:{'#':'PUB'}, + pub: 'asdf', + alias: 'mark', + born: 1 + } + } + } + }, s)}); + + var app = gun.get(s.soul); + + app.get('alias').get('mark').map().val(function(alias){ + done.alias = alias; + }); + + setTimeout(function(){ + app.get('alias').map().map().get('born').on(function(data){ + expect(data).to.be(1); + expect(done.alias.pub).to.be("asdf"); + expect(done.alias.alias).to.be("mark"); + expect(done.alias.born).to.be(1); + if(done.c){ return } done.c = 1; + done(); + }); + },300); + }); + + 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'); + + var check = {}; + list.map(function(user){ return user.age === 27? user.name + "thezombie" : u }).on(function(data){ + //console.log('data:', data); + check[data] = true; + if(check.alicethezombie && check.bobthezombie){ + if(done.c){return}done.c=1; + done(); + } + }); + 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}); + }); + + it('val and then map', function(done){ + var gun = Gun(), s = 'val/then/map', u; + var list = gun.get(s); + + list.set(gun.get('alice').put({name: 'alice', age: 27})); + list.set(gun.get('bob').put({name: 'bob', age: 27})); + list.set(gun.get('carl').put({name: 'carl', age: 29})); + list.set(gun.get('dave').put({name: 'dave', age: 25})); + + var check = {}; + list.val().map().on(function(data, key){ + check[key] = data; + clearTimeout(done.to); + done.to = setTimeout(function(){ + if(check.alice && check.bob && check.carl && check.dave && done.last){ + expect(check.alice.age).to.be(27); + expect(check.bob.age).to.be(28); + expect(check.carl.age).to.be(29); + expect(check.dave.age).to.be(25); + expect(check.eve).to.not.be.ok(); + if(done.c){return}done.c=1; + done(); + } + },600); + }); + setTimeout(function(){ + list.set(gun.get('eve').put({name: 'eve', age: 30})); + gun.get('bob').get('age').put(28); + done.last = true; + },300); + }); + + it('check null on map', function(done){ + var list = gun.get('myList'); + list.map(function(value, id){ + if("hello world" === value){ + done.one = true; + } + if(null === value){ + done.two = true; + } + if(done.one && done.two){ + if(done.c){ return } done.c = 1; + done(); + } + }); + list.get('message').put('hello world'); // outputs "message: hello world" + list.get('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('opt', function(ctx){ + ctx.on('out', function(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){ + expect(val).to.be('hello'); + if(done.c){ return } done.c = 1; + setTimeout(function(){ + done(); + },1700); + }); + }); + + it('val should now get called if no data is found', function(done){ + var gun = Gun(); + + gun.get('nv/foo').get('bar').get('baz').val(function(val, key){ + //console.log('*******', key, val); + expect(val).to.be(undefined); + done.fbb = true; + }); + + gun.get('nv/totesnothing').val(function(val, key){ + //console.log('***********', key, val); + expect(val).to.be(undefined); + done.t = true; + }); + + gun.get('nv/bz').get('lul').val(function(val, key){ + //console.log('*****************', key, val); + expect(val).to.be(undefined); + done.bzl = true; + if(done.fbb && done.t && done.bzl){ + if(done.c){ return } done.c = 1; + done(); + } + }); + }); + + it('Callbacks should have database safe data copies', function(done){ + var gun = Gun(); + + gun.get('ds/safe').put({a: 1}); + + gun.get('ds/safe').on(function(data){ + data.b = 2; + }); + + gun.get('ds/safe').val(function(data){ + expect(gun._.root._.graph['ds/safe'].b).to.not.be.ok(); + if(done.c){ return } done.c = 1; + done(); + }); + }); + return; + it.only('Memory management', function(done){ + this.timeout(9999999); + var gun = Gun(), c = 100000, big = "big"; + while(--c){big += "big"} + c = 0; + setInterval(function(){ + var key = Gun.text.random(5); + gun.get(key).put({data: big}); + setTimeout(function(){ + gun.get(key).off(); + },10); + if(typeof process === 'undefined'){ return } + var mem = process.memoryUsage(); + console.log(((mem.heapUsed / mem.heapTotal) * 100).toFixed(0) + '% memory'); + console.log(Object.keys(gun._.graph).length, 'item in memory graph:', Object.keys(gun._.graph)); + },25); + }); + + return; + it.only('Custom extensions are chainable', function(done){ + Gun.chain.filter = function(filter){ + var chain = this.chain(); + var context = this; + var _tags; + context.val(function(obj, key){ + if(!obj.tags){ + console.warn('Not tagged to anything!'); + context._.valid = false; + chain._.on('in', {get: key, gun: this}); + return false; + } else { + _tags = Gun.obj.ify(obj.tags); + if(Array.isArray(filter)){ + context._.valid = filter.every(function(f){ return ( _tags[f] && _tags[f]==1) }); + if(context._.valid){ + chain._.on('in', {get: key, put: obj, gun: this}); + return context; + } else { + console.log("that was wrong"); + chain._.on('in', {get: key, put: undefined, gun: this}); + } + return false; + } else { + console.warn('filter should be an Array'); + return false; + } + } + }); + return chain; + } + + var gun = Gun(); + + var fake1 = gun.get('fake1').put({name:'faker1',tags:JSON.stringify({a:1,b:0,c:1})}); + var fake2 = gun.get('fake2').put({name:'faker2',tags:JSON.stringify({a:1,b:1,c:1})}); + var list = gun.get('list'); + list.set(fake1); + list.set(fake2); + + gun.get('fake1')//.map() + .filter(['a','b']) // Gun.chain.filter = function(tags){ .... } + .get(function(no){console.log("NO!", no)}) + .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(function(v,f){ console.log( "event on A: ", f, v ) } ); + gunB.get( "some path B" ).map(function(v,f){ console.log( "event on B: ", f, v ) } ); + gunC.get( "some path C" ).map(function(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"} } } }; + + var alice = {name: "alice" } + + var gun = Gun(); + + var root = gun.get( "root" ); + root.put( dfltSansUsers ); + + var alice = gun.get( "alice" ).put( { name: "alice" } ); + console.log( "Failed after this" ); + root.path( "1.sites.1.users" ).put( { 1: alice } ); + console.log( "Failed before this" ); + }); + + it.only('get any any none', function(done){ + gun.get('full/none').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + console.log("*****", data); + expect(data).to.be(undefined); + }); + gun.get('full/none').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + console.log("*****2", data); + expect(data).to.be(undefined); + done(); + }); + }); + + it('get any any none later', function(done){ + gun.get('full/none/later').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("*****", data); + expect(data).to.be(undefined); + }); + setTimeout(function(){ + gun.get('full/none/later').get(function(at, ev){ + var err = at.err, data = at.put, field = at.get; + //console.log("*****2", data); + expect(data).to.be(undefined); + done(); + }); + },400); + });return; + + it('get get any parallel', function(done){ + var s = Gun.state.map();s.soul = 'parallel/get/get'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!" + } + }, s)}); + gun.get('parallel/get/get').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 1", data); + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + }); + gun.get('parallel/get/get').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 2", data); + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + done(); + }); + }); + + it('get get any parallel later', function(done){ + var s = Gun.state.map();s.soul = 'parallel/get/get/later'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!" + } + }, s)}); + gun.get('parallel/get/get/later').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 1", data); + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + }); + setTimeout(function(){ + gun.get('parallel/get/get/later').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 2", data); + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + done(); + }); + },400); + }); + + it('get get any none', function(done){ + var s = Gun.state.map();s.soul = 'get/get/none'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 31, + name: "alice" + } + }, s)}); + var c = 0, s = 0; + gun.get('get/get/none').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 1", data); + c++; + s = 0; + expect(data).to.be(undefined); + }); + s = 1; + gun.get('get/get/none').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 2", data); + c++; + //if(s){ c++ } // TODO: Talk to Jesse about this. + expect(data).to.be(undefined); + if(2 === c){ // We want 2 replies for each `any`, once from LS replying with the soul (but not the field), and once from WSP replying that the soul couldn't be found. + // Wrong! I think we've changed this, such that lS handles it alone and not WSP. + done(); + } + }); + }); + + it('get get any none later', function(done){ + var s = Gun.state.map();s.soul = 'get/get/none/later'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + alice: { + age: 31, + name: "alice" + } + }, s)}); + var c = 0; + gun.get('get/get/none/later').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 1", data); + c++; + expect(data).to.be(undefined); + }); + setTimeout(function(){ + gun.get('get/get/none/later').path('bob').any(function(err, data, field, at, ev){ + //console.log("***** 2", data); + c++; + expect(data).to.be(undefined); + //if(3 === c){ // Because we already have active listeners cached waiting for data to pipe in, BUT we have already received multiple responses that the data isn't found, the "not found" is cached and so we get an immediate response just from cache. If later data does get piped in, this will still get called. + done(); + //} + }); + },400); + }); + + it('get get primitive get any', function(done){ + var s = Gun.state.map();s.soul = 'get/get/prim'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: "is awesome" + }, s)}); + gun.get('get/get/prim').path('bob').path('age').any(function(err, data, field, at, ev){ + //console.log("***** 1", data); + expect(data).to.be(undefined); + }); + gun.get('get/get/prim').path('bob').path('age').any(function(err, data, field, at, ev){ + //console.log("***** 2", data); + expect(data).to.be(undefined); + done(); + }); + }); + + it('get put any', function(done){ + var s = Gun.state.map();s.soul = 'get/put/any'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + here: "we go" + }, s)}); + console.debug.i=1;console.log("---------------"); + gun.get('get/put/any') + .put({}) + .any(function(err, data, field, at, ev){ + console.log("***** 1", data); + done(); + }); + }); + return; + it('get any, get put any', function(done){ + var s = Gun.state.map();s.soul = 'get/any/get/put/any'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + here: "we go" + }, s)}); + gun.get('get/any/get/put/any') + .any(function(err, data, field, at, ev){ + if(done.first){ return } // it is okay for `any` to get called multiple times. + //console.log("***** 1", data); + expect(data.here).to.be('we go'); + if(done.first){ + expect(data.yes).to.be('please'); + } + done.first=1; + }); + setTimeout(function(){ + gun.get('get/any/get/put/any') + .put({yes: 'please'}) + .any(function(err, data, field, at, ev){ + if(done.second){ return } // it is okay for `any` to get called multiple times. + //console.log("***** 2", data); + expect(data.here).to.be('we go'); + expect(data.yes).to.be('please'); + done(); + done.second = 1; + }); + },400); + }); + + it('mutate pointer to primitive deep on', function(done){ + var s = Gun.state.map();s.soul = 'change/pointer'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + gun.get('change/pointer').path('bob').path('pet').any(function(err, data, f, at, ev){ + //console.log("***", data);return setTimeout(function(){asdf},500); + if(done.c){ + expect(data).to.be(undefined); + return; + } + expect(data.name).to.be('Fluffy'); + expect(data.species).to.be('kitty'); + done.c = 1; + }); + setTimeout(function(){ + gun.get('change/pointer').put({ + bob: null + }); + setTimeout(function(){ + gun.get('change/pointer').put({ + bob: "hello!" + }); + },400); + },400); + gun.get('change/pointer').any(function(err, data){ + //console.log("****************", data); + if(2 <= done.e && data.bob){ + expect(data.bob).to.be('hello!'); + done(); + return; + } + if(1 <= done.e){ + expect(data.bob).to.be(null); + done.e = 2; + return; + } + expect(Gun.val.rel.is(data.bob)).to.be.ok(); + done.e = 1; + }); + }); + + it('get only soul', function(done){ + var s = Gun.state.map();s.soul = 'only/soul'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + gun.get('only/soul')/*.path('bob')*/.any(function(err, data){ + expect(Gun.obj.empty(data, '_')).to.be.ok(); + done(); + }, {'.': null}); + }); + + it('get path only soul', function(done){ + var s = Gun.state.map();s.soul = 'only/p/soul'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + gun.get('only/p/soul').path('bob').any(function(err, data){ + //console.log("*********", err, data); + expect(Gun.val.rel.is(data)).to.be.ok(); + //expect(Gun.obj.empty(data, '_')).to.be.ok(); + done(); + }, {'.': null}); + }); + + it('mutate pointer to self', function(done){ + var s = Gun.state.map();s.soul = 'change/pointer/point'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + gun.get('change/pointer/point').path('bob').any(function(err, data){ + if(done.c){ + expect(data.age).to.be(30); + expect(data.name).to.be('Bob!'); + expect(Gun.val.rel.is(data.pet)).to.be.ok(); + expect(done.c).to.be(1); + done(); + done.c = 2; + return; + } + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + expect(Gun.val.rel.is(data.pet)).to.be.ok(); + done.c=1; + }); + setTimeout(function(){ + gun.get('change/pointer/point').path('bob').put({age: 30}); + },400); + }); + it('mutate pointer to self deep', function(done){ + var s = Gun.state.map();s.soul = 'change/pointer/point/deep'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + gun.get('change/pointer/point/deep').path('bob').any(function(err, data){ + //console.log("***", data); + if(done.c){ + expect(data.age).to.be(30); + expect(data.name).to.be('Bob!'); + expect(Gun.val.rel.is(data.pet)).to.be.ok(); + done(); + return; + } + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + expect(Gun.val.rel.is(data.pet)).to.be.ok(); + done.c=1; + }); + setTimeout(function(){ + gun.get('change/pointer/point/deep').path('bob').path('age').put(30); + },400); + }); + + it('mutate pointer to primitive after any', function(done){ + var s = Gun.state.map();s.soul = 'change/pointer/to/prime'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: {_: {'#': 'asdffdsa'}, + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + var bob = gun.get('asdffdsa').any(function(err, data){ + //console.log("***", data); + }); + gun.get('change/pointer/to/prime').path('bob').any(function(err, data, f, at){ + //console.log("***********", data); + if(!Gun.obj.is(data)){ + expect(data).to.be(3); + if(done.c){return} + done();done.c=1; + return; + } + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + expect(Gun.val.rel.is(data.pet)).to.be.ok(); + }); + setTimeout(function(){ + gun.get('change/pointer/to/prime').path('bob').put(3); + setTimeout(function(){ + bob.put({age: 30}); + },100); + },400); + }); + + it('mutate pointer to primitive after any deep', function(done){ + var s = Gun.state.map();s.soul = 'change/pointer/to/prime/deep'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: { + age: 29, + name: "Bob!", + pet: {_: {'#': 'sadffads'}, + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + var cat = gun.get('sadffads').any(function(err, data){ + //console.log("***", data); + }); + gun.get('change/pointer/to/prime/deep').path('bob').path('pet').any(function(err, data){ + //console.log("*********", data); + if(!Gun.obj.is(data)){ + expect(data).to.be(undefined); + if(done.c){return} + done();done.c=1; + return; + } + expect(data.species).to.be('kitty'); + expect(data.name).to.be('Fluffy'); + }); + setTimeout(function(){ + gun.get('change/pointer/to/prime/deep').path('bob').put(3); + setTimeout(function(){ + cat.put({laser_eyes: true}); + },100); + },400); + }); + return; + it.only('mutate pointer to another pointer after any', function(done){ + var s = Gun.state.map();s.soul = 'change/pointer/to/pointer'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({ + bob: {_: {'#': 'dafssfad'}, + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }, s)}); + var bob = gun.get('dafssfad').any(function(err, data){ + console.log("***", data); + }); + console.debug.i=1;console.log("--------------------"); + gun.get('change/pointer/to/pointer').path('bob').any(function(err, data){ + console.log("*********", data);return; + if(done.soul && done.soul !== Gun.node.soul(data)){ + expect(Gun.node.soul(data)).to.be('fsdaadsf'); + expect(data.cat).to.be(true); + expect(data.age).to.not.be.ok(); + expect(data.name).to.not.be.ok(); + expect(data.pet).to.not.be.ok(); + if(done.c){return} + done();done.c=1; + return; + } + expect(done.soul = Gun.node.soul(data)).to.be('dafssfad'); + expect(data.age).to.be(29); + expect(data.name).to.be('Bob!'); + expect(Gun.val.rel.is(data.pet)).to.be.ok(); + }); + return; + setTimeout(function(){ + gun.get('change/pointer/to/pointer').path('bob').put(Gun.node.ify({cat: true}, 'fsdaadsf')); + setTimeout(function(){ + bob.put({age: 30}); + },100); + },400); + }); + return; + it.only('deep freeze put', function(done){ + gun.get('deep/freeze').put({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }); + gun.get('deep/freeze').path('bob').path('pet').any(function(err, data){ + console.log("********************", data); + expect(data.name).to.be('Fluffy'); + expect(data.species).to.be('kitty'); + setTimeout(function(){ + done(); + },100); + }); + }); + + it('deep freezing put', function(done){ + gun.get('deep/freeze/ing').put({ + bob: { + age: 29, + name: "Bob!", + pet: { + name: "Fluffy", + species: "kitty" + } + } + }); + gun.get('deep/freeze/ing').path('bob').path('pet').any(function(err, data){ + //console.log("******** bob's pet", data); + if(done.c){ + expect(data).to.be(undefined); + return; + } + expect(data.name).to.be('Fluffy'); + expect(data.species).to.be('kitty'); + done.c=1; + }); + gun.get('deep/freeze/ing').put({bob: 'lol'}); + gun.get('deep/freeze/ing').path('bob').path('pet').any(function(err, data){ + //console.log("********* 2 bob's pet", data);return; + expect(data).to.be(undefined); + done(); + }); + }); + return; + it('put put put put', function(){ + var gun = Gun(); + var get = gun.get('put/put/put/put'); + get.put({}); + get.put({ + all: { + the: { + way: 'down' + } + } + }); + get.put({foo: 'bar'}); + get.any(function(err,data){ + //console.log("data", data); + expect(Gun.val.rel.is(data.all)).to.be.ok(); + expect(data.foo).to.be('bar'); + }); + }); + + it('perf put', function(done){ + var gun = Gun(); + var hey = gun.get('heylo'); + hey.put({hello: "world"}); + hey.any(function(err, data){ + expect(data.hello).to.be('world'); + done(); + }); + }); + + it('put', function(done){ + gun.put("hello", function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('put NaN', function(done){ + gun.put({num: NaN}, function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('put date', function(done){ + gun.put({date: new Date()}, function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('put regex', function(done){ + gun.put({reg: /regex/i}, function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('put node', function(done){ + gun.put({hello: "world"}, function(err, ok){ + expect(err).to.not.be.ok(); + done(); + }); + }); + + it('put node then value', function(done){ + var ref = gun.put({hello: "world"}); + //console.log("---------"); + ref.put('hello', function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('put node then put', function(done){ + gun.put({hello: "world"}).put({goodbye: "world"}, function(err, ok){ + expect(err).to.not.be.ok(); + done(); + }); + }); + + it('put node with soul get soul', function(done){ + gun.put({_: {'#': 'foo'}, hello: 'world'}) + .get({'#': 'foo'}, function(err, node){ + expect(err).to.not.be.ok(); + expect(Gun.node.soul(node)).to.be('foo'); + expect(node.hello).to.be('world'); + if(done.c){ return } + done(); done.c = 1; + }) + }); + + it('put node with soul get soul tweak', function(done){ + Gun().put({_: {'#': 'foo'}, hello: 'world'}); + setTimeout(function(){ + var gun = Gun(); + gun.put({_: {'#': 'foo'}, boo: 'bear'}) + .get({'#': 'foo'}, function(err, node){ + if(done.c >= 1){ return } + //console.log("**********", err, node); + expect(Gun.node.soul(node)).to.be('foo'); + expect(err).to.not.be.ok(); + expect(node.boo).to.be('bear'); + //if(!done.c){ return done.c = 1 } done.c = 2; + //expect(node.hello).to.be('world'); + done(); done.c = 2; + }) + },100); + }); + + it('put node key get', function(done){ + gun.put({hello: "key"}).key('yes/key', function(err, ok){ + //console.log("***", err, ok); + expect(err).to.not.be.ok(); + done.w = 1; if(done.c){ return } if(done.r){ done(); done.c = 1 }; + }).get('yes/key', function(err, node){ + //console.log("*******", err, node); + expect(err).to.not.be.ok(); + expect(Gun.node.soul(node)).to.be('yes/key'); + expect(node.hello).to.be('key'); + done.r = 1; if(done.c){ return } if(done.w){ done(); done.c = 1 }; + }); + }); + + it('put node key gun get', function(done){ + gun.put({hello: "a key"}).key('yes/a/key', function(err, ok){ + expect(err).to.not.be.ok(); + }); + gun.get('yes/a/key', function(err, node){ + expect(err).to.not.be.ok(); + expect(node.hello).to.be('a key'); + if(done.c){ return } + done(); done.c = 1; + }); + }); + + it('gun key', function(){ // Revisit this behavior? + try{ gun.key('fail/key') } + catch(err){ + expect(err).to.be.ok(); + } + }); + + it('get key no override', function(done){ + var gun = Gun(); + gun.put({cream: 'pie'}).key('cream/pie').get('cream/pie', function(err, node){ + expect(Gun.node.soul(node)).to.be('cream/pie'); + if(done.c){ return } + if(node.cream && node.pie){ + expect(node.cream).to.be('pie'); + expect(node.pie).to.be('cream'); + done(); done.c = 1; + } return; + if(done.c >= 2){ return } + if(done.c){ done(); done.c = 2; return; } done.c = 1; + }); + gun.get('cream/pie').key('pie/cream'); + gun.get('pie/cream').put({pie: 'cream'}); + }); + + it('get key', function(done){ + gun.get('yes/key', function(err, node){ + expect(err).to.not.be.ok(); + expect(node.hello).to.be('key'); + }).key('hello/key', function(err, ok){ + expect(err).to.not.be.ok(); + done.key = true; + if(!done.c && done.yes){ done();done.c=1; } + }).key('yes/hello', function(err, ok){ + expect(err).to.not.be.ok(); + done.yes = true; + if(!done.c && done.key){ done();done.c=1; } + }); + }); + + it('get key null', function(done){ + gun.get('yes/key').key('', function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('key node has no key relations', function(done){ + var gun = Gun(); + gun.put({hello: 'world'}).key('hello/earth'); + gun.put({continent: 'africa'}).key('hello/earth'); + gun.put({place: 'asia'}).key('hello/earth'); + gun.put({north: 'america'}).key('hello/galaxy'); + gun.put({south: 'pole'}).key('hello/galaxy'); + gun.get('hello/earth').key('hello/galaxy', function(err, ok){ + expect(err).to.not.be.ok(); + }); + var node = gun.Back(-1)._.graph['hello/earth'] || {}; // TODO: IS THIS CORRECT? + expect(node['hello/galaxy']).to.not.be.ok(); + gun.get('hello/earth', function(err, pseudo){ + expect(pseudo.hello).to.be('world'); + expect(pseudo.continent).to.be('africa'); + expect(pseudo.place).to.be('asia'); + expect(pseudo.north).to.not.be.ok(); + }); + var galaxy = gun.Back(-1)._.graph['hello/galaxy'] || {}; // TODO: IS THIS CORRECT? + expect(galaxy['hello/earth']).to.not.be.ok(); + gun.get('hello/galaxy', function(err, pseudo){ + if(done.c || !pseudo.hello || !pseudo.south || !pseudo.place || !pseudo.continent || !pseudo.north){ return } + expect(pseudo.hello).to.be('world'); + expect(pseudo.south).to.be('pole'); + expect(pseudo.place).to.be('asia'); + expect(pseudo.continent).to.be('africa'); + expect(pseudo.north).to.be('america'); + expect(pseudo['hello/earth']).to.not.be.ok(); + expect(pseudo['#hello/earth#']).to.not.be.ok(); + done(); done.c = 1; + }); + }); + + function soulnode(gun, kn, r){ // TODO: WARNING! Key implementation has changed significantly. Tests are somewhat hardcoded, sad day. + r = r || []; + kn = Gun.obj.copy(kn); + delete kn._; + expect(Gun.obj.empty(kn, '##')).to.be.ok(); + kn = gun.Back(-1)._.graph[Gun.val.rel.is(kn['##'])]; + Gun.node.is(kn, function(node, s){ + var n = gun.Back(-1)._.graph[s]; + if(Gun.obj.has(n, '##')){ + soulnode(gun, n, r); + return; + } + r.push(s); + }); + return r; + } + + it('get node put node merge', function(done){ + gun.get('hello/key', function(err, node){ + if(done.soul){ return } + expect(err).to.not.be.ok(); + expect(node.hello).to.be('key'); + done.soul = Gun.node.soul(node); + }).put({hi: 'you'}, function(err, ok){ + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph[done.soul], soul; + expect(keynode.hi).to.not.be.ok(); + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + var node = gun.Back(-1)._.graph[soul]; + expect(node.hello).to.be('key'); + expect(node.hi).to.be('you'); + }).on(function(node){ + if(done.c){ return } + //expect(done.soul).to.be(Gun.node.soul(node)); // TODO: DISCUSSION! This has changed? + expect(node.hi).to.be('you'); + expect(node.hello).to.be('key'); + done(); done.c = 1; + }); + }); + + it('get null put node never', function(done){ // TODO: GET returns nothing, and then doing a PUT? + gun.get(null, function(err, ok){ + expect(err).to.be.ok(); + done.err = true; + }).put({hi: 'you'}, function(err, ok){ + done.flag = true; + }); + setTimeout(function(){ + expect(done.err).to.be.ok(); + expect(done.flag).to.not.be.ok(); + done(); + }, 500); + }); + + it('get key no data put', function(done){ + var gun = Gun({init: true}); + gun.get('this/key/definitely/does/not/exist', function(err, data){ + done.gcb = true; + done.err = err; + expect(err).to.not.be.ok(); + expect(data).to.not.be.ok(); + }).put({testing: 'stuff'}, function(err, ok){ + done.flag = true; + }); + setTimeout(function(){ + expect(done.gcb).to.be.ok(); + expect(done.err).to.not.be.ok(); + expect(done.flag).to.not.be.ok(); + done(); + }, 500); + }); + + it('get node put node merge conflict', function(done){ + gun.get('hello/key', function(err, node){ + if(done.soul){ return } + expect(err).to.not.be.ok(); + expect(node.hello).to.be('key'); + expect(node.hi).to.be('you'); + done.soul = Gun.node.soul(node); + }).put({hi: 'overwritten'}, function(err, ok){ + if(done.c){ return } + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph[done.soul], soul; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + var node = gun.Back(-1)._.graph[soul]; + expect(node.hello).to.be('key'); + expect(node.hi).to.be('overwritten'); + done.w = 1; if(done.r){ done(); done.c = 1 }; + }).on(function(node){ + if(done.c){ return } + //expect(done.soul).to.be(Gun.node.soul(node)); // since put has changed chains, do we keep the pseudomerge key context? + expect(node.hello).to.be('key'); + expect(node.hi).to.be('overwritten'); + done.r = 1; if(done.w){ done(); done.c = 1 }; + }); + }); + + it('get key path put', function(done){ + var gun = Gun().put({foo:'lol', extra: 'yes'}).key('key/path/put'); + var data = gun.get('key/path/put'); + data.path('foo').put('epic'); + data.val(function(val, field){ + expect(val.foo).to.be('epic'); + expect(Gun.node.soul(val)).to.be('key/path/put'); + done(); + }); + }); + + it('put node path', function(done){ + var gun = Gun(); + gun.put({hello: 'world'}).path('hello', function(err, val, field){ + if(done.end){ return } // it is okay for path's callback to be called multiple times. + expect(err).to.not.be.ok(); + expect(field).to.be('hello'); + expect(val).to.be('world'); + done(); done.end = true; + }); + }); + + it('put node path path', function(done){ + var gun = Gun(); + //console.debug.i=1;console.log("-----------------"); + var g = gun.put({hello: {little: 'world'}}).path('hello').path('little', function(err, val, field, cat){ + if(done.end){ return } // it is okay for path's callback to be called multiple times. + expect(err).to.not.be.ok(); + expect(field).to.be('little'); + expect(val).to.be('world'); + done(); done.end = true; + }); + }); + + it('put node path rel', function(done){ + gun.put({foo: {bar: 'lol'}}).path('foo', function(err, val, field){ + //console.log("*****", err, val, field); + if(done.end){ return } // it is okay for path's callback to be called multiple times. + expect(err).to.not.be.ok(); + expect(field).to.be('foo'); + expect(val.bar).to.be('lol'); + done(); done.end = true; + }); + }); + + it('get node path', function(done){ + gun.get('hello/key').path('hi', function(err, val, field){ + if(done.end){ return } // it is okay for path's callback to be called multiple times. + expect(err).to.not.be.ok(); + expect(field).to.be('hi'); + expect(val).to.be('overwritten'); + done(); done.end = true; + }); + }); + + it('put node get field', function(done){ // future feature. + var gun = Gun(); + gun.put({_:{'#': 'soul/field'}, hi: 'lol', foo: 'bar'});//.key('key/field'); + gun.get({'#': 'soul/field', '.': 'hi'}, function(err, val){ + //expect(val.hi).to.be('lol'); // TODO: REVISE API? + expect(val).to.be('lol'); + //expect(Gun.obj.has(val,'foo')).to.not.be.ok(); + done(); + }) + }); + + it('get node path put value', function(done){ + gun.get('hello/key', function(err, node){ + expect(err).to.not.be.ok(); + if(done.soul){ return } + expect(node.hi).to.be('overwritten'); + done.soul = Gun.node.soul(node); + }).path('hi').put('again', function(err, ok){ + if(done.c){ return } + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph[done.soul], soul; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + var node = gun.Back(-1)._.graph[done.sub = soul]; + expect(node.hello).to.be('key'); + expect(node.hi).to.be('again'); + done.w = 1; if(done.r){ done(); done.c = 1 }; + }).on(function(val, field){ + if(done.c){ return } + expect(val).to.be('again'); + expect(field).to.be('hi'); + done.r = 1; if(done.w){ done(); done.c = 1 }; + }); + }); + + it('get node path put object', function(done){ + var foo = gun.get('hello/key', function(err, node){ + if(done.soul){ return } + expect(err).to.not.be.ok(); + expect(node.hi).to.be('again'); + expect(node.hello).to.be('key'); + done.soul = Gun.node.soul(node); + }).path('hi').put({yay: "value"}, function(err, ok){ + if(done.c){ return } + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph[done.soul], soul; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + var root = gun.Back(-1)._.graph[soul]; + expect(root.hello).to.be('key'); + expect(root.yay).to.not.be.ok(); + expect(Gun.val.rel.is(root.hi)).to.be.ok(); + expect(Gun.val.rel.is(root.hi)).to.not.be(soul); + var node = gun.Back(-1)._.graph[Gun.val.rel.is(root.hi)]; + expect(node.yay).to.be('value'); + if(done.sub){ expect(done.sub).to.be(Gun.val.rel.is(root.hi)) } + else { done.sub = Gun.val.rel.is(root.hi) } + done.w = 1; if(done.r){ done(); done.c = 1 }; + }).on(function(node, field){ + if(done.c){ return } + expect(field).to.be('hi'); + expect(node.yay).to.be('value'); + if(done.sub){ expect(done.sub).to.be(Gun.node.soul(node)) } + else { done.sub = Gun.node.soul(node) } + done.r = 1; if(done.w){ done(); done.c = 1 }; + }); + }); + + it('get path wire', function(done){ + var gun = Gun(); + var get = gun.get('shallow/path'); + var path = get.path('one'); + var put = path.put('good'); + put.val(function(val, field){ + expect(val).to.be('good'); + expect(field).to.be('one'); + done(); + }); + }); + + it('get path wire shallow', function(done){ + var gun = Gun(); + var get = gun.get('slightly/shallow/path'); + var path = get.path('one'); + var put = path.put({you: 'are', here: 1}); + put.val(function(val, field){ + //console.log('***********', field, val); + expect(val.you).to.be('are'); + expect(val.here).to.be(1); + expect(field).to.be('one'); + done(); + }); + }); + + it('get put, Gun get path', function(done){ // For testing lazy eval that it works on cb style. + var gun = Gun(); + gun.get('test').put({you: {are: 'cool'}}); + // TODO: BUG!? Occasionally has a stack overflow???? :/ + setTimeout(function(){ + var g = Gun(); // TODO: NOTE! This will not work for in-memory only. This means it might not be viable as a test for core. + g.get('test').path('you', function(e,d){ + if(!d || done.c){ return } + expect(d.are).to.be('cool'); + done.c = true; + setTimeout(function(){ + done(); + },10); + }); + },250); + }); + + it('get put, Gun get path to path', function(done){ // For testing lazy eval that it works on cb style. + var gun = Gun(); + gun.get('test1').put({you: {are: 'cool'}}); + setTimeout(function(){ + var g = Gun(); // TODO: NOTE! This will not work for in-memory only. This means it might not be viable as a test for core. + var p = g.get('test1').path('you'); + setTimeout(function(){ + p.path('are', function(e,d){ + if(!d || done.c){ return } + expect(d).to.be('cool'); + done();done.c = true; + }); + },100); + + },100) + }); + + it('get put, Gun get path path', function(done){ // For testing lazy eval that it works on cb style. + var gun = Gun(); + gun.get('test2').put({you: {are: 'cool'}}); + setTimeout(function(){ + var g = Gun(); // TODO: NOTE! This will not work for in-memory only. This means it might not be viable as a test for core. + var p = g.get('test2').path('you').path('are', function(e,d){ + if(!d || done.c){ return } + expect(d).to.be('cool'); + done();done.c=true; + }); + },100); + }); + + it('any any not', function(done){ + var s = Gun.state.map(); + s.soul = 'a'; + Gun.on('put', {gun: gun, put: Gun.graph.ify({b: 1, c: 2}, s)}); + function cb(e,d,f,a){ + if('b' === f && 1 === d){ + done.b = true; + } + if('c' === f && 2 === d){ + done.c = true; + } + if('d' === f && !d){ + done.d = true; + } + if(done.done){ return } + if(done.b && done.c && done.d){ + done.done = true; + done(); + } + } + gun.get('a').path('b').any(cb);//.err(cb).not(cb).on(cb).val(cb); + gun.get('a').path('c').any(cb);//.err(cb).not(cb).on(cb).val(cb); + gun.get('a').path('d').any(cb);//.err(cb).not(cb).on(cb).val(cb); + }); + + it('any not any not any not', function(done){ + function cb(e,d,f,a){ + if('b' === f && !d){ + done.b = true; + } + if('c' === f && !d){ + done.c = true; + } + if('d' === f && !d){ + done.d = true; + } + if(done.b && done.c && done.d){ + done(); + } + } + gun.get('x').path('b').any(cb);//.err(cb).not(cb).on(cb).val(cb); + gun.get('x').path('c').any(cb);//.err(cb).not(cb).on(cb).val(cb); + gun.get('x').path('d').any(cb);//.err(cb).not(cb).on(cb).val(cb); + }); + + it('get put, put deep', function(done){ + var gun = Gun(); + var get = gun.get('put/deep/ish'); + get.put({}); + get.val(function(data){ // TODO: API CHANGE! Empty objects should react. + //console.log("...1", data); + expect(Gun.obj.empty(data, '_')).to.be.ok(); // API CHANGED, + //expect(Gun.val.rel.is(data.very)).to.be.ok(); + });//, {wait: 10000}); + setTimeout(function(){ + var put = get.put({ + very: { + deep: { + ly: { + oriented: true + } + } + } + }); + get.val(function(data){ + //console.log("...2", data); + expect(Gun.val.rel.is(data.very)).to.be.ok(); + }); + setTimeout(function(){ + put.val(function(data){ + //console.log("...3", data); + expect(Gun.val.rel.is(data.very)).to.be.ok(); + done.val = true; + }); + var p = put.path('very'); + p.put({we: 'have gone!'}); + setTimeout(function(){ + p.val(function(data){ + //console.log("...4", data); + expect(data.we).to.be('have gone!'); + expect(Gun.val.rel.is(data.deep)).to.be.ok(); + }); + p.put('EXPLODE'); + setTimeout(function(){ + expect(done.val).to.be.ok(); + done(); + },5); + },150); + },250); + },110); + }); + + it('get path wire shallow swoop', function(done){ + var gun = Gun(); + var get = gun.get('slightly/shallow/path/swoop'); + var path = get.path('one.two'); + var put = path.put({oh: 'okay'}); + put.val(function(val, field){ + //console.log("****", field, val); + expect(val.oh).to.be('okay'); + expect(field).to.be('two'); + done(); + }); + }); + + it('get path wiring', function(done){ + var gun = Gun(); + var get = gun.get('deep/path'); + var path = get.path('one.two'); + var path3 = path.path('three'); + var put = path3.put({you: 'found', the: 'bottom!'}); + put.val(function(val, field){ + //console.log("********1********", field, val); + expect(val.you).to.be('found'); + expect(val.the).to.be('bottom!'); + expect(field).to.be('three'); + }); + gun.get('deep/path').path('one.two.three.you').put('are').val(function(val, field){ + //console.log("********2*********", field, val);return; + expect(val).to.be('are'); + expect(field).to.be('you'); + done(); + }); + }); + + it('get node path put object merge isolated', function(done){ + // MORAL OF THE STORY: in KEY ON.GET check for change as NODE = AT.CHANGE || GUN.__.GRAPH[AT.soul] && Gun.node.soul(NODE, 'KEY') === 1; BAM! + var gun = Gun(); + var put = gun.put({hello: 'key'}).key('hello/key/iso') + var get = gun.get('hello/key/iso'); + var puthi = get.put({hi: 'you'}); + puthi.on(function(node){ + if(done.hi){ return } + //console.log(1, node); + expect(node.hello).to.be('key'); + expect(node.hi).to.be('you'); + done.hi = 1; + }); + setTimeout(function(){ + var get2 = gun.get('hello/key/iso'); + var path2 = get2.path('hi'); + path2._.id = 'path2'; + var putyay = path2.put({yay: "value"}); + putyay.on(function(node, field){ + if(done.yay){ return } + expect(field).to.be('hi'); + expect(node.yay).to.be('value'); + done.yay = true; + }); + setTimeout(function(){ + var get3 = gun.get('hello/key/iso'); + var path3 = get3.path('hi'); + path3._.id = 'path3'; + var puthappy = path3.put({happy: "faces"}); + puthappy.on(function(node, field){ + //console.log(3, field, node); + expect(field).to.be('hi'); + expect(node.happy).to.be('faces'); + expect(node.yay).to.be('value'); + setTimeout(function(){ + console.log("******************************"); + done(); + },200); + }); + },100); + },100); + }); + + it('get node path put object merge', function(done){ + var g = gun.get('hello/key', function(err, node){ + if(done.soul){ return } + expect(err).to.not.be.ok(); + expect(done.ref = Gun.val.rel.is(node.hi)).to.be.ok(); + done.soul = Gun.node.soul(node); + }); + g.path('hi').put({happy: "faces"}, function(err, ok){ + if(done.c){ return } + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph[done.soul], soul; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + var root = gun.Back(-1)._.graph[soul]; + var sub = gun.Back(-1)._.graph[done.ref]; + expect(root.hello).to.be('key'); + expect(root.yay).to.not.be.ok(); + expect(Gun.node.soul(sub)).to.be(done.ref); + expect(sub.yay).to.be('value'); + expect(sub.happy).to.be('faces'); + if(done.sub){ expect(done.sub).to.be(done.ref) } + else { done.sub = done.ref } + done.w = 1; if(done.r){ done(); done.c = 1 }; + }).on(function(node, field){ + if(done.c){ return } + expect(field).to.be('hi'); + expect(node.happy).to.be('faces'); + expect(node.yay).to.be('value'); + if(done.sub){ expect(done.sub).to.be(Gun.node.soul(node)) } + else { done.sub = Gun.node.soul(node) } + done.r = 1; if(done.w){ done(); done.c = 1 }; + }); + }); + + it('get node path put value conflict relation', function(done){ + gun.get('hello/key', function(err, node){ + if(done.soul){ return } + expect(err).to.not.be.ok(); + expect(done.ref = Gun.val.rel.is(node.hi)).to.be.ok(); + done.soul = Gun.node.soul(node); + }).path('hi').put('crushed', function(err, ok){ + if(done.c){ return } + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph[done.soul], soul; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + var root = gun.Back(-1)._.graph[soul]; + var sub = gun.Back(-1)._.graph[done.ref]; + expect(root.hello).to.be('key'); + expect(root.yay).to.not.be.ok(); + expect(Gun.node.soul(sub)).to.be(done.ref); + expect(sub.yay).to.be('value'); + expect(sub.happy).to.be('faces'); + expect(root.hi).to.be('crushed'); + done.w = 1; if(done.r){ done(); done.c = 1 }; + }).on(function(val, field){ + if(done.c){ return } + expect(field).to.be('hi'); + expect(val).to.be('crushed'); + done.r = 1; if(done.w){ done(); done.c = 1 }; + }); + }); + + it.skip('put gun node', function(done){ + var mark = gun.put({age: 23, name: "Mark Nadal"}); + var amber = gun.put({age: 23, name: "Amber Nadal"}); + mark.path('wife').put(amber, function(err){ + expect(err).to.not.be.ok(); + }); + mark.path('wife.name').val(function(val){ + expect(val).to.be("Amber Nadal"); + }); + }); + + it('put val', function(done){ + gun.put({hello: "world"}).val(function(val){ + expect(val.hello).to.be('world'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + it('put key val', function(done){ + gun.put({hello: "world"}).key('hello/world').val(function(val, field){ + if(done.c){ return } + expect(val.hello).to.be('world'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + it('get val', function(done){ + gun.get('hello/world').val(function(val, field){ + expect(val.hello).to.be('world'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + it('get path', function(done){ + gun.get('hello/world').path('hello').val(function(val){ + //console.log("**************", val); + expect(val).to.be('world'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 1900); + }); + + it('get put path', function(done){ + gun.get('hello/world').put({hello: 'Mark'}).path('hello').val(function(val, field){ + expect(val).to.be('Mark'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + it('get path put', function(done){ + gun.get('hello/world').path('hello').put('World').val(function(val){ + expect(val).to.be('World'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + it('get empty put', function(done){ + var gun = Gun({init: true}); + gun.get('nothing/here').put({far: "wide"}, function(err, ok){ + done.put = true; + }); + gun.get({'#': 'asdfoobar'}).put({far: "wide"}, function(err, ok){ + done.put2 = true; + }); + setTimeout(function(){ + expect(done.put).to.not.be.ok(); + expect(done.put2).to.not.be.ok(); + done(); + }, 100) + }); + + it('get path empty put val', function(done){ + var gun = Gun({init: true}).put({hello: "Mark"}).key('hello/world/not'); + gun.get('hello/world/not').path('earth').put('mars').val(function(val){ + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.not.be.ok(); // CHANGELOG: API 0.3 BREAKING CHANGE, .put is suppose to be dependent on the previous chain, which means it SHOULD NOT PUT on an empty path. + done(); + }, 100); + }); + + it('get empty put val implicit', function(done){ + var gun = Gun(); + var get = gun.get('hello/imp/world'); + var put = get.put({planet: 'the earth'}); + put.val(function(val){ + expect(val.planet).to.be('the earth'); + done(); + }); + }); + + it('get empty path put val implicit split', function(done){ + var gun = Gun(); + var get = gun.get('hello/imp/where'); + var path = get.path('where'); + var put = path.put('the mars'); + var val = put.val(function(val, field){ + expect(field).to.be('where'); + expect(val).to.be('the mars'); + done(); + }); + }); + + it('get path empty put val implicit', function(done){ + gun.get('hello/world').path('earth').put('mars').val(function(val, field){ + expect(val).to.be('mars'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + it('get path val', function(done){ + var gun = Gun({init: true}).put({hello: "Mark"}).key('hello/world/not'); + gun.get('hello/world').path('earth').put('mars'); + gun.get('hello/world/not').path('earth').val(function(val){ + expect(val).to.be('mars'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.not.be.ok(); + done(); + }, 100); + }); + + it('get path val implicit', function(done){ + gun.get('hello/world').path('earth').val(function(val){ + expect(val).to.be('mars'); + expect(done.c).to.not.be.ok(); + done.c = 1; + }); + setTimeout(function(){ + expect(done.c).to.be.ok(); + done(); + }, 100); + }); + + describe('some nots', function(){ + it('get not kick val', function(done){ + gun.get("some/empty/thing").not(function(key, kick){ // that if you call not first + this.put({now: 'exists'}).key(key); // you can put stuff + }).val(function(val){ // and THEN still retrieve it. + expect(val.now).to.be('exists'); + done(); + }); + }); + + it('get not kick val when it already exists', function(done){ + var foo; + foo = gun.get("some/empty/thing"); + foo.not(function(key, kick){ + done.not = true; + this.put({now: 'THIS SHOULD NOT HAPPEN'}).key(key); + }).val(function(val){ + expect(val.now).to.be('exists'); + expect(done.not).to.not.be.ok(); + done(); + }); + }); + }); + + it('put path val sub', function(done){ + gun.put({last: {some: 'object'}}).path('last').val(function(val){ + expect(val.some).to.be('object'); + done(); + }); + }); + //return; + it('chain ordering', function(done){ + var sec = gun.get('order/second'); + var res = Gun.on.stun(sec); + gun.get('order/first', function(){ // this has a race condition against the third get. However if it fulfills first... + //console.log('callback', 0); + done.zero = true; + expect(done.one).to.not.be.ok(); + expect(done.two).to.not.be.ok(); + res(function(){ + sec.any(function(){ // then this guy should be run before the third get, since it is queued first relative to this soul. + //console.log('callback', 1); + done.one = true; + expect(done.zero).to.be.ok(); + expect(done.one).to.be.ok(); + expect(done.two).to.not.be.ok(); + res(); + }); + }); + }); + + gun.get('order/second', function(){ + //console.log('callback', 2); + done.two = true; + expect(done.zero).to.be.ok(); + expect(done.one).to.be.ok(); + expect(done.two).to.be.ok(); + done(); + }); + }); + + it('get put null', function(done){ + gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ + //console.log("**", field, val); + expect(field).to.be('last'); + expect(val.some).to.be('object'); + }).put(null).val(function(val, field){ + //console.log("******", field, val); + expect(field).to.be('last'); + expect(val).to.be(null); + done(); + }); + }); + + it('Gun get put null', function(done){ // flip flop bug + var gun = Gun(); + gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ + //console.log("**", field, val); + done.some = true; + expect(val.some).to.be('object'); + }).put(null).val(function(val, field){ + //console.log("********", field, val); + expect(val).to.be(null); + expect(done.some).to.be.ok(); + done(); + }); + }); + + it('var put key path', function(done){ // contexts should be able to be saved to a variable + var foo = gun.put({foo: 'bar'}).key('foo/bar'); + foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original + setTimeout(function(){ + foo.path('foo').val(function(val){ // and then the original should be able to be reused later + expect(val).to.be('bar'); // this should work + done(); + }); + }, 500); + }); + + it('var get path', function(done){ // contexts should be able to be saved to a variable + var foo = gun.get('foo/bar'); + foo.path('hello.world.nowhere'); // this should become a sub-context, that doesn't alter the original + setTimeout(function(){ + foo.path('foo').val(function(val){ // and then the original should be able to be reused later + expect(val).to.be('bar'); // this should work + done(); + }); + }, 500); + }); + + it('get not put val path val', function(done){ + var todos = gun.get("examples/list/foobar").not(function(key){ + this.put({ + id: 'foobar', + title: 'awesome title', + todos: {} + }).key(key); + }).val(function(data){ + expect(data.id).to.be('foobar'); + //}).path('todos').val(function(todos, field){ + }).path('todos').val(function(todos, field){ + expect(field).to.be('todos'); + expect(todos).to.not.have.property('id'); + done(); + }, {empty: true}); // API CHANGED! .val fires on empty by default now. + }); + + it('put circular ref', function(done){ + var data = {}; + data[0] = "DATA!"; + data.a = {c: 'd', e: 1, f: true}; + data.b = {x: 2, y: 'z'}; + data.a.kid = data.b; + data.b.parent = data.a; + gun.put(data, function(err, ok){ + expect(err).to.not.be.ok(); + }).val(function(val){ + setTimeout(function(){ // TODO: Is this cheating? I don't think so cause we are using things outside of the API! + var a = gun.Back(-1)._.graph[Gun.val.rel.is(val.a)]; + var b = gun.Back(-1)._.graph[Gun.val.rel.is(val.b)]; + expect(Gun.val.rel.is(val.a)).to.be(Gun.node.soul(a)); + expect(Gun.val.rel.is(val.b)).to.be(Gun.node.soul(b)); + expect(Gun.val.rel.is(a.kid)).to.be(Gun.node.soul(b)); + expect(Gun.val.rel.is(b.parent)).to.be(Gun.node.soul(a)); + done(); + },10); + }); + }); + + it('gun put path and some changes node', function(done){ done.c = 0; + var ref = gun.put({ + foo: {bar: 'lol'} + }); + var sub = ref.path('foo').on(function(val){ + done.c++; + if(val){ + expect(val.extra).to.not.be.ok(); + } + if(done.c === 1){ + expect(val.bar).to.be('lol'); + ref.put({foo: 'hi'}); + return; + } + if(done.c === 2){ + expect(val).to.be('hi'); + done(); + } + }); + }); + + it('gun put two nodes, link one, path and detach', function(done){ done.c = 0; + // this test is not written yet! + var ref = gun.put({ + foo: {bar: 'lol'} + }); + var sub = ref.path('foo').on(function(val){ + done.c++; + if(val){ + expect(val.extra).to.not.be.ok(); + } + if(done.c === 1){ + expect(val.bar).to.be('lol'); + ref.put({foo: 'hi'}); + return; + } + if(done.c === 2){ + expect(val).to.be('hi'); + done(); + } + }); + // ref.put({foo: {extra: 'field'}}); + }); + + it('gun put path deep primitive', function(done){ + gun.put({ + foo: { + bar: { + lol: true + } + } + }).path('foo.bar.lol').val(function(val){ + expect(val).to.be(true); + done(); + }); + }); + + it('gun put path deep node', function(done){ + gun.put({ + foo: { + bar: { + lol: {ok: true} + } + } + }).path('foo.bar.lol').val(function(val){ + expect(val.ok).to.be(true); + done(); + }); + }); + + it('put circular deep', function(done){ + var mark = { + age: 23, + name: "Mark Nadal" + } + var amber = { + age: 23, + name: "Amber Nadal", + phd: true + } + mark.wife = amber; + amber.husband = mark; + var cat = { + age: 3, + name: "Hobbes" + } + mark.pet = cat; + amber.pet = cat; + cat.owner = mark; + cat.master = amber; + //console.debug.i=1;console.log("------------"); + gun.put(mark, function(err, ok){ + expect(err).to.not.be.ok(); + }).val(function(val){ + expect(val.age).to.be(23); + expect(val.name).to.be("Mark Nadal"); + expect(Gun.val.rel.is(val.wife)).to.be.ok(); + expect(Gun.val.rel.is(val.pet)).to.be.ok(); + }).path('wife.pet.name').val(function(val){ + //console.debug(1, "*****************", val); + expect(val).to.be('Hobbes'); + }).back.path('pet.master').val(function(val){ + //console.log("*****************", val); + expect(val.name).to.be("Amber Nadal"); + expect(val.phd).to.be.ok(); + expect(val.age).to.be(23); + expect(Gun.val.rel.is(val.pet)).to.be.ok(); + done(); + }); + }); + + it('key get', function(done){ + var gun = Gun(); + gun.get('key/get').put({yay: 'something'}).key('index/yay'); + gun.get('index/yay', function(err, node){ + expect(node.yay).to.be('something'); + if(done.c){return} + done();done.c=1; + }); + }); + + it('put partial sub merge', function(done){ + var gun = Gun(); + var mark = gun.put({name: "Mark", wife: { name: "Amber" }}).key('person/mark').val(function(mark){ + //console.log("VAL1", mark); + done.marksoul = Gun.node.soul(mark); + expect(mark.name).to.be("Mark"); + }); + mark.put({age: 23, wife: {age: 23}}); + setTimeout(function(){ + mark.put({citizen: "USA", wife: {citizen: "USA"}}).val(function(mark){ + //console.log("VAL2", mark, gun); + expect(mark.name).to.be("Mark"); + expect(mark.age).to.be(23); + expect(mark.citizen).to.be("USA"); + this.path('wife').on(function(Amber){ // TODO: turn this .on back into a .val + //console.log("VAL3", Amber); + if(done.c){ return } + expect(done.c).to.not.be.ok(); // RELATED TO BELOW #"CONTEXT NO DOUBLE EMIT". + expect(Amber.name).to.be("Amber"); + expect(Amber.age).to.be(23); + expect(Amber.citizen).to.be("USA"); + done();done.c=1; + }); + }); + }, 500); + }); + + it('path path', function(done){ + var deep = gun.put({some: {deeply: {nested: 'value'}}}); + deep.path('some.deeply.nested').val(function(val){ + expect(val).to.be('value'); + }); + deep.path('some').path('deeply').path('nested').val(function(val){ + expect(val).to.be('value'); + done(); + }); + }); + + it('context null put value val error', function(done){ + gun.put("oh yes", function(err){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('context no double emit', function(done){ // annoying problem where somehow the gun.path in a put starts subscribing and firing to its context if we let get handle emitting for the chain in put. + var c = 0; + var gun = Gun(); + var fo = gun.put({fo: 'bar'}); + Gun.log.ba = 1; + fo.put({ba: {}}).val(function(obj, field){ + c += 1; + expect(c).to.be(1); + done(); + }); + Gun.log.ba = 0; + var ba = fo.path('ba'); + ba.put({co: 'do'}); + }); + + describe('random', function(){ + var foo; + it('context null put node', function(done){ + foo = gun.put({foo: 'bar'}).val(function(obj){ + expect(obj.foo).to.be('bar'); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('context node put val', function(done){ + // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! + foo.put('banana', function(err){ + expect(err).to.be.ok(); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('context node put node', function(done){ + // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! + foo.put({bar: {zoo: 'who'}}).val(function(obj, field){ + //console.log("terribly terrilby unpleasant", field, obj); + expect(obj.foo).to.be('bar'); + expect(Gun.val.rel.is(obj.bar)).to.ok(); + done(); //setTimeout(function(){ done() },1); + }); + }); + + var bar; + it('context node and field of relation put node', function(done){ + // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! + bar = foo.path('bar'); + expect(gleak.check()).to.not.be.ok(); + bar.put({combo: 'double'}).val(function(obj, field){ + //expect(obj.zoo).to.be('who'); + expect(obj.combo).to.be('double'); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('context node and field put value', function(done){ + // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! + var tar = foo.path('tar'); + tar.put('zebra').val(function(val){ + expect(val).to.be('zebra'); + done(); //setTimeout(function(){ done() },1); + }); + }); + + it('context node and field, put node', function(done){ + // EFFECTIVELY a TIMEOUT from the previous test. NO LONGER! + bar.path('combo').put({another: 'node'}).val(function(obj){ + expect(obj.another).to.be('node'); + // double .vals here also RELATED to the #"context no double emit" but because of a faulty .not or .init system. + bar.val(function(node){ + expect(Gun.val.rel.is(node.combo)).to.be.ok(); + expect(Gun.val.rel.is(node.combo)).to.be(Gun.node.soul(obj)); + done(); //setTimeout(function(){ done() },1); + }); + }); + }); + }); + + it('val path put val', function(done){ + var gun = Gun(); + + var al = gun.put({gender:'m', age:30, name:'alfred'}).key('user/alfred'); + var beth = gun.put({gender:'f', age:22, name:'beth'}).key('user/beth'); + + al.val(function(a){ + beth.put({friend: a}, function(err, ok){ + expect(err).to.not.be.ok(); + }).path('friend').val(function(aa){ + expect(Gun.node.soul(a)).to.be(Gun.node.soul(aa)); + done(); + }); + }); + + }); + // TODO: Write a test that tests for keysoul has a key meta indicator. + // TODO: A soulsoul does not have a key meta indicator. + // TODO: Souls match their graph. + it('val path put val key', function(done){ // bug discovered from Jose's visualizer + var gun = Gun(), s = Gun.time.is(), n = function(){ return Gun.time.is() } + this.timeout(5000); + + gun.put({gender:'m', age:30, name:'alfred'}).key('user/alfred'); + gun.put({gender:'f', age:22, name:'beth' }).key('user/beth'); + //gun.get('user/beth').path('friend').put(gun.get('user/alfred')); // ideal format which we have a future test for. + gun.get('user/alfred').val(function(a){ + //console.log("*****", a); + //expect(a[Gun._.meta]['key']).to.be.ok(); + gun.get('user/beth').put({friend: a}, function(err, ok){ // b - friend_of -> a + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph['user/alfred']; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + }); + gun.get('user/beth').val(function(b){ + //console.log("beth", b); + gun.get('user/alfred').put({friend: b}).val(function(al){ // a - friend_of -> b + //console.log("al again", al); + gun.get('user/beth').put({cat: {name: "fluffy", age: 3, coat: "tabby"}}).val(function(bet){ + gun.get('user/alfred').path('friend.cat').key('the/cat'); + gun.get('the/cat').val(function(c){ + //console.log("cat!!!", c); + expect(c.name).to.be('fluffy'); + expect(c.age).to.be(3); + expect(c.coat).to.be('tabby'); + done(); + }); + }); + }); + }); + }); + }); + + it('map', function(done){ + var c = 0, set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); + set.map(function(obj, field){ + c++; + if(field === 'a'){ + expect(obj.here).to.be('you'); + } + if(field === 'b'){ + expect(obj.go).to.be('dear'); + } + if(field === 'c'){ + expect(obj.sir).to.be('!'); + } + if(c === 3){ + done(); + } + }) + }); + + it.skip('key soul', function(done){ // TODO: Deprecated? Maybe. + var gun = Gun(); + gun.key('me', function(err, ok){ + expect(err).to.not.be.ok(); + var keynode = gun.Back(-1)._.graph['me']; + var c = soulnode(gun, keynode), soul = c[0]; + expect(c.length).to.be(1); + + expect(soul).to.be('qwertyasdfzxcv'); + done(); + }, 'qwertyasdfzxcv'); + }); + + it.skip('no false positive null emit', function(done){ // TODO: THE API HAS CHANGED! REDO TEHSE! + var gun = Gun({wire: {get: function(key, cb){ + var g = {}; + g[soul] = {_: {'#': soul, '>': {'a': 0}}, + 'a': 'b' + }; + cb(null, g); + g = {}; + g[soul] = {_: {'#': soul, '>': {'c': 0}}, + 'c': 'd' + }; + cb(null, g); + g = {}; + g[soul] = {_: {'#': soul }}; + cb(null, g); + cb(); // false trigger! + }}}), soul = Gun.text.random(); + gun.get(soul).not(function(err, ok){ + done.fail = true; + }).val(function(val){ + setTimeout(function(){ + expect(val.a).to.be('b'); + expect(val.c).to.be('d'); + expect(done.fail).to.not.be.ok(); + done(); + },5); + }); + }); + + it.skip('unique val on stream', function(done){ // TODO: THE API HAS CHANGED! REDO TEHSE! + var gun = Gun({wire: {get: function(key, cb){ + if(Gun.obj.has(key, Gun._.soul)){ + key = key[Gun._.soul]; + var node = tmp.graph[key]; + cb(null, node); + cb(null, Gun.is.node.ify({}, key)); + cb(null, {}); + } + }}}), tmp = {graph: {}}; + tmp.graph[tmp.soul = Gun.text.random()] = tmp.node = {a: 'b', c: 'd'}; + Gun.is.node.ify(tmp.node, tmp.soul); + + tmp.graph['me'] = tmp.keynode = {}; + Gun.obj.put(tmp.rel = {}, Gun._.soul, tmp.soul); + tmp.keynode[tmp.soul] = tmp.rel; + Gun.is.node.ify(tmp.keynode, 'me'); + tmp.keynode[Gun._.meta]['key'] = 1; + + gun.get('me', function(err, data){ + + }).val(function(val){ + done.count = (done.count || 0) + 1; + setTimeout(function(){ + expect(val.a).to.be('b'); + expect(val.c).to.be('d'); + expect(done.count).to.be(1); + done(); + },5); + }); + }); + + it.skip('unique path val on stream', function(done){ // TODO: THE API HAS CHANGED! REDO TEHSE! + var gun = Gun({wire: {get: function(key, cb){ + var n = {}; + n = {_: {'#': soul, '>': {'a': 0}}, + 'a': 'a' + }; + cb(null, n); + n = {}; + n = {_: {'#': soul, '>': {'a': 1}}, + 'a': 'b' + }; + cb(null, n); + n = {}; + n = {_: {'#': soul }}; + cb(null, n); + }}}), soul = Gun.text.random(); + + gun.get(soul).path('a').val(function(val){ + done.count = (done.count || 0) + 1; + setTimeout(function(){ + expect(val).to.be('b'); + expect(done.count).to.be(1); + done(); + },5); + }); + }); + + it('double not', function(done){ // from the thought tutorial + var gun = Gun(gopt).get('thoughts').not(function(key){ + this.put({}).key(key); + }); + + setTimeout(function(){ + gun.not(function(){ + done.not = true; + }).val(function(){ + expect(done.not).to.not.be.ok(); + done(); + }, {empty: true}); + }, 10); + }); + + it('node path node path node path', function(done){ + var gun = Gun(/*gopt*/); + var data = gun.get('data'); + gun.put({ + a: 1, + b: 2, + c: 3 + }).key('data'); + data.path('a', function(e, v, f){ + //console.log("FIRST", e,v,f); + expect(done.D).to.not.be.ok(); + if(done.a){return} + expect(done.a).to.not.be.ok(); + expect(v).to.be(1); + done.a = true; + }); + data.path('b', function(e, v, f){ + //console.log("SECOND", e,v,f); + expect(done.D).to.not.be.ok(); + if(done.b){return} + expect(done.b).to.not.be.ok(); + expect(v).to.be(2); + done.b = true; + }); + data.path('c', function(e, v, f){ + //console.log("THIRD", e,v,f); + expect(done.D).to.not.be.ok(); + if(done.c){return} + expect(done.c).to.not.be.ok(); + expect(v).to.be(3); + done.c = true; + }); + setTimeout(function(){//return; + done.D=true; + data.put({d: 4}); + expect(done.a).to.be.ok(); + expect(done.b).to.be.ok(); + expect(done.c).to.be.ok(); + done(); + },250); + }); + + it('node path obj node path obj node path obj', function(done){ + var gun = Gun(); + var data = gun.get('data1'); + gun.put({ + a: {v: 1}, + b: {v: 2}, + c: {v: 3} + }).key('data1'); + data.path('a', function(e, v, f){ + //console.log("FIRST", f,v); + expect(done.D).to.not.be.ok(); + if(done.a){return} + expect(done.a).to.not.be.ok(); + expect(v.v).to.be(1); + done.a = true; + }); + data.path('b', function(e, v, f){ + //console.log("SECOND", f,v); + expect(done.D).to.not.be.ok(); + if(done.b){return} + expect(done.b).to.not.be.ok(); + expect(v.v).to.be(2); + done.b = true; + }); + data.path('c', function(e, v, f){ + //console.log("THIRD", f,v); + expect(done.D).to.not.be.ok(); + if(done.c){return} + expect(done.c).to.not.be.ok(); + expect(v.v).to.be(3); + done.c = true; + }); + setTimeout(function(){ + done.D = true; + //data.put({d: {v: 4}}); + expect(done.a).to.be.ok(); + expect(done.b).to.be.ok(); + expect(done.c).to.be.ok(); + done(); + },100); + }); + + describe('prototype crash', function(){ + it('instance.key', function(done){ + Gun().key('oye', function(err){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('instance.on', function(done){ + Gun().on(); + done(); + }); + + it('instance.path', function(done){ + Gun().path('oye', function(err){ + expect(err).to.be.ok(); + done(); + }); + }); + + it('instance.map', function(done){ + Gun().map(); + done(); + }); + + it('instance.not', function(done){ + Gun().not(); + done(); + }); + + it('instance.val', function(done){ + Gun().val(); + done(); + }); + }); + + it('implicit put on empty get', function(done){ + var gun = Gun().get('init'); + gun.on(function(val){ + expect(val.not).to.be(true); + if(done.c){ return } done(); done.c = 1; + }); + gun.put({not: true}); + }); + + it.skip('implicit put on empty get explicit not', function(done){ // HUH? This seems like wrong behavior. + var gun = Gun().get('init/not').not(); + gun.on(function(val){ + console.log("??", val); + done.c = 1; + }); + gun.put({not: true}); + setTimeout(function(){ + expect(done.c).to.not.be.ok(); + done(); + },1); + }); + + it('no implicit put on empty get', function(done){ + var gun = Gun({init: true}).get('not/init'); + gun.on(function(val){ + console.log("hmmm???", val); + expect(val.not).to.be(true); + if(done.c){ return } done.c = 1; + }); + gun.put({not: true}); + setTimeout(function(){ + expect(done.c).to.not.be.ok(); + done(); + },1); + }); + + it('no implicit put on empty get explicit init', function(done){ + var gun = Gun({init: true}).get('not/init/init').init(); + gun.on(function(val){ + if(val.not){ + expect(val.not).to.be(true); + if(done.c){return} + done();done.c=1; + return; + } + expect(Gun.obj.empty(val, '_')).to.be.ok(); + }); + gun.put({not: true}) + }); + + it('init', function(done){ + var gun = Gun().get('init/todo').init(); + gun.on(function(val){ + console.log("*******", val); + if(done.c){return} + if(val.data){ + expect(val.data).to.be('initialized!'); + done();done.c=1; + return; + } + expect(Gun.obj.empty(val, '_')).to.be.ok(); + }); + gun.put({data: 'initialized!'}); + }); + + describe('map able', function(){ + + it('map chain', function(done){ + Gun().put({a:1, b:2}).map().on(function(v,f){ + done[f] = v; + if(done.a && done.b){ + done(); + } + }); + }); + + it('map chain after', function(done){ + var g = Gun().get('m/c/a'); + g.map().on(function(v,f){ + done[f] = v; + if(done.a && done.b){ + done(); + } + }); + g.put({a:1, b:2}); + }); + + it('map chain map to', function(done){ + var g = Gun().get('m/c/m/to'); + var obj = { + a: {x: 1, y: 2, z: 3}, + b: {u: 4, v: 5, w: 6} + }, check = {x:1,y:1,z:1,u:1,v:1,w:1}; + g.map().map().on(function(v,f){ + check[f] = 0; + if(Gun.obj.map(check, function(v,f){if(v){return true}})){return} + done(); + }); + setTimeout(function(){ + g.put(obj); + },110); + }); + + it('map chain map', function(done){ + var g = Gun().get('m/c/m'); + var obj = { + a: {x: 1, y: 2, z: 3}, + b: {u: 4, v: 5, w: 6} + }, check = {x:1,y:1,z:1,u:1,v:1,w:1}; + g.map().map().on(function(v,f){ + check[f] = 0; + if(Gun.obj.map(check, function(v,f){if(v){return true}})){return} + done(); + }); + g.put(obj); + }); + + it('map chain map before', function(done){ + var g = Gun().get('m/c/m/b'); + var obj = { + a: {x: 1, y: 2, z: 3}, + b: {u: 4, v: 5, w: 6} + }, check = {x:1,y:1,z:1,u:1,v:1,w:1}; + g.put(obj); + g.map().map().on(function(v,f){ + check[f] = 0; + if(Gun.obj.map(check, function(v,f){if(v){return true}})){return} + done(); + }); + }); + }); + + it('init todo', function(done){ + var gun = Gun(), todo = gun.get('init/todo/early'); + todo.path('random1').put('eat chocolate'); + todo.map().on(function(val, field){ + expect(val).to.be('eat chocolate'); + expect(field).to.be('random1'); + if(done.c){ return } done(); done.c = 1; + }); + }); + + it('init todo defer', function(done){ + var gun = Gun(), todo = gun.get('init/todo/defer'); + todo.map().on(function(val, field){ + expect(val).to.be('eat chocolate'); + expect(field).to.be('random1'); + if(done.c){ return } done(); done.c = 1; + }); + setTimeout(function(){ + todo.path('random1').put('eat chocolate'); + }, 100); + }); + + /* // CHANGELOG: API 0.3 BREAKING CHANGE, .set has been deprecated! + it('set', function(done){ + done.c = 0; + var u, gun = Gun(); + gun.get('set').set().set().val(function(val){ + var keynode = gun.__.graph['set']; + expect(Gun.node.soul.ify(keynode, Gun._.key)).to.be.ok(); + Gun.is.node(keynode, function(rel, soul){ + rel = gun.__.by(soul).node; + expect(Gun.obj.empty(rel, Gun._.meta)).to.be.ok(); + }); + done.c += 1; + setTimeout(function(){ + expect(done.c).to.be(1); + done() + },10); + }); + }); + + it('root set', function(done){ + var gun = Gun().set(); + gun.on(function(val, field){ + expect(Gun.obj.empty(val, Gun._.meta)).to.be.ok(); + if(done.c){return} done(); done.c = 1; + }); + }); + + // TODO: BUG! We need 2 more tests... without .set()... and multiple paths on the same node. + it('set multiple', function(done){ // kinda related to flip flop? + var gun = Gun().get('sets').set(), i = 0; + gun.val(function(val){ + console.log("TEST 1", val); + expect(Gun.obj.empty(val, Gun._.meta)).to.be.ok(); + expect(Gun.node.soul(val)).to.be('sets'); + var keynode = gun.__.graph['sets']; + expect(Gun.obj.empty(keynode, Gun._.meta)).to.not.be.ok(); + }); + gun.set(1); //.set(2).set(3).set(4); // if you set an object you'd have to do a `.back` + gun.map(function(val, field){ + //gun.map().val(function(val, field){ // TODO: SEAN! DON'T LET ME FORGET! + console.log("\n TEST 2+", field, val); + return; + i += 1; + expect(val).to.be(i); + if(i % 4 === 0){ + setTimeout(function(){ + done.i = 0; + Gun.obj.map(gun.__.graph, function(){ done.i++ }); + expect(done.i).to.be(1); // make sure there isn't double. + Gun.log.verbose = false; + done() + },10); + } + }); + gun.set(2); + }); + */ + + it('val should not print relation', function(done){ // #132 + var users = Gun().get('example').path('users'); + users.path(Gun.text.random()).put('bob'); + users.path(Gun.text.random()).put('sam'); + setTimeout(function(){ + users.val(function(v){ + expect(Gun.val.rel.is(v)).to.not.be.ok(); + expect(Object.keys(v).length).to.be(3); + done(); + }); + },100); + }); + + it('peer 1 get key, peer 2 put key, peer 1 val', function(done){ + var hooks = {get: function(key, cb, opt){ + cb(); + }, put: function(nodes, cb, opt){ + Gun.union(gun1, nodes); + cb(); + }}, + gun1 = Gun({wire: {get: hooks.get}}).get('race') + , gun2 = Gun({wire: hooks}); //.get('race'); + + setTimeout(function(){ + gun2.put({the: 'data'}).key('race'); + setTimeout(function(){ + gun1.on(function(val){ + expect(val.the).to.be('data'); + if(done.c){ return } done(); done.c = 1; + }); + },10); + },10); + }); + + it('get pseudo merge', function(done){ + var gun = Gun(); + + gun.put({a: 1, z: -1}).key('pseudo'); + gun.put({b: 2, z: 0}).key('pseudo'); + + gun.get('pseudo').val(function(val){ + expect(val.a).to.be(1); + expect(val.b).to.be(2); + expect(val.z).to.be(0); + done(); + }); + }); + + it('get pseudo merge on', function(done){ + var gun = Gun(); + + gun.put({a: 1, z: -1}).key('pseudon'); + gun.put({b: 2, z: 0}).key('pseudon'); + + gun.get('pseudon').on(function(val){ + if(done.val){ return } // TODO: Maybe prevent repeat ons where there is no diff? (may not happen to after 1.0.0) + done.val = val; + expect(val.a).to.be(1); + expect(val.b).to.be(2); + expect(val.z).to.be(0); + done(); + }); + }); + + it.skip('get pseudo merge across peers', function(done){ // TODO: These tests should be replaced with PANIC tests! + // ctx.halt + var acb, bcb, ag, bg; + Gun.on('opt').event(function(gun, o){ + if(connect){ return } + gun.__.opt.wire = {get: function(key, cb, opt){ + key = key[Gun._.soul]; + if(o.alice){ acb = cb; ag = gun.__.graph; } else { bcb = cb; bg = gun.__.graph; } + var other = (o.alice? gun2 : gun1); + if(connect){ + var node = other.__.graph[key]; + cb(null, node); + } else { + cb(); + } + }, put: function(nodes, cb, opt){ + var other = (o.alice? gun2 : gun1); + if(connect){ + Gun.union(other, nodes); + } + cb(); + }} + }); + function pushAtoB(key){ + var node = ag[key]; + bcb(null, node); + } + function pushBtoA(key){ + var node = bg[key]; + acb(null, node); + } + var connect, gun1 = Gun({alice: true}).get('pseudo/merge').put({hello: 'world!'})/*.not(function(key){ + this.put({hello: "world!"}).key(key); + })*/, gun2; + gun1.val(function(val){ + expect(val.hello).to.be('world!'); + }); + setTimeout(function(){ + gun2 = Gun({bob: true}).get('pseudo/merge').put({hi: 'mars!'})/*.not(function(key){ + this.put({hi: "mars!"}).key(key); + });*/ + gun2.val(function(val){ + expect(val.hi).to.be('mars!'); + }); + setTimeout(function(){ + // CONNECT THE TWO PEERS + connect = true; + pushBtoA('pseudo/merge'); + pushAtoB('pseudo/merge'); + //gun1.get('pseudo/merge', null, {force: true}); // fake a browser refersh, in real world we should auto-reconnect + //gun2.get('pseudo/merge', null, {force: true}); // fake a browser refersh, in real world we should auto-reconnect + setTimeout(function(){ + gun1.val(function(val){ + expect(val.hello).to.be('world!'); + expect(val.hi).to.be('mars!'); + done.gun1 = true; + }); + //return; + gun2.val(function(val){ + expect(val.hello).to.be('world!'); + expect(val.hi).to.be('mars!'); + expect(done.gun1).to.be.ok(); + Gun({}); + done(); + }); + },10); + },10); + },10); + }); + + it("get map val -> map val", function(done){ // Terje's bug + var gun = Gun(); // we can test GUN locally. + var passengers = gun.get('passengers').not(function(key){ + this.put({'randombob': { + name: "Bob", + location: {'lat': '37.6159', 'lng': '-128.5'}, + direction: '128.2' + }, 'randomfred': { + name: "Fred", + location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, + direction: 'f128.2' + }}).key(key); + }); // this is now a list of passengers that we will map over. + var ctx = {n: 0, d: 0, l: 0}; + passengers.map().val(function(passenger, id){ + this.map().val(function(change, field){ + if('name' == field){ expect(change).to.be(passenger.name); ctx.n++ } + if('direction' == field){ expect(change).to.be(passenger.direction); ctx.d++ } + if('location' == field){ + delete change._; ctx.l++; + if('Bob' == passenger.name){ + expect(change).to.eql({'lat': '37.6159', 'lng': '-128.5'}); + } else { + expect(change).to.eql({'lat': 'f37.6159', 'lng': 'f-128.5'}); + } + } + if(ctx.n == 2 && ctx.d == 2 && ctx.l == 2){ done() } + }); + }); + }); + + it("put map update sub", function(done){ + var g = Gun(); + var list = gun.get('map/sub'); + list.put({a: {x:1}, b: {y: 1}}); + var check = {}; + list.map().on(function(v,f){ + check[f] = v; + if(done.c){return} + if(check.a && check.b && check.a.w){ + expect(check.a.x).to.be(1); + expect(check.b.y).to.be(1); + expect(check.a.w).to.be(2); + done.c=1; + done(); + } + }); + list.path('a').path('w').put(2); + }); + + it("put map update sub val", function(done){ + var g = Gun(); + var list = gun.get('map/sub/val'); + list.put({a: {x:1}, b: {y: 1}}); + list.path('a').path('w').put(2); + var check = {}; + list.map().val(function(v,f){ + check[f] = v; + console.log("*************************", f,v); + if(check.a && check.b){ + expect(check.a.w).to.be(2); + expect(check.a.x).to.be(1); + expect(check.b.y).to.be(1); + done(); + } + }, {wait: 400}); + }); + + it("put map update sub val after", function(done){ + var g = Gun(); + var list = gun.get('map/sub/val/after'); + var check = {}; + list.map().val(function(v,f){ + check[f] = v; + if(check.a && check.b){ + setTimeout(function(){ + expect(check.a.x).to.be(1); + expect(check.b.y).to.be(1); + expect(check.a.w).to.not.be.ok(); + expect(done.c).to.not.be.ok(); + done();done.c=1; + },400); + } + }); + list.put({a: {x:1}, b: {y: 1}}); + setTimeout(function(){ + list.path('a').path('w').put(2); + },300); + }); + + it("put map update sub val after to", function(done){ + var g = Gun(); + var list = gun.get('map/sub/val/after/to'); + var check = {}; + list.map().val(function(v,f){ + //console.log("*************", f,v);return; + check[f] = v; + if(check.a && check.b){ + setTimeout(function(){ + expect(check.a.x).to.be(1); + expect(check.b.y).to.be(1); + expect(check.a.w).to.be(2); + expect(done.c).to.not.be.ok(); + done();done.c=1; + },200); + } + }); + list.put({a: {x:1}, b: {y: 1}}); + list.path('a').path('w').put(2); + }); + + it("put map simple after", function(done){ + var g = Gun(); + var list = gun.get('map/simple/after'); + var check = {}; + list.map().val(function(v,f){ + check[f] = v; + if(check.a && check.b){ + setTimeout(function(){ + expect(check.a).to.be(2); + expect(check.b).to.be(1); + expect(done.c).to.not.be.ok(); + done();done.c=1; + },200); + } + }); + list.put({a: 1, b: 1}); + list.path('a').put(2); + }); + + it("put map simple after to", function(done){ + var g = Gun(); + var list = gun.get('map/simple/after/to'); + var check = {}; + list.map().val(function(v,f){ + check[f] = v; + if(check.a && check.b){ + setTimeout(function(){ + expect(check.a).to.be(1); + expect(check.b).to.be(1); + expect(done.c).to.not.be.ok(); + done();done.c=1; + },200); + } + }); + list.put({a: 1, b: 1}); + setTimeout(function(){ + list.path('a').put(2); + },300); + }); + + it("put map", function(done){ + var gun = Gun(); + var get = gun.get('map/that'); + var put = gun.put({a: 1, b: 2, c: 3}).key('map/that'); + get.map(function(v,f){ + if(1 === v){ done.a = true } + if(2 === v){ done.b = true } + if(3 === v){ done.c = true } + if(done.a && done.b && done.c){ + if(done.done){ return } + done(); done.done = 1; + } + }); + }); + + it("put map before", function(done){ + var gun = Gun(); + var get = gun.get('map/that/before'); + get.map(function(v,f){ + if(1 === v){ done.a = true } + if(2 === v){ done.b = true } + if(3 === v){ done.c = true } + if(done.a && done.b && done.c){ + if(done.done){ return } + done(); done.done = 1; + } + }); + var put = get.put({a: 1, b: 2, c: 3}); + }); + + it("get map map val", function(done){ // Terje's bug + var gun = Gun(/*{init: true}*/); // we can test GUN locally. + var passengers = gun.get('passengers/map').not(function(key){ + gun.put({randombob: { + name: "Bob", + location: {'lat': '37.6159', 'lng': '-128.5'}, + direction: '128.2' + }}).key(key); + }); // this is now a list of passengers that we will map over. + var ctx = {n: 0, d: 0, l: 0}; + passengers.map().map().val(function(val, field){ + if('name' == field){ expect(val).to.be(!ctx.n? 'Bob' : 'Fred'); ctx.n++ } + if('direction' == field){ expect(val).to.be(!ctx.d? '128.2' : 'f128.2'); ctx.d++ } + if('location' == field){ + delete val._; + if(!ctx.l){ + expect(val).to.eql({'lat': '37.6159', 'lng': '-128.5'}); + } else { + expect(val).to.eql({'lat': 'f37.6159', 'lng': 'f-128.5'}); + } + ctx.l++; + } + if(ctx.n == 2 && ctx.d == 2 && ctx.l == 2){ done() } + }); + setTimeout(function(){ + passengers.put({randomfred: { + name: "Fred", + location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, + direction: 'f128.2' + }}); + },400); + }); + + it("not before map deep after conflict", function(done){ + var gun = Gun(); + var g = gun.get('n/b/l/a/c').not(function(k){ + console.log("not", k); + gun.put({ + a: { + x:1, + y:1 + } + }).key('n/b/l/a/c'); + }); + var check = {a:{},b:{}}, F = 'a'; + g.map().map().val(function(v,f){ + var c = check[F]; + c[f] = v; + if(check.b && check.b.x && check.b.y){ + expect(check.a.x).to.be(1); + expect(check.a.y).to.be(1); + expect(check.b.x).to.be(1); + expect(check.b.y).to.be(1); + done(); + } + }); + setTimeout(function(){ + F = 'b'; + g.put({b: {x:1,y:1}}); + },400); + }); + + it("not before map deep after", function(done){ + var gun = Gun(); + var g = gun.get('n/b/l/a').not(function(k){ + console.log("not", k); + gun.put({ + a: { + x:1, + y:1 + } + }).key('n/b/l/a'); + }); + var check = {}; + g.map().map().val(function(v,f){ + check[f] = v; + if(check.x && check.y && check.w && check.u){ + expect(check.x).to.be(1); + expect(check.y).to.be(1); + expect(check.w).to.be(1); + expect(check.u.deep).to.be(true); + done(); + } + }); + setTimeout(function(){ + g.put({b: {w:1,u:{deep:true}}}); + },400); + }); + + it("before map after", function(done){ + var gun = Gun(); + var g = gun.get('b/l/a'); + g.put({a: {x:1,y:1}}); + var check = {}; + g.map().map().val(function(v,f){ + check[f] = v; + if(check.x && check.y && check.w && check.u && check.z){ + expect(check.x).to.be(1); + expect(check.w).to.be(1); + expect(check.u).to.be(1); + expect(check.y).to.be(2); + expect(check.z).to.be(1); + done(); + } + }); + setTimeout(function(){ + g.put({b: {w:1,u:1,y:2,z:1}}); + },150); + }); + + it("before map deep after", function(done){ + var gun = Gun(); + var g = gun.get('b/d/l/a'); + g.put({a: {x:1,y:1}}); + var check = {}; + g.map().map().val(function(v,f){ + check[f] = v; + if(check.x && check.y && check.w && check.u){ + expect(check.x).to.be(1); + expect(check.y).to.be(1); + expect(check.w).to.be(1); + expect(check.u.deep).to.be(true); + done(); + } + }); + setTimeout(function(){ + g.put({b: {w:1,u:{deep:true}}}); + },150); + }); + + it("get map map map map", function(done){ + var gun = Gun(); + var g = gun.get('m/m/m/m'); + console.log(" // TODO: BUG!!! If you make them have the same fields, they do not both iterate."); + g.put({ + a: { + b: { + c: { + d: 1, + e: 2, + f: 3 + } + } + }, + u: { + v: { + w: { + d: 1, + e: 2, + f: 3 + } + } + } + }); + var check = {}; + g.map().map().map().map().val(function(v,f){ + check[f] = (check[f] || 0) + 1; + if(check.d === 2 && check.e === 2 && check.f === 2){ + done(); + } + }); + }); + + it("get users map path path any", function(done){ + var gun = Gun(); + var check = {}; + gun.get('g/n/m/f').map().path('spouse').path('work').any(function(e,v,f){ + console.log("********", f,v, this); + check[v.name] = true; + if(check["ACME INC"] && check["GUN INC"]){ + done(); + } + }); + gun.put({_:{'#':'g/n/m/f'}, + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + }, + }); + }); + + it("get users map path path val after", function(done){ + var gun = Gun(); + gun.put({_:{'#':'g/n/m/f/a'}, + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + }, + }); + setTimeout(function(){ + //console.debug.i=1;console.log("----------------------"); + var check = {}; + gun.get('g/n/m/f/a').map().path('spouse').path('work').any(function(e,v,f){ + console.log("********", f,v, this); + check[v.name] = true; + if(check["ACME INC"] && check["GUN INC"]){ + done(); + } + }); + },100); + }); + + it("get users map path path any later", function(done){ + var gun = Gun(); + gun.get('g/n/m/f/l').map().path('spouse').path('work'); + gun.put({_:{'#':'g/n/m/f/l'}, + alice: { + name: "alice", + age: 24, + spouse: { + name: "carl", + age: 25, + work: { + name: "GUN INC" + } + }, + bout: {huh:1} + }, + bob: { + name: "bob", + age: 26, + spouse: { + name: "diana", + age: 27, + work: { + name: "ACME INC" + } + } + }, + }); + setTimeout(function(){ + var work = {}; + console.log("..........", gun); + return; + gun.get('g/n/m/f').map().path('spouse').path('work').any(function(e,v,f){ + console.log("********", f,v, this); + return; + check[v.name] = true; + if(check["ACME INC"] && check["GUN INC"]){ + done(); + } + }); + },100); + }); + + it("get graph node field ref", function(done){ + var gun = Gun(); + gun.put({data: {a: 1, b: 2}}, null, 'g/n/f') + console.debug.i=1;console.log("-----------------"); + gun.get('g/n/f').path('data').path('a').any(function(b,a){ + console.log(":D", a,b); + expect(a).to.be(1); + done(); + }); + }); + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + return; + it("get map path val", function(done){ // Terje's bug + var gun = Gun(); + var ctx = {l: -1, d: 0}; + var passengers = gun.get('passengers/path').not(function(key){ + this.put({randombob: { + name: "Bob", + location: {'lat': '37.6159', 'lng': '-128.5'}, + direction: '128.2' + }}).key(key); + }); + passengers.map().path('location.lng').val(function(val, field){ + //passengers.map().path('location.lng').on(function(val, field){ + console.log("******", field, val); + expect(field).to.be('lng'); + if(ctx.l){ + expect(val).to.be('-128.5'); + } else { + expect(val).to.eql('f-128.5'); + } + ctx.l++; + if(ctx.l){ done() } + }); + setTimeout(function(){ + console.debug.i=1;console.log("-------------------------------------"); + passengers.put({randomfred: { + name: "Fred", + location: {'lat': 'f37.6159', 'lng': 'f-128.5'}, + direction: 'f128.2' + }}); + },300); + }); + + it("FILT ER FILTER", function(done){ + var g = Gun(); + var a = gun.put({}); + var b = gun.put({age: 19, name: "bob"}); + + console.debug.i=1;console.log("~~~~~~~~~~~~~~~~~~~~~~~~~"); + (window.ALICE = a.filter()).path('spouse.name').on(function(a,b){ + console.log("1", b,a); + }); + + a.put({age: 24, name: "alice", spouse: {name: "carl"}}); + return; + b.filter().on(function(a,b){ + console.log("2", b,a); + }); + }); + + it("map path before", function(done){ + var gun = Gun(); + var g = gun.put({a: {x:1}, b: {x:2}, c: {x:3}}); + var c = 0; + var m = g.map().path('x').on(function(v,f){ + console.log("*********************", f,v); + if(3 === ++c && 3 === v){ + done(); + } + }); + }); + + it("map path", function(done){ + var gun = Gun(); + var g = gun.get('map/path/ing'); + var c = 0; + var m = g.map().path('x').on(function(v,f){ + if(3 === ++c && 3 === v){ + done(); + } + }); + g.put({a: {x:1}, b: {x:2}, c: {x:3}}); + }); + + it("map path path", function(done){ + var gun = Gun(); + var g = gun.get('map/path/path/ing'); + var c = 0; + var m = g.map().path('x.y').on(function(v,f){ + //console.log("Hmmmm", f,v); + if(3 === ++c && 3 === v){ + done(); + } + }); + g.put({a: {x:{y:1}}, b: {x:{y:2}}, c: {x:{y:3}}}); + }); + + it("put path deep val -> path val", function(done){ // Terje's bug + var gun = Gun(); + gun.put({you: {have: {got: {to: {be: {kidding: "me!"}}}}}}).path('you.have.got.to.be').val(function(val, field){ + expect(val.kidding).to.be('me!'); + this.path('kidding').val(function(val){ + expect(val).to.be('me!'); + done(); + }); + }); + }); + + it("get set path put, map path val -> path val", function(done){ // Terje's bug + var gun = Gun(); + var ctx = {l: -1, d: 0}; + var passengers = gun; //.get('passengers/set/path'); + passengers = passengers.put({randombob: {name: 'Bob', direction: {}}}); + passengers.path('randombob.direction', function(err, ok, field){ + }).put({lol: {just: 'kidding', dude: '!'}}); + passengers.map().path('direction.lol').val(function(val){ + this.path('just').val(function(val){ + expect(val).to.be('kidding'); + }).back.path('dude').val(function(val){ + expect(val).to.be('!'); + done(); + }); + }) + }); + + it('path should not slowdown', function(done){ + this.timeout(5000); + var gun = Gun().put({ + history: {} + }); + //console.log("---------- setup data done -----------"); + var prev, diff, max = 25, total = 100, largest = -1, gone = {}; + //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.path('history').map(function(time, index){ + diff = Gun.time.is() - time; + expect(gone[index]).to.not.be.ok(); + gone[index] = diff; + largest = (largest < diff)? diff : largest; + //console.log(turns, index, 'largest', largest, diff, '???', diff > max, diff, max); + expect(diff > max).to.not.be.ok(); + }); + var turns = 0; + var many = setInterval(function(){ + if(turns > total || (diff || 0) > (max + 5)){ + clearTimeout(many); + expect(Gun.num.is(diff)).to.be.ok(); + if(done.c){ return } done(); done.c = 1; + return; + } + prev = Gun.time.is(); + var put = {}; put[turns += 1] = prev; + //console.log("put", put); + gun.put({history: put}); + }, 1); + }); + + it('path rel should not slowdown', function(done){ + this.timeout(5000); + var gun = Gun(/*gopt*/).put({ + history: {} + }); + var prev, diff, max = 100, total = 20, largest = -1, gone = {}; + var run = 0; + gun.path('history').map(function(entry, index){ + //if(!entry){ return } // TODO: BUG! KNOWN BUG!!!!!!! FIX!!!!! + ++run; + var i = run+''; + expect(i).to.be(index+''); + expect(i).to.be(entry.x+''); + expect(i).to.be(entry.y+''); + expect(i).to.be(entry.direction+''); + expect(entry.axis).to.be.ok(); + expect(entry.direction).to.be.ok(); + if(run > total){ + if(done.c){ return } + setTimeout(function(){ + done(); + done.c=true; + },20); + } + return; + //console.log("THE GRAPH\n", gun.__.graph); + //expect(gone[index]).to.not.be.ok(); + gone[index] = diff; + diff = Gun.time.is() - (entry.time || prev); + largest = (largest < diff)? diff : largest; + //console.log('turn', turns, 'index', index, 'diff', diff, 'largest', largest); + expect(diff > max).to.not.be.ok(); + }); + + var turns = 0; + var many = setInterval(function(){ + if(turns > total || diff > (max + 5)){ + //console.log("was it", turns > total, 'or', diff > (max + 5)); + clearTimeout(many); + return; + expect(Gun.num.is(diff)).to.be.ok(); + if(done.c){ return } done(); done.c = 1; + return; + } + prev = Gun.time.is(); + turns += 1; + var val = { + x: turns, + y: turns, + axis: 'y', + direction: turns, + time: prev + } + var put = {}; put[turns] = val; + gun.put({history: put}); + //gun.path(['history', turns += 1]).put({ + },1); + }); + + it.skip('paths rel should not slowdown', function(done){ // TODO: NEED TO ADD THIS NEW TEST! + this.timeout(5000); + //this.timeout(60000); + + //Gun.log.debug = 1; console.log("~~~~~ START ~~~~~~"); + var gun = Gun(gopt).put({ + history: {} + }); + //console.log("-------- DATA SET UP -----------"); + var prev, diff, max = 100, total = 100, largest = -1, gone = {}; + gun.path('history').map(function(entry, index){ + //if(!entry){ return } // TODO: BUG! KNOWN BUG!!!!!!! FIX!!!!! + //console.log("WAT", index, entry); + //console.log("THE GRAPH\n", gun.__.graph); + //expect(gone[index]).to.not.be.ok(); + gone[index] = diff; + diff = Gun.time.is() - (entry.time || prev); + largest = (largest < diff)? diff : largest; + console.log('turn', turns, 'index', index, 'diff', diff, 'largest', largest); + expect(diff > max).to.not.be.ok(); + }); + + var turns = 0; + //console.log("------------ PATH MAP SET UP --------------"); + var many = setInterval(function(){ + if(turns > total || diff > (max + 5)){ + clearTimeout(many); + expect(Gun.num.is(diff)).to.be.ok(); + if(done.c){ return } done(); done.c = 1; + return; + } + prev = Gun.time.is(); + Gun.log.base = Gun.log.ref = Gun.log.fer = prev; + //if(turns === 0){ Gun.log.debug = 1; console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); } + //console.log("-------------- ", turns + 1, "-----------------"); + var val = { + TURN: turns + 1, + x: 1, + y: 1, + axis: 'y', + direction: 1, + time: prev + } + //var put = {}; put[turns += 1] = val; + //gun.put({history: put}); + gun.path(['history', turns += 1]).put(val); + },1); + }); + + it("gun get on, later gun put key", function(done){ + var gun = Gun(); + + var keyC = gun.get('keyC').on(function(val){ + expect(val.hello).to.be('world'); + if(done.done){ return } + done.done = true; + done(); + }); + + setTimeout(function(){ + gun.put({hello: 'world'}).key('keyC'); + }, 100); + }); + + it('gun get put, sub path put, original val', function(done){ // bug from Jesse working on Trace // + var gun = Gun(gopt).get('players'); + + gun.put({ + taken: true, + history: {0: {}, 1: {}} + }); + + gun + .path('history') + .put(null) + .back + .path('taken') + .put(false) + + // TODO: BUG! There is a variation of this, where we just do `.val` rather than `gun.val` and `.val` by itself (chained off of the sub-paths) doesn't even get called. :( + gun.on(function(players){ // this val is subscribed to the original put and therefore does not get any of the sub-path listeners, therefore it gets called EARLY with the original/old data rather than waiting for the sub-path data to "finish" and then get called. + expect(players.history).to.be(null); + expect(players.taken).to.be(false); + if(done.c){ return } done(); done.c = 1; + }); + }); + + it("gun put recursive path slowdown", function(done){ + this.timeout(5000); + var gun = Gun(); + gun.__.opt.wire.put = null; + function put(num, t) { + var now = new Date().getTime(); + var cb; + for (var i = 1; i <= num; i++) { + if (i === num) { + cb = function (err, ok) { + console.log(num + 'ops: ' + (new Date().getTime() - now)/1000 + 's'); + } + } + Gun.ify({ //hello: 'world'}, cb); + deeply: { + nested: i + } + })(cb); + } + return new Date().getTime() - now; + } + /* + put(1); + put(2); + put(10); + put(50); + put(100); + put(1000); + put(5000);*/ + put(1000, true); + + var gun2 = Gun(); + gun2.__.opt.wire.put = null; + function put2(num, t) { + var now = new Date().getTime(); + var cb; + for (var i = 1; i <= num; i++) { + if (i === num) { + cb = function () { + console.log(num + ' API ops: ' + (new Date().getTime() - now)/1000 + 's'); + t && done(); + } + } + gun2.put({ //hello: 'world'}, cb); + deeply: { + nested: i + } + }, cb); + } + return new Date().getTime() - now; + } + Gun.log.start = Gun.time.is(); + put2(1); + put2(1000); // TODO: BUG! Interesting! If you add another 0 it causes a stack overflow! If I make Gun.time.now() not recurse then it runs but takes 4x as long. Even on the 10k ops there seems to be about a 4x overhead with the API versus raw serializer. + put2(1, true); + //put2(2); + //put2(10); + //put2(50); + //put2(100, true); + //put2(5000, true); + } ); + + it('choke time.now by using a while loop', function(){ + var i = 10; //100000; // causes an overflow. + while(--i){ + Gun.time.now(); + } + }); + /* // TODO: These tests should be deleted. + it("test timeout", function(done){ return done(); + var i = 1000, start = Date.now(); + while(i--){ + setTimeout(function(){ + console.log("ended in", (Date.now() - start)/1000); + },0); + } + return; + Gun.schedule(start, function(){ + console.log("ended in", (Date.now() - start)/1000); + }); + setImmediate(function(){ + console.log("ended in", (Date.now() - start)/1000); + }); + process.nextTick(function(){ + console.log("ended in", (Date.now() - start)/1000); + }); + }); + it("test assignment", function(done){ + var env = {graph: {}}; + function speed(other){ + var i = 10000; + while(i--){ + var $ = {soul: Gun.text.random()}; + var at = {node: {_: {}}}; + var obj = { + deeply: { + nested: 'lol' + } + } + env.graph[at.node._[Gun._.soul] = at.soul = $.soul] = at.node + } + } + var start = Date.now(); + speed(); + console.log('wat', (Date.now() - start)/1000); + }); + it("test fn call", function(done){ + function speed(i, cb){ + var r = 0; + while(i--){ + if(cb){ + cb(i); + } else { + r += i; + } + } + } + var start = Date.now(); + speed(100000000); + console.log('no fn', (Date.now() - start)/1000); + var start = Date.now(), r = 0; + speed(100000000, function(i){ r += i }); + console.log('w/ fn', (Date.now() - start)/1000); + var start = Date.now(), r = 0; + function foo(i){ r += i } + speed(100000000, foo); + console.log('w/ named fn', (Date.now() - start)/1000); + }); + it("gun put recursive path slowdown MUTANT TEST", function(done){ + this.timeout(30000); + + Gun.chain.put = function(val, cb, opt){ + var gun = this.chain(), obj; + var drift = Gun.time.now(), call = {}; + cb = cb || function(){}; + gun._.at('soul').event( + //( + function($){ + var chain = $.gun || gun; + var ctx = {}, obj = val, $ = Gun.obj.copy($); + var hash = $.field? $.soul + $.field : ($.from? $.from + ($.at || '') : $.soul); + if(call[hash]){ return } + gun.__.meta($.soul).put = true; + call[hash] = true; + if(Gun.is.val(obj)){ + if($.from && $.at){ + $.soul = $.from; + $.field = $.at; + } // no else! + if(!$.field){ + return cb.call(gun, {err: Gun.log("No field exists for " + (typeof obj) + "!")}); + } else + if(gun.__.graph[$.soul]){ + ctx.tmp = {}; + ctx.tmp[ctx.field = $.field] = obj; + obj = ctx.tmp; + } else { + return cb.call(gun, {err: Gun.log("No node exists to put " + (typeof obj) + " in!")}); + } + } + if(Gun.obj.is(obj)){ + if($.field && !ctx.field){ + ctx.tmp = {}; + ctx.tmp[ctx.field = $.field] = obj; + obj = ctx.tmp; + } + Gun.ify(obj || val, function(env, cb){ + var at; + if(!env || !(at = env.at) || !env.at.node){ return } + if(!at.node._){ + at.node._ = {}; + } + if(!Gun.node.soul(at.node)){ + if(obj === at.obj){ + env.graph[at.node._[Gun._.soul] = at.soul = $.soul] = at.node; + cb(at, at.soul); + } else { + function path(err, data){ + if(at.soul){ return } + at.soul = Gun.node.soul(data) || Gun.node.soul(at.obj) || Gun.roulette.call(gun); // TODO: refactor Gun.roulette! + env.graph[at.node._[Gun._.soul] = at.soul] = at.node; + //var start = performance.now(); + cb(at, at.soul); + //first = performance.now() - start;(first > .05) && console.log('here'); + }; + ($.empty && !$.field)? path() : chain.back.path(at.path || [], path, {once: true, end: true}); // TODO: clean this up. + } + //var diff1 = (first - start), diff2 = (second - first), diff3 = (third - second); + //(diff1 || diff2 || diff3) && console.log(diff1, ' ', diff2, ' ', diff3); + } + if(!at.node._[Gun._.state]){ + at.node._[Gun._.state] = {}; + } + if(!at.field){ return } + at.node._[Gun._.state][at.field] = drift; + })(function(err, ify){ + //console.log("chain.put PUT <----", ify.graph, '\n'); + if(err || ify.err){ return cb.call(gun, err || ify.err) } + if(err = Gun.union(gun, ify.graph).err){ return cb.call(gun, err) } + if($.from = Gun.val.rel.is(ify.root[$.field])){ $.soul = $.from; $.field = null } + Gun.obj.map(ify.graph, function(node, soul){ Gun.union(gun, Gun.union.pseudo(soul)) }); + gun._.at('soul').emit({soul: $.soul, field: $.field, key: $.key, PUT: 'SOUL', WAS: 'ON'}); // WAS ON + //return cb(null, true); + if(Gun.fns.is(ctx.hook = gun.__.opt.hooks.put)){ + ctx.hook(ify.graph, function(err, data){ // now iterate through those nodes to a persistence layer and get a callback once all are saved + if(err){ return cb.call(gun, err) } + return cb.call(gun, null, data); + }, opt); + } else { + //console.Log("Warning! You have no persistence layer to save to!"); + cb.call(gun, null); // This is in memory success, hardly "success" at all. + } + }); + } + }) + gun._.at('soul').emit({soul: Gun.roulette.call(gun), field: null, empty: true}); + return gun; + } + + var gun = Gun(); //.get('bug').put({}); + gun.__.opt.hooks.put = null; + function put(num, t) { + var now = new Date().getTime(); + var cb; + for (var i = 1; i <= num; i++) { + if (i === num) { + cb = function (err, ok) { + console.log(num + 'MUTANT ops: ' + (new Date().getTime() - now)/1000 + 's'); + t && done(); + } + } + gun.put({ //hello: 'world'}, cb); + deeply: { + nested: i + } + }, cb); + } + return new Date().getTime() - now; + } + + //put(1, true); + //put(2); + //put(10); + //put(50); + //put(100); + //put(1000); + //put(5000); + put(10000, true); + }); + */ + it("gun get empty set, path not -> this put", function(done){ // Issue #99 #101, bug in survey and trace game. + var test = {c: 0}, u; + var gun = Gun(); + var game = gun.get('some/not/yet/set/put/thing').not(function(key){ + gun.put({alias: {}}).key(key); + });//.set(); + var me = game.path('alias').on(function(val){ + if(!done.put){ return } + expect(val).to.not.be(u); + expect(val.a).to.be('b'); + var meid = Gun.node.soul(val); + var self = this; + /* + expect(self === game).to.not.be.ok(); + expect(self === me).to.be.ok(); + */ + if(done.c){ return } done(); done.c = 1; + }); + setTimeout(function(){ + done.put = true; + me.put({a: 'b'}); + },100); + }); + + it("gun get empty set path empty later path put multi", function(done){ // Issue #99 #101, bug in survey and trace game. // ctx.halt + done.c = 0; + var gun = Gun(); + var data = gun.get('some/not/yet/set/put/thing/2'); + var path = data.path('sub'); + function put(d, t, f){ + setTimeout(function(){ + path.put(d, function(err, ok){ + expect(err).to.not.be.ok(); + done.c++; + if(f && done.c >= 3){ + done(); + } + }); + },t || 10); + }; + put({on: 'bus', not: 'transparent'}); + put({on: null, not: 'torrent'}, 200); + put({on: 'sub', not: 'parent'}, 250, true); + }); + + it("ToDo", function(done){ // Simulate ToDo app! + var gun = Gun().get('example/todo/data'); + gun.on(function renderToDo(val){ + if(done.done){ return } + if(done.clear){ + done.done = true; + expect(val[done.id]).to.not.be.ok(); + return done(); + } + delete val._; + Gun.obj.map(val, function(val, field){ return done.id = field; }); + expect(val[done.id]).to.be('groceries'); + }); + setTimeout(function(){ // form submit + gun.path('random1').put("groceries"); + setTimeout(function(){ // clear off element + done.clear = true; + gun.path(done.id).put(null); + },100); + },200); + }); + + it("gun put null path on put sub object", function(done){ // consensus4's bug + done.c = 1; + var gun = Gun(); + //Gun.log.verbose = true; + var game = gun.put({board: null, teamA: null, teamB: null, turn: null}).key('the/game'); + game.path('board').on(function(board, field){ + expect(field).to.be('board'); + if(done.c === 1){ + expect(board).to.not.be.ok(); + } + if(done.c === 2){ + if(!board[11] || !board[22] || !board[33]){ return } + done.c++; + delete board._; + expect(board).to.be.eql({11: ' ', 22: ' ', 33: 'A'}); + done(); + } + }); + setTimeout(function(){ + done.c++; + game.put({board: {11: ' ', 22: ' ', 33: 'A'}}); + },100); + }); + + it("get init put map -> put, foreach gun path map", function(done){ // replicate Jesse's Trace game bug + done.c = 0; + var gun = Gun(gopt).opt({init: true}) + .get('players').init() + .put({ + 0: { + num: 0 + }, + 1: { + num: 1 + }, + 2: { + num: 2 + }, + 3: { + num: 3 + } + }, function(err,ok){ + expect(done.c++).to.be(0); + }).val(function(p){ + done.p = Gun.node.soul(p); + done.m = Gun.val.rel.is(p[0]); + expect(Gun.val.rel.is(p[0])).to.be.ok(); + expect(Gun.val.rel.is(p[1])).to.be.ok(); + expect(Gun.val.rel.is(p[2])).to.be.ok(); + expect(Gun.val.rel.is(p[3])).to.be.ok(); + }) + + var players = [], me; + gun.map(function (player, number) { + players[number] = player; + players[number].history = []; + if (!player.taken && !me) { + this.put({ + taken: true, + history: { + 0: {x: 1, y: 2} + } + }, function(err,ok){}); + me = number; + } + }); + + Gun.list.map([0, 1, 2, 3], function (player, number) { + number = number - 1; + gun + .path(number + '.history') + .map(function (entry, logNum) { + done.c++; + players[number].history[logNum] = entry; + expect(entry.x).to.be(1); + expect(entry.y).to.be(2); + setTimeout(function(){ + expect(done.c).to.be(2); + done(); + },100); + }); + }); + }); + + it("gun get path empty val", function(done){ // flip flop bug + done.c = 0; + var u; + var gun = Gun(gopt); + var game = gun.get('game1/players'); + var me = game.path('player1').val(function(val){ + if(!done.c){ done.fail = true } + expect(val).to.not.be(u); + expect(val.x).to.be(0); + expect(val.y).to.be(0); + expect(done.fail).to.not.be.ok(); + done(); + }); + setTimeout(function(){ + done.c++; + expect(done.fail).to.not.be.ok(); + me.put({x: 0, y: 0}); + },10); + }); + + it("gun get path empty on", function(done){ + done.c = 0; + var u; + var gun = Gun(gopt); + var game = gun.get('game2/players'); + var me = game.path('player2').on(function(val){ + if(!done.c){ done.fail = true } + expect(done.fail).to.not.be.ok(); + expect(val).to.not.be(u); + if(done.done || !val.x || !val.y){ return } // it is okay if ON gets called many times, this protects against that. + // TODO: although it would be nice if we could minimize the amount of duplications. (may not happen to after 1.0.0) + expect(val.x).to.be(1); + expect(val.y).to.be(1); + done.done = true; + done(); + }); + setTimeout(function(){ + done.c++; + expect(done.fail).to.not.be.ok(); + me.put({x: 1, y: 1}); + },10); + }); + + it("gun get path empty not", function(done){ + var u; + var gun = Gun(gopt).opt({init: true}) + var game = gun.get('game3/players').init(); + var me = game.path('player3').not(function(field){ + expect(field).to.be('player3'); + done(); + }); + }); + + it("gun get path empty init", function(done){ + var u; + var gun = Gun(gopt).opt({init: true}); + var game = gun.get('game4/players').init(); + var me = game.path('player4').init().path('alias').init().put({oh: 'awesome'}).val(function(val, field){ + expect(val.oh).to.be('awesome'); + expect(field).to.be('alias'); + done(); + }) + }); + + it("no invalid graph", function(done){ + var gun = Gun({wire:{ + put: function(graph){ + expect(Gun.is.graph(graph)).to.be.ok(); + if(done.c){ return } if(done.on){ done(); done.c = 1 } + } + }}).get('example/todo/data/graph'); + gun.on(function renderToDo(val){ + done.on = true; + }); + setTimeout(function(){ + gun.path(Gun.text.random()).put('hoorah'); + },100) + }); + + it("no undefined field", function(done){ + var gun = Gun(); + var chat = gun.get('example/chat/data/graph/field').not(function(key){ + gun.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); + }); + chat.map().val(function renderToDo(val, field){ + expect(field).to.be.ok(); + expect(val.who).to.be.ok(); + expect(val.when).to.be.ok(); + expect(val.what).to.be.ok(); + if(done.c >= 2){ return } + if(done.c === 1){ done() } + done.c = done.c || 0; + done.c += 1; + }); + setTimeout(function(){ + var msg = {}; + msg.when = Gun.time.is(); + msg.what = "lol!"; + msg.who = "Alice"; + chat.path(msg.when + '_' + Gun.text.random(4)).put(msg); + },100); + }); + /* // This test didn't work for what I was wanting to test :(, will either remove it or modify it if I think of a clever solution to test what I want to test. + it("simulate json app", function(done){ + var peers = {}; + peers.server = Gun(); + function wipeServer(){ + peers.server = Gun(); + } + var gopt = {wire:{ + put: function(graph, cb){ + Gun.union(peers.server, graph); + cb(null); + } + ,get: function(lex, cb){ + setTimeout(function(){ + var soul = lex[Gun._.soul]; + if(peers.localStorage){ + var g = peers.localStorage; + console.log("VIA LOCALSTORAGE!", lex, g[soul]); + if(g[soul]){ + var n = g[soul]; + cb(null, n); + cb(null, Gun.is.node.ify({}, soul)); + cb(null, {}); + } + } + setTimeout(function(){ + var graph = peers.server.__.graph; + console.log("VIA the SERVER!!", lex, graph[soul]); + if(!graph[soul]){ + cb(null); + cb(null, {}); + return; + } + var node = graph[soul]; + cb(null, node); + cb(null, Gun.is.node.ify({}, soul)); + cb(null, {}); + },5); + },5); + } + }} + peers.gun = Gun(gopt); + function reload(){ + peers.localStorage = Gun.obj.copy(peers.gun.__.graph); + peers.gun2 = Gun(gopt); + } + var ref = peers.gun.get('example/json/data/test'); + setTimeout(function(){ + ref.path('hello').put("value"); + setTimeout(function(){ + wipeServer(); + reload(); + setTimeout(function(){ + Gun.log.debug = 1; console.log("~~~~~~~~~~~~~~~~~~~"); + var ref = peers.gun2.get('example/json/data/test'); + ref.on(function(data){ + console.log("on!", data); + }); + },100); + },100); + },100); + }); + */ + it("simulate chat app", function(done){ + var server = Gun(); + var gopt = {wire:{ + put: function(graph, cb){ + Gun.union(server, graph); + cb(null); + } + ,get: function(lex, cb){ + setTimeout(function(){ + var soul = lex[Gun._.soul]; + var graph = server.__.graph; + //console.log('server replying', soul, graph); + if(!graph[soul]){ + //console.log("replying to Alice...", null); + cb(null); + cb(null, {}); + return; + } + var node = graph[soul]; + //console.log("replying to Bob...", node); + cb(null, node); + cb(null, Gun.is.node.ify({}, soul)); + cb(null, {}); + },5); + } + }} + var gun = Gun(gopt); + var chat = gun.get('example/chat/data/graph/field').not(function(key){ + gun.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); + }); + chat.map().val(function renderToDo(val, field){ + //console.log("ALICE", field, val); + expect(field).to.be.ok(); + expect(val.who).to.be.ok(); + expect(val.when).to.be.ok(); + expect(val.what).to.be.ok(); + }); + setTimeout(function(){ + var gun2 = Gun(gopt); + //Gun.log.debug =1; console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + var chat2 = gun2.get('example/chat/data/graph/field').not(function(key){ + //console.log("BOB's key", key); + gun2.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key(key); + }); + chat2.map().val(function renderToDo(val, field){ + //console.log("BOB", field, val); + expect(field).to.be.ok(); + expect(val.who).to.be.ok(); + expect(val.when).to.be.ok(); + expect(val.what).to.be.ok(); + done(); + }); + },100); + }); + + it.skip("gun path via gun path", function(done){ // TODO: Future feature? + var gun = Gun(); + var book = gun.put({ name: 'Potato Cooking' }); + var author = gun.put({ name: 'Bob Bobson' }); + author.path(book.path('name')).put(book); + }); + + it("gun set", function(done){ + var gun = Gun(); + var users = gun.get('users'); + var alice = gun.put({name: 'alice', birth: Math.random()}).key('person/alice'); + var bob = gun.put({name: 'bob', birth: Math.random()}).key('person/bob'); + var carl = gun.put({name: 'carl', birth: Math.random()}).key('person/carl'); + var dave = gun.put({name: 'dave', birth: Math.random()}).key('person/dave'); + + // Test set with new object + var alan = users.set({name: 'alan', birth: Math.random()}).key('person/alan'); + alan.val(function(alan) { + // Test set with node + dave.path('friends').set(alan); + }); + + users.set(alice); + users.set(bob); + users.set(carl); + users.set(dave); + + alice.path('friends').set(bob).back.set(carl); + bob.path('friends').set(alice); + dave.path('friends').set(alice).back.set(carl); + + var team = gun.get('team/lions').put({name: "Lions"}); + team.path('members').set(alice); + team.path('members').set(bob); + team.path('members').set(alan); // Test set with set + + alice.path('team').put(team); + bob.path('team').put(team); + + dave.path('friends').map().path('team.members').map().val(function(member){ + //console.log("Dave's friend is on a team that has", member.name, "on it."); + if('alice' === member.name){ + done.alice = true; + } else + if('bob' === member.name){ + done.bob = true; + } else + if('alan' === member.name){ + done.alan = true; + } else + { + expect(member).to.not.be.ok(); + } + if(done.alice && done.bob && done.alan){ + setTimeout(function(){ + done(); + },10); + } + }); + }); + + it("localStorage", function(done){ + var localStorage = localStorage || {clear:function(){}}; + localStorage.clear(); + var gun = Gun(); + + + var text = Gun.text.random(1024 * 1024 * 6); + gun.put({i: text}, function(err, ok){ + if(done.c){ return } + if(!err){ return done() } + var text = "If you are seeing this message, it means the localStorage error was caught successfully rather than it crashing and stopping replication to peers. Also, the error is now reported back to you via the put callback. Here it is!"; + localStorage.clear(); + done(); done.c = 1; + }); + }); + + it("get context", function(done){ // TODO: HUH?????? This was randomly causing errors? + var gun = Gun(); + var ref = gun.get('ctx/lol').get('ctx/foo').put({hello: 'world'}); + gun.get('ctx/lol').val(function(implicit){ + done.fail = true; + expect(implicit).to.not.be.ok(); + }); + gun.get('ctx/lol').not(function(){ + done.please = true; + }); + gun.get('ctx/foo').val(function(data){ + expect(data.hello).to.be('world'); + expect(done.fail).to.not.be.ok(); + expect(done.please).to.be.ok(); + done(); + }); + }); + + it.skip("chaining val", function(done){ // Not implemented yet! + var gun = Gun(); + gun.get('users').set(gun.put({name: 'alice'})); + gun.get('users').set(gun.put({name: 'bob'}));; + gun.get('users').val().map(function(person){ + if(person.name === 'alice'){ + done.alice = true; + } + if(person.name === 'bob'){ + done.bob = true; + } + if(person.name === 'carl'){ + done.carl = true; + } + }); + gun.get('users').set(gun.put({name: 'carl'})); + setTimeout(function(){ + console.log('wha?', done.alice, done.bob, done.carl); + expect(done.alice).to.be.ok(); + expect(done.bob).to.be.ok(); + expect(done.carl).to.not.be.ok(); + done(); + },10); + }); + + it.skip('Deep async change not updating', function (done) { // Issue #167 TODO: NEEDS TO BE ADDED TO 0.5 BRANCH! + // object nested three layers deep + // must be at least three layers + var obj = { 1: { 2: { data: false } } } + + // define gun and place the deep object + gun = Gun().get('deep change').put(obj) + + // listen for changes + Gun.log.debug = 1; console.log("------------------"); + gun.path('1.2.data').on(function (data) { + console.log("??????", data); + if (data) { + // gun will never receive the "true" update + done(); + } + }) + + // asynchronously set data + // synchronous deviations will succeed + setTimeout(function () { + obj[1][2].data = true + gun.put(obj); + }, 50) + }); + + it('should allow more than 2 items depthwise', function (done) { // Issue #186 + var gun = Gun(); + var list = gun.get('list'); + // create a list two layers deep + list.put({ + depth: 1, + next: { + depth: 2 + } + }); + + //Gun.log.verbose=true;Gun.log.debug=1;console.log("----------------------"); + // append a third item + list.path('next').put({ + to: { + depth: 3 + } + }); + setTimeout(function(){ + + //list.path('next').val('wat'); + + //console.log("!!!!!!", gun.__.graph); + + // try to read the third item + list.path('next.to').val(function () { // TODO: BUG! If this is 'next.next' as with the data, then it fails. + done(); + }); + },100); + }); + + it("Batch put status update not save", function(done){ // TODO: ADD TO 0.5 BRANCH. Stefdv's bug. + var obj = { + a: 1, + b: 2, + c: 3, + d: 4, + e: 5, + f: 6, + g: 7, + h: 8, + i: 9, + j: 10, + k: 11, + l: 12, + m: 13, + n: 14, + o: 15, + p: 16, + q: 17, + r: 18, + s: 19, + t: 20 + } + + var bsmi = { + group1: { + item1: { + 10: Gun.obj.copy(obj) + } + }/*, + group2: { + item2: { + 10: Gun.obj.copy(obj) + } + }*/ + } + + var gun = Gun(); + var BSMI = gun.get('bsmi').put(bsmi); + + // path is + //BSMI is a set holding all items + //var allPaths = ["1116.1116-A7001.10","1354.1354-E1930.10"] + var allPaths = ["group1.item1.10"];//,"group2.item2.10"] + allPaths.forEach(function(path) { + BSMI.path(path).put({status:false}); + }); + setTimeout(function(){ + BSMI.path(allPaths[0]).val(function(a,b,c){ + expect(a.a).to.be(1); + expect(a.b).to.be(2); + expect(a.c).to.be(3); + expect(a.d).to.be(4); + expect(a.e).to.be(5); + expect(a.f).to.be(6); + expect(a.g).to.be(7); + expect(a.h).to.be(8); + expect(a.i).to.be(9); + expect(a.j).to.be(10); + expect(a.k).to.be(11); + expect(a.l).to.be(12); + expect(a.m).to.be(13); + expect(a.n).to.be(14); + expect(a.o).to.be(15); + expect(a.p).to.be(16); + expect(a.q).to.be(17); + expect(a.r).to.be(18); + expect(a.s).to.be(19); + expect(a.t).to.be(20); + expect(a.status).to.be(false); + done(); + }); + },100); + }); + + it("Don't put on parents", function(done){ // TODO: ADD TO 0.5 BRANCH! // Another Stefdv find. + var test = gun.get('test'); + test.path('try.this.at.lvl4').put({msg:'hoi'}) + test.val(function(node,b){ + delete node._; + expect(Gun.obj.empty(node, 'try')).to.be.ok(); + node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.try)]); + + delete node._; + expect(Gun.obj.empty(node, 'this')).to.be.ok(); + node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.this)]); + + delete node._; + expect(Gun.obj.empty(node, 'at')).to.be.ok(); + node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.at)]); + + delete node._; + expect(Gun.obj.empty(node, 'lvl4')).to.be.ok(); + node = Gun.obj.copy(gun.__.graph[Gun.val.rel.is(node.lvl4)]); + + delete node._; + expect(Gun.obj.empty(node, 'msg')).to.be.ok(); + expect(node.msg).to.be('hoi'); + done(); + }); + }); + + it("Deep not fails to fire", function(done){ // @d3x0r's bug! + var gun = Gun().get("org.d3x0r.voxelarium.local." + Gun.text.random()); + + var player = gun.path( "player" ); + + player.path("id").not(function(){ + done.not = true; + //console.log("Not is run!"); + var id = 'fluffy'; + var world = 0; + player.path("id").put(id); + player.path("world_id").put(world); + }).val(function(data){ + //console.log("we have value!", data); + expect(done.not).to.be.ok(); + expect(data).to.be('fluffy'); + done(); + }); + + }); + /* + depp.on(log).path('spouse').on(log).path('pet').on(log); + // 0) Depp & Heide & dog + // 1) dog + // 2) cat + // 3) cat + // 4) Julie & cat + + depp.path('spouse.pet.name').on(log).put('pearls'); + depp.path('spouse.pet.name').put('paws').on(log); + depp.path('spouse.pet.name').on(log).not(log); + // 0: fluffy + // 1: fluff + // 3: bacon + // 9: `.not` + + depp.path('spouse.pet.name').val().on(log); + // 0: fluffy + // 1: fluff + */ + }); + + Gun.SEA && describe('SEA', function(){ + console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!'); + var alias = 'dude'; + var pass = 'my secret password'; + var userKeys = ['pub', 'priv']; + var clearText = 'My precious secret!'; + var encKeys = ['ct', 'iv', 's']; + + ['callback', 'Promise'].forEach(function(type){ + describe(type, function(){ + it('proof', function(done){ + var check = function(proof){ + expect(proof).to.not.be(undefined); + expect(proof).to.not.be(''); + done(); + } + // proof - generates PBKDF2 hash from user's alias and password + // which is then used to decrypt user's auth record + if(type === 'callback'){ + Gun.SEA.proof(alias, pass, check); + } else { + Gun.SEA.proof(alias, pass).then(check).catch(done); + } + }); + + it('pair', function(done){ + var check = function(key){ + expect(key).to.not.be(undefined); + expect(key).to.not.be(''); + expect(key).to.have.keys(userKeys); + userKeys.map(function(fld){ + expect(key[fld]).to.not.be(undefined); + expect(key[fld]).to.not.be(''); + }); + done(); + }; + // pair - generates ECDH key pair (for new user when created) + if(type === 'callback'){ + Gun.SEA.pair(check); + } else { + Gun.SEA.pair().then(check).catch(done);; + } + }); + + it('en', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(jsonSecret){ + expect(jsonSecret).to.not.be(undefined); + expect(jsonSecret).to.not.be(''); + expect(jsonSecret).to.not.eql(clearText); + expect(jsonSecret).to.not.eql(JSON.stringify(clearText)); + var objSecret = JSON.parse(jsonSecret); + expect(objSecret).to.have.keys(encKeys); + encKeys.map(function(key){ + expect(objSecret[key]).to.not.be(undefined); + expect(objSecret[key]).to.not.be(''); + }); + done(); + }; + // en - encrypts JSON data using user's private or derived ECDH key + if(type === 'callback'){ + Gun.SEA.en(JSON.stringify(clearText), key.priv, check); + } else { + Gun.SEA.en(JSON.stringify(clearText), key.priv).then(check); + } + }).catch(function(e){done(e)}); + }); + + it('sign', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(signature){ + expect(signature).to.not.be(undefined); + expect(signature).to.not.be(''); + expect(signature).to.not.eql(key.pub); + done(); + }; + // sign - calculates signature for data using user's private ECDH key + if(type === 'callback'){ + Gun.SEA.sign(key.pub, key.priv, check); + } else { + Gun.SEA.sign(key.pub, key.priv).then(check); + } + }).catch(function(e){done(e)}); + }); + + it('verify', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(ok){ + expect(ok).to.not.be(undefined); + expect(ok).to.not.be(''); + expect(ok).to.be(true); + done(); + }; + // sign - calculates signature for data using user's private ECDH key + Gun.SEA.sign(key.pub, key.priv).then(function(signature){ + if(type === 'callback'){ + Gun.SEA.verify(key.pub, key.pub, signature, check); + } else { + Gun.SEA.verify(key.pub, key.pub, signature).then(check); + } + }); + }).catch(function(e){done(e)}); + }); + + it('de', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(jsonText){ + expect(jsonText).to.not.be(undefined); + expect(jsonText).to.not.be(''); + expect(jsonText).to.not.eql(clearText); + var decryptedSecret = JSON.parse(jsonText); + expect(decryptedSecret).to.not.be(undefined); + expect(decryptedSecret).to.not.be(''); + expect(decryptedSecret).to.be.eql(clearText); + done(); + }; + Gun.SEA.en(JSON.stringify(clearText), key.priv).then(function(jsonSecret){ + // de - decrypts JSON data using user's private or derived ECDH key + if(type === 'callback'){ + Gun.SEA.de(jsonSecret, key.priv, check); + } else { + Gun.SEA.de(jsonSecret, key.priv).then(check); + } + }); + }).catch(function(e){done(e)}); + }); + + it('derive', function(done){ + Gun.SEA.pair().then(function(txKey){ + return Gun.SEA.pair().then(function(rxKey){ + return { tx: txKey, rx: rxKey }; + }); + }).then(function(keys){ + var check = function(shared){ + expect(shared).to.not.be(undefined); + expect(shared).to.not.be(''); + [keys.rx.pub, keys.rx.priv, keys.tx.pub, keys.tx.priv] + .map(function(val){ + expect(shared).to.not.eql(val); + }); + done(); + }; + // derive - provides shared secret for both receiver and sender + // which can be used to encrypt or sign data + if(type === 'callback'){ + Gun.SEA.derive(keys.rx.pub, keys.tx.priv, check); + } else { + Gun.SEA.derive(keys.rx.pub, keys.tx.priv).then(check); + } + }).catch(function(e){done(e)}); + }); + + it('write', function(done){ + Gun.SEA.pair().then(function(key){ + Gun.SEA.sign(key.pub, key.priv).then(function(signature){ + var check = function(result){ + expect(result).to.not.be(undefined); + expect(result).to.not.be(''); + expect(result.slice(0, 4)).to.eql('SEA['); + var parts = JSON.parse(result.slice(3)); + expect(parts).to.not.be(undefined); + expect(parts[0]).to.be.eql(key.pub); + expect(parts[1]).to.be.eql(signature); + done(); + }; + // write - wraps data to 'SEA["data","signature"]' + if(type === 'callback'){ + Gun.SEA.write(key.pub, key.priv, check); + } else { + Gun.SEA.write(key.pub, key.priv).then(check); + } + }); + }).catch(function(e){done(e)}); + }); + + it('read', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(result){ + expect(result).to.not.be(undefined); + expect(result).to.not.be(''); + expect(result).to.be.equal(key.pub); + done(); + }; + Gun.SEA.sign(key.pub, key.priv).then(function(signature){ + Gun.SEA.write(key.pub, key.priv).then(function(signed){ + // read - unwraps data from 'SEA["data","signature"]' + if(type === 'callback'){ + Gun.SEA.read(signed, key.pub, check); + } else { + Gun.SEA.read(signed, key.pub).then(check); + } + }); + }); + }).catch(function(e){done(e)}); + }); + }); + }); + }); + + Gun().user && describe('User', function(){ + console.log('TODO: User! THIS IS AN EARLY ALPHA!!!'); + var alias = 'dude'; + var pass = 'my secret password'; + var user = Gun().user(); + Gun.log.off = true; // Supress all console logging + + ['callback', 'Promise'].forEach(function(type){ + describe(type, function(){ + describe('create', function(){ + it('new', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + done(); + }; + // Gun.user.create - creates new user + if(type === 'callback'){ + user.create(alias+type, pass, check); + } else { + user.create(alias+type, pass).then(check).catch(done); + } + }); + it('conflict', function(done){ + Gun.log.off = true; // Supress all console logging + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).not.to.be(undefined); + expect(ack.err).not.to.be(''); + done(); + }; + // Gun.user.create - fails to create existing user + if(type === 'callback'){ + user.create(alias+type, pass, check); + } else { + user.create(alias+type, pass).then(function(ack){ + done('Failed to decline creating existing user!'); + }).catch(check); + } + }); + }); + + describe('auth', function(){ + it('login', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + done(); + }; + var props = {alias: alias+type, pass: pass}; + // Gun.user.auth - authenticates existing user + if(type === 'callback'){ + user.auth(props, check); + } else { + user.auth(props).then(check).catch(done); + } + }); + + it('wrong password', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + done(); + }; + var props = {alias: alias+type, pass: pass+'not'}; + if(type === 'callback'){ + user.auth(props, check); + } else { + user.auth(props).then(function(ack){ + done('Unexpected login success!'); + }).catch(check); + } + }); + + it('unknown alias', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + done(); + }; + var props = {alias: alias+type+'not', pass: pass}; + if(type === 'callback'){ + user.auth(props, check); + } else { + user.auth(props).then(function(ack){ + done('Unexpected login success!'); + }).catch(check); + } + }); + + it('new password', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + done(); + }; + var props = {alias: alias+type, pass: pass, newpass: pass+' new'}; + // Gun.user.auth - with newpass props sets new password + if(type === 'callback'){ + user.auth(props, check); + } else { + user.auth(props).then(check).catch(done); + } + }); + + it('failed new password', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + done(); + }; + var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'}; + if(type === 'callback'){ + user.auth(props, check); + } else { + user.auth(props).then(function(ack){ + done('Unexpected password change success!'); + }).catch(check); + } + }); + }); + + describe('remember', function(){ + it.skip('TBD', function(done){ + done(); + }); + }); + }); + }); + Gun.log.off = false; + }); + + describe('Streams', function(){ + console.log("TODO: BUG! Upgrade UNION tests to new internal API!"); + return; + var gun = Gun(), g = function(){ + return Gun({wire: {get: ctx.get}}); + }, ctx = {gen: 9, extra: 100, network: 2}; + + it('prep hook', function(done){ + this.timeout(ctx.gen * ctx.extra); + var peer = Gun(), ref; + ctx.get = function(key, cb){ + var c = 0; + cb = cb || function(){}; + key = key[Gun._.soul]; + if('big' !== key){ return cb(null) } + setTimeout(function badNetwork(){ + c += 1; + var soul = Gun.node.soul(ref); + var graph = {}; + var data = /*graph[soul] = */ {_: {'#': soul, '>': {}}}; + if(!ref['f' + c]){ + return cb(null, data), cb(null, {}); + } + data._[Gun._.state]['f' + c] = ref._[Gun._.state]['f' + c]; + data['f' + c] = ref['f' + c]; + cb(null, data); + setTimeout(badNetwork, ctx.network); + },ctx.network); + } + ctx.get.fake = {}; + for(var i = 1; i < (ctx.gen) + 1; i++){ + ctx.get.fake['f'+i] = i; + ctx.length = i; + } + ctx.get.fake = Gun.is.node.ify(ctx.get.fake, 'big'); + var big = peer.put(ctx.get.fake).val(function(val){ + ref = val; + ctx.get({'#': 'big'}, function(err, graph){ + if(Gun.obj.empty(graph)){ done() } + }); + gun.opt({wire: {get: ctx.get}}); + }); + }); + + it('map chain', function(done){ + var set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); + set.map().val(function(obj, field){ + if(obj.here){ + done.a = obj.here; + expect(obj.here).to.be('you'); + } + if(obj.go){ + done.b = obj.go; + expect(obj.go).to.be('dear'); + } + if(obj.sir){ + done.c = obj.sir; + expect(obj.sir).to.be('!'); + } + if(done.a && done.b && done.c){ + done(); + } + }); + }); + + it('map chain path', function(done){ + var set = gun.put({ + a: {name: "Mark", + pet: {coat: "tabby", name: "Hobbes"} + }, b: {name: "Alice", + pet: {coat: "calico", name: "Cali"} + }, c: {name: "Bob", + pet: {coat: "tux", name: "Casper"} + } + }); + set.map().path('pet').val(function(obj, field){ + if(obj.name === 'Hobbes'){ + done.hobbes = obj.name; + expect(obj.name).to.be('Hobbes'); + expect(obj.coat).to.be('tabby'); + } + if(obj.name === 'Cali'){ + done.cali = obj.name; + expect(obj.name).to.be('Cali'); + expect(obj.coat).to.be('calico'); + } + if(obj.name === 'Casper'){ + done.casper = obj.name; + expect(obj.name).to.be('Casper'); + expect(obj.coat).to.be('tux'); + } + if(done.hobbes && done.cali && done.casper){ + done(); + } + }); + }); + + it('get big on', function(done){ + this.timeout(ctx.gen * ctx.extra); + var test = {c: 0, last: 0}; + g().get('big').on(function(val){ + if(test.done){ return console.log("hey yo! you got duplication on your ons!"); } + delete val._; + if(val['f' + (test.last + 1)]){ + test.c += 1; + test.last += 1; + } + var obj = {}; + for(var i = 1; i < test.c + 1; i++){ + obj['f'+i] = i; + } + expect(val).to.eql(obj); + if(test.c === ctx.length){ + test.done = true; + done(); + } + }); + }); + + it('get big on delta', function(done){ + this.timeout(ctx.gen * ctx.extra); + var test = {c: 0, seen: {}}; + g().get('big').on(function(val){ + delete val._; + if(test.seen['f' + test.c]){ return } + test.seen['f' + test.c] = true; + test.c += 1; + var obj = {}; + obj['f' + test.c] = test.c; + expect(val).to.eql(obj); + if(test.c === ctx.length){ + done(); + } + }, true); + }); + + it('get val', function(done){ + this.timeout(ctx.gen * ctx.extra); + g().get('big').val(function(obj){ + delete obj._; + expect(obj.f1).to.be(1); + expect(obj['f' + ctx.length]).to.be(ctx.length); + var raw = Gun.obj.copy(ctx.get.fake); + delete raw._; + expect(obj).to.be.eql(raw); + Gun.log.debug = 0; + done(); + }); + }); + + it('get big map val', function(done){ + this.timeout(ctx.gen * ctx.extra); + var test = {c: 0, seen: {}}; + g().get('big').map().val(function(val, field){ + if(test.seen[field]){ return } + test.seen[field] = true; + delete val._; + expect(field).to.be('f' + (test.c += 1)); + expect(val).to.be(test.c); + if(test.c === ctx.length){ + done(); + } + }); + }); + + it('val emits all data', function(done){ // bug in chat app + var chat = Gun().get('example/chat/data').not(function(){ + this.put({1: {who: 'Welcome', what: "to the chat app!", when: 0}}).key('example/chat/data'); + }); + chat.put({random1: {who: 'mark', what: "1", when: 1}}); + chat.put({random2: {who: 'mark', what: "2", when: 2}}); + chat.put({random3: {who: 'mark', what: "3", when: 3}}); + chat.put({random4: {who: 'mark', what: "4", when: 4}}); + chat.put({random5: {who: 'mark', what: "5", when: 5}}); + var seen = {1: false, 2: false, 3: false, 4: false, 5: false} + setTimeout(function(){ + chat.map(function(m){ }).val(function(msg, field){ + var msg = Gun.obj.copy(msg); + if(msg.what){ + expect(msg.what).to.be.ok(); + seen[msg.when] = true; + } + if(!Gun.obj.map(seen, function(boo){ if(!boo){ return true } })){ + done(); + } + }); + }, 100); + }); + }); +}); From 8bb6e675996a6c1c92447686b6903480ef39e0c8 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 31 Aug 2017 12:28:06 +0300 Subject: [PATCH 02/29] Refactored back with twist User.auth, added User.leave & test cases --- sea.js | 25 +++++++------ test/common.js | 98 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 92 insertions(+), 31 deletions(-) diff --git a/sea.js b/sea.js index face5334..067da9dc 100644 --- a/sea.js +++ b/sea.js @@ -44,7 +44,8 @@ var user = root._.user || (root._.user = root.chain()); // create a user context. user.create = User.create; // attach a factory method to it. user.auth = User.auth; // and a login method. - user.remember = User.remember; // and a credentials persisting method. + user.leave = User.leave; // and a logout method. + // TODO: definitely needed this: user.delete = User.delete; return user; // return the user! } @@ -120,9 +121,10 @@ if (cb){doCreate(cb, cb)} else {return new Promise(doCreate)} }; // now that we have created a user, we want to authenticate them! - User.auth = function(props, cb){ - var alias = props.alias, pass = props.pass, newpass = props.newpass; + User.auth = function(alias, pass, cb, opt){ + var opts = opt || (typeof cb !== 'function' && cb) || {}; var root = this.back(-1); + cb = typeof cb === 'function' && cb; var doAuth = function(resolve, reject){ // load all public keys associated with the username alias we want to log in with. root.get('alias/'+alias).get(function(at, ev){ @@ -168,10 +170,10 @@ // emit an auth event, useful for page redirects and stuff. Gun.on('auth', user._); } - if(newpass) { + if(opts.newpass) { // password update so encrypt private key using new pwd + salt var newsalt = Gun.text.random(64); - SEA.proof(newpass, newsalt).then(function(proof){ + SEA.proof(opts.newpass, newsalt).then(function(proof){ SEA.en(priv, proof).then(function(encVal){ return SEA.write(encVal, priv).then(function(sAuth){ return { pub: key, auth: sAuth }; @@ -211,13 +213,14 @@ }; if (cb){doAuth(cb, cb)} else {return new Promise(doAuth)} }; - // now that we have created a user, we want to authenticate them! - User.remember = function(props, cb){ - var doRemember = function(resolve, reject){ - Gun.log('User.remember is TODO: still'); - reject({ err: 'Not implemented.' }); + // now that we authenticated a user, we want to support logout too! + User.leave = function(cb){ + var root = this.back(-1); + var doLogout = function(resolve, reject){ + root._.user = root.chain(); + resolve({ok: 0}); } - if (cb){doRemember(cb, cb)} else {return new Promise(doRemember)} + if (cb){doLogout(cb, cb)} else {return new Promise(doLogout)} }; // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. diff --git a/test/common.js b/test/common.js index 5e972897..4a246d4a 100644 --- a/test/common.js +++ b/test/common.js @@ -7843,7 +7843,7 @@ describe('Gun', function(){ var encKeys = ['ct', 'iv', 's']; ['callback', 'Promise'].forEach(function(type){ - describe(type, function(){ + describe(type+':', function(){ it('proof', function(done){ var check = function(proof){ expect(proof).to.not.be(undefined); @@ -8037,11 +8037,12 @@ describe('Gun', function(){ console.log('TODO: User! THIS IS AN EARLY ALPHA!!!'); var alias = 'dude'; var pass = 'my secret password'; - var user = Gun().user(); + var gun = Gun(); + var user = gun.user(); Gun.log.off = true; // Supress all console logging ['callback', 'Promise'].forEach(function(type){ - describe(type, function(){ + describe(type+':', function(){ describe('create', function(){ it('new', function(done){ var check = function(ack){ @@ -8086,12 +8087,11 @@ describe('Gun', function(){ expect(ack).to.not.have.key('err'); done(); }; - var props = {alias: alias+type, pass: pass}; // Gun.user.auth - authenticates existing user if(type === 'callback'){ - user.auth(props, check); + user.auth(alias+type, pass, check); } else { - user.auth(props).then(check).catch(done); + user.auth(alias+type, pass).then(check).catch(done); } }); @@ -8104,11 +8104,10 @@ describe('Gun', function(){ expect(ack.err).to.not.be(''); done(); }; - var props = {alias: alias+type, pass: pass+'not'}; if(type === 'callback'){ - user.auth(props, check); + user.auth(alias+type, pass+'not', check); } else { - user.auth(props).then(function(ack){ + user.auth(alias+type, pass+'not').then(function(ack){ done('Unexpected login success!'); }).catch(check); } @@ -8123,11 +8122,10 @@ describe('Gun', function(){ expect(ack.err).to.not.be(''); done(); }; - var props = {alias: alias+type+'not', pass: pass}; if(type === 'callback'){ - user.auth(props, check); + user.auth(alias+type+'not', pass, check); } else { - user.auth(props).then(function(ack){ + user.auth(alias+type+'not', pass).then(function(ack){ done('Unexpected login success!'); }).catch(check); } @@ -8140,12 +8138,12 @@ describe('Gun', function(){ expect(ack).to.not.have.key('err'); done(); }; - var props = {alias: alias+type, pass: pass, newpass: pass+' new'}; // Gun.user.auth - with newpass props sets new password if(type === 'callback'){ - user.auth(props, check); + user.auth(alias+type, pass, check, {newpass: pass+' new'}); } else { - user.auth(props).then(check).catch(done); + user.auth(alias+type, pass, {newpass: pass+' new'}).then(check) + .catch(done); } }); @@ -8160,19 +8158,79 @@ describe('Gun', function(){ }; var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'}; if(type === 'callback'){ - user.auth(props, check); + user.auth(alias+type, pass+'not', check, {newpass: pass+' new'}); } else { - user.auth(props).then(function(ack){ + user.auth(alias+type, pass+'not', {newpass: pass+' new'}) + .then(function(ack){ done('Unexpected password change success!'); }).catch(check); } }); + + it.skip('no recall no session storing'); }); - describe('remember', function(){ - it.skip('TBD', function(done){ - done(); + describe('leave', function(){ + it('valid session', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + done(); + }; + user.auth(alias+type, pass+' new').then(function(usr){ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + // Gun.user.leave - performs logout for authenticated user + if(type === 'callback'){ + user.leave(check); + } else { + user.leave().then(check).catch(done); + } + }).catch(done); }); + + it('no session', function(done){ + var check = function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + done(); + }; + user.leave().then(function(ack){ + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + if(type === 'callback'){ + user.leave(check); + } else { + user.leave().then(check).catch(done); + } + }); + }); + }); + + describe('delete - TODO: how?', function(){ + it.skip('existing authenticated user'); + it.skip('unauthenticated user'); + }); + + describe('recall', function(){ + it.skip('with zero validity auth skips storing'); + it.skip('with validity auth stores session'); + it.skip('valid session'); + it.skip('expired session'); + it.skip('changed password'); + it.skip('no session'); + }); + + describe('alive', function(){ + it.skip('valid session'); + it.skip('expired session'); + it.skip('recall hook session manipulation'); }); }); }); From 41c0f9ed79dc40db8174da703d59e51ab878a080 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 31 Aug 2017 14:54:34 +0300 Subject: [PATCH 03/29] Added User.delete & one test case - refactored test cases for smoother operation in case of error(s) --- sea.js | 241 ++++++++++++++++++++++++++----------------------- test/common.js | 145 ++++++++++++++++++++--------- 2 files changed, 230 insertions(+), 156 deletions(-) diff --git a/sea.js b/sea.js index 067da9dc..47562199 100644 --- a/sea.js +++ b/sea.js @@ -45,7 +45,7 @@ user.create = User.create; // attach a factory method to it. user.auth = User.auth; // and a login method. user.leave = User.leave; // and a logout method. - // TODO: definitely needed this: user.delete = User.delete; + user.delete = User.delete; // and, account delete method. return user; // return the user! } @@ -71,12 +71,53 @@ }()); + // This internal auth func - more used in future... + function authenticate(alias,pass,root){ + return new Promise(function(resolve, reject){ + // load all public keys associated with the username alias we want to log in with. + root.get('alias/'+alias).get(function(at, ev){ + ev.off(); + if(!at.put){ + // if no user, don't do anything. + var err = 'No user!'; + Gun.log(err); + return reject({err: err}); + } + // then attempt to log into each one until we find ours! + // (if two users have the same username AND the same password... that would be bad) + Gun.obj.map(at.put, function(val, key){ + // grab the account associated with this public key. + root.get(key).get(function(at, ev){ + key = key.slice(4); + ev.off(); + if(!at.put){return} // reject({err: 'Public key does not exist!'}) + // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) + SEA.read(at.put.salt, key).then(function(salt){ + return SEA.proof(pass, salt); + }).then(function(proof){ + // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + return SEA.read(at.put.auth, key).then(function(auth){ + return SEA.de(auth, proof); + }).catch(function(){reject({err: 'Failed to decrypt private key!'})}); + }).then(function(priv){ + // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. + // if we were successful, then that meanswe're logged in! + return (priv && resolve({pub: key, priv: priv, at: at})) + // Or else we failed to log in... + || reject({err: 'Failed to decrypt private key!'}); + }).catch(function(){reject({err: 'Failed to create proof!'})}); + }); + }); + }); + }); + }; + // How does it work? function User(){}; // Well first we have to actually create a user. That is what this function does. User.create = function(alias, pass, cb){ var root = this.back(-1); - var doCreate = function(resolve, reject){ + var doIt = function(resolve, reject){ // Because more than 1 user might have the same username, we treat the alias as a list of those users. root.get('alias/'+alias).get(function(at, ev){ ev.off(); @@ -118,109 +159,89 @@ }); }); }; - if (cb){doCreate(cb, cb)} else {return new Promise(doCreate)} + if (cb){doIt(cb, cb)} else {return new Promise(doIt)} }; // now that we have created a user, we want to authenticate them! - User.auth = function(alias, pass, cb, opt){ + User.auth = function(alias,pass,cb,opt){ var opts = opt || (typeof cb !== 'function' && cb) || {}; var root = this.back(-1); cb = typeof cb === 'function' && cb; - var doAuth = function(resolve, reject){ - // load all public keys associated with the username alias we want to log in with. - root.get('alias/'+alias).get(function(at, ev){ - ev.off(); - if(!at.put){ - // if no user, don't do anything. - var err = 'No user!'; - Gun.log(err); - return reject({err: err}); + var doIt = function(resolve, reject){ + authenticate(alias, pass, root).then(function(key){ + // we're logged in! + function doLogin(){ + var user = root._.user; + // add our credentials in-memory only to our root gun instance + user._ = key.at.gun._; + // so that way we can use the credentials to encrypt/decrypt data + user._.is = user.is = {}; + // that is input/output through gun (see below) + user._.sea = key.priv; + user._.pub = key.pub; + //console.log("authorized", user._); + // callbacks success with the user data credentials. + resolve(user._); + // emit an auth event, useful for page redirects and stuff. + Gun.on('auth', user._); } - // then attempt to log into each one until we find ours! - // (if two users have the same username AND the same password... that would be bad) - Gun.obj.map(at.put, function(val, key){ - // grab the account associated with this public key. - root.get(key).get(function(at, ev){ - key = key.slice(4); - ev.off(); - if(!at.put){return} - // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) - SEA.read(at.put.salt, key).then(function(salt){ - return SEA.proof(pass, salt); - }).then(function(proof){ - // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - return SEA.read(at.put.auth, key).then(function(auth){ - return SEA.de(auth, proof); + if(opts.newpass) { + // password update so encrypt private key using new pwd + salt + var newsalt = Gun.text.random(64); + SEA.proof(opts.newpass, newsalt).then(function(proof){ + SEA.en(key.priv, proof).then(function(encVal){ + return SEA.write(encVal, key.priv).then(function(sAuth){ + return { pub: key.pub, auth: sAuth }; }); - }).then(function(priv){ - // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. - if(priv){ // if we were successful, then that means... - // we're logged in! - function doLogin(){ - var user = root._.user; - // add our credentials in-memory only to our root gun instance - user._ = at.gun._; - // so that way we can use the credentials to encrypt/decrypt data - user._.is = user.is = {}; - // that is input/output through gun (see below) - user._.sea = priv; - user._.pub = key; - //console.log("authorized", user._); - // callbacks success with the user data credentials. - resolve(user._); - // emit an auth event, useful for page redirects and stuff. - Gun.on('auth', user._); - } - if(opts.newpass) { - // password update so encrypt private key using new pwd + salt - var newsalt = Gun.text.random(64); - SEA.proof(opts.newpass, newsalt).then(function(proof){ - SEA.en(priv, proof).then(function(encVal){ - return SEA.write(encVal, priv).then(function(sAuth){ - return { pub: key, auth: sAuth }; - }); - }).then(function(user){ - return SEA.write(alias, priv).then(function(sAlias){ - user.alias = sAlias; return user; - }); - }).then(function(user){ - return SEA.write(newsalt, priv).then(function(sSalt){ - user.salt = sSalt; return user; - }); - }).then(function(user){ - var tmp = 'pub/'+key; - // awesome, now we can update the user using public key ID. - root.get(tmp).put(user); - // then we're done - doLogin(); - }); - }); - } else { - doLogin(); - } - return; - } - // Or else we failed to log in... - }).catch(function(e){ - Gun.log('Failed to sign in!'); - reject({err: 'Attempt failed'}); + }).then(function(user){ + return SEA.write(alias, key.priv).then(function(sAlias){ + user.alias = sAlias; return user; + }); + }).then(function(user){ + return SEA.write(newsalt, key.priv).then(function(sSalt){ + user.salt = sSalt; return user; + }); + }).then(function(user){ + var tmp = 'pub/'+key.pub; + // awesome, now we can update the user using public key ID. + root.get(tmp).put(user); + // then we're done + doLogin(); }); }); - // if (!found) { - // reject({err: 'Public key does not exist!'}) - // } - }); + } else { + doLogin(); + } + return; + }).catch(function(e){ + Gun.log('Failed to sign in!'); + reject({err: 'Auth attempt failed'}); }); }; - if (cb){doAuth(cb, cb)} else {return new Promise(doAuth)} + if (cb){doIt(cb, cb)} else {return new Promise(doIt)} }; // now that we authenticated a user, we want to support logout too! User.leave = function(cb){ var root = this.back(-1); - var doLogout = function(resolve, reject){ + var doIt = function(resolve, reject){ root._.user = root.chain(); resolve({ok: 0}); } - if (cb){doLogout(cb, cb)} else {return new Promise(doLogout)} + if (cb){doIt(cb, cb)} else {return new Promise(doIt)} + }; + // If authenticated user wants to delete his/her account, let's support it! + User.delete = function(alias,pass,cb){ + var root = this.back(-1); + var doIt = function(resolve, reject){ + authenticate(alias, pass, root).then(function(key){ + root.get(key.pub).put(null); + root._.user = root.chain(); + resolve({ok: 0}); + }).catch(function(e){ + Gun.log('User.delete failed! Error:', e); + reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''}); + }); + } + if (cb){doIt(cb, cb)} else {return new Promise(doIt)} }; // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. @@ -372,14 +393,14 @@ }; // Does enc/dec key like OpenSSL - works with CryptoJS encryption/decryption - function makeKey(p, s) { - var ps = Buffer.concat([ new Buffer(p, 'utf8'), s ]); + function makeKey(p,s){ + var ps = Buffer.concat([new Buffer(p, 'utf8'), s]); var h128 = new Buffer(nodeCrypto.createHash('md5').update(ps).digest('hex'), 'hex'); // TODO: 'md5' is insecure, do we need OpenSSL compatibility anymore ? return Buffer.concat([ h128, new Buffer(nodeCrypto.createHash('md5').update( - Buffer.concat([ h128, ps ]).toString('base64'), 'base64' + Buffer.concat([h128, ps]).toString('base64'), 'base64' ).digest('hex'), 'hex') ]); } @@ -391,7 +412,7 @@ // create a wrapper library around NodeJS crypto & ecCrypto and Web Crypto API. // now wrap the various AES, ECDSA, PBKDF2 functions we called above. SEA.proof = function(pass,salt,cb){ - var doProof = (typeof window !== 'undefined' && function(resolve, reject){ + var doIt = (typeof window !== 'undefined' && function(resolve, reject){ crypto.subtle.importKey( // For browser crypto.subtle works fine 'raw', new TextEncoder().encode(pass), {name: 'PBKDF2'}, false, ['deriveBits'] ).then(function(key){ @@ -409,29 +430,29 @@ resolve(!err && hash && hash.toString('base64')); }); }; - if(cb){doProof(cb, function(){cb()})} else {return new Promise(doProof)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.pair = function(cb){ - var doPair = function(resolve, reject){ + var doIt = function(resolve, reject){ var priv = nodeCrypto.randomBytes(32); resolve({ pub: new Buffer(ecCrypto.getPublic(priv), 'binary').toString('hex'), priv: new Buffer(priv, 'binary').toString('hex') }); }; - if(cb){doPair(cb, function(){cb()})} else {return new Promise(doPair)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.derive = function(m,p,cb){ - var doDerive = function(resolve, reject){ + var doIt = function(resolve, reject){ ecCrypto.derive(new Buffer(p, 'hex'), new Buffer(m, 'hex')) .then(function(secret){ resolve(new Buffer(secret, 'binary').toString('hex')); }).catch(function(e){Gun.log(e); reject(e)}); }; - if(cb){doDerive(cb, function(){cb()})} else {return new Promise(doDerive)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; - SEA.sign = function(m, p, cb){ - var doSign = function(resolve, reject){ + SEA.sign = function(m,p,cb){ + var doIt = function(resolve, reject){ ecCrypto.sign( new Buffer(p, 'hex'), nodeCrypto.createHash(nHash).update(JSON.stringify(m), 'utf8').digest() @@ -439,20 +460,20 @@ resolve(new Buffer(sig, 'binary').toString('hex')); }).catch(function(e){Gun.log(e); reject(e)}); }; - if(cb){doSign(cb, function(){cb()})} else {return new Promise(doSign)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.verify = function(m, p, s, cb){ - var doVerify = function(resolve, reject){ + var doIt = function(resolve, reject){ ecCrypto.verify( new Buffer(p, 'hex'), nodeCrypto.createHash(nHash).update(JSON.stringify(m), 'utf8').digest(), new Buffer(s, 'hex') ).then(function(){resolve(true)}).catch(function(e){Gun.log(e);reject(e)}) }; - if(cb){doVerify(cb, function(){cb()})} else {return new Promise(doVerify)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.en = function(m,p,cb){ - var doEncrypt = function(resolve, reject){ + var doIt = function(resolve, reject){ var s = nodeCrypto.randomBytes(8); var iv = nodeCrypto.randomBytes(16); var r = {iv: iv.toString('hex'), s: s.toString('hex')}; @@ -476,10 +497,10 @@ resolve(JSON.stringify(r)); } }; - if(cb){doEncrypt(cb, function(){cb()})} else {return new Promise(doEncrypt)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.de = function(m,p,cb){ - var doDecrypt = function(resolve, reject){ + var doIt = function(resolve, reject){ var d = JSON.parse(m); var key = makeKey(p, new Buffer(d.s, 'hex')); var iv = new Buffer(d.iv, 'hex'); @@ -502,20 +523,18 @@ resolve(r); } }; - if(cb){doDecrypt(cb, function(){cb()})} else {return new Promise(doDecrypt)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.write = function(m,p,cb){ - var doSign = function(resolve, reject) { + var doIt = function(resolve, reject) { SEA.sign(m, p).then(function(signature){ resolve('SEA'+JSON.stringify([m,signature])); }).catch(function(e){Gun.log(e); reject(e)}); }; - if(cb){doSign(cb, function(){cb()})} else {return new Promise(doSign)} - // TODO: what's this ? - // return JSON.stringify([m,SEA.sign(m,p)]); + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; SEA.read = function(m,p,cb){ - var doRead = function(resolve, reject) { + var doIt = function(resolve, reject) { if(!m){ return resolve(); } if(!m.slice || 'SEA[' !== m.slice(0,4)){ return resolve(m); } m = m.slice(3); @@ -526,7 +545,7 @@ resolve(ok && m[0]); }); }; - if(cb){doRead(cb, function(){cb()})} else {return new Promise(doRead)} + if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; Gun.SEA = SEA; diff --git a/test/common.js b/test/common.js index 4a246d4a..5362db5e 100644 --- a/test/common.js +++ b/test/common.js @@ -8046,9 +8046,11 @@ describe('Gun', function(){ describe('create', function(){ it('new', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + }catch(e){done(e); return}; done(); }; // Gun.user.create - creates new user @@ -8061,11 +8063,13 @@ describe('Gun', function(){ it('conflict', function(done){ Gun.log.off = true; // Supress all console logging var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).not.to.be(undefined); - expect(ack.err).not.to.be(''); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).not.to.be(undefined); + expect(ack.err).not.to.be(''); + }catch(e){done(e); return}; done(); }; // Gun.user.create - fails to create existing user @@ -8082,9 +8086,11 @@ describe('Gun', function(){ describe('auth', function(){ it('login', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + }catch(e){done(e); return}; done(); }; // Gun.user.auth - authenticates existing user @@ -8097,11 +8103,13 @@ describe('Gun', function(){ it('wrong password', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).to.not.be(undefined); - expect(ack.err).to.not.be(''); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + }catch(e){done(e); return}; done(); }; if(type === 'callback'){ @@ -8115,11 +8123,13 @@ describe('Gun', function(){ it('unknown alias', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).to.not.be(undefined); - expect(ack.err).to.not.be(''); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + }catch(e){done(e); return}; done(); }; if(type === 'callback'){ @@ -8133,9 +8143,11 @@ describe('Gun', function(){ it('new password', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + }catch(e){done(e); return}; done(); }; // Gun.user.auth - with newpass props sets new password @@ -8149,11 +8161,13 @@ describe('Gun', function(){ it('failed new password', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).to.not.be(undefined); - expect(ack.err).to.not.be(''); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + }catch(e){done(e); return}; done(); }; var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'}; @@ -8173,18 +8187,22 @@ describe('Gun', function(){ describe('leave', function(){ it('valid session', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + }catch(e){done(e); return}; done(); }; user.auth(alias+type, pass+' new').then(function(usr){ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){done(e); return}; // Gun.user.leave - performs logout for authenticated user if(type === 'callback'){ user.leave(check); @@ -8196,10 +8214,12 @@ describe('Gun', function(){ it('no session', function(done){ var check = function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.key('ok'); + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + }catch(e){done(e); return}; done(); }; user.leave().then(function(ack){ @@ -8213,9 +8233,44 @@ describe('Gun', function(){ }); }); - describe('delete - TODO: how?', function(){ - it.skip('existing authenticated user'); - it.skip('unauthenticated user'); + describe('delete', function(){ + it('existing authenticated user', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + }catch(e){done(e); return}; + done(); + }; + var aalias = alias+type+'del'; + user.create(aalias, pass).catch(check).then(function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + }catch(e){done(e); return}; + user.auth(aalias, pass).then(function(usr){ + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){done(e); return}; + // Gun.user.delete - deöetes existing user account + if(type === 'callback'){ + user.delete(alias+type, pass+' new', check); + } else { + user.delete(alias+type, pass+' new').then(check).catch(done); + } + }).catch(done); + }); + }); + + it.skip('unauthenticated existing user'); + it.skip('unauthenticated other user'); }); describe('recall', function(){ From bff2fdece9ea1d47bfb5ace7a018902763a2cfbc Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 31 Aug 2017 20:19:22 +0300 Subject: [PATCH 04/29] Finished User.delete tests & fixed mysterious password update bug --- sea.js | 93 ++++++++++++++++++---------------- test/common.js | 132 +++++++++++++++++++++++++++++++------------------ 2 files changed, 136 insertions(+), 89 deletions(-) diff --git a/sea.js b/sea.js index 47562199..864543f6 100644 --- a/sea.js +++ b/sea.js @@ -75,38 +75,52 @@ function authenticate(alias,pass,root){ return new Promise(function(resolve, reject){ // load all public keys associated with the username alias we want to log in with. - root.get('alias/'+alias).get(function(at, ev){ - ev.off(); - if(!at.put){ + root.get('alias/'+alias).get(function(rat, rev){ + rev.off(); + if(!rat.put){ // if no user, don't do anything. var err = 'No user!'; Gun.log(err); return reject({err: err}); } - // then attempt to log into each one until we find ours! - // (if two users have the same username AND the same password... that would be bad) - Gun.obj.map(at.put, function(val, key){ + // then figuring out all possible candidates having matching username + var aliases = []; + Gun.obj.map(rat.put, function(at, key){ // grab the account associated with this public key. root.get(key).get(function(at, ev){ + if(!key.slice || 'pub/' !== key.slice(0,4)){return} key = key.slice(4); ev.off(); - if(!at.put){return} // reject({err: 'Public key does not exist!'}) - // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) - SEA.read(at.put.salt, key).then(function(salt){ - return SEA.proof(pass, salt); - }).then(function(proof){ - // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - return SEA.read(at.put.auth, key).then(function(auth){ - return SEA.de(auth, proof); - }).catch(function(){reject({err: 'Failed to decrypt private key!'})}); + if(!at.put){return} + aliases.push({key: key, at: at}); + }); + }); + if (!aliases.length){return reject({err: 'Public key does not exist!'})} + // then attempt to log into each one until we find ours! + // (if two users have the same username AND the same password... that would be bad) + aliases.forEach(function(one, index){ + var at = one.at, key = one.key; + var remaining = (aliases.length - index) > 1; + if(!at.put){ + return (!remaining) && reject({err: 'Public key does not exist!'}) + } + // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) + SEA.read(at.put.salt, key).then(function(salt){ + return SEA.proof(pass, salt); + }).then(function(proof){ + // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + return SEA.read(at.put.auth, key).then(function(auth){ + return SEA.de(auth, proof); + }).catch(function(e){ }).then(function(priv){ // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. // if we were successful, then that meanswe're logged in! - return (priv && resolve({pub: key, priv: priv, at: at})) + return remaining ? undefined // Not done yet + : priv ? resolve({pub: key, priv: priv, at: at}) // Or else we failed to log in... - || reject({err: 'Failed to decrypt private key!'}); - }).catch(function(){reject({err: 'Failed to create proof!'})}); - }); + : reject({err: 'Failed to decrypt private key!'}); + }); + }).catch(function(){reject({err: 'Failed to create proof!'})}); }); }); }); @@ -189,19 +203,14 @@ var newsalt = Gun.text.random(64); SEA.proof(opts.newpass, newsalt).then(function(proof){ SEA.en(key.priv, proof).then(function(encVal){ - return SEA.write(encVal, key.priv).then(function(sAuth){ - return { pub: key.pub, auth: sAuth }; - }); + return { + alias: alias, + pub: key.pub, + auth: encVal, + salt: newsalt + }; }).then(function(user){ - return SEA.write(alias, key.priv).then(function(sAlias){ - user.alias = sAlias; return user; - }); - }).then(function(user){ - return SEA.write(newsalt, key.priv).then(function(sSalt){ - user.salt = sSalt; return user; - }); - }).then(function(user){ - var tmp = 'pub/'+key.pub; + var tmp = 'pub/'+user.pub; // awesome, now we can update the user using public key ID. root.get(tmp).put(user); // then we're done @@ -211,10 +220,9 @@ } else { doLogin(); } - return; }).catch(function(e){ Gun.log('Failed to sign in!'); - reject({err: 'Auth attempt failed'}); + reject({err: 'Auth attempt failed! Reason: '+(e && e.err) || e || ''}); }); }; if (cb){doIt(cb, cb)} else {return new Promise(doIt)} @@ -395,13 +403,11 @@ // Does enc/dec key like OpenSSL - works with CryptoJS encryption/decryption function makeKey(p,s){ var ps = Buffer.concat([new Buffer(p, 'utf8'), s]); - var h128 = new Buffer(nodeCrypto.createHash('md5').update(ps).digest('hex'), 'hex'); + var h128 = nodeCrypto.createHash('md5').update(ps).digest(); // TODO: 'md5' is insecure, do we need OpenSSL compatibility anymore ? return Buffer.concat([ h128, - new Buffer(nodeCrypto.createHash('md5').update( - Buffer.concat([h128, ps]).toString('base64'), 'base64' - ).digest('hex'), 'hex') + nodeCrypto.createHash('md5').update(Buffer.concat([h128, ps])).digest() ]); } @@ -525,8 +531,13 @@ }; if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; - SEA.write = function(m,p,cb){ + SEA.write = function(mm,p,cb){ var doIt = function(resolve, reject) { + var m = mm; + if(!m.slice || 'SEA[' !== m.slice(0,4)){m = mm} + m = m.slice(3); + try{m = JSON.parse(m); + }catch(e){m = mm} SEA.sign(m, p).then(function(signature){ resolve('SEA'+JSON.stringify([m,signature])); }).catch(function(e){Gun.log(e); reject(e)}); @@ -536,14 +547,12 @@ SEA.read = function(m,p,cb){ var doIt = function(resolve, reject) { if(!m){ return resolve(); } - if(!m.slice || 'SEA[' !== m.slice(0,4)){ return resolve(m); } + if(!m.slice || 'SEA[' !== m.slice(0,4)){return resolve(m)} m = m.slice(3); try{m = JSON.parse(m); }catch(e){ return reject(e); } m = m || ''; - SEA.verify(m[0], p, m[1]).then(function(ok){ - resolve(ok && m[0]); - }); + SEA.verify(m[0], p, m[1]).then(function(ok){resolve(ok && m[0])}); }; if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; diff --git a/test/common.js b/test/common.js index 5362db5e..2027f2b6 100644 --- a/test/common.js +++ b/test/common.js @@ -19,8 +19,8 @@ var root; //Gun.log.squelch = true; var gleak = {globals: {}, check: function(){ // via tobyho var leaked = [] - for (var key in gleak.globe){ if (!(key in gleak.globals)){ leaked.push(key)} } - if (leaked.length > 0){ console.log("GLOBAL LEAK!", leaked); return leaked } + for (var key in gleak.globe){ if(!(key in gleak.globals)){ leaked.push(key)} } + if(leaked.length > 0){ console.log("GLOBAL LEAK!", leaked); return leaked } }}; (function(env){ for (var key in (gleak.globe = env)){ gleak.globals[key] = true } @@ -6900,7 +6900,7 @@ describe('Gun', function(){ var now = new Date().getTime(); var cb; for (var i = 1; i <= num; i++) { - if (i === num) { + if(i === num) { cb = function (err, ok) { console.log(num + 'ops: ' + (new Date().getTime() - now)/1000 + 's'); } @@ -6929,7 +6929,7 @@ describe('Gun', function(){ var now = new Date().getTime(); var cb; for (var i = 1; i <= num; i++) { - if (i === num) { + if(i === num) { cb = function () { console.log(num + ' API ops: ' + (new Date().getTime() - now)/1000 + 's'); t && done(); @@ -7117,7 +7117,7 @@ describe('Gun', function(){ var now = new Date().getTime(); var cb; for (var i = 1; i <= num; i++) { - if (i === num) { + if(i === num) { cb = function (err, ok) { console.log(num + 'MUTANT ops: ' + (new Date().getTime() - now)/1000 + 's'); t && done(); @@ -7265,7 +7265,7 @@ describe('Gun', function(){ gun.map(function (player, number) { players[number] = player; players[number].history = []; - if (!player.taken && !me) { + if(!player.taken && !me) { this.put({ taken: true, history: { @@ -7645,7 +7645,7 @@ describe('Gun', function(){ Gun.log.debug = 1; console.log("------------------"); gun.path('1.2.data').on(function (data) { console.log("??????", data); - if (data) { + if(data) { // gun will never receive the "true" update done(); } @@ -8196,19 +8196,25 @@ describe('Gun', function(){ }catch(e){done(e); return}; done(); }; - user.auth(alias+type, pass+' new').then(function(usr){ - try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); - }catch(e){done(e); return}; - // Gun.user.leave - performs logout for authenticated user - if(type === 'callback'){ - user.leave(check); - } else { - user.leave().then(check).catch(done); - } + var usr = alias+type+'leave'; + user.create(usr, pass).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + user.auth(usr, pass).then(function(usr){ + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){done(e); return}; + // Gun.user.leave - performs logout for authenticated user + if(type === 'callback'){ + user.leave(check); + } else { + user.leave().then(check).catch(done); + } + }).catch(done); }).catch(done); }); @@ -8222,20 +8228,28 @@ describe('Gun', function(){ }catch(e){done(e); return}; done(); }; - user.leave().then(function(ack){ - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - if(type === 'callback'){ - user.leave(check); - } else { - user.leave().then(check).catch(done); - } - }); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + if(type === 'callback'){ + user.leave(check); + } else { + user.leave().then(check).catch(done); + } }); }); describe('delete', function(){ - it('existing authenticated user', function(done){ - var check = function(ack){ + var usr = alias+type+'del'; + + var createUser = function(a, p){ + return user.create(a, p).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + return ack; + }); + }; + var check = function(done){ + return function(ack){ try{ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); @@ -8245,32 +8259,56 @@ describe('Gun', function(){ }catch(e){done(e); return}; done(); }; - var aalias = alias+type+'del'; - user.create(aalias, pass).catch(check).then(function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - }catch(e){done(e); return}; - user.auth(aalias, pass).then(function(usr){ + }; + + it('existing authenticated user', function(done){ + createUser(usr, pass).then(function(){ + user.auth(usr, pass).then(function(ack){ try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('put'); }catch(e){done(e); return}; - // Gun.user.delete - deöetes existing user account + // Gun.user.delete - deletes existing user account if(type === 'callback'){ - user.delete(alias+type, pass+' new', check); + user.delete(usr, pass, check(done)); } else { - user.delete(alias+type, pass+' new').then(check).catch(done); + user.delete(usr, pass).then(check(done)).catch(done); } }).catch(done); + }).catch(done); + }); + + it('unauthenticated existing user', function(done){ + createUser(usr, pass).catch(function(){}) + .then(function(){ + if(type === 'callback'){ + user.delete(usr, pass, check(done)); + } else { + user.delete(usr, pass).then(check(done)).catch(done); + } }); }); - it.skip('unauthenticated existing user'); - it.skip('unauthenticated other user'); + it('non-existing user', function(done){ + var notFound = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('put'); + expect(ack).to.have.key('err'); + }catch(e){done(e); return}; + done(); + }; + if(type === 'callback'){ + user.delete('someone', 'password guess', notFound); + } else { + user.delete('someone', 'password guess').then(function(){ + done('Unexpectedly deleted guessed user!'); + }).catch(notFound); + } + }); }); describe('recall', function(){ From 2f69bddc2ad6be15fa76d252873eeb4969b42f0e Mon Sep 17 00:00:00 2001 From: mhelander Date: Fri, 1 Sep 2017 23:07:30 +0300 Subject: [PATCH 05/29] Serious refactoring & structuring sea.js to handle 'remember-me'. Pegs more testing and test cases --- package.json | 1 + sea.js | 466 +++++++++++++++++++++++++++++++++++++------------ test/common.js | 226 +++++++++++++++++++++++- 3 files changed, 577 insertions(+), 116 deletions(-) diff --git a/package.json b/package.json index 06ac37bf..95fd2347 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "buffer": "^5.0.7", "eccrypto": "^1.0.3", "formidable": ">=1.1.1", + "node-localstorage": "^1.3.0", "subtle": "^0.1.8", "text-encoding": "^0.6.4", "ws": "~>2.2.3" diff --git a/sea.js b/sea.js index 864543f6..4d98aede 100644 --- a/sea.js +++ b/sea.js @@ -12,19 +12,28 @@ var Gun = (typeof window !== 'undefined' ? window : global).Gun || require('./gun'); - // Following enable Web Cryptography API use in NodeJS - var crypto = (typeof window !== 'undefined' && window.crypto) - || { subtle: require('subtle') }; + var crypto, TextEncoder, TextDecoder, localStorage, sessionStorage; - var TextEncoder = (typeof window !== 'undefined' && window.TextEncoder) - || require('text-encoding').TextEncoder; - var TextDecoder = (typeof window !== 'undefined' && window.TextDecoder) - || require('text-encoding').TextDecoder; + if (typeof window !== 'undefined') { + crypto = window.crypto; + TextEncoder = window.TextEncoder; + TextDecoder = window.TextDecoder; + localStorage = window.localStorage; + sessionStorage = window.sessionStorage; + } else { + crypto = { subtle: require('subtle') }; // Web Cryptography API for NodeJS + TextEncoder = require('text-encoding').TextEncoder; + TextDecoder = require('text-encoding').TextDecoder; + // Let's have Storage for NodeJS / testing + localStorage = new require('node-localstorage').LocalStorage('local'); + sessionStorage = new require('node-localstorage').LocalStorage('session'); + } if(typeof Buffer === 'undefined'){ var Buffer = require('buffer').Buffer; } + // Encryption parameters - TODO: maybe to be changed via init? var pbkdf2 = { hash: 'SHA-256', // Was 'SHA-1' iter: 50000, @@ -37,42 +46,36 @@ enc: 'aes-256-cbc' }; + // These are used to persist user's authentication "session" + var authsettings = { + validity: 60 * 60 * 12, // 12 hours + session: true, + // or return new Promise(function(resolve, reject){(resolve(props))}) + hook: function(props) {return props} // { iat, exp, alias, proof } + }; + // let's extend the gun chain with a `user` function. // only one user can be logged in at a time, per gun instance. Gun.chain.user = function(){ var root = this.back(-1); // always reference the root gun instance. var user = root._.user || (root._.user = root.chain()); // create a user context. - user.create = User.create; // attach a factory method to it. - user.auth = User.auth; // and a login method. - user.leave = User.leave; // and a logout method. - user.delete = User.delete; // and, account delete method. + // then methods... + [ 'create', // factory + 'auth', // login + 'leave', // logout + 'delete', // account delete + 'recall', // existing auth boostrap + 'alive' // keep/check auth validity + ].forEach(function(method){ + user[method] = User[method]; + }); return user; // return the user! } - // EXAMPLE! Use it this way: - ;(function(){return; - localStorage.clear(); + // Practical examples about usage found from ./test/common.js - var gun = Gun(); - var user = gun.user(); - - Gun.on('auth', function(at){ - // do something once logged in. - }); - Gun.on('secure', function(at){ - // enforce some rules about shared app level data - var no; - if(no){ return } - this.to.next(at); - }); - - user.create("test", "password"); // create a user from a username alias and a password phrase. - user.auth("test", "password"); // authenticate and log in the user! - - }()); - - // This internal auth func - more used in future... - function authenticate(alias,pass,root){ + // This is internal func queries public key(s) for alias. + function querygunaliases(alias,root){ return new Promise(function(resolve, reject){ // load all public keys associated with the username alias we want to log in with. root.get('alias/'+alias).get(function(rat, rev){ @@ -85,49 +88,234 @@ } // then figuring out all possible candidates having matching username var aliases = []; - Gun.obj.map(rat.put, function(at, key){ + Gun.obj.map(rat.put, function(at, pub){ // grab the account associated with this public key. - root.get(key).get(function(at, ev){ - if(!key.slice || 'pub/' !== key.slice(0,4)){return} - key = key.slice(4); + root.get(pub).get(function(at, ev){ + if(!pub.slice || 'pub/' !== pub.slice(0,4)){return} + pub = pub.slice(4); ev.off(); if(!at.put){return} - aliases.push({key: key, at: at}); + aliases.push({pub: pub, at: at}); }); }); - if (!aliases.length){return reject({err: 'Public key does not exist!'})} + return aliases.length && resolve(aliases) + || reject({err: 'Public key does not exist!'}) + }); + }); + } + + // This is internal User authentication func. + function authenticate(alias,pass,root){ + return new Promise(function(resolve, reject){ + // load all public keys associated with the username alias we want to log in with. + querygunaliases(alias, root).then(function(aliases){ // then attempt to log into each one until we find ours! // (if two users have the same username AND the same password... that would be bad) aliases.forEach(function(one, index){ - var at = one.at, key = one.key; + var at = one.at, pub = one.pub; var remaining = (aliases.length - index) > 1; if(!at.put){ return (!remaining) && reject({err: 'Public key does not exist!'}) } // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) - SEA.read(at.put.salt, key).then(function(salt){ - return SEA.proof(pass, salt); - }).then(function(proof){ + SEA.read(at.put.salt, pub).then(function(salt){ + return SEA.proof(pass, salt) + .catch(function(e){reject({err: 'Failed to create proof!'})}); + }).catch(function(e){reject({err: 'Failed to create proof!'})}) + .then(function(proof){ // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - return SEA.read(at.put.auth, key).then(function(auth){ - return SEA.de(auth, proof); - }).catch(function(e){ + return SEA.read(at.put.auth, pub).then(function(auth){ + return SEA.de(auth, proof) + .catch(function(e){reject({err: 'Failed to decrypt secret!'})}); }).then(function(priv){ // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. // if we were successful, then that meanswe're logged in! return remaining ? undefined // Not done yet - : priv ? resolve({pub: key, priv: priv, at: at}) + : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) // Or else we failed to log in... : reject({err: 'Failed to decrypt private key!'}); - }); - }).catch(function(){reject({err: 'Failed to create proof!'})}); + }).catch(function(e){reject({err: 'Failed read secret!'})}); + }); }); - }); + }).catch(function(e){reject({err: e})}); }); }; + // This internal func finalizes User authentication + function finalizelogin(alias,key,root,opts){ + var user = root._.user; + // add our credentials in-memory only to our root gun instance + user._ = key.at.gun._; + // so that way we can use the credentials to encrypt/decrypt data + user._.is = user.is = {}; + // that is input/output through gun (see below) + user._.alias = alias; + user._.sea = key.priv; + user._.pub = key.pub; + //console.log("authorized", user._); + // persist authentication + return authpersist(user._, key.proof, opts) + .then(function(){ + // emit an auth event, useful for page redirects and stuff. + Gun.on('auth', user._); + // returns success with the user data credentials. + return user._; + }); + } + + // This internal func persists User authentication if so configured + function authpersist(user,proof,opts){ + // opts = { pin: 'string' } + // authsettings.session = true // disables PIN method + // TODO: how this works: + // called when app bootstraps, with wanted options + // IF authsettings.validity === 0 THEN no remember-me, ever + // IF authsettings.session === true THEN no window.localStorage in use; nor PIN + // ELSE if no PIN then window.sessionStorage + return new Promise(function(resolve, reject){ + var pin = Gun.obj.has(opts, 'pin') && opts.pin; + var doIt = function(props){ + if (props.alias) { + if (props.proof && props.iat) { + pin = pin && new Buffer(pin, 'utf8').toString('base64'); + var remember = (pin && {alias: props.alias, pin: pin }) || props; + var protected = !authsettings.session && pin && props; + + return SEA.write(JSON.stringify(remember), user.sea).then(function(signed){ + sessionStorage.setItem('user', props.alias); + sessionStorage.setItem('remember', signed); + if (!protected) { + localStorage.removeItem('remember'); + } + return !protected || SEA.en(protected, pin).then(function(encrypted){ + return encrypted && SEA.write(encrypted, user.sea) + .then(function(encsig){ + localStorage.setItem('remember', encsig); + }); + }); + }).then(function(){ + resolve({ok: 0}); + }).catch(function(){reject({err: 'Session persisting failed!'});}); + } else { + localStorage.removeItem('remember'); + sessionStorage.removeItem('user'); + sessionStorage.removeItem('remember'); + } + } + resolve({ok: 0}); + }; + var args = { alias: user.alias }; + + if(proof && authsettings.validity){ + args.proof = proof; + args.iat = Math.ceil(Date.now() / 1000); // seconds + args.exp = authsettings.validity * 60; // seconds + var props = authsettings.hook(args); + if(props instanceof Promise){props.then(doIt); + } else {doIt(props)} + } else { + doIt(args); + } + }); + } + + // This internal func recalls persisted User authentication if so configured + function authrecall(root){ + return new Promise(function(resolve, reject){ + var remember = sessionStorage.getItem('remember'); + var alias = sessionStorage.getItem('user'); + var err = 'Not authenticated'; + + // Already authenticated? + if(Gun.obj.has(root._.user._, 'pub')){ + return resolve(root._.user._.pub); + } + // No, got alias? + if (alias && remember){ + return querygunaliases(alias, root).then(function(aliases){ + return new Promise(function(resolve, reject){ + // then attempt to log into each one until we find ours! + // (if two users have the same username AND the same password... that would be bad) + aliases.forEach(function(one, index){ + var at = one.at, pub = one.pub; + var remaining = (aliases.length - index) > 1; + if(!at.put){ + return (!remaining) && reject({err: 'Public key does not exist!'}) + } + // got pub, time to unwrap Storage data... + return SEA.read(remember, pub).then(function(props){ + props = !props.slice ? props : JSON.parse(props); + // Got PIN ? + if(Gun.obj.has(props, 'pin')){ + // Yes! We can get localStorage secret if signature is ok + return SEA.read(localStorage.getItem('remember'), pub) + .then(function(encrypted){ + // And decrypt it + return SEA.de(encrypted, props.pin); + }).then(function(decr){ + decr = !decr.slice ? decr : JSON.parse(decr); + // And return proof if for matching alias + return Gun.obj.has(decr, 'proof') + && Gun.obj.has(decr, 'alias') && decr.alias === alias + && decr.proof; + }); + } + // No PIN, let's try short-term proof if for matching alias + return Gun.obj.has(props, 'proof') + && Gun.obj.has(props, 'alias') && props.alias === alias + && props.proof; + }).then(function(proof){ + if (!proof){return reject({err: 'No secret found!'})} + // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + return SEA.read(at.put.auth, pub).then(function(auth){ + return SEA.de(auth, proof) + .catch(function(e){reject({err: 'Failed to decrypt secret!'})}); + }).then(function(priv){ + // now we have AES decrypted the private key, + // if we were successful, then that means we're logged in! + return remaining ? undefined // Not done yet + : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) + // Or else we failed to log in... + : reject({err: 'Failed to decrypt private key!'}); + }).catch(function(e){reject({err: 'Failed read secret!'})}); + }).catch(function(e){ + reject({err: 'Failed to access stored credentials!'})}) + }); + }); + }).then(function(user){ + return finalizelogin(alias, user, root).then(resolve) + .catch(function(e){ + Gun.log('Failed to finalize login with new password!'); + reject({err: 'Finalizing new password login failed! Reason: '+(e && e.err) || e || ''}); + }); + }); + } + reject({err: 'No authentication session found!'}); + }); + } + + // This internal func executes logout actions + function authleave(root, alias){ + return function(resolve, reject){ + // remove persisted authentication + authpersist((alias && { alias: alias }) || root._.user._).then(function(){ + root._.user = root.chain(); + resolve({ok: 0}); + }); + }; + } + + // This internal func returns hashed data for signing + function nodehash(m){ + try{ + m = m.slice ? m : JSON.stringify(m); + var ret = nodeCrypto.createHash(nHash).update(m, 'utf8').digest(); + return ret; + }catch(e){return m} + } + // How does it work? - function User(){}; + function User(){} // Well first we have to actually create a user. That is what this function does. User.create = function(alias, pass, cb){ var root = this.back(-1); @@ -173,32 +361,19 @@ }); }); }; - if (cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else {return new Promise(doIt)} }; // now that we have created a user, we want to authenticate them! User.auth = function(alias,pass,cb,opt){ - var opts = opt || (typeof cb !== 'function' && cb) || {}; + var opts = opt || (typeof cb !== 'function' && cb); var root = this.back(-1); cb = typeof cb === 'function' && cb; + var doIt = function(resolve, reject){ authenticate(alias, pass, root).then(function(key){ // we're logged in! - function doLogin(){ - var user = root._.user; - // add our credentials in-memory only to our root gun instance - user._ = key.at.gun._; - // so that way we can use the credentials to encrypt/decrypt data - user._.is = user.is = {}; - // that is input/output through gun (see below) - user._.sea = key.priv; - user._.pub = key.pub; - //console.log("authorized", user._); - // callbacks success with the user data credentials. - resolve(user._); - // emit an auth event, useful for page redirects and stuff. - Gun.on('auth', user._); - } - if(opts.newpass) { + var pin = Gun.obj.has(opts, 'pin') && { pin: opts.pin }; + if(Gun.obj.has(opts, 'newpass')){ // password update so encrypt private key using new pwd + salt var newsalt = Gun.text.random(64); SEA.proof(opts.newpass, newsalt).then(function(proof){ @@ -212,44 +387,115 @@ }).then(function(user){ var tmp = 'pub/'+user.pub; // awesome, now we can update the user using public key ID. + root.get(tmp).put(null); root.get(tmp).put(user); // then we're done - doLogin(); + finalizelogin(alias, key, root, pin).then(resolve) + .catch(function(e){ + Gun.log('Failed to finalize login with new password!'); + reject({err: 'Finalizing new password login failed! Reason: '+(e && e.err) || e || ''}); + }); + }).catch(function(e){ + Gun.log('Failed encrypt private key using new password!'); + reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''}); }); + }).catch(function(e){ + Gun.log('Failed to set new password!'); + reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''}); }); } else { - doLogin(); + finalizelogin(alias, key, root, pin).then(resolve) + .catch(function(e){ + Gun.log('Failed to finalize login!'); + reject({err: 'Finalizing login failed! Reason: '+(e && e.err) || e || ''}); + }); } }).catch(function(e){ Gun.log('Failed to sign in!'); reject({err: 'Auth attempt failed! Reason: '+(e && e.err) || e || ''}); }); }; - if (cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else {return new Promise(doIt)} }; - // now that we authenticated a user, we want to support logout too! User.leave = function(cb){ var root = this.back(-1); - var doIt = function(resolve, reject){ - root._.user = root.chain(); - resolve({ok: 0}); - } - if (cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){authleave(root)(cb, cb)} else {return new Promise(authleave(root))} }; // If authenticated user wants to delete his/her account, let's support it! User.delete = function(alias,pass,cb){ var root = this.back(-1); var doIt = function(resolve, reject){ authenticate(alias, pass, root).then(function(key){ - root.get(key.pub).put(null); - root._.user = root.chain(); - resolve({ok: 0}); + new Promise(authleave(root, alias)).catch(function(){}) + .then(function(){ + root.get('pub/'+key.pub).put(null); + root._.user = root.chain(); + resolve({ok: 0}); + }).catch(function(e){ + Gun.log('User.delete failed! Error:', e); + reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''}); + }); }).catch(function(e){ - Gun.log('User.delete failed! Error:', e); + Gun.log('User.delete authentication failed! Error:', e); reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''}); }); + }; + if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + }; + // If authentication is to be remembered over reloads or browser closing, + // set validity time in seconds. + User.recall = function(validity,cb,opts){ + if(!opts){ + if(typeof cb !== 'function' && !Gun.val.is(cb)){ + opts = cb; + cb = undefined; + } } - if (cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(!cb){ + if(typeof validity === 'function'){ + cb = validity; + validity = undefined; + } else if(!Gun.val.is(validity)){ + opts = validity; + validity = undefined; + } + } + var doIt = function(resolve, reject){ + // opts = { hook: function({ iat, exp, alias, proof }), + // session: false } // true disables PIN requirement/support + // iat == Date.now() when issued, exp == seconds to expire from iat + // TODO: how this works: + // called when app bootstraps, with wanted options + // IF validity === 0 THEN no remember-me, ever + // IF opt.session === true THEN no window.localStorage in use; nor PIN + if(Gun.val.is(validity)){ + authsettings.validity = validity; + } + if(Gun.obj.has(opts, 'session')){ + authsettings.session = opts.session; + } + if(Gun.obj.has(opts, 'hook')){ + authsettings.hook = opt.hook; + } + // TODO: per authsettings, dig possibly existing auth data and + // call SEA.auth + resolve({ok: 0, pub: 'TBD'}) + }; + if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + }; + User.alive = function(cb){ + var root = this.back(-1); + var doIt = function(resolve, reject){ + authrecall(root).then(function(){ + // All is good. Should we do something more with actual recalled data? + resolve(root._.user._) + }).catch(function(e){ + var err = 'No session!'; + Gun.log(err); + reject({ err: err }); + }); + }; + if(cb){doIt(cb, cb)} else {return new Promise(doIt)} }; // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. @@ -353,7 +599,6 @@ }); } // (if we are lying about our signature, other peer's will reject our update) } - // TODO: this likely isn't working as expected SEA.read(val, tmp).then(function(data){ if(u === (val = data)){ // make sure the signature matches the account it claims to be on. return no = true; // reject any updates that are signed with a mismatched account. @@ -372,7 +617,6 @@ if(tmp = sea.own[soul]){ // not special case, if we receive an update on an ID associated with a public key, then Gun.obj.map(node, function(val, key){ // for each over the property/values if('_' === key){ return } - // TODO: this likely isn't working as expected SEA.read(val, tmp).then(function(data){ if(u === (val = data)){ // and verify they were signed by the associated public key! return no = true; // reject the update if it fails to match. @@ -433,7 +677,8 @@ }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); }) || function(resolve, reject){ // For NodeJS crypto.pkdf2 rocks nodeCrypto.pbkdf2(pass,new Buffer(salt, 'utf8'),pbkdf2.iter,pbkdf2.ks,nHash,function(err,hash){ - resolve(!err && hash && hash.toString('base64')); + if(err){return reject(e)} + resolve(hash && hash.toString('base64')); }); }; if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} @@ -459,10 +704,7 @@ }; SEA.sign = function(m,p,cb){ var doIt = function(resolve, reject){ - ecCrypto.sign( - new Buffer(p, 'hex'), - nodeCrypto.createHash(nHash).update(JSON.stringify(m), 'utf8').digest() - ).then(function(sig){ + ecCrypto.sign(new Buffer(p, 'hex'), nodehash(m)).then(function(sig){ resolve(new Buffer(sig, 'binary').toString('hex')); }).catch(function(e){Gun.log(e); reject(e)}); }; @@ -470,11 +712,9 @@ }; SEA.verify = function(m, p, s, cb){ var doIt = function(resolve, reject){ - ecCrypto.verify( - new Buffer(p, 'hex'), - nodeCrypto.createHash(nHash).update(JSON.stringify(m), 'utf8').digest(), - new Buffer(s, 'hex') - ).then(function(){resolve(true)}).catch(function(e){Gun.log(e);reject(e)}) + ecCrypto.verify(new Buffer(p, 'hex'), nodehash(m), new Buffer(s, 'hex')) + .then(function(){resolve(true)}) + .catch(function(e){Gun.log(e);reject(e)}) }; if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; @@ -484,12 +724,13 @@ var iv = nodeCrypto.randomBytes(16); var r = {iv: iv.toString('hex'), s: s.toString('hex')}; var key = makeKey(p, s); - if (typeof window !== 'undefined'){ // Browser doesn't run createCipheriv + m = (m.slice && m) || JSON.stringify(m); + if(typeof window !== 'undefined'){ // Browser doesn't run createCipheriv crypto.subtle.importKey('raw', key, 'AES-CBC', false, ['encrypt']) .then(function(aesKey){ crypto.subtle.encrypt({ name: 'AES-CBC', iv: iv - }, aesKey, new TextEncoder().encode(JSON.stringify(m))).then(function(ct){ + }, aesKey, new TextEncoder().encode(m)).then(function(ct){ r.ct = new Buffer(ct, 'binary').toString('base64'); return JSON.stringify(r); }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); @@ -507,7 +748,7 @@ }; SEA.de = function(m,p,cb){ var doIt = function(resolve, reject){ - var d = JSON.parse(m); + var d = !m.slice ? m : JSON.parse(m); var key = makeKey(p, new Buffer(d.s, 'hex')); var iv = new Buffer(d.iv, 'hex'); if (typeof window !== 'undefined'){ // Browser doesn't run createDecipheriv @@ -517,8 +758,7 @@ name: 'AES-CBC', iv: iv }, aesKey, new Buffer(d.ct, 'base64')).then(function(ct){ var ctUtf8 = new TextDecoder('utf8').decode(ct); - var ret = JSON.parse(ctUtf8); - return ret; + return !ctUtf8.slice ? ctUtf8 : JSON.parse(ctUtf8); }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); }).catch(function(e){Gun.log(e); reject(e)}); } else { // NodeJS doesn't support crypto.subtle.importKey properly @@ -533,11 +773,18 @@ }; SEA.write = function(mm,p,cb){ var doIt = function(resolve, reject) { - var m = mm; - if(!m.slice || 'SEA[' !== m.slice(0,4)){m = mm} - m = m.slice(3); - try{m = JSON.parse(m); - }catch(e){m = mm} + // TODO: something's bugging double 'SEA[]' treatment to mm... + var m; + if(!mm.slice){ + m = JSON.stringify(m); + } else if('SEA[' !== mm.slice(0,4)){ + m = mm; + } else { // Needs to remove previous signature envelope + try{m = JSON.parse(mm.slice(3))[0]; + }catch(e){m = mm} + while(m.slice){m = !m.slice ? m : JSON.parse(m)} + m = JSON.stringify(m); + } SEA.sign(m, p).then(function(signature){ resolve('SEA'+JSON.stringify([m,signature])); }).catch(function(e){Gun.log(e); reject(e)}); @@ -549,10 +796,11 @@ if(!m){ return resolve(); } if(!m.slice || 'SEA[' !== m.slice(0,4)){return resolve(m)} m = m.slice(3); - try{m = JSON.parse(m); - }catch(e){ return reject(e); } + try{m = !m.slice ? m : JSON.parse(m);}catch(e){return reject(e);} m = m || ''; - SEA.verify(m[0], p, m[1]).then(function(ok){resolve(ok && m[0])}); + SEA.verify(m[0], p, m[1]).then(function(ok){ + resolve(ok && m[0]) + }); }; if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; diff --git a/test/common.js b/test/common.js index 2027f2b6..8413a667 100644 --- a/test/common.js +++ b/test/common.js @@ -1,7 +1,16 @@ var root; (function(env){ root = env.window ? env.window : global; - env.window && root.localStorage && root.localStorage.clear(); + + if(!root.sessionStorage){ + root.sessionStorage = new require('node-localstorage').LocalStorage('session'); + } + root.sessionStorage.clear(); + if(!root.localStorage){ + root.localStorage = new require('node-localstorage').LocalStorage('local'); + } + root.localStorage.clear(); + try{ require('fs').unlinkSync('data.json') }catch(e){} //root.Gun = root.Gun || require('../gun'); if(root.Gun){ @@ -8084,6 +8093,14 @@ describe('Gun', function(){ }); describe('auth', function(){ + var checkStorage = function(done){ + return function(){ +console.log('auth remember:', root.sessionStorage.getItem('remember')) +console.log('auth protected:', root.localStorage.getItem('remember')) + done(); + }; + }; + it('login', function(done){ var check = function(ack){ try{ @@ -8181,7 +8198,18 @@ describe('Gun', function(){ } }); - it.skip('no recall no session storing'); + it('without PIN auth session stored to sessionStorage', function(done){ + user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done); + }); + + it('with PIN auth session stored to sessionStorage', function(done){ + if(type === 'callback'){ + user.auth(alias+type, pass+' new', checkStorage(done), {pin: 'PIN'}); + } else { + user.auth(alias+type, pass+' new', {pin: 'PIN'}) + .then(checkStorage(done)).catch(done); + } + }); }); describe('leave', function(){ @@ -8312,17 +8340,201 @@ describe('Gun', function(){ }); describe('recall', function(){ - it.skip('with zero validity auth skips storing'); - it.skip('with validity auth stores session'); - it.skip('valid session'); + var doCheck = function(done, hasPin){ + return function(){ + expect(root.sessionStorage.getItem('user')).to.not.be(undefined); + expect(root.sessionStorage.getItem('user')).to.not.be(''); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + if(hasPin){ + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + } + done(); + }; + }; + it('with PIN auth session stored to localStorage', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new', { pin: 'PIN' }) + .then(doCheck(done, true)).catch(done); + }; + if(type === 'callback'){ + user.recall(doAction, { session: false }); + } else { + user.recall({ session: false }).then(doAction).catch(done) + } + }); + + it('without PIN auth session stored to sessionStorage', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new').then(doCheck(done)); + }; + if(type === 'callback'){ + user.recall(doAction, { session: false }); + } else { + user.recall({ session: false }).then(doAction).catch(done) + } + }); + + it('no validity no session storing', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); + }; + if(type === 'callback'){ + user.recall(0, doAction); + } else { + user.recall(0).then(doAction).catch(done); + } + }); + + it('validity but no PIN stored to sessionStorage', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); + }; + if(type === 'callback'){ + user.recall(12 * 60 * 60, doAction, {session: false}); + } else { + user.recall(12 * 60 * 60, {session: false}).then(doAction) + .catch(done); + } + }); + + it('valid sessionStorage session', function(done){ + var check = function(ack){ + // TODO: check + done(); + }; + user.auth(alias+type, pass+' new').then(function(usr){ + var sUser; + var sRemember; + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + expect(root.sessionStorage.getItem('user')).to.be(alias+type); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + + sUser = root.sessionStorage.getItem('user'); + sRemember = root.sessionStorage.getItem('remember'); + }catch(e){done(e); return}; + user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(root.sessionStorage.getItem('user')).to.not.be(sUser); + expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); + }catch(e){done(e); return}; + + root.sessionStorage.setItem('user', sUser); + root.sessionStorage.setItem('remember', sRemember); + + user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) + .catch(done); + }).catch(done); + }).catch(done); + }); + + it('valid localStorage session', function(done){ + var check = function(ack){ + // TODO: check + done(); + }; + user.auth(alias+type, pass+' new', { pin: 'PIN' }).then(function(usr){ + var sUser; + var sRemember; + var lRemember; + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + expect(root.sessionStorage.getItem('user')).to.be(alias+type); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + + sUser = root.sessionStorage.getItem('user'); + sRemember = root.sessionStorage.getItem('remember'); + lRemember = root.localStorage.getItem('remember'); + }catch(e){done(e); return}; + user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(root.sessionStorage.getItem('user')).to.not.be(sUser); + expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); + expect(root.localStorage.getItem('remember')).to.not.be(lRemember); + }catch(e){done(e); return}; + + root.sessionStorage.setItem('user', sUser); + root.sessionStorage.setItem('remember', sRemember); + root.localStorage.setItem('remember', lRemember); + + user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) + .catch(done); + }).catch(done); + }).catch(done); + }); + + it.skip('invalid sessionStorage session'); it.skip('expired session'); it.skip('changed password'); it.skip('no session'); }); describe('alive', function(){ - it.skip('valid session'); - it.skip('expired session'); + it('valid session', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.keys(['sea', 'pub']); + }catch(e){done(e); return}; + done(); + }; + var usr = alias+type+'alive'; + user.create(usr, pass).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + user.auth(usr, pass, { pin: 'PIN' }).then(function(usr){ + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){done(e); return}; + // Gun.user.alive - keeps/checks User authentiation state + if(type === 'callback'){ + user.alive(check); + } else { + user.alive().then(check).catch(done); + } + }).catch(done); + }).catch(done); + }); + + it('expired session', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.keys(['sea', 'pub']); + expect(ack).to.have.key('err'); + }catch(e){done(e); return}; + done(); + }; + user.leave().catch(function(){}).then(function(){ + user.alive().then(function(){ + done('Unexpected alive session!'); + }).catch(check); + }).catch(done); + }); + it.skip('recall hook session manipulation'); }); }); From 49e76826d80ae85eb98cd31a488db49449e541ab Mon Sep 17 00:00:00 2001 From: mhelander Date: Mon, 4 Sep 2017 11:15:06 +0300 Subject: [PATCH 06/29] 'remember-me' now fully functional with essential test cases --- sea.js | 212 ++++++++++++++++++++++++++++++------------------- test/common.js | 84 +++++++++++++++----- 2 files changed, 193 insertions(+), 103 deletions(-) diff --git a/sea.js b/sea.js index 4d98aede..aedf2bd2 100644 --- a/sea.js +++ b/sea.js @@ -51,7 +51,7 @@ validity: 60 * 60 * 12, // 12 hours session: true, // or return new Promise(function(resolve, reject){(resolve(props))}) - hook: function(props) {return props} // { iat, exp, alias, proof } + hook: function(props) {return props} // { iat, exp, alias, remember } }; // let's extend the gun chain with a `user` function. @@ -115,7 +115,7 @@ var at = one.at, pub = one.pub; var remaining = (aliases.length - index) > 1; if(!at.put){ - return (!remaining) && reject({err: 'Public key does not exist!'}) + return (!remaining) && reject({err: 'Public key does not exist!'}); } // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) SEA.read(at.put.salt, pub).then(function(salt){ @@ -124,16 +124,21 @@ }).catch(function(e){reject({err: 'Failed to create proof!'})}) .then(function(proof){ // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - return SEA.read(at.put.auth, pub).then(function(auth){ + SEA.read(at.put.auth, pub).then(function(auth){ return SEA.de(auth, proof) .catch(function(e){reject({err: 'Failed to decrypt secret!'})}); }).then(function(priv){ // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. // if we were successful, then that meanswe're logged in! - return remaining ? undefined // Not done yet - : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) - // Or else we failed to log in... - : reject({err: 'Failed to decrypt private key!'}); + if(priv){ + resolve({pub: pub, priv: priv, at: at, proof: proof}) + } else if(!remaining){ + reject({err: 'Public key does not exist!'}); + } + // return remaining ? undefined // Not done yet + // : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) + // // Or else we failed to log in... + // : reject({err: 'Failed to decrypt private key!'}); }).catch(function(e){reject({err: 'Failed read secret!'})}); }); }); @@ -154,8 +159,7 @@ user._.pub = key.pub; //console.log("authorized", user._); // persist authentication - return authpersist(user._, key.proof, opts) - .then(function(){ + return authpersist(user._, key.proof, opts).then(function(){ // emit an auth event, useful for page redirects and stuff. Gun.on('auth', user._); // returns success with the user data credentials. @@ -163,6 +167,42 @@ }); } + function updatestorage(proof,priv,pin){ + return function(props){ + return new Promise(function(resolve, reject){ + if(!Gun.obj.has(props, 'alias')){return resolve()} + if (proof && Gun.obj.has(props, 'iat')) { + props.proof = proof; + delete props.remember; // Not stored if present + + var remember = (pin && {alias: props.alias, pin: pin }) || props; + var protected = !authsettings.session && pin && props; + + return SEA.write(JSON.stringify(remember), priv).then(function(signed){ + sessionStorage.setItem('user', props.alias); + sessionStorage.setItem('remember', signed); + if (!protected) { + localStorage.removeItem('remember'); + } + return !protected || SEA.en(protected, pin).then(function(encrypted){ + return encrypted && SEA.write(encrypted, priv) + .then(function(encsig){ + localStorage.setItem('remember', encsig); + }).catch(reject); + }).catch(reject); + }).then(function(){ + resolve(props); + }).catch(function(e){reject({err: 'Session persisting failed!'})}); + } else { + localStorage.removeItem('remember'); + sessionStorage.removeItem('user'); + sessionStorage.removeItem('remember'); + } + resolve(props); + }); + } + } + // This internal func persists User authentication if so configured function authpersist(user,proof,opts){ // opts = { pin: 'string' } @@ -172,51 +212,25 @@ // IF authsettings.validity === 0 THEN no remember-me, ever // IF authsettings.session === true THEN no window.localStorage in use; nor PIN // ELSE if no PIN then window.sessionStorage - return new Promise(function(resolve, reject){ - var pin = Gun.obj.has(opts, 'pin') && opts.pin; - var doIt = function(props){ - if (props.alias) { - if (props.proof && props.iat) { - pin = pin && new Buffer(pin, 'utf8').toString('base64'); - var remember = (pin && {alias: props.alias, pin: pin }) || props; - var protected = !authsettings.session && pin && props; + var pin = Gun.obj.has(opts, 'pin') && opts.pin + && new Buffer(opts.pin, 'utf8').toString('base64'); + var args = { alias: user.alias }; - return SEA.write(JSON.stringify(remember), user.sea).then(function(signed){ - sessionStorage.setItem('user', props.alias); - sessionStorage.setItem('remember', signed); - if (!protected) { - localStorage.removeItem('remember'); - } - return !protected || SEA.en(protected, pin).then(function(encrypted){ - return encrypted && SEA.write(encrypted, user.sea) - .then(function(encsig){ - localStorage.setItem('remember', encsig); - }); - }); - }).then(function(){ - resolve({ok: 0}); - }).catch(function(){reject({err: 'Session persisting failed!'});}); - } else { - localStorage.removeItem('remember'); - sessionStorage.removeItem('user'); - sessionStorage.removeItem('remember'); - } - } - resolve({ok: 0}); - }; - var args = { alias: user.alias }; - - if(proof && authsettings.validity){ - args.proof = proof; - args.iat = Math.ceil(Date.now() / 1000); // seconds - args.exp = authsettings.validity * 60; // seconds - var props = authsettings.hook(args); - if(props instanceof Promise){props.then(doIt); - } else {doIt(props)} - } else { - doIt(args); + if(proof && authsettings.validity){ + args.iat = Math.ceil(Date.now() / 1000); // seconds + args.exp = authsettings.validity * 60; // seconds + if (Gun.obj.has(opts, 'pin')){ + args.remember = true; // for hook - not stored } - }); + var props = authsettings.hook(args); + if(props instanceof Promise){ + return props.then(updatestorage(proof, user.sea, pin)); + } else { + return updatestorage(proof, user.sea, pin)(props); + } + } else { + return updatestorage()(args); + } } // This internal func recalls persisted User authentication if so configured @@ -225,6 +239,7 @@ var remember = sessionStorage.getItem('remember'); var alias = sessionStorage.getItem('user'); var err = 'Not authenticated'; + var pin; // Already authenticated? if(Gun.obj.has(root._.user._, 'pub')){ @@ -243,29 +258,55 @@ return (!remaining) && reject({err: 'Public key does not exist!'}) } // got pub, time to unwrap Storage data... - return SEA.read(remember, pub).then(function(props){ + return SEA.read(remember, pub, true).then(function(props){ props = !props.slice ? props : JSON.parse(props); + var checkProps = function(decr){ + return new Promise(function(resolve){ + if(Gun.obj.has(decr, 'proof') + && Gun.obj.has(decr, 'alias') && decr.alias === alias){ + var proof = decr.proof; + var iat = decr.iat; // No way hook to update this + delete decr.proof; // We're not gonna give proof to hook! + var doIt = function(args){ + if(Math.floor(Date.now() / 1000) < (iat + args.exp)){ + args.iat = iat; + args.proof = proof; + return args; + } else {Gun.log('Authentication expired!')} + }; + var hooked = authsettings.hook(decr); + return resolve(((hooked instanceof Promise) + && hooked.then(doIt)) + || doIt(decr)); + } + resolve(); + }); + }; // Got PIN ? if(Gun.obj.has(props, 'pin')){ + pin = props.pin; // Yes! We can get localStorage secret if signature is ok return SEA.read(localStorage.getItem('remember'), pub) .then(function(encrypted){ // And decrypt it - return SEA.de(encrypted, props.pin); + return SEA.de(encrypted, pin); }).then(function(decr){ decr = !decr.slice ? decr : JSON.parse(decr); // And return proof if for matching alias - return Gun.obj.has(decr, 'proof') - && Gun.obj.has(decr, 'alias') && decr.alias === alias - && decr.proof; + return checkProps(decr); }); } // No PIN, let's try short-term proof if for matching alias - return Gun.obj.has(props, 'proof') - && Gun.obj.has(props, 'alias') && props.alias === alias - && props.proof; - }).then(function(proof){ - if (!proof){return reject({err: 'No secret found!'})} + return checkProps(props); + }).then(function(args){ + var proof = args && args.proof; + if (!proof){ + return updatestorage()(args).then(function(){ + reject({err: 'No secret found!'}); + }).catch(function(){ + reject({err: 'No secret found!'}); + }); + } // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. return SEA.read(at.put.auth, pub).then(function(auth){ return SEA.de(auth, proof) @@ -273,21 +314,24 @@ }).then(function(priv){ // now we have AES decrypted the private key, // if we were successful, then that means we're logged in! - return remaining ? undefined // Not done yet - : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) - // Or else we failed to log in... - : reject({err: 'Failed to decrypt private key!'}); + return updatestorage(proof, priv, pin)(args).then(function(){ + return remaining ? undefined // Not done yet + : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) + // Or else we failed to log in... + : reject({err: 'Failed to decrypt private key!'}); + }).catch(function(e){reject({err: 'Failed to store credentials!'})}); }).catch(function(e){reject({err: 'Failed read secret!'})}); }).catch(function(e){ reject({err: 'Failed to access stored credentials!'})}) }); }); }).then(function(user){ - return finalizelogin(alias, user, root).then(resolve) - .catch(function(e){ + finalizelogin(alias, user, root).then(resolve).catch(function(e){ Gun.log('Failed to finalize login with new password!'); reject({err: 'Finalizing new password login failed! Reason: '+(e && e.err) || e || ''}); }); + }).catch(function(e){ + reject({err: 'No authentication session found!'}); }); } reject({err: 'No authentication session found!'}); @@ -390,8 +434,7 @@ root.get(tmp).put(null); root.get(tmp).put(user); // then we're done - finalizelogin(alias, key, root, pin).then(resolve) - .catch(function(e){ + finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){ Gun.log('Failed to finalize login with new password!'); reject({err: 'Finalizing new password login failed! Reason: '+(e && e.err) || e || ''}); }); @@ -404,8 +447,7 @@ reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''}); }); } else { - finalizelogin(alias, key, root, pin).then(resolve) - .catch(function(e){ + finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){ Gun.log('Failed to finalize login!'); reject({err: 'Finalizing login failed! Reason: '+(e && e.err) || e || ''}); }); @@ -445,6 +487,7 @@ // If authentication is to be remembered over reloads or browser closing, // set validity time in seconds. User.recall = function(validity,cb,opts){ + var root = this.back(-1); if(!opts){ if(typeof cb !== 'function' && !Gun.val.is(cb)){ opts = cb; @@ -477,9 +520,14 @@ if(Gun.obj.has(opts, 'hook')){ authsettings.hook = opt.hook; } - // TODO: per authsettings, dig possibly existing auth data and - // call SEA.auth - resolve({ok: 0, pub: 'TBD'}) + authrecall(root).then(function(props){ + // All is good. Should we do something more with actual recalled data? + resolve(root._.user._) + }).catch(function(e){ + var err = 'No session!'; + Gun.log(err); + resolve({ err: err }); + }); }; if(cb){doIt(cb, cb)} else {return new Promise(doIt)} }; @@ -676,10 +724,10 @@ return new Buffer(result, 'binary').toString('base64'); }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); }) || function(resolve, reject){ // For NodeJS crypto.pkdf2 rocks - nodeCrypto.pbkdf2(pass,new Buffer(salt, 'utf8'),pbkdf2.iter,pbkdf2.ks,nHash,function(err,hash){ - if(err){return reject(e)} + try{ + var hash = nodeCrypto.pbkdf2Sync(pass,new Buffer(salt, 'utf8'),pbkdf2.iter,pbkdf2.ks,nHash); resolve(hash && hash.toString('base64')); - }); + }catch(e){reject(e)}; }; if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; @@ -793,16 +841,16 @@ }; SEA.read = function(m,p,cb){ var doIt = function(resolve, reject) { - if(!m){ return resolve(); } + if(!m){return resolve()} if(!m.slice || 'SEA[' !== m.slice(0,4)){return resolve(m)} m = m.slice(3); - try{m = !m.slice ? m : JSON.parse(m);}catch(e){return reject(e);} + try{m = !m.slice ? m : JSON.parse(m);}catch(e){return reject(e)} m = m || ''; SEA.verify(m[0], p, m[1]).then(function(ok){ resolve(ok && m[0]) - }); + }).catch(function(e){reject(e)}); }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb && typeof cb === 'function'){doIt(cb, function(){cb()})} else {return new Promise(doIt)} }; Gun.SEA = SEA; diff --git a/test/common.js b/test/common.js index 8413a667..508bd2d6 100644 --- a/test/common.js +++ b/test/common.js @@ -8093,10 +8093,16 @@ describe('Gun', function(){ }); describe('auth', function(){ - var checkStorage = function(done){ + var checkStorage = function(done, hasPin){ return function(){ -console.log('auth remember:', root.sessionStorage.getItem('remember')) -console.log('auth protected:', root.localStorage.getItem('remember')) + expect(root.sessionStorage.getItem('user')).to.not.be(undefined); + expect(root.sessionStorage.getItem('user')).to.not.be(''); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + if(hasPin){ + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + } done(); }; }; @@ -8204,10 +8210,10 @@ console.log('auth protected:', root.localStorage.getItem('remember')) it('with PIN auth session stored to sessionStorage', function(done){ if(type === 'callback'){ - user.auth(alias+type, pass+' new', checkStorage(done), {pin: 'PIN'}); + user.auth(alias+type, pass+' new', checkStorage(done, true), {pin: 'PIN'}); } else { user.auth(alias+type, pass+' new', {pin: 'PIN'}) - .then(checkStorage(done)).catch(done); + .then(checkStorage(done, true)).catch(done); } }); }); @@ -8350,9 +8356,25 @@ console.log('auth protected:', root.localStorage.getItem('remember')) expect(root.localStorage.getItem('remember')).to.not.be(undefined); expect(root.localStorage.getItem('remember')).to.not.be(''); } - done(); + return done(); }; }; + // This re-constructs 'remember-me' data modified by manipulate func + var manipulateStorage = function(manipulate, hasPin){ + var usr = gun.back(-1)._.user; + var remember = hasPin ? localStorage.getItem('remember') + : sessionStorage.getItem('remember'); + return Gun.SEA.read(remember, usr._.pub).then(function(props){ + props = manipulate(JSON.parse(props)); + return Gun.SEA.write(JSON.stringify(props), usr._.sea) + .then(function(remember){ + // remember = JSON.stringify(remember); + return hasPin ? sessionStorage.setItem('remember', remember) + : sessionStorage.setItem('remember', remember); + }); + }); + }; + it('with PIN auth session stored to localStorage', function(done){ var doAction = function(){ user.auth(alias+type, pass+' new', { pin: 'PIN' }) @@ -8369,11 +8391,13 @@ console.log('auth protected:', root.localStorage.getItem('remember')) var doAction = function(){ user.auth(alias+type, pass+' new').then(doCheck(done)); }; - if(type === 'callback'){ - user.recall(doAction, { session: false }); - } else { - user.recall({ session: false }).then(doAction).catch(done) - } + user.leave().then(function(){ + if(type === 'callback'){ + user.recall(doAction, { session: false }); + } else { + user.recall({ session: false }).then(doAction).catch(done) + } + }).catch(done); }); it('no validity no session storing', function(done){ @@ -8400,10 +8424,6 @@ console.log('auth protected:', root.localStorage.getItem('remember')) }); it('valid sessionStorage session', function(done){ - var check = function(ack){ - // TODO: check - done(); - }; user.auth(alias+type, pass+' new').then(function(usr){ var sUser; var sRemember; @@ -8436,11 +8456,7 @@ console.log('auth protected:', root.localStorage.getItem('remember')) }).catch(done); }); - it('valid localStorage session', function(done){ - var check = function(ack){ - // TODO: check - done(); - }; + it('valid localStorage session bootstrap', function(done){ user.auth(alias+type, pass+' new', { pin: 'PIN' }).then(function(usr){ var sUser; var sRemember; @@ -8460,6 +8476,7 @@ console.log('auth protected:', root.localStorage.getItem('remember')) sRemember = root.sessionStorage.getItem('remember'); lRemember = root.localStorage.getItem('remember'); }catch(e){done(e); return}; + user.leave().then(function(ack){ try{ expect(ack).to.have.key('ok'); @@ -8480,7 +8497,32 @@ console.log('auth protected:', root.localStorage.getItem('remember')) }); it.skip('invalid sessionStorage session'); - it.skip('expired session'); + it.skip('valid localStorage data but not in sessionStorage'); + + it('expired session', function(done){ + user.recall(60, {session: true}).then(function(){ + return user.auth(alias+type, pass+' new'); + }).then(doCheck(function(){ + // Storage data OK, let's back up time of auth 65 minutes + return manipulateStorage(function(props){ + props.iat -= 65 * 60; + return props; + }, false); + })).then(function(){ + // Simulate browser reload + gun.back(-1)._.user = gun.back(-1).chain(); + // TODO: re-make sessionStorage.remember to 65 seconds past + user.recall(60, {session: true}).then(function(props){ + expect(props).to.not.be(undefined); + expect(props).to.not.be(''); + expect(props).to.have.key('err'); + expect(props.err).to.not.be(undefined); + expect(props.err).to.not.be(''); + done(); + }).catch(done); + }).catch(done); + }); + it.skip('changed password'); it.skip('no session'); }); From 276f2a04e78a03d7e29708280d283847a94021f7 Mon Sep 17 00:00:00 2001 From: mhelander Date: Mon, 4 Sep 2017 20:21:22 +0300 Subject: [PATCH 07/29] Finally all required tests are in place and SEA functional with 'remember-me' --- sea.js | 187 +++++++++++++++++++++++++------------------------ test/common.js | 106 ++++++++++++++++++++++------ 2 files changed, 180 insertions(+), 113 deletions(-) diff --git a/sea.js b/sea.js index aedf2bd2..116f09f7 100644 --- a/sea.js +++ b/sea.js @@ -50,8 +50,8 @@ var authsettings = { validity: 60 * 60 * 12, // 12 hours session: true, + hook: function(props) { return props } // { iat, exp, alias, remember } // or return new Promise(function(resolve, reject){(resolve(props))}) - hook: function(props) {return props} // { iat, exp, alias, remember } }; // let's extend the gun chain with a `user` function. @@ -91,10 +91,10 @@ Gun.obj.map(rat.put, function(at, pub){ // grab the account associated with this public key. root.get(pub).get(function(at, ev){ - if(!pub.slice || 'pub/' !== pub.slice(0,4)){return} + if(!pub.slice || 'pub/' !== pub.slice(0,4)){ return } pub = pub.slice(4); ev.off(); - if(!at.put){return} + if(!at.put){ return } aliases.push({pub: pub, at: at}); }); }); @@ -115,18 +115,18 @@ var at = one.at, pub = one.pub; var remaining = (aliases.length - index) > 1; if(!at.put){ - return (!remaining) && reject({err: 'Public key does not exist!'}); + return !remaining && reject({err: 'Public key does not exist!'}); } // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) SEA.read(at.put.salt, pub).then(function(salt){ return SEA.proof(pass, salt) - .catch(function(e){reject({err: 'Failed to create proof!'})}); - }).catch(function(e){reject({err: 'Failed to create proof!'})}) + .catch(function(e){ reject({err: 'Failed to create proof!'}) }); + }).catch(function(e){ reject({err: 'Failed to create proof!'}) }) .then(function(proof){ // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. SEA.read(at.put.auth, pub).then(function(auth){ return SEA.de(auth, proof) - .catch(function(e){reject({err: 'Failed to decrypt secret!'})}); + .catch(function(e){ reject({err: 'Failed to decrypt secret!'}) }); }).then(function(priv){ // now we have AES decrypted the private key, from when we encrypted it with the proof at registration. // if we were successful, then that meanswe're logged in! @@ -139,10 +139,10 @@ // : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) // // Or else we failed to log in... // : reject({err: 'Failed to decrypt private key!'}); - }).catch(function(e){reject({err: 'Failed read secret!'})}); + }).catch(function(e){ reject({err: 'Failed read secret!'})} ); }); }); - }).catch(function(e){reject({err: e})}); + }).catch(function(e){ reject({err: e}) }); }); }; @@ -170,7 +170,7 @@ function updatestorage(proof,priv,pin){ return function(props){ return new Promise(function(resolve, reject){ - if(!Gun.obj.has(props, 'alias')){return resolve()} + if(!Gun.obj.has(props, 'alias')){ return resolve() } if (proof && Gun.obj.has(props, 'iat')) { props.proof = proof; delete props.remember; // Not stored if present @@ -192,7 +192,7 @@ }).catch(reject); }).then(function(){ resolve(props); - }).catch(function(e){reject({err: 'Session persisting failed!'})}); + }).catch(function(e){ reject({err: 'Session persisting failed!'}) }); } else { localStorage.removeItem('remember'); sessionStorage.removeItem('user'); @@ -255,7 +255,7 @@ var at = one.at, pub = one.pub; var remaining = (aliases.length - index) > 1; if(!at.put){ - return (!remaining) && reject({err: 'Public key does not exist!'}) + return !remaining && reject({err: 'Public key does not exist!'}) } // got pub, time to unwrap Storage data... return SEA.read(remember, pub, true).then(function(props){ @@ -272,7 +272,7 @@ args.iat = iat; args.proof = proof; return args; - } else {Gun.log('Authentication expired!')} + } else { Gun.log('Authentication expired!') } }; var hooked = authsettings.hook(decr); return resolve(((hooked instanceof Promise) @@ -310,7 +310,7 @@ // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. return SEA.read(at.put.auth, pub).then(function(auth){ return SEA.de(auth, proof) - .catch(function(e){reject({err: 'Failed to decrypt secret!'})}); + .catch(function(e){ reject({err: 'Failed to decrypt secret!'}) }); }).then(function(priv){ // now we have AES decrypted the private key, // if we were successful, then that means we're logged in! @@ -319,10 +319,9 @@ : priv ? resolve({pub: pub, priv: priv, at: at, proof: proof}) // Or else we failed to log in... : reject({err: 'Failed to decrypt private key!'}); - }).catch(function(e){reject({err: 'Failed to store credentials!'})}); - }).catch(function(e){reject({err: 'Failed read secret!'})}); - }).catch(function(e){ - reject({err: 'Failed to access stored credentials!'})}) + }).catch(function(e){ reject({err: 'Failed to store credentials!'}) }); + }).catch(function(e){ reject({err: 'Failed read secret!'}) }); + }).catch(function(e){ reject({err: 'Failed to access stored credentials!'}) }) }); }); }).then(function(user){ @@ -355,7 +354,7 @@ m = m.slice ? m : JSON.stringify(m); var ret = nodeCrypto.createHash(nHash).update(m, 'utf8').digest(); return ret; - }catch(e){return m} + }catch(e){ return m } } // How does it work? @@ -373,25 +372,25 @@ Gun.log(err); return reject({err: err}); } - var user = {alias: alias, salt: Gun.text.random(64)}; + var salt = Gun.text.random(64); // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it. - SEA.proof(pass, user.salt).then(function(proof){ + SEA.proof(pass, salt).then(function(proof){ // this will take some short amount of time to produce a proof, which slows brute force attacks. SEA.pair().then(function(pair){ // now we have generated a brand new ECDSA key pair for the user account. - user.pub = pair.pub; + var user = { pub: pair.pub, priv: pair.priv }; // the user's public key doesn't need to be signed. But everything else needs to be signed with it! - SEA.write(alias, pair.priv).then(function(sAlias){ - user.alias = sAlias; - return SEA.write(user.salt, pair.priv); - }).then(function(sSalt){ - user.salt = sSalt; + SEA.write(alias, pair.priv).then(function(signedalias){ + user.alias = signedalias; + return SEA.write(salt, pair.priv); + }).then(function(signedsalt){ + user.salt = signedsalt; // to keep the private key safe, we AES encrypt it with the proof of work! return SEA.en(pair.priv, proof); - }).then(function(encVal){ - return SEA.write(encVal, pair.priv); - }).then(function(sAuth){ - user.auth = sAuth; + }).then(function(encryptedpriv){ + return SEA.write(encryptedpriv, pair.priv); + }).then(function(encsigauth){ + user.auth = encsigauth; var tmp = 'pub/'+pair.pub; //console.log("create", user, pair.pub); // awesome, now we can actually save the user with their public key as their ID. @@ -400,12 +399,12 @@ var ref = root.get('alias/'+alias).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp))); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) resolve({ok: 0, pub: pair.pub}); - }); - }); + }).catch(function(e){ Gun.log('SEA.en or SEA.write calls failed!'); reject(e) }); + }).catch(function(e){ Gun.log('SEA.pair call failed!'); reject(e) }); }); }); }; - if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else { return new Promise(doIt) } }; // now that we have created a user, we want to authenticate them! User.auth = function(alias,pass,cb,opt){ @@ -420,30 +419,34 @@ if(Gun.obj.has(opts, 'newpass')){ // password update so encrypt private key using new pwd + salt var newsalt = Gun.text.random(64); - SEA.proof(opts.newpass, newsalt).then(function(proof){ - SEA.en(key.priv, proof).then(function(encVal){ - return { - alias: alias, - pub: key.pub, - auth: encVal, - salt: newsalt - }; - }).then(function(user){ - var tmp = 'pub/'+user.pub; - // awesome, now we can update the user using public key ID. - root.get(tmp).put(null); - root.get(tmp).put(user); - // then we're done - finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){ - Gun.log('Failed to finalize login with new password!'); - reject({err: 'Finalizing new password login failed! Reason: '+(e && e.err) || e || ''}); + SEA.proof(opts.newpass, newsalt).then(function(newproof){ + return SEA.en(key.priv, newproof).then(function(encryptedpriv){ + return SEA.write(encryptedpriv, key.priv); + }); + }).then(function(encsigauth){ + return SEA.write(newsalt, key.priv).then(function(signedsalt){ + return SEA.write(alias, key.priv).then(function(signedalias){ + return { + alias: signedalias, + salt: signedsalt, + auth: encsigauth, + pub: key.pub + }; }); - }).catch(function(e){ - Gun.log('Failed encrypt private key using new password!'); - reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''}); + }); + }).then(function(user){ + var tmp = 'pub/'+user.pub; + // awesome, now we can update the user using public key ID. + // root.get(tmp).put(null); + root.get(tmp).put(user); + + // then we're done + finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){ + Gun.log('Failed to finalize login with new password!'); + reject({err: 'Finalizing new password login failed! Reason: '+(e && e.err) || e || ''}); }); }).catch(function(e){ - Gun.log('Failed to set new password!'); + Gun.log('Failed encrypt private key using new password!'); reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''}); }); } else { @@ -457,11 +460,11 @@ reject({err: 'Auth attempt failed! Reason: '+(e && e.err) || e || ''}); }); }; - if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else { return new Promise(doIt) } }; User.leave = function(cb){ var root = this.back(-1); - if(cb){authleave(root)(cb, cb)} else {return new Promise(authleave(root))} + if(cb){authleave(root)(cb, cb)} else { return new Promise(authleave(root)) } }; // If authenticated user wants to delete his/her account, let's support it! User.delete = function(alias,pass,cb){ @@ -482,7 +485,7 @@ reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''}); }); }; - if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else { return new Promise(doIt) } }; // If authentication is to be remembered over reloads or browser closing, // set validity time in seconds. @@ -529,7 +532,7 @@ resolve({ err: err }); }); }; - if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else { return new Promise(doIt) } }; User.alive = function(cb){ var root = this.back(-1); @@ -543,7 +546,7 @@ reject({ err: err }); }); }; - if(cb){doIt(cb, cb)} else {return new Promise(doIt)} + if(cb){doIt(cb, cb)} else { return new Promise(doIt) } }; // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. @@ -582,7 +585,7 @@ } }); }); - }) + }); // signature handles data output, it is a proxy to the security function. function signature(at){ @@ -722,14 +725,14 @@ }, key, pbkdf2.ks*8); }).then(function(result){ return new Buffer(result, 'binary').toString('base64'); - }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); + }).then(resolve).catch(function(e){ Gun.log(e); reject(e) }); }) || function(resolve, reject){ // For NodeJS crypto.pkdf2 rocks try{ var hash = nodeCrypto.pbkdf2Sync(pass,new Buffer(salt, 'utf8'),pbkdf2.iter,pbkdf2.ks,nHash); resolve(hash && hash.toString('base64')); - }catch(e){reject(e)}; + }catch(e){ reject(e) }; }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.pair = function(cb){ var doIt = function(resolve, reject){ @@ -739,16 +742,16 @@ priv: new Buffer(priv, 'binary').toString('hex') }); }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.derive = function(m,p,cb){ var doIt = function(resolve, reject){ ecCrypto.derive(new Buffer(p, 'hex'), new Buffer(m, 'hex')) .then(function(secret){ resolve(new Buffer(secret, 'binary').toString('hex')); - }).catch(function(e){Gun.log(e); reject(e)}); + }).catch(function(e){ Gun.log(e); reject(e) }); }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.sign = function(m,p,cb){ var doIt = function(resolve, reject){ @@ -756,15 +759,15 @@ resolve(new Buffer(sig, 'binary').toString('hex')); }).catch(function(e){Gun.log(e); reject(e)}); }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.verify = function(m, p, s, cb){ var doIt = function(resolve, reject){ ecCrypto.verify(new Buffer(p, 'hex'), nodehash(m), new Buffer(s, 'hex')) .then(function(){resolve(true)}) - .catch(function(e){Gun.log(e);reject(e)}) + .catch(function(e){ Gun.log(e);reject(e) }) }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.en = function(m,p,cb){ var doIt = function(resolve, reject){ @@ -781,25 +784,25 @@ }, aesKey, new TextEncoder().encode(m)).then(function(ct){ r.ct = new Buffer(ct, 'binary').toString('base64'); return JSON.stringify(r); - }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); - }).catch(function(e){Gun.log(e); reject(e)}); + }).then(resolve).catch(function(e){ Gun.log(e); reject(e) }); + }).catch(function(e){ Gun.log(e); reject(e)} ); } else { // NodeJS doesn't support crypto.subtle.importKey properly try{ var cipher = nodeCrypto.createCipheriv(aes.enc, key, iv); r.ct = cipher.update(m, 'utf8', 'base64'); r.ct += cipher.final('base64'); - }catch(e){Gun.log(e); return reject(e)} + }catch(e){ Gun.log(e); return reject(e) } resolve(JSON.stringify(r)); } }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.de = function(m,p,cb){ var doIt = function(resolve, reject){ var d = !m.slice ? m : JSON.parse(m); var key = makeKey(p, new Buffer(d.s, 'hex')); var iv = new Buffer(d.iv, 'hex'); - if (typeof window !== 'undefined'){ // Browser doesn't run createDecipheriv + if(typeof window !== 'undefined'){ // Browser doesn't run createDecipheriv crypto.subtle.importKey('raw', key, 'AES-CBC', false, ['decrypt']) .then(function(aesKey){ crypto.subtle.decrypt({ @@ -813,44 +816,44 @@ try{ var decipher = nodeCrypto.createDecipheriv(aes.enc, key, iv); r = decipher.update(d.ct, 'base64', 'utf8') + decipher.final('utf8'); - }catch(e){Gun.log(e); return reject(e)} + }catch(e){ Gun.log(e); return reject(e) } resolve(r); } }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.write = function(mm,p,cb){ var doIt = function(resolve, reject) { // TODO: something's bugging double 'SEA[]' treatment to mm... - var m; - if(!mm.slice){ - m = JSON.stringify(m); - } else if('SEA[' !== mm.slice(0,4)){ - m = mm; - } else { // Needs to remove previous signature envelope - try{m = JSON.parse(mm.slice(3))[0]; - }catch(e){m = mm} - while(m.slice){m = !m.slice ? m : JSON.parse(m)} - m = JSON.stringify(m); + var m = mm; + if(mm.slice){ + // Needs to remove previous signature envelope + while('SEA[' === m.slice(0,4)){ + try{m = JSON.parse(m.slice(3))[0]; + }catch(e){ m = mm; break } + } } + m = m.slice ? m : JSON.stringify(m); SEA.sign(m, p).then(function(signature){ resolve('SEA'+JSON.stringify([m,signature])); }).catch(function(e){Gun.log(e); reject(e)}); }; - if(cb){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb){doIt(cb, function(){cb()})} else { return new Promise(doIt) } }; SEA.read = function(m,p,cb){ var doIt = function(resolve, reject) { - if(!m){return resolve()} - if(!m.slice || 'SEA[' !== m.slice(0,4)){return resolve(m)} + if(!m){ return resolve() } + if(!m.slice || 'SEA[' !== m.slice(0,4)){ return resolve(m) } m = m.slice(3); - try{m = !m.slice ? m : JSON.parse(m);}catch(e){return reject(e)} + try{m = !m.slice ? m : JSON.parse(m); + }catch(e){ return reject(e) } m = m || ''; SEA.verify(m[0], p, m[1]).then(function(ok){ resolve(ok && m[0]) }).catch(function(e){reject(e)}); }; - if(cb && typeof cb === 'function'){doIt(cb, function(){cb()})} else {return new Promise(doIt)} + if(cb && typeof cb === 'function'){doIt(cb, function(){cb()}) + } else { return new Promise(doIt) } }; Gun.SEA = SEA; diff --git a/test/common.js b/test/common.js index 508bd2d6..0442a82b 100644 --- a/test/common.js +++ b/test/common.js @@ -2,6 +2,11 @@ var root; (function(env){ root = env.window ? env.window : global; + // process.on('unhandledRejection', error => { + // // This gives real data about where rejection truly happened... + // console.log('unhandledRejection', error); + // }); + if(!root.sessionStorage){ root.sessionStorage = new require('node-localstorage').LocalStorage('session'); } @@ -8050,6 +8055,9 @@ describe('Gun', function(){ var user = gun.user(); Gun.log.off = true; // Supress all console logging + // Simulate browser reload + gun.back(-1)._.user = gun.back(-1).chain(); + ['callback', 'Promise'].forEach(function(type){ describe(type+':', function(){ describe('create', function(){ @@ -8059,7 +8067,7 @@ describe('Gun', function(){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); expect(ack).to.have.keys(['ok','pub']); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; // Gun.user.create - creates new user @@ -8078,7 +8086,7 @@ describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).not.to.be(undefined); expect(ack.err).not.to.be(''); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; // Gun.user.create - fails to create existing user @@ -8113,7 +8121,7 @@ describe('Gun', function(){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; // Gun.user.auth - authenticates existing user @@ -8132,7 +8140,7 @@ describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).to.not.be(undefined); expect(ack.err).to.not.be(''); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; if(type === 'callback'){ @@ -8152,7 +8160,7 @@ describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).to.not.be(undefined); expect(ack.err).to.not.be(''); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; if(type === 'callback'){ @@ -8170,7 +8178,7 @@ describe('Gun', function(){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; // Gun.user.auth - with newpass props sets new password @@ -8190,7 +8198,7 @@ describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).to.not.be(undefined); expect(ack.err).to.not.be(''); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'}; @@ -8227,7 +8235,7 @@ describe('Gun', function(){ expect(ack).to.not.have.key('err'); expect(ack).to.have.key('ok'); expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; var usr = alias+type+'leave'; @@ -8241,7 +8249,7 @@ describe('Gun', function(){ expect(usr).to.not.be(''); expect(usr).to.not.have.key('err'); expect(usr).to.have.key('put'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; // Gun.user.leave - performs logout for authenticated user if(type === 'callback'){ user.leave(check); @@ -8259,7 +8267,7 @@ describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); expect(ack).to.have.key('ok'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); @@ -8290,7 +8298,7 @@ describe('Gun', function(){ expect(ack).to.not.have.key('err'); expect(ack).to.have.key('ok'); expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; }; @@ -8303,7 +8311,7 @@ describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); expect(ack).to.have.key('put'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; // Gun.user.delete - deletes existing user account if(type === 'callback'){ user.delete(usr, pass, check(done)); @@ -8332,7 +8340,7 @@ describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('put'); expect(ack).to.have.key('err'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; if(type === 'callback'){ @@ -8438,14 +8446,14 @@ describe('Gun', function(){ sUser = root.sessionStorage.getItem('user'); sRemember = root.sessionStorage.getItem('remember'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; user.leave().then(function(ack){ try{ expect(ack).to.have.key('ok'); expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); expect(root.sessionStorage.getItem('user')).to.not.be(sUser); expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; root.sessionStorage.setItem('user', sUser); root.sessionStorage.setItem('remember', sRemember); @@ -8475,7 +8483,7 @@ describe('Gun', function(){ sUser = root.sessionStorage.getItem('user'); sRemember = root.sessionStorage.getItem('remember'); lRemember = root.localStorage.getItem('remember'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; user.leave().then(function(ack){ try{ @@ -8484,7 +8492,7 @@ describe('Gun', function(){ expect(root.sessionStorage.getItem('user')).to.not.be(sUser); expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); expect(root.localStorage.getItem('remember')).to.not.be(lRemember); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; root.sessionStorage.setItem('user', sUser); root.sessionStorage.setItem('remember', sRemember); @@ -8523,7 +8531,63 @@ describe('Gun', function(){ }).catch(done); }); - it.skip('changed password'); + it('changed password', function(done){ + user.recall(60, {session: false}).then(function(){ + return user.auth(alias+type, pass+' new', { pin: 'PIN' }); + }).then(function(usr){ + var sUser; + var sRemember; + var lRemember; + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + expect(root.sessionStorage.getItem('user')).to.be(alias+type); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + + sUser = root.sessionStorage.getItem('user'); + sRemember = root.sessionStorage.getItem('remember'); + lRemember = root.localStorage.getItem('remember'); + }catch(e){ done(e); return }; + // Time to do new login with new password set + user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + }catch(e){ done(e); return }; + + return user.auth(alias+type, pass+' new', {newpass: pass, pin: 'PIN' }).then(function(usr){ + expect(usr).to.not.have.key('err'); + }); + }).then(function(){ + return user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + }catch(e){ done(e); return }; + gun.back(-1)._.user = gun.back(-1).chain(); + }); + }).then(function(){ + // Simulate browser reload + // Call back previous remember data + root.sessionStorage.setItem('user', sUser); + root.sessionStorage.setItem('remember', sRemember); + root.localStorage.setItem('remember', lRemember); + + user.recall(60, {session: false}).then(function(props){ + expect(props).to.not.be(undefined); + expect(props).to.not.be(''); + expect(props).to.have.key('err'); + expect(props.err).to.not.be(undefined); + expect(props.err).to.not.be(''); + done(); + }).catch(done); + }).catch(done); + }).catch(done); + }); + it.skip('no session'); }); @@ -8535,7 +8599,7 @@ describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); expect(ack).to.have.keys(['sea', 'pub']); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; var usr = alias+type+'alive'; @@ -8549,7 +8613,7 @@ describe('Gun', function(){ expect(usr).to.not.be(''); expect(usr).to.not.have.key('err'); expect(usr).to.have.key('put'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; // Gun.user.alive - keeps/checks User authentiation state if(type === 'callback'){ user.alive(check); @@ -8567,7 +8631,7 @@ describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.keys(['sea', 'pub']); expect(ack).to.have.key('err'); - }catch(e){done(e); return}; + }catch(e){ done(e); return }; done(); }; user.leave().catch(function(){}).then(function(){ From 0d920931a5653c953f74a45b1bc8a4b39bada051 Mon Sep 17 00:00:00 2001 From: mhelander Date: Mon, 4 Sep 2017 22:27:35 +0300 Subject: [PATCH 08/29] sea.js test cases separated - run using 'SEA=true yarn test' command --- test/common.js | 807 +------------------------------------------------ test/sea.js | 807 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 813 insertions(+), 801 deletions(-) create mode 100644 test/sea.js diff --git a/test/common.js b/test/common.js index 0442a82b..7424fd50 100644 --- a/test/common.js +++ b/test/common.js @@ -1,4 +1,5 @@ var root; + (function(env){ root = env.window ? env.window : global; @@ -22,7 +23,9 @@ var root; root.Gun = root.Gun; } else { root.Gun = require('../gun'); - Gun.SEA = require('../sea'); // TODO: breaks original deep tests! + if (process.env.SEA) { + Gun.SEA = require('../sea'); // TODO: breaks original deep tests! + } Gun.serve = require('../lib/serve'); //require('./s3'); //require('./uws'); @@ -30,12 +33,14 @@ var root; require('../lib/file'); } }(this)); + //Gun.log.squelch = true; var gleak = {globals: {}, check: function(){ // via tobyho var leaked = [] for (var key in gleak.globe){ if(!(key in gleak.globals)){ leaked.push(key)} } if(leaked.length > 0){ console.log("GLOBAL LEAK!", leaked); return leaked } }}; + (function(env){ for (var key in (gleak.globe = env)){ gleak.globals[key] = true } }(this)); @@ -7848,806 +7853,6 @@ describe('Gun', function(){ */ }); - Gun.SEA && describe('SEA', function(){ - console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!'); - var alias = 'dude'; - var pass = 'my secret password'; - var userKeys = ['pub', 'priv']; - var clearText = 'My precious secret!'; - var encKeys = ['ct', 'iv', 's']; - - ['callback', 'Promise'].forEach(function(type){ - describe(type+':', function(){ - it('proof', function(done){ - var check = function(proof){ - expect(proof).to.not.be(undefined); - expect(proof).to.not.be(''); - done(); - } - // proof - generates PBKDF2 hash from user's alias and password - // which is then used to decrypt user's auth record - if(type === 'callback'){ - Gun.SEA.proof(alias, pass, check); - } else { - Gun.SEA.proof(alias, pass).then(check).catch(done); - } - }); - - it('pair', function(done){ - var check = function(key){ - expect(key).to.not.be(undefined); - expect(key).to.not.be(''); - expect(key).to.have.keys(userKeys); - userKeys.map(function(fld){ - expect(key[fld]).to.not.be(undefined); - expect(key[fld]).to.not.be(''); - }); - done(); - }; - // pair - generates ECDH key pair (for new user when created) - if(type === 'callback'){ - Gun.SEA.pair(check); - } else { - Gun.SEA.pair().then(check).catch(done);; - } - }); - - it('en', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(jsonSecret){ - expect(jsonSecret).to.not.be(undefined); - expect(jsonSecret).to.not.be(''); - expect(jsonSecret).to.not.eql(clearText); - expect(jsonSecret).to.not.eql(JSON.stringify(clearText)); - var objSecret = JSON.parse(jsonSecret); - expect(objSecret).to.have.keys(encKeys); - encKeys.map(function(key){ - expect(objSecret[key]).to.not.be(undefined); - expect(objSecret[key]).to.not.be(''); - }); - done(); - }; - // en - encrypts JSON data using user's private or derived ECDH key - if(type === 'callback'){ - Gun.SEA.en(JSON.stringify(clearText), key.priv, check); - } else { - Gun.SEA.en(JSON.stringify(clearText), key.priv).then(check); - } - }).catch(function(e){done(e)}); - }); - - it('sign', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(signature){ - expect(signature).to.not.be(undefined); - expect(signature).to.not.be(''); - expect(signature).to.not.eql(key.pub); - done(); - }; - // sign - calculates signature for data using user's private ECDH key - if(type === 'callback'){ - Gun.SEA.sign(key.pub, key.priv, check); - } else { - Gun.SEA.sign(key.pub, key.priv).then(check); - } - }).catch(function(e){done(e)}); - }); - - it('verify', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(ok){ - expect(ok).to.not.be(undefined); - expect(ok).to.not.be(''); - expect(ok).to.be(true); - done(); - }; - // sign - calculates signature for data using user's private ECDH key - Gun.SEA.sign(key.pub, key.priv).then(function(signature){ - if(type === 'callback'){ - Gun.SEA.verify(key.pub, key.pub, signature, check); - } else { - Gun.SEA.verify(key.pub, key.pub, signature).then(check); - } - }); - }).catch(function(e){done(e)}); - }); - - it('de', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(jsonText){ - expect(jsonText).to.not.be(undefined); - expect(jsonText).to.not.be(''); - expect(jsonText).to.not.eql(clearText); - var decryptedSecret = JSON.parse(jsonText); - expect(decryptedSecret).to.not.be(undefined); - expect(decryptedSecret).to.not.be(''); - expect(decryptedSecret).to.be.eql(clearText); - done(); - }; - Gun.SEA.en(JSON.stringify(clearText), key.priv).then(function(jsonSecret){ - // de - decrypts JSON data using user's private or derived ECDH key - if(type === 'callback'){ - Gun.SEA.de(jsonSecret, key.priv, check); - } else { - Gun.SEA.de(jsonSecret, key.priv).then(check); - } - }); - }).catch(function(e){done(e)}); - }); - - it('derive', function(done){ - Gun.SEA.pair().then(function(txKey){ - return Gun.SEA.pair().then(function(rxKey){ - return { tx: txKey, rx: rxKey }; - }); - }).then(function(keys){ - var check = function(shared){ - expect(shared).to.not.be(undefined); - expect(shared).to.not.be(''); - [keys.rx.pub, keys.rx.priv, keys.tx.pub, keys.tx.priv] - .map(function(val){ - expect(shared).to.not.eql(val); - }); - done(); - }; - // derive - provides shared secret for both receiver and sender - // which can be used to encrypt or sign data - if(type === 'callback'){ - Gun.SEA.derive(keys.rx.pub, keys.tx.priv, check); - } else { - Gun.SEA.derive(keys.rx.pub, keys.tx.priv).then(check); - } - }).catch(function(e){done(e)}); - }); - - it('write', function(done){ - Gun.SEA.pair().then(function(key){ - Gun.SEA.sign(key.pub, key.priv).then(function(signature){ - var check = function(result){ - expect(result).to.not.be(undefined); - expect(result).to.not.be(''); - expect(result.slice(0, 4)).to.eql('SEA['); - var parts = JSON.parse(result.slice(3)); - expect(parts).to.not.be(undefined); - expect(parts[0]).to.be.eql(key.pub); - expect(parts[1]).to.be.eql(signature); - done(); - }; - // write - wraps data to 'SEA["data","signature"]' - if(type === 'callback'){ - Gun.SEA.write(key.pub, key.priv, check); - } else { - Gun.SEA.write(key.pub, key.priv).then(check); - } - }); - }).catch(function(e){done(e)}); - }); - - it('read', function(done){ - Gun.SEA.pair().then(function(key){ - var check = function(result){ - expect(result).to.not.be(undefined); - expect(result).to.not.be(''); - expect(result).to.be.equal(key.pub); - done(); - }; - Gun.SEA.sign(key.pub, key.priv).then(function(signature){ - Gun.SEA.write(key.pub, key.priv).then(function(signed){ - // read - unwraps data from 'SEA["data","signature"]' - if(type === 'callback'){ - Gun.SEA.read(signed, key.pub, check); - } else { - Gun.SEA.read(signed, key.pub).then(check); - } - }); - }); - }).catch(function(e){done(e)}); - }); - }); - }); - }); - - Gun().user && describe('User', function(){ - console.log('TODO: User! THIS IS AN EARLY ALPHA!!!'); - var alias = 'dude'; - var pass = 'my secret password'; - var gun = Gun(); - var user = gun.user(); - Gun.log.off = true; // Supress all console logging - - // Simulate browser reload - gun.back(-1)._.user = gun.back(-1).chain(); - - ['callback', 'Promise'].forEach(function(type){ - describe(type+':', function(){ - describe('create', function(){ - it('new', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - }catch(e){ done(e); return }; - done(); - }; - // Gun.user.create - creates new user - if(type === 'callback'){ - user.create(alias+type, pass, check); - } else { - user.create(alias+type, pass).then(check).catch(done); - } - }); - it('conflict', function(done){ - Gun.log.off = true; // Supress all console logging - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).not.to.be(undefined); - expect(ack.err).not.to.be(''); - }catch(e){ done(e); return }; - done(); - }; - // Gun.user.create - fails to create existing user - if(type === 'callback'){ - user.create(alias+type, pass, check); - } else { - user.create(alias+type, pass).then(function(ack){ - done('Failed to decline creating existing user!'); - }).catch(check); - } - }); - }); - - describe('auth', function(){ - var checkStorage = function(done, hasPin){ - return function(){ - expect(root.sessionStorage.getItem('user')).to.not.be(undefined); - expect(root.sessionStorage.getItem('user')).to.not.be(''); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); - if(hasPin){ - expect(root.localStorage.getItem('remember')).to.not.be(undefined); - expect(root.localStorage.getItem('remember')).to.not.be(''); - } - done(); - }; - }; - - it('login', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - }catch(e){ done(e); return }; - done(); - }; - // Gun.user.auth - authenticates existing user - if(type === 'callback'){ - user.auth(alias+type, pass, check); - } else { - user.auth(alias+type, pass).then(check).catch(done); - } - }); - - it('wrong password', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).to.not.be(undefined); - expect(ack.err).to.not.be(''); - }catch(e){ done(e); return }; - done(); - }; - if(type === 'callback'){ - user.auth(alias+type, pass+'not', check); - } else { - user.auth(alias+type, pass+'not').then(function(ack){ - done('Unexpected login success!'); - }).catch(check); - } - }); - - it('unknown alias', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).to.not.be(undefined); - expect(ack.err).to.not.be(''); - }catch(e){ done(e); return }; - done(); - }; - if(type === 'callback'){ - user.auth(alias+type+'not', pass, check); - } else { - user.auth(alias+type+'not', pass).then(function(ack){ - done('Unexpected login success!'); - }).catch(check); - } - }); - - it('new password', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - }catch(e){ done(e); return }; - done(); - }; - // Gun.user.auth - with newpass props sets new password - if(type === 'callback'){ - user.auth(alias+type, pass, check, {newpass: pass+' new'}); - } else { - user.auth(alias+type, pass, {newpass: pass+' new'}).then(check) - .catch(done); - } - }); - - it('failed new password', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.key('err'); - expect(ack.err).to.not.be(undefined); - expect(ack.err).to.not.be(''); - }catch(e){ done(e); return }; - done(); - }; - var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'}; - if(type === 'callback'){ - user.auth(alias+type, pass+'not', check, {newpass: pass+' new'}); - } else { - user.auth(alias+type, pass+'not', {newpass: pass+' new'}) - .then(function(ack){ - done('Unexpected password change success!'); - }).catch(check); - } - }); - - it('without PIN auth session stored to sessionStorage', function(done){ - user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done); - }); - - it('with PIN auth session stored to sessionStorage', function(done){ - if(type === 'callback'){ - user.auth(alias+type, pass+' new', checkStorage(done, true), {pin: 'PIN'}); - } else { - user.auth(alias+type, pass+' new', {pin: 'PIN'}) - .then(checkStorage(done, true)).catch(done); - } - }); - }); - - describe('leave', function(){ - it('valid session', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - }catch(e){ done(e); return }; - done(); - }; - var usr = alias+type+'leave'; - user.create(usr, pass).then(function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - user.auth(usr, pass).then(function(usr){ - try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); - }catch(e){ done(e); return }; - // Gun.user.leave - performs logout for authenticated user - if(type === 'callback'){ - user.leave(check); - } else { - user.leave().then(check).catch(done); - } - }).catch(done); - }).catch(done); - }); - - it('no session', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.key('ok'); - }catch(e){ done(e); return }; - done(); - }; - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - if(type === 'callback'){ - user.leave(check); - } else { - user.leave().then(check).catch(done); - } - }); - }); - - describe('delete', function(){ - var usr = alias+type+'del'; - - var createUser = function(a, p){ - return user.create(a, p).then(function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - return ack; - }); - }; - var check = function(done){ - return function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - }catch(e){ done(e); return }; - done(); - }; - }; - - it('existing authenticated user', function(done){ - createUser(usr, pass).then(function(){ - user.auth(usr, pass).then(function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.key('put'); - }catch(e){ done(e); return }; - // Gun.user.delete - deletes existing user account - if(type === 'callback'){ - user.delete(usr, pass, check(done)); - } else { - user.delete(usr, pass).then(check(done)).catch(done); - } - }).catch(done); - }).catch(done); - }); - - it('unauthenticated existing user', function(done){ - createUser(usr, pass).catch(function(){}) - .then(function(){ - if(type === 'callback'){ - user.delete(usr, pass, check(done)); - } else { - user.delete(usr, pass).then(check(done)).catch(done); - } - }); - }); - - it('non-existing user', function(done){ - var notFound = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('put'); - expect(ack).to.have.key('err'); - }catch(e){ done(e); return }; - done(); - }; - if(type === 'callback'){ - user.delete('someone', 'password guess', notFound); - } else { - user.delete('someone', 'password guess').then(function(){ - done('Unexpectedly deleted guessed user!'); - }).catch(notFound); - } - }); - }); - - describe('recall', function(){ - var doCheck = function(done, hasPin){ - return function(){ - expect(root.sessionStorage.getItem('user')).to.not.be(undefined); - expect(root.sessionStorage.getItem('user')).to.not.be(''); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); - if(hasPin){ - expect(root.localStorage.getItem('remember')).to.not.be(undefined); - expect(root.localStorage.getItem('remember')).to.not.be(''); - } - return done(); - }; - }; - // This re-constructs 'remember-me' data modified by manipulate func - var manipulateStorage = function(manipulate, hasPin){ - var usr = gun.back(-1)._.user; - var remember = hasPin ? localStorage.getItem('remember') - : sessionStorage.getItem('remember'); - return Gun.SEA.read(remember, usr._.pub).then(function(props){ - props = manipulate(JSON.parse(props)); - return Gun.SEA.write(JSON.stringify(props), usr._.sea) - .then(function(remember){ - // remember = JSON.stringify(remember); - return hasPin ? sessionStorage.setItem('remember', remember) - : sessionStorage.setItem('remember', remember); - }); - }); - }; - - it('with PIN auth session stored to localStorage', function(done){ - var doAction = function(){ - user.auth(alias+type, pass+' new', { pin: 'PIN' }) - .then(doCheck(done, true)).catch(done); - }; - if(type === 'callback'){ - user.recall(doAction, { session: false }); - } else { - user.recall({ session: false }).then(doAction).catch(done) - } - }); - - it('without PIN auth session stored to sessionStorage', function(done){ - var doAction = function(){ - user.auth(alias+type, pass+' new').then(doCheck(done)); - }; - user.leave().then(function(){ - if(type === 'callback'){ - user.recall(doAction, { session: false }); - } else { - user.recall({ session: false }).then(doAction).catch(done) - } - }).catch(done); - }); - - it('no validity no session storing', function(done){ - var doAction = function(){ - user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); - }; - if(type === 'callback'){ - user.recall(0, doAction); - } else { - user.recall(0).then(doAction).catch(done); - } - }); - - it('validity but no PIN stored to sessionStorage', function(done){ - var doAction = function(){ - user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); - }; - if(type === 'callback'){ - user.recall(12 * 60 * 60, doAction, {session: false}); - } else { - user.recall(12 * 60 * 60, {session: false}).then(doAction) - .catch(done); - } - }); - - it('valid sessionStorage session', function(done){ - user.auth(alias+type, pass+' new').then(function(usr){ - var sUser; - var sRemember; - try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); - expect(root.sessionStorage.getItem('user')).to.be(alias+type); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); - - sUser = root.sessionStorage.getItem('user'); - sRemember = root.sessionStorage.getItem('remember'); - }catch(e){ done(e); return }; - user.leave().then(function(ack){ - try{ - expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - expect(root.sessionStorage.getItem('user')).to.not.be(sUser); - expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); - }catch(e){ done(e); return }; - - root.sessionStorage.setItem('user', sUser); - root.sessionStorage.setItem('remember', sRemember); - - user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) - .catch(done); - }).catch(done); - }).catch(done); - }); - - it('valid localStorage session bootstrap', function(done){ - user.auth(alias+type, pass+' new', { pin: 'PIN' }).then(function(usr){ - var sUser; - var sRemember; - var lRemember; - try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); - expect(root.sessionStorage.getItem('user')).to.be(alias+type); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); - expect(root.localStorage.getItem('remember')).to.not.be(undefined); - expect(root.localStorage.getItem('remember')).to.not.be(''); - - sUser = root.sessionStorage.getItem('user'); - sRemember = root.sessionStorage.getItem('remember'); - lRemember = root.localStorage.getItem('remember'); - }catch(e){ done(e); return }; - - user.leave().then(function(ack){ - try{ - expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); - expect(root.sessionStorage.getItem('user')).to.not.be(sUser); - expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); - expect(root.localStorage.getItem('remember')).to.not.be(lRemember); - }catch(e){ done(e); return }; - - root.sessionStorage.setItem('user', sUser); - root.sessionStorage.setItem('remember', sRemember); - root.localStorage.setItem('remember', lRemember); - - user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) - .catch(done); - }).catch(done); - }).catch(done); - }); - - it.skip('invalid sessionStorage session'); - it.skip('valid localStorage data but not in sessionStorage'); - - it('expired session', function(done){ - user.recall(60, {session: true}).then(function(){ - return user.auth(alias+type, pass+' new'); - }).then(doCheck(function(){ - // Storage data OK, let's back up time of auth 65 minutes - return manipulateStorage(function(props){ - props.iat -= 65 * 60; - return props; - }, false); - })).then(function(){ - // Simulate browser reload - gun.back(-1)._.user = gun.back(-1).chain(); - // TODO: re-make sessionStorage.remember to 65 seconds past - user.recall(60, {session: true}).then(function(props){ - expect(props).to.not.be(undefined); - expect(props).to.not.be(''); - expect(props).to.have.key('err'); - expect(props.err).to.not.be(undefined); - expect(props.err).to.not.be(''); - done(); - }).catch(done); - }).catch(done); - }); - - it('changed password', function(done){ - user.recall(60, {session: false}).then(function(){ - return user.auth(alias+type, pass+' new', { pin: 'PIN' }); - }).then(function(usr){ - var sUser; - var sRemember; - var lRemember; - try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); - expect(root.sessionStorage.getItem('user')).to.be(alias+type); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); - expect(root.localStorage.getItem('remember')).to.not.be(undefined); - expect(root.localStorage.getItem('remember')).to.not.be(''); - - sUser = root.sessionStorage.getItem('user'); - sRemember = root.sessionStorage.getItem('remember'); - lRemember = root.localStorage.getItem('remember'); - }catch(e){ done(e); return }; - // Time to do new login with new password set - user.leave().then(function(ack){ - try{ - expect(ack).to.have.key('ok'); - }catch(e){ done(e); return }; - - return user.auth(alias+type, pass+' new', {newpass: pass, pin: 'PIN' }).then(function(usr){ - expect(usr).to.not.have.key('err'); - }); - }).then(function(){ - return user.leave().then(function(ack){ - try{ - expect(ack).to.have.key('ok'); - }catch(e){ done(e); return }; - gun.back(-1)._.user = gun.back(-1).chain(); - }); - }).then(function(){ - // Simulate browser reload - // Call back previous remember data - root.sessionStorage.setItem('user', sUser); - root.sessionStorage.setItem('remember', sRemember); - root.localStorage.setItem('remember', lRemember); - - user.recall(60, {session: false}).then(function(props){ - expect(props).to.not.be(undefined); - expect(props).to.not.be(''); - expect(props).to.have.key('err'); - expect(props.err).to.not.be(undefined); - expect(props.err).to.not.be(''); - done(); - }).catch(done); - }).catch(done); - }).catch(done); - }); - - it.skip('no session'); - }); - - describe('alive', function(){ - it('valid session', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.key('err'); - expect(ack).to.have.keys(['sea', 'pub']); - }catch(e){ done(e); return }; - done(); - }; - var usr = alias+type+'alive'; - user.create(usr, pass).then(function(ack){ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - user.auth(usr, pass, { pin: 'PIN' }).then(function(usr){ - try{ - expect(usr).to.not.be(undefined); - expect(usr).to.not.be(''); - expect(usr).to.not.have.key('err'); - expect(usr).to.have.key('put'); - }catch(e){ done(e); return }; - // Gun.user.alive - keeps/checks User authentiation state - if(type === 'callback'){ - user.alive(check); - } else { - user.alive().then(check).catch(done); - } - }).catch(done); - }).catch(done); - }); - - it('expired session', function(done){ - var check = function(ack){ - try{ - expect(ack).to.not.be(undefined); - expect(ack).to.not.be(''); - expect(ack).to.not.have.keys(['sea', 'pub']); - expect(ack).to.have.key('err'); - }catch(e){ done(e); return }; - done(); - }; - user.leave().catch(function(){}).then(function(){ - user.alive().then(function(){ - done('Unexpected alive session!'); - }).catch(check); - }).catch(done); - }); - - it.skip('recall hook session manipulation'); - }); - }); - }); - Gun.log.off = false; - }); - describe('Streams', function(){ console.log("TODO: BUG! Upgrade UNION tests to new internal API!"); return; diff --git a/test/sea.js b/test/sea.js new file mode 100644 index 00000000..982f03ab --- /dev/null +++ b/test/sea.js @@ -0,0 +1,807 @@ +var root; + +(function(env){ + root = env.window ? env.window : global; +}(this)); + +Gun.SEA && describe('SEA', function(){ + console.log('TODO: SEA! THIS IS AN EARLY ALPHA!!!'); + var alias = 'dude'; + var pass = 'my secret password'; + var userKeys = ['pub', 'priv']; + var clearText = 'My precious secret!'; + var encKeys = ['ct', 'iv', 's']; + + ['callback', 'Promise'].forEach(function(type){ + describe(type+':', function(){ + it('proof', function(done){ + var check = function(proof){ + expect(proof).to.not.be(undefined); + expect(proof).to.not.be(''); + done(); + } + // proof - generates PBKDF2 hash from user's alias and password + // which is then used to decrypt user's auth record + if(type === 'callback'){ + Gun.SEA.proof(alias, pass, check); + } else { + Gun.SEA.proof(alias, pass).then(check).catch(done); + } + }); + + it('pair', function(done){ + var check = function(key){ + expect(key).to.not.be(undefined); + expect(key).to.not.be(''); + expect(key).to.have.keys(userKeys); + userKeys.map(function(fld){ + expect(key[fld]).to.not.be(undefined); + expect(key[fld]).to.not.be(''); + }); + done(); + }; + // pair - generates ECDH key pair (for new user when created) + if(type === 'callback'){ + Gun.SEA.pair(check); + } else { + Gun.SEA.pair().then(check).catch(done);; + } + }); + + it('en', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(jsonSecret){ + expect(jsonSecret).to.not.be(undefined); + expect(jsonSecret).to.not.be(''); + expect(jsonSecret).to.not.eql(clearText); + expect(jsonSecret).to.not.eql(JSON.stringify(clearText)); + var objSecret = JSON.parse(jsonSecret); + expect(objSecret).to.have.keys(encKeys); + encKeys.map(function(key){ + expect(objSecret[key]).to.not.be(undefined); + expect(objSecret[key]).to.not.be(''); + }); + done(); + }; + // en - encrypts JSON data using user's private or derived ECDH key + if(type === 'callback'){ + Gun.SEA.en(JSON.stringify(clearText), key.priv, check); + } else { + Gun.SEA.en(JSON.stringify(clearText), key.priv).then(check); + } + }).catch(function(e){done(e)}); + }); + + it('sign', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(signature){ + expect(signature).to.not.be(undefined); + expect(signature).to.not.be(''); + expect(signature).to.not.eql(key.pub); + done(); + }; + // sign - calculates signature for data using user's private ECDH key + if(type === 'callback'){ + Gun.SEA.sign(key.pub, key.priv, check); + } else { + Gun.SEA.sign(key.pub, key.priv).then(check); + } + }).catch(function(e){done(e)}); + }); + + it('verify', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(ok){ + expect(ok).to.not.be(undefined); + expect(ok).to.not.be(''); + expect(ok).to.be(true); + done(); + }; + // sign - calculates signature for data using user's private ECDH key + Gun.SEA.sign(key.pub, key.priv).then(function(signature){ + if(type === 'callback'){ + Gun.SEA.verify(key.pub, key.pub, signature, check); + } else { + Gun.SEA.verify(key.pub, key.pub, signature).then(check); + } + }); + }).catch(function(e){done(e)}); + }); + + it('de', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(jsonText){ + expect(jsonText).to.not.be(undefined); + expect(jsonText).to.not.be(''); + expect(jsonText).to.not.eql(clearText); + var decryptedSecret = JSON.parse(jsonText); + expect(decryptedSecret).to.not.be(undefined); + expect(decryptedSecret).to.not.be(''); + expect(decryptedSecret).to.be.eql(clearText); + done(); + }; + Gun.SEA.en(JSON.stringify(clearText), key.priv).then(function(jsonSecret){ + // de - decrypts JSON data using user's private or derived ECDH key + if(type === 'callback'){ + Gun.SEA.de(jsonSecret, key.priv, check); + } else { + Gun.SEA.de(jsonSecret, key.priv).then(check); + } + }); + }).catch(function(e){done(e)}); + }); + + it('derive', function(done){ + Gun.SEA.pair().then(function(txKey){ + return Gun.SEA.pair().then(function(rxKey){ + return { tx: txKey, rx: rxKey }; + }); + }).then(function(keys){ + var check = function(shared){ + expect(shared).to.not.be(undefined); + expect(shared).to.not.be(''); + [keys.rx.pub, keys.rx.priv, keys.tx.pub, keys.tx.priv] + .map(function(val){ + expect(shared).to.not.eql(val); + }); + done(); + }; + // derive - provides shared secret for both receiver and sender + // which can be used to encrypt or sign data + if(type === 'callback'){ + Gun.SEA.derive(keys.rx.pub, keys.tx.priv, check); + } else { + Gun.SEA.derive(keys.rx.pub, keys.tx.priv).then(check); + } + }).catch(function(e){done(e)}); + }); + + it('write', function(done){ + Gun.SEA.pair().then(function(key){ + Gun.SEA.sign(key.pub, key.priv).then(function(signature){ + var check = function(result){ + expect(result).to.not.be(undefined); + expect(result).to.not.be(''); + expect(result.slice(0, 4)).to.eql('SEA['); + var parts = JSON.parse(result.slice(3)); + expect(parts).to.not.be(undefined); + expect(parts[0]).to.be.eql(key.pub); + expect(parts[1]).to.be.eql(signature); + done(); + }; + // write - wraps data to 'SEA["data","signature"]' + if(type === 'callback'){ + Gun.SEA.write(key.pub, key.priv, check); + } else { + Gun.SEA.write(key.pub, key.priv).then(check); + } + }); + }).catch(function(e){done(e)}); + }); + + it('read', function(done){ + Gun.SEA.pair().then(function(key){ + var check = function(result){ + expect(result).to.not.be(undefined); + expect(result).to.not.be(''); + expect(result).to.be.equal(key.pub); + done(); + }; + Gun.SEA.sign(key.pub, key.priv).then(function(signature){ + Gun.SEA.write(key.pub, key.priv).then(function(signed){ + // read - unwraps data from 'SEA["data","signature"]' + if(type === 'callback'){ + Gun.SEA.read(signed, key.pub, check); + } else { + Gun.SEA.read(signed, key.pub).then(check); + } + }); + }); + }).catch(function(e){done(e)}); + }); + }); + }); +}); + +Gun().user && describe('Gun', function(){ + describe('User', function(){ + console.log('TODO: User! THIS IS AN EARLY ALPHA!!!'); + var alias = 'dude'; + var pass = 'my secret password'; + var gun = Gun(); + var user = gun.user(); + Gun.log.off = true; // Supress all console logging + + // Simulate browser reload + gun.back(-1)._.user = gun.back(-1).chain(); + + ['callback', 'Promise'].forEach(function(type){ + describe(type+':', function(){ + describe('create', function(){ + it('new', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + }catch(e){ done(e); return }; + done(); + }; + // Gun.user.create - creates new user + if(type === 'callback'){ + user.create(alias+type, pass, check); + } else { + user.create(alias+type, pass).then(check).catch(done); + } + }); + it('conflict', function(done){ + Gun.log.off = true; // Supress all console logging + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).not.to.be(undefined); + expect(ack.err).not.to.be(''); + }catch(e){ done(e); return }; + done(); + }; + // Gun.user.create - fails to create existing user + if(type === 'callback'){ + user.create(alias+type, pass, check); + } else { + user.create(alias+type, pass).then(function(ack){ + done('Failed to decline creating existing user!'); + }).catch(check); + } + }); + }); + + describe('auth', function(){ + var checkStorage = function(done, hasPin){ + return function(){ + expect(root.sessionStorage.getItem('user')).to.not.be(undefined); + expect(root.sessionStorage.getItem('user')).to.not.be(''); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + if(hasPin){ + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + } + done(); + }; + }; + + it('login', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + }catch(e){ done(e); return }; + done(); + }; + // Gun.user.auth - authenticates existing user + if(type === 'callback'){ + user.auth(alias+type, pass, check); + } else { + user.auth(alias+type, pass).then(check).catch(done); + } + }); + + it('wrong password', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + }catch(e){ done(e); return }; + done(); + }; + if(type === 'callback'){ + user.auth(alias+type, pass+'not', check); + } else { + user.auth(alias+type, pass+'not').then(function(ack){ + done('Unexpected login success!'); + }).catch(check); + } + }); + + it('unknown alias', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + }catch(e){ done(e); return }; + done(); + }; + if(type === 'callback'){ + user.auth(alias+type+'not', pass, check); + } else { + user.auth(alias+type+'not', pass).then(function(ack){ + done('Unexpected login success!'); + }).catch(check); + } + }); + + it('new password', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + }catch(e){ done(e); return }; + done(); + }; + // Gun.user.auth - with newpass props sets new password + if(type === 'callback'){ + user.auth(alias+type, pass, check, {newpass: pass+' new'}); + } else { + user.auth(alias+type, pass, {newpass: pass+' new'}).then(check) + .catch(done); + } + }); + + it('failed new password', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + }catch(e){ done(e); return }; + done(); + }; + var props = {alias: alias+type, pass: pass+'not', newpass: pass+' new'}; + if(type === 'callback'){ + user.auth(alias+type, pass+'not', check, {newpass: pass+' new'}); + } else { + user.auth(alias+type, pass+'not', {newpass: pass+' new'}) + .then(function(ack){ + done('Unexpected password change success!'); + }).catch(check); + } + }); + + it('without PIN auth session stored to sessionStorage', function(done){ + user.auth(alias+type, pass+' new').then(checkStorage(done)).catch(done); + }); + + it('with PIN auth session stored to sessionStorage', function(done){ + if(type === 'callback'){ + user.auth(alias+type, pass+' new', checkStorage(done, true), {pin: 'PIN'}); + } else { + user.auth(alias+type, pass+' new', {pin: 'PIN'}) + .then(checkStorage(done, true)).catch(done); + } + }); + }); + + describe('leave', function(){ + it('valid session', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + }catch(e){ done(e); return }; + done(); + }; + var usr = alias+type+'leave'; + user.create(usr, pass).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + user.auth(usr, pass).then(function(usr){ + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){ done(e); return }; + // Gun.user.leave - performs logout for authenticated user + if(type === 'callback'){ + user.leave(check); + } else { + user.leave().then(check).catch(done); + } + }).catch(done); + }).catch(done); + }); + + it('no session', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + }catch(e){ done(e); return }; + done(); + }; + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + if(type === 'callback'){ + user.leave(check); + } else { + user.leave().then(check).catch(done); + } + }); + }); + + describe('delete', function(){ + var usr = alias+type+'del'; + + var createUser = function(a, p){ + return user.create(a, p).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + return ack; + }); + }; + var check = function(done){ + return function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + }catch(e){ done(e); return }; + done(); + }; + }; + + it('existing authenticated user', function(done){ + createUser(usr, pass).then(function(){ + user.auth(usr, pass).then(function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.key('put'); + }catch(e){ done(e); return }; + // Gun.user.delete - deletes existing user account + if(type === 'callback'){ + user.delete(usr, pass, check(done)); + } else { + user.delete(usr, pass).then(check(done)).catch(done); + } + }).catch(done); + }).catch(done); + }); + + it('unauthenticated existing user', function(done){ + createUser(usr, pass).catch(function(){}) + .then(function(){ + if(type === 'callback'){ + user.delete(usr, pass, check(done)); + } else { + user.delete(usr, pass).then(check(done)).catch(done); + } + }); + }); + + it('non-existing user', function(done){ + var notFound = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('put'); + expect(ack).to.have.key('err'); + }catch(e){ done(e); return }; + done(); + }; + if(type === 'callback'){ + user.delete('someone', 'password guess', notFound); + } else { + user.delete('someone', 'password guess').then(function(){ + done('Unexpectedly deleted guessed user!'); + }).catch(notFound); + } + }); + }); + + describe('recall', function(){ + var doCheck = function(done, hasPin){ + return function(){ + expect(root.sessionStorage.getItem('user')).to.not.be(undefined); + expect(root.sessionStorage.getItem('user')).to.not.be(''); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + if(hasPin){ + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + } + return done(); + }; + }; + // This re-constructs 'remember-me' data modified by manipulate func + var manipulateStorage = function(manipulate, hasPin){ + var usr = gun.back(-1)._.user; + var remember = hasPin ? localStorage.getItem('remember') + : sessionStorage.getItem('remember'); + return Gun.SEA.read(remember, usr._.pub).then(function(props){ + props = manipulate(JSON.parse(props)); + return Gun.SEA.write(JSON.stringify(props), usr._.sea) + .then(function(remember){ + // remember = JSON.stringify(remember); + return hasPin ? sessionStorage.setItem('remember', remember) + : sessionStorage.setItem('remember', remember); + }); + }); + }; + + it('with PIN auth session stored to localStorage', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new', { pin: 'PIN' }) + .then(doCheck(done, true)).catch(done); + }; + if(type === 'callback'){ + user.recall(doAction, { session: false }); + } else { + user.recall({ session: false }).then(doAction).catch(done) + } + }); + + it('without PIN auth session stored to sessionStorage', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new').then(doCheck(done)); + }; + user.leave().then(function(){ + if(type === 'callback'){ + user.recall(doAction, { session: false }); + } else { + user.recall({ session: false }).then(doAction).catch(done) + } + }).catch(done); + }); + + it('no validity no session storing', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); + }; + if(type === 'callback'){ + user.recall(0, doAction); + } else { + user.recall(0).then(doAction).catch(done); + } + }); + + it('validity but no PIN stored to sessionStorage', function(done){ + var doAction = function(){ + user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); + }; + if(type === 'callback'){ + user.recall(12 * 60 * 60, doAction, {session: false}); + } else { + user.recall(12 * 60 * 60, {session: false}).then(doAction) + .catch(done); + } + }); + + it('valid sessionStorage session', function(done){ + user.auth(alias+type, pass+' new').then(function(usr){ + var sUser; + var sRemember; + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + expect(root.sessionStorage.getItem('user')).to.be(alias+type); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + + sUser = root.sessionStorage.getItem('user'); + sRemember = root.sessionStorage.getItem('remember'); + }catch(e){ done(e); return }; + user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(root.sessionStorage.getItem('user')).to.not.be(sUser); + expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); + }catch(e){ done(e); return }; + + root.sessionStorage.setItem('user', sUser); + root.sessionStorage.setItem('remember', sRemember); + + user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) + .catch(done); + }).catch(done); + }).catch(done); + }); + + it('valid localStorage session bootstrap', function(done){ + user.auth(alias+type, pass+' new', { pin: 'PIN' }).then(function(usr){ + var sUser; + var sRemember; + var lRemember; + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + expect(root.sessionStorage.getItem('user')).to.be(alias+type); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + + sUser = root.sessionStorage.getItem('user'); + sRemember = root.sessionStorage.getItem('remember'); + lRemember = root.localStorage.getItem('remember'); + }catch(e){ done(e); return }; + + user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(root.sessionStorage.getItem('user')).to.not.be(sUser); + expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); + expect(root.localStorage.getItem('remember')).to.not.be(lRemember); + }catch(e){ done(e); return }; + + root.sessionStorage.setItem('user', sUser); + root.sessionStorage.setItem('remember', sRemember); + root.localStorage.setItem('remember', lRemember); + + user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) + .catch(done); + }).catch(done); + }).catch(done); + }); + + it.skip('invalid sessionStorage session'); + it.skip('valid localStorage data but not in sessionStorage'); + + it('expired session', function(done){ + user.recall(60, {session: true}).then(function(){ + return user.auth(alias+type, pass+' new'); + }).then(doCheck(function(){ + // Storage data OK, let's back up time of auth 65 minutes + return manipulateStorage(function(props){ + props.iat -= 65 * 60; + return props; + }, false); + })).then(function(){ + // Simulate browser reload + gun.back(-1)._.user = gun.back(-1).chain(); + // TODO: re-make sessionStorage.remember to 65 seconds past + user.recall(60, {session: true}).then(function(props){ + expect(props).to.not.be(undefined); + expect(props).to.not.be(''); + expect(props).to.have.key('err'); + expect(props.err).to.not.be(undefined); + expect(props.err).to.not.be(''); + done(); + }).catch(done); + }).catch(done); + }); + + it('changed password', function(done){ + user.recall(60, {session: false}).then(function(){ + return user.auth(alias+type, pass+' new', { pin: 'PIN' }); + }).then(function(usr){ + var sUser; + var sRemember; + var lRemember; + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + expect(root.sessionStorage.getItem('user')).to.be(alias+type); + expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); + expect(root.sessionStorage.getItem('remember')).to.not.be(''); + expect(root.localStorage.getItem('remember')).to.not.be(undefined); + expect(root.localStorage.getItem('remember')).to.not.be(''); + + sUser = root.sessionStorage.getItem('user'); + sRemember = root.sessionStorage.getItem('remember'); + lRemember = root.localStorage.getItem('remember'); + }catch(e){ done(e); return }; + // Time to do new login with new password set + user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + }catch(e){ done(e); return }; + + return user.auth(alias+type, pass+' new', {newpass: pass, pin: 'PIN' }).then(function(usr){ + expect(usr).to.not.have.key('err'); + }); + }).then(function(){ + return user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + }catch(e){ done(e); return }; + gun.back(-1)._.user = gun.back(-1).chain(); + }); + }).then(function(){ + // Simulate browser reload + // Call back previous remember data + root.sessionStorage.setItem('user', sUser); + root.sessionStorage.setItem('remember', sRemember); + root.localStorage.setItem('remember', lRemember); + + user.recall(60, {session: false}).then(function(props){ + expect(props).to.not.be(undefined); + expect(props).to.not.be(''); + expect(props).to.have.key('err'); + expect(props.err).to.not.be(undefined); + expect(props.err).to.not.be(''); + done(); + }).catch(done); + }).catch(done); + }).catch(done); + }); + + it.skip('no session'); + }); + + describe('alive', function(){ + it('valid session', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.key('err'); + expect(ack).to.have.keys(['sea', 'pub']); + }catch(e){ done(e); return }; + done(); + }; + var usr = alias+type+'alive'; + user.create(usr, pass).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.keys(['ok','pub']); + user.auth(usr, pass, { pin: 'PIN' }).then(function(usr){ + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){ done(e); return }; + // Gun.user.alive - keeps/checks User authentiation state + if(type === 'callback'){ + user.alive(check); + } else { + user.alive().then(check).catch(done); + } + }).catch(done); + }).catch(done); + }); + + it('expired session', function(done){ + var check = function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.keys(['sea', 'pub']); + expect(ack).to.have.key('err'); + }catch(e){ done(e); return }; + done(); + }; + user.leave().catch(function(){}).then(function(){ + user.alive().then(function(){ + done('Unexpected alive session!'); + }).catch(check); + }).catch(done); + }); + + it.skip('recall hook session manipulation'); + }); + }); + }); + Gun.log.off = false; + }); +}); From b0b87a14a0a805ac728a31188eca7d4cca0a3d99 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 7 Sep 2017 00:48:22 +0300 Subject: [PATCH 09/29] All test cases completed & some bugfixes & 'remember-me' recovery with PIN now supported --- sea.js | 278 +++++++++++++++++++++++++++---------------------- test/common.js | 2 +- test/sea.js | 257 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 360 insertions(+), 177 deletions(-) diff --git a/sea.js b/sea.js index 116f09f7..26ce443c 100644 --- a/sea.js +++ b/sea.js @@ -14,7 +14,7 @@ var crypto, TextEncoder, TextDecoder, localStorage, sessionStorage; - if (typeof window !== 'undefined') { + if(typeof window !== 'undefined'){ crypto = window.crypto; TextEncoder = window.TextEncoder; TextDecoder = window.TextDecoder; @@ -46,12 +46,17 @@ enc: 'aes-256-cbc' }; + var _initial_authsettings = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + session: true, + hook: function(props){ return props } // { iat, exp, alias, remember } + // or return new Promise(function(resolve, reject){(resolve(props))}) + } // These are used to persist user's authentication "session" var authsettings = { - validity: 60 * 60 * 12, // 12 hours - session: true, - hook: function(props) { return props } // { iat, exp, alias, remember } - // or return new Promise(function(resolve, reject){(resolve(props))}) + validity: _initial_authsettings.validity, + session: _initial_authsettings.session, + hook: _initial_authsettings.hook }; // let's extend the gun chain with a `user` function. @@ -84,7 +89,7 @@ // if no user, don't do anything. var err = 'No user!'; Gun.log(err); - return reject({err: err}); + return reject(err); } // then figuring out all possible candidates having matching username var aliases = []; @@ -99,7 +104,7 @@ }); }); return aliases.length && resolve(aliases) - || reject({err: 'Public key does not exist!'}) + || reject('Public key does not exist!') }); }); } @@ -171,7 +176,7 @@ return function(props){ return new Promise(function(resolve, reject){ if(!Gun.obj.has(props, 'alias')){ return resolve() } - if (proof && Gun.obj.has(props, 'iat')) { + if(proof && Gun.obj.has(props, 'iat')){ props.proof = proof; delete props.remember; // Not stored if present @@ -181,18 +186,16 @@ return SEA.write(JSON.stringify(remember), priv).then(function(signed){ sessionStorage.setItem('user', props.alias); sessionStorage.setItem('remember', signed); - if (!protected) { + if(!protected){ localStorage.removeItem('remember'); } return !protected || SEA.en(protected, pin).then(function(encrypted){ - return encrypted && SEA.write(encrypted, priv) - .then(function(encsig){ + return encrypted && SEA.write(encrypted, priv).then(function(encsig){ localStorage.setItem('remember', encsig); }).catch(reject); }).catch(reject); - }).then(function(){ - resolve(props); - }).catch(function(e){ reject({err: 'Session persisting failed!'}) }); + }).then(function(){ resolve(props); }) + .catch(function(e){ reject({err: 'Session persisting failed!'}) }); } else { localStorage.removeItem('remember'); sessionStorage.removeItem('user'); @@ -214,39 +217,66 @@ // ELSE if no PIN then window.sessionStorage var pin = Gun.obj.has(opts, 'pin') && opts.pin && new Buffer(opts.pin, 'utf8').toString('base64'); - var args = { alias: user.alias }; - if(proof && authsettings.validity){ + if(proof && user && user.alias && authsettings.validity){ + var args = { alias: user.alias }; args.iat = Math.ceil(Date.now() / 1000); // seconds - args.exp = authsettings.validity * 60; // seconds - if (Gun.obj.has(opts, 'pin')){ + args.exp = authsettings.validity; // seconds + if(Gun.obj.has(opts, 'pin')){ args.remember = true; // for hook - not stored } var props = authsettings.hook(args); if(props instanceof Promise){ return props.then(updatestorage(proof, user.sea, pin)); - } else { - return updatestorage(proof, user.sea, pin)(props); } - } else { - return updatestorage()(args); + return updatestorage(proof, user.sea, pin)(props); } + return updatestorage()({alias: 'delete'}); } // This internal func recalls persisted User authentication if so configured - function authrecall(root){ + function authrecall(root,authprops){ return new Promise(function(resolve, reject){ - var remember = sessionStorage.getItem('remember'); - var alias = sessionStorage.getItem('user'); - var err = 'Not authenticated'; - var pin; + var remember = authprops || sessionStorage.getItem('remember'); + var alias = Gun.obj.has(authprops, 'alias') && authprops.alias + || sessionStorage.getItem('user'); + var pin = Gun.obj.has(authprops, 'pin') + && new Buffer(authprops.pin, 'utf8').toString('base64'); + + var checkRememberData = function(decr){ + if(Gun.obj.has(decr, 'proof') + && Gun.obj.has(decr, 'alias') && decr.alias === alias){ + var proof = decr.proof; + var iat = decr.iat; // No way hook to update this + delete decr.proof; // We're not gonna give proof to hook! + var checkNotExpired = function(args){ + if(Math.floor(Date.now() / 1000) < (iat + args.exp)){ + args.iat = iat; + args.proof = proof; + return args; + } else { + Gun.log('Authentication expired!') } + }; + var hooked = authsettings.hook(decr); + return ((hooked instanceof Promise) + && hooked.then(checkNotExpired)) || checkNotExpired(hooked); + } + }; + var readAndDecrypt = function(data, pub, key){ + return SEA.read(data, pub).then(function(encrypted){ + return SEA.de(encrypted, key); + }).then(function(decrypted){ + try{ return decrypted.slice ? JSON.parse(decrypted) : decrypted }catch(e){} + return decrypted; + }); + }; // Already authenticated? - if(Gun.obj.has(root._.user._, 'pub')){ - return resolve(root._.user._.pub); + if(Gun.obj.has(root._.user._, 'pub') && Gun.obj.has(root._.user._, 'sea')){ + return resolve(root._.user._); } // No, got alias? - if (alias && remember){ + if(alias && remember){ return querygunaliases(alias, root).then(function(aliases){ return new Promise(function(resolve, reject){ // then attempt to log into each one until we find ours! @@ -257,60 +287,31 @@ if(!at.put){ return !remaining && reject({err: 'Public key does not exist!'}) } - // got pub, time to unwrap Storage data... - return SEA.read(remember, pub, true).then(function(props){ - props = !props.slice ? props : JSON.parse(props); - var checkProps = function(decr){ - return new Promise(function(resolve){ - if(Gun.obj.has(decr, 'proof') - && Gun.obj.has(decr, 'alias') && decr.alias === alias){ - var proof = decr.proof; - var iat = decr.iat; // No way hook to update this - delete decr.proof; // We're not gonna give proof to hook! - var doIt = function(args){ - if(Math.floor(Date.now() / 1000) < (iat + args.exp)){ - args.iat = iat; - args.proof = proof; - return args; - } else { Gun.log('Authentication expired!') } - }; - var hooked = authsettings.hook(decr); - return resolve(((hooked instanceof Promise) - && hooked.then(doIt)) - || doIt(decr)); - } - resolve(); - }); - }; - // Got PIN ? - if(Gun.obj.has(props, 'pin')){ - pin = props.pin; - // Yes! We can get localStorage secret if signature is ok - return SEA.read(localStorage.getItem('remember'), pub) - .then(function(encrypted){ - // And decrypt it - return SEA.de(encrypted, pin); - }).then(function(decr){ - decr = !decr.slice ? decr : JSON.parse(decr); - // And return proof if for matching alias - return checkProps(decr); - }); + // got pub, time to try auth with alias & PIN... + return ((pin && Promise.resolve({pin: pin, alias: alias})) + // or just unwrap Storage data... + || SEA.read(remember, pub, true)).then(function(props){ + try{ props = props.slice ? JSON.parse(props) : props }catch(e){} + if(Gun.obj.has(props, 'pin') && Gun.obj.has(props, 'alias') + && props.alias === alias){ + pin = props.pin; // Got PIN so get localStorage secret if signature is ok + return readAndDecrypt(localStorage.getItem('remember'), pub, pin) + .then(checkRememberData); // And return proof if for matching alias } // No PIN, let's try short-term proof if for matching alias - return checkProps(props); + return checkRememberData(props); }).then(function(args){ var proof = args && args.proof; - if (!proof){ - return updatestorage()(args).then(function(){ - reject({err: 'No secret found!'}); + if(!proof){ + return (!args && reject({err: 'No valid authentication session found!'})) + || updatestorage()(args).then(function(){ + reject({err: 'Expired session!'}); }).catch(function(){ - reject({err: 'No secret found!'}); + reject({err: 'Expired session!'}); }); } - // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - return SEA.read(at.put.auth, pub).then(function(auth){ - return SEA.de(auth, proof) - .catch(function(e){ reject({err: 'Failed to decrypt secret!'}) }); + return readAndDecrypt(at.put.auth, pub, proof).catch(function(e){ + return !remaining && reject({err: 'Failed to decrypt private key!'}); }).then(function(priv){ // now we have AES decrypted the private key, // if we were successful, then that means we're logged in! @@ -333,7 +334,9 @@ reject({err: 'No authentication session found!'}); }); } - reject({err: 'No authentication session found!'}); + reject({ + err: (localStorage.getItem('remember') && 'Missing PIN and alias!') + || 'No authentication session found!'}); }); } @@ -341,10 +344,18 @@ function authleave(root, alias){ return function(resolve, reject){ // remove persisted authentication - authpersist((alias && { alias: alias }) || root._.user._).then(function(){ - root._.user = root.chain(); + user = root._.user; + alias = alias || (user._ && user._.alias); + var doIt = function(){ + // TODO: is this correct way to 'logout' user from Gun.User ? + [ 'alias', 'sea', 'pub' ].forEach(function(key){ + delete user._[key]; + }); + user._.is = user.is = {}; + // Let's use default resolve({ok: 0}); - }); + }; + authpersist(alias && { alias: alias }).then(doIt).catch(doIt); }; } @@ -352,8 +363,7 @@ function nodehash(m){ try{ m = m.slice ? m : JSON.stringify(m); - var ret = nodeCrypto.createHash(nHash).update(m, 'utf8').digest(); - return ret; + return nodeCrypto.createHash(nHash).update(m, 'utf8').digest(); }catch(e){ return m } } @@ -413,6 +423,16 @@ cb = typeof cb === 'function' && cb; var doIt = function(resolve, reject){ + // TODO: !pass && opt.pin => try to recall + // return reject({err: 'Auth attempt failed! Reason: No session data for alias & PIN'}); + if(!pass && Gun.obj.has(opts, 'pin')){ + return authrecall(root, {alias: alias, pin: opts.pin}).then(function(props){ + resolve(props); + }).catch(function(e){ + reject({err: 'Auth attempt failed! Reason: No session data for alias & PIN'}); + }); + } + authenticate(alias, pass, root).then(function(key){ // we're logged in! var pin = Gun.obj.has(opts, 'pin') && { pin: opts.pin }; @@ -439,7 +459,6 @@ // awesome, now we can update the user using public key ID. // root.get(tmp).put(null); root.get(tmp).put(user); - // then we're done finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){ Gun.log('Failed to finalize login with new password!'); @@ -447,17 +466,17 @@ }); }).catch(function(e){ Gun.log('Failed encrypt private key using new password!'); - reject({err: 'Password set attempt failed! Reason: '+(e && e.err) || e || ''}); + reject({err: 'Password set attempt failed! Reason: ' + (e && e.err) || e || ''}); }); } else { finalizelogin(alias, key, root, pin).then(resolve).catch(function(e){ Gun.log('Failed to finalize login!'); - reject({err: 'Finalizing login failed! Reason: '+(e && e.err) || e || ''}); + reject({err: 'Finalizing login failed! Reason: ' + (e && e.err) || e || ''}); }); } }).catch(function(e){ Gun.log('Failed to sign in!'); - reject({err: 'Auth attempt failed! Reason: '+(e && e.err) || e || ''}); + reject({err: 'Auth attempt failed! Reason: ' + (e && e.err) || e || ''}); }); }; if(cb){doIt(cb, cb)} else { return new Promise(doIt) } @@ -473,37 +492,46 @@ authenticate(alias, pass, root).then(function(key){ new Promise(authleave(root, alias)).catch(function(){}) .then(function(){ + // Delete user data root.get('pub/'+key.pub).put(null); - root._.user = root.chain(); + // Wipe user data from memory + user = root._.user; + // TODO: is this correct way to 'logout' user from Gun.User ? + [ 'alias', 'sea', 'pub' ].forEach(function(key){ + delete user._[key]; + }); + user._.is = user.is = {}; resolve({ok: 0}); }).catch(function(e){ Gun.log('User.delete failed! Error:', e); - reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''}); + reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''}); }); }).catch(function(e){ Gun.log('User.delete authentication failed! Error:', e); - reject({err: 'Delete attempt failed! Reason:'+(e && e.err) || e || ''}); + reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''}); }); }; if(cb){doIt(cb, cb)} else { return new Promise(doIt) } }; // If authentication is to be remembered over reloads or browser closing, - // set validity time in seconds. - User.recall = function(validity,cb,opts){ + // set validity time in minutes. + User.recall = function(v,cb,o){ var root = this.back(-1); - if(!opts){ - if(typeof cb !== 'function' && !Gun.val.is(cb)){ - opts = cb; - cb = undefined; - } + var validity, callback, opts; + if(!o && typeof cb !== 'function' && !Gun.val.is(cb)){ + opts = cb; + } else { + callback = cb; } - if(!cb){ - if(typeof validity === 'function'){ - cb = validity; - validity = undefined; - } else if(!Gun.val.is(validity)){ - opts = validity; - validity = undefined; + if(!callback){ + if(typeof v === 'function'){ + callback = v; + validity = _initial_authsettings.validity; + } else if(!Gun.val.is(v)){ + opts = v; + validity = _initial_authsettings.validity; + } else { + validity = v * 60; // minutes to seconds } } var doIt = function(resolve, reject){ @@ -514,25 +542,21 @@ // called when app bootstraps, with wanted options // IF validity === 0 THEN no remember-me, ever // IF opt.session === true THEN no window.localStorage in use; nor PIN - if(Gun.val.is(validity)){ - authsettings.validity = validity; - } + authsettings.validity = typeof validity !== 'undefined' ? validity + : _initial_authsettings.validity; if(Gun.obj.has(opts, 'session')){ authsettings.session = opts.session; } - if(Gun.obj.has(opts, 'hook')){ - authsettings.hook = opt.hook; - } - authrecall(root).then(function(props){ - // All is good. Should we do something more with actual recalled data? - resolve(root._.user._) - }).catch(function(e){ + authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function') + ? opts.hook : _initial_authsettings.hook; + // All is good. Should we do something more with actual recalled data? + authrecall(root).then(resolve).catch(function(e){ var err = 'No session!'; Gun.log(err); - resolve({ err: err }); + resolve({ err: (e && e.err) || err }); }); }; - if(cb){doIt(cb, cb)} else { return new Promise(doIt) } + if(callback){doIt(callback, callback)} else { return new Promise(doIt) } }; User.alive = function(cb){ var root = this.back(-1); @@ -643,7 +667,7 @@ if(val === tmp){ return } // the account MUST have a `pub` property that equals the ID of the public key. return no = true; // if not, reject the update. } - if(at.user){ // if we are logged in + if(at.user && at.user._){ // if we are logged in if(tmp === at.user._.pub){ // as this user SEA.write(val, at.user._.sea).then(function(data){ val = node[key] = data; // then sign our updates as we output them. @@ -789,8 +813,7 @@ } else { // NodeJS doesn't support crypto.subtle.importKey properly try{ var cipher = nodeCrypto.createCipheriv(aes.enc, key, iv); - r.ct = cipher.update(m, 'utf8', 'base64'); - r.ct += cipher.final('base64'); + r.ct = cipher.update(m, 'utf8', 'base64') + cipher.final('base64'); }catch(e){ Gun.log(e); return reject(e) } resolve(JSON.stringify(r)); } @@ -799,23 +822,24 @@ }; SEA.de = function(m,p,cb){ var doIt = function(resolve, reject){ - var d = !m.slice ? m : JSON.parse(m); - var key = makeKey(p, new Buffer(d.s, 'hex')); - var iv = new Buffer(d.iv, 'hex'); + try{ m = m.slice ? JSON.parse(m) : m }catch(e){} + var key = makeKey(p, new Buffer(m.s, 'hex')); + var iv = new Buffer(m.iv, 'hex'); if(typeof window !== 'undefined'){ // Browser doesn't run createDecipheriv crypto.subtle.importKey('raw', key, 'AES-CBC', false, ['decrypt']) .then(function(aesKey){ crypto.subtle.decrypt({ name: 'AES-CBC', iv: iv - }, aesKey, new Buffer(d.ct, 'base64')).then(function(ct){ + }, aesKey, new Buffer(m.ct, 'base64')).then(function(ct){ var ctUtf8 = new TextDecoder('utf8').decode(ct); - return !ctUtf8.slice ? ctUtf8 : JSON.parse(ctUtf8); + try{ return ctUtf8.slice ? JSON.parse(ctUtf8) : ctUtf8; + }catch(e){ return ctUtf8 } }).then(resolve).catch(function(e){Gun.log(e); reject(e)}); }).catch(function(e){Gun.log(e); reject(e)}); } else { // NodeJS doesn't support crypto.subtle.importKey properly try{ var decipher = nodeCrypto.createDecipheriv(aes.enc, key, iv); - r = decipher.update(d.ct, 'base64', 'utf8') + decipher.final('utf8'); + r = decipher.update(m.ct, 'base64', 'utf8') + decipher.final('utf8'); }catch(e){ Gun.log(e); return reject(e) } resolve(r); } @@ -829,7 +853,7 @@ if(mm.slice){ // Needs to remove previous signature envelope while('SEA[' === m.slice(0,4)){ - try{m = JSON.parse(m.slice(3))[0]; + try{ m = JSON.parse(m.slice(3))[0]; }catch(e){ m = mm; break } } } @@ -845,7 +869,7 @@ if(!m){ return resolve() } if(!m.slice || 'SEA[' !== m.slice(0,4)){ return resolve(m) } m = m.slice(3); - try{m = !m.slice ? m : JSON.parse(m); + try{ m = m.slice ? JSON.parse(m) : m; }catch(e){ return reject(e) } m = m || ''; SEA.verify(m[0], p, m[1]).then(function(ok){ diff --git a/test/common.js b/test/common.js index 7424fd50..ff82ea69 100644 --- a/test/common.js +++ b/test/common.js @@ -186,7 +186,7 @@ describe('Performance', function(){ return; // performance tests describe('Gun', function(){ var t = {}; - describe('Utility', function(){ + !Gun.SEA && describe('Utility', function(){ var u; /* // causes logger to no longer log. it('verbose console.log debugging', function(done) { diff --git a/test/sea.js b/test/sea.js index 982f03ab..8c456379 100644 --- a/test/sea.js +++ b/test/sea.js @@ -212,18 +212,39 @@ Gun().user && describe('Gun', function(){ var user = gun.user(); Gun.log.off = true; // Supress all console logging - // Simulate browser reload - gun.back(-1)._.user = gun.back(-1).chain(); + var throwOutUser = function(wipeStorageData){ + // Get rid of authenticated Gun user + var user = gun.back(-1)._.user; + // TODO: is this correct way to 'logout' user from Gun.User ? + [ 'alias', 'sea', 'pub' ].forEach(function(key){ + delete user._[key]; + }); + user._.is = user.is = {}; + + if(wipeStorageData){ + // ... and persisted session + localStorage.removeItem('remember') + sessionStorage.removeItem('remember'); + sessionStorage.removeItem('alias'); + } + }; ['callback', 'Promise'].forEach(function(type){ describe(type+':', function(){ + beforeEach(function(done){ + // Simulate browser reload + throwOutUser(true); + done(); + }); + describe('create', function(){ + it('new', function(done){ var check = function(ack){ try{ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); + expect(ack).to.have.keys([ 'ok', 'pub' ]); }catch(e){ done(e); return }; done(); }; @@ -234,6 +255,7 @@ Gun().user && describe('Gun', function(){ user.create(alias+type, pass).then(check).catch(done); } }); + it('conflict', function(done){ Gun.log.off = true; // Supress all console logging var check = function(ack){ @@ -243,6 +265,7 @@ Gun().user && describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).not.to.be(undefined); expect(ack.err).not.to.be(''); + expect(ack.err.toLowerCase().indexOf('already created')).not.to.be(-1); }catch(e){ done(e); return }; done(); }; @@ -297,6 +320,8 @@ Gun().user && describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).to.not.be(undefined); expect(ack.err).to.not.be(''); + expect(ack.err.toLowerCase().indexOf('failed to decrypt secret')) + .not.to.be(-1); }catch(e){ done(e); return }; done(); }; @@ -317,6 +342,7 @@ Gun().user && describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).to.not.be(undefined); expect(ack.err).to.not.be(''); + expect(ack.err.toLowerCase().indexOf('no user')).not.to.be(-1); }catch(e){ done(e); return }; done(); }; @@ -355,6 +381,8 @@ Gun().user && describe('Gun', function(){ expect(ack).to.have.key('err'); expect(ack.err).to.not.be(undefined); expect(ack.err).to.not.be(''); + expect(ack.err.toLowerCase().indexOf('failed to decrypt secret')) + .not.to.be(-1); }catch(e){ done(e); return }; done(); }; @@ -391,7 +419,7 @@ Gun().user && describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); }catch(e){ done(e); return }; done(); }; @@ -399,7 +427,7 @@ Gun().user && describe('Gun', function(){ user.create(usr, pass).then(function(ack){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); + expect(ack).to.have.keys([ 'ok', 'pub' ]); user.auth(usr, pass).then(function(usr){ try{ expect(usr).to.not.be(undefined); @@ -427,7 +455,7 @@ Gun().user && describe('Gun', function(){ }catch(e){ done(e); return }; done(); }; - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); if(type === 'callback'){ user.leave(check); } else { @@ -443,7 +471,7 @@ Gun().user && describe('Gun', function(){ return user.create(a, p).then(function(ack){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); + expect(ack).to.have.keys([ 'ok', 'pub' ]); return ack; }); }; @@ -454,7 +482,7 @@ Gun().user && describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); }catch(e){ done(e); return }; done(); }; @@ -497,6 +525,7 @@ Gun().user && describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('put'); expect(ack).to.have.key('err'); + expect(ack.err.toLowerCase().indexOf('no user')).not.to.be(-1); }catch(e){ done(e); return }; done(); }; @@ -511,29 +540,51 @@ Gun().user && describe('Gun', function(){ }); describe('recall', function(){ - var doCheck = function(done, hasPin){ - return function(){ - expect(root.sessionStorage.getItem('user')).to.not.be(undefined); - expect(root.sessionStorage.getItem('user')).to.not.be(''); - expect(root.sessionStorage.getItem('remember')).to.not.be(undefined); - expect(root.sessionStorage.getItem('remember')).to.not.be(''); + var doCheck = function(done, hasPin, wantAck){ + expect(typeof done).to.be('function'); + return function(ack){ + var user = root.sessionStorage.getItem('user'); + var sRemember = root.sessionStorage.getItem('remember'); + expect(user).to.not.be(undefined); + expect(user).to.not.be(''); + expect(sRemember).to.not.be(undefined); + expect(sRemember).to.not.be(''); if(hasPin){ - expect(root.localStorage.getItem('remember')).to.not.be(undefined); - expect(root.localStorage.getItem('remember')).to.not.be(''); + var lRemember = root.localStorage.getItem('remember'); + expect(lRemember).to.not.be(undefined); + expect(lRemember).to.not.be(''); } - return done(); + // NOTE: done can be Promise returning function + var ret; + if (wantAck) { + [ 'err', 'pub', 'sea', 'alias', 'put' ].forEach(function(key){ + if(typeof ack[key] !== 'undefined'){ + (ret = ret || {})[key] = ack[key]; + } + }); + } + return done(ret); }; }; // This re-constructs 'remember-me' data modified by manipulate func var manipulateStorage = function(manipulate, hasPin){ + expect(typeof manipulate).to.be('function'); + // We'll use Gun internal User data var usr = gun.back(-1)._.user; + expect(usr).to.not.be(undefined); + expect(usr).to.have.key('_'); + expect(usr._).to.have.keys([ 'pub', 'sea' ]); + // ... to validate 'remember' data var remember = hasPin ? localStorage.getItem('remember') : sessionStorage.getItem('remember'); return Gun.SEA.read(remember, usr._.pub).then(function(props){ - props = manipulate(JSON.parse(props)); + try{ props && (props = JSON.parse(props)) }catch(e){} + return props; + }).then(manipulate).then(function(props){ + expect(props).to.not.be(undefined); + expect(props).to.not.be(''); return Gun.SEA.write(JSON.stringify(props), usr._.sea) .then(function(remember){ - // remember = JSON.stringify(remember); return hasPin ? sessionStorage.setItem('remember', remember) : sessionStorage.setItem('remember', remember); }); @@ -581,9 +632,9 @@ Gun().user && describe('Gun', function(){ user.auth(alias+type, pass+' new').then(doCheck(done)).catch(done); }; if(type === 'callback'){ - user.recall(12 * 60 * 60, doAction, {session: false}); + user.recall(12 * 60, doAction, {session: false}); } else { - user.recall(12 * 60 * 60, {session: false}).then(doAction) + user.recall(12 * 60, {session: false}).then(doAction) .catch(done); } }); @@ -607,7 +658,7 @@ Gun().user && describe('Gun', function(){ user.leave().then(function(ack){ try{ expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); expect(root.sessionStorage.getItem('user')).to.not.be(sUser); expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); }catch(e){ done(e); return }; @@ -615,7 +666,7 @@ Gun().user && describe('Gun', function(){ root.sessionStorage.setItem('user', sUser); root.sessionStorage.setItem('remember', sRemember); - user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) + user.recall(12 * 60, {session: false}).then(doCheck(done)) .catch(done); }).catch(done); }).catch(done); @@ -645,7 +696,7 @@ Gun().user && describe('Gun', function(){ user.leave().then(function(ack){ try{ expect(ack).to.have.key('ok'); - expect(gun.back(-1)._.user).to.not.have.keys(['sea', 'pub']); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); expect(root.sessionStorage.getItem('user')).to.not.be(sUser); expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); expect(root.localStorage.getItem('remember')).to.not.be(lRemember); @@ -655,34 +706,115 @@ Gun().user && describe('Gun', function(){ root.sessionStorage.setItem('remember', sRemember); root.localStorage.setItem('remember', lRemember); - user.recall(12 * 60 * 60, {session: false}).then(doCheck(done)) + user.recall(12 * 60, {session: false}).then(doCheck(done)) .catch(done); }).catch(done); }).catch(done); }); - it.skip('invalid sessionStorage session'); - it.skip('valid localStorage data but not in sessionStorage'); + it('valid localStorage session bootstraps using PIN', function(done){ + user.recall(12 * 60, {session: false}).then(function(){ + return user.auth(alias+type, pass+' new', { pin: 'PIN' }); + }).then(doCheck(function(){ + // Let's save remember props + var sUser = root.sessionStorage.getItem('user'); + var sRemember = root.sessionStorage.getItem('remember'); + var lRemember = root.localStorage.getItem('remember'); + // Then logout user + return user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); + expect(root.sessionStorage.getItem('user')).to.not.be(sUser); + expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); + expect(root.localStorage.getItem('remember')).to.not.be(lRemember); + }catch(e){ done(e); return }; + // Then restore localStorage remember data, skip sessionStorage + root.localStorage.setItem('remember', lRemember); + }); + }, true)).then(function(){ + // Then try to recall authentication + return user.recall(12 * 60, {session: false}).then(function(props){ + try{ + expect(props).to.not.be(undefined); + expect(props).to.not.be(''); + expect(props).to.have.key('err'); + // Which fails to missing PIN + expect(props.err.toLowerCase() + .indexOf('missing pin')).not.to.be(-1); + }catch(e){ done(e); return }; + // Ok, time to try auth with alias & PIN + return user.auth(alias+type, undefined, { pin: 'PIN' }); + }); + }).then(doCheck(function(usr){ + try{ + expect(usr).to.not.be(undefined); + expect(usr).to.not.be(''); + expect(usr).to.not.have.key('err'); + expect(usr).to.have.key('put'); + }catch(e){ done(e); return }; + // We've recalled authenticated session using alias & PIN! + done(); + }, true, true)).catch(done); + }); - it('expired session', function(done){ + it('valid localStorage session fails to bootstrap using wrong PIN', + function(done){ + user.recall(12 * 60, {session: false}).then(function(){ + return user.auth(alias+type, pass+' new', { pin: 'PIN' }); + }).then(doCheck(function(){ + var sUser = root.sessionStorage.getItem('user'); + var sRemember = root.sessionStorage.getItem('remember'); + var lRemember = root.localStorage.getItem('remember'); + return user.leave().then(function(ack){ + try{ + expect(ack).to.have.key('ok'); + expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); + expect(root.sessionStorage.getItem('user')).to.not.be(sUser); + expect(root.sessionStorage.getItem('remember')).to.not.be(sRemember); + expect(root.localStorage.getItem('remember')).to.not.be(lRemember); + }catch(e){ done(e); return }; + root.localStorage.setItem('remember', lRemember); + }); + }, true)).then(function(){ + // Ok, time to try auth with alias & PIN + return user.auth(alias+type, undefined, { pin: 'PiN' }); + }).then(function(){ + done('Unexpected login success!'); + }).catch(function(ack){ + try{ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.have.key('err'); + expect(ack.err.toLowerCase() + .indexOf('no session data for alias & pin')).not.to.be(-1); + }catch(e){ done(e); return }; + // We've recalled authenticated session using alias & PIN! + done(); + }); + }); + + it('expired session fails to bootstrap', function(done){ user.recall(60, {session: true}).then(function(){ return user.auth(alias+type, pass+' new'); }).then(doCheck(function(){ - // Storage data OK, let's back up time of auth 65 minutes + // Storage data OK, let's back up time of auth to exp + 65 seconds return manipulateStorage(function(props){ - props.iat -= 65 * 60; - return props; + var ret = Object.assign({}, props, { iat: props.iat - 65 - props.exp }); + return ret; }, false); })).then(function(){ // Simulate browser reload - gun.back(-1)._.user = gun.back(-1).chain(); - // TODO: re-make sessionStorage.remember to 65 seconds past - user.recall(60, {session: true}).then(function(props){ - expect(props).to.not.be(undefined); - expect(props).to.not.be(''); - expect(props).to.have.key('err'); - expect(props.err).to.not.be(undefined); - expect(props.err).to.not.be(''); + throwOutUser(); + user.recall(60, {session: true}).then(function(ack){ + expect(ack).to.not.be(undefined); + expect(ack).to.not.be(''); + expect(ack).to.not.have.keys([ 'pub', 'sea' ]); + expect(ack).to.have.key('err'); + expect(ack.err).to.not.be(undefined); + expect(ack.err).to.not.be(''); + expect(ack.err.toLowerCase() + .indexOf('no authentication session')).not.to.be(-1); done(); }).catch(done); }).catch(done); @@ -724,7 +856,7 @@ Gun().user && describe('Gun', function(){ try{ expect(ack).to.have.key('ok'); }catch(e){ done(e); return }; - gun.back(-1)._.user = gun.back(-1).chain(); + throwOutUser(); }); }).then(function(){ // Simulate browser reload @@ -739,13 +871,35 @@ Gun().user && describe('Gun', function(){ expect(props).to.have.key('err'); expect(props.err).to.not.be(undefined); expect(props.err).to.not.be(''); + expect(props.err.toLowerCase() + .indexOf('no authentication session')).not.to.be(-1); done(); }).catch(done); }).catch(done); }).catch(done); }); - it.skip('no session'); + it('recall hook session manipulation', function(done){ + var exp; + var hookFunc = function(props){ + exp = props.exp * 2; + var ret = Object.assign({}, props, { exp: exp }); + return (type === 'callback' && ret) || new Promise(function(resolve){ + resolve(ret); + }); + }; + user.recall(60, { session: true, hook: hookFunc }).then(function(){ + return user.auth(alias+type, pass); + }).then(function(){ + // Storage data OK, let's back up time of auth 65 minutes + return manipulateStorage(function(props){ + expect(props).to.not.be(undefined); + expect(props).to.have.key('exp'); + expect(props.exp).to.be(exp); + return props; + }, false); + }).then(done).catch(done); + }); }); describe('alive', function(){ @@ -755,16 +909,16 @@ Gun().user && describe('Gun', function(){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); - expect(ack).to.have.keys(['sea', 'pub']); + expect(ack).to.have.keys([ 'sea', 'pub' ]); }catch(e){ done(e); return }; done(); }; - var usr = alias+type+'alive'; - user.create(usr, pass).then(function(ack){ + var aliveUser = alias+type+'alive'; + user.create(aliveUser, pass).then(function(ack){ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); - expect(ack).to.have.keys(['ok','pub']); - user.auth(usr, pass, { pin: 'PIN' }).then(function(usr){ + expect(ack).to.have.keys([ 'ok', 'pub' ]); + user.auth(aliveUser, pass, { pin: 'PIN' }).then(function(usr){ try{ expect(usr).to.not.be(undefined); expect(usr).to.not.be(''); @@ -786,8 +940,9 @@ Gun().user && describe('Gun', function(){ try{ expect(ack).to.not.be(undefined); expect(ack).to.not.be(''); - expect(ack).to.not.have.keys(['sea', 'pub']); + expect(ack).to.not.have.keys([ 'sea', 'pub' ]); expect(ack).to.have.key('err'); + expect(ack.err.toLowerCase().indexOf('no session')).not.to.be(-1); }catch(e){ done(e); return }; done(); }; @@ -797,11 +952,15 @@ Gun().user && describe('Gun', function(){ }).catch(check); }).catch(done); }); - - it.skip('recall hook session manipulation'); }); }); }); + + process.env.SEA_CHANNEL && describe('User channel', function(){ + it.skip('create'); + it.skip('add member'); + }); + Gun.log.off = false; }); }); From 4307220438f6a64b17f6f5d802998a83324cc593 Mon Sep 17 00:00:00 2001 From: mhelander Date: Fri, 22 Sep 2017 09:45:32 +0300 Subject: [PATCH 10/29] Merge conflight resolving bug fixed --- sea.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/sea.js b/sea.js index cedce29a..5a105a90 100644 --- a/sea.js +++ b/sea.js @@ -89,19 +89,6 @@ }); } - var _initial_authsettings = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - session: true, - hook: function(props){ return props } // { iat, exp, alias, remember } - // or return new Promise(function(resolve, reject){(resolve(props))}) - } - // These are used to persist user's authentication "session" - var authsettings = { - validity: _initial_authsettings.validity, - session: _initial_authsettings.session, - hook: _initial_authsettings.hook - }; - // let's extend the gun chain with a `user` function. // only one user can be logged in at a time, per gun instance. Gun.chain.user = function(){ @@ -438,7 +425,7 @@ // This internal func executes logout actions function authleave(root, alias){ return function(resolve, reject){ - var user = root._.user;m + var user = root._.user; alias = alias || (user._ && user._.alias); var doIt = function(){ // TODO: is this correct way to 'logout' user from Gun.User ? From f1e7e68ae9084d7bf5a0f6490661299a38b7ac25 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 12 Oct 2017 09:30:38 +0300 Subject: [PATCH 11/29] Updated NodeJS --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c5be5fd..97c9a153 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/amark/gun#readme", "engines": { - "node": ">=0.6.6" + "node": ">=8.6.0" }, "dependencies": { "@std/esm": "^0.8.3", From c76ca389a77524ab99bded096186263005553ebe Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 12 Oct 2017 11:14:19 +0300 Subject: [PATCH 12/29] Updated NodeJS for Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e0fda2bf..1df94fda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - 8.6.0 - 0.11 - 0.12 - 4.0 From a4ed023ff9fca44606ff22a1388f29bf442c495e Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 12 Oct 2017 11:18:41 +0300 Subject: [PATCH 13/29] Removed old NodeJS lines to skip Travis testing --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1df94fda..24a37a52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: node_js node_js: - 8.6.0 - - 0.11 - - 0.12 - 4.0 - 4.2 - 5.0 From 79aaa861cc72f3c9c414211b490b47d77ff564a5 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 12 Oct 2017 11:28:04 +0300 Subject: [PATCH 14/29] Added SEA tests by running mocha twice --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bd2a660..8bddad73 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "node examples/http.js 8080", "prepublish": "npm run unbuild", - "test": "mocha", + "test": "mocha && SEA=true mocha", "e2e": "mocha e2e/distributed.js", "docker": "hooks/build", "unbuild": "node lib/unbuild.js && uglifyjs gun.js -o gun.min.js -c -m" From 4910d472219450fde24ece6e663b8529f97c11a6 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 12 Oct 2017 11:37:59 +0300 Subject: [PATCH 15/29] Removed old 4 & 5 NodeJS versions to pass Travis testing --- .travis.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24a37a52..99eac98a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ -language: node_js -node_js: - - 8.6.0 - - 4.0 - - 4.2 - - 5.0 - - 6.8 - - 7.9 +language: node_js +node_js: + - 8.6.0 + - 6.8 + - 7.9 From fd82f2c73a4d40367106dd51742041d5fd9ba148 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 12 Oct 2017 11:45:26 +0300 Subject: [PATCH 16/29] Removed old 7.9 NodeJS version too to pass Travis testing --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 99eac98a..476ba329 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: node_js node_js: - 8.6.0 - 6.8 - - 7.9 From 59e5b01b1727e1fa6d72a7f7fee97d9f717a9cf2 Mon Sep 17 00:00:00 2001 From: mhelander Date: Mon, 15 Jan 2018 20:32:50 +0200 Subject: [PATCH 17/29] Buffer module replace by safe-buffer in SEA --- gun.min.js | 2 +- package.json | 3 ++- sea.js | 14 +++++++------- src/chain.js | 8 ++++++-- src/root.js | 3 ++- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/gun.min.js b/gun.min.js index 4052af51..0a4c40f1 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1 +1 @@ -!function(){function t(n){function o(t){return t.split("/").slice(-1).toString().replace(".js","")}return n.slice?t[o(n)]:function(e,i){n(e={exports:{}}),t[o(i)]=e.exports}}var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global);var o=(n=n||{}).console||{log:function(){}};if("undefined"!=typeof module)var e=module;t(function(t){var n={};n.fns=n.fn={is:function(t){return!!t&&"function"==typeof t}},n.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},n.num={is:function(t){return!e(t)&&(t-parseFloat(t)+1>=0||1/0===t||-1/0===t)}},n.text={is:function(t){return"string"==typeof t}},n.text.ify=function(t){return n.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},n.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";t>0;)o+=n.charAt(Math.floor(Math.random()*n.length)),t--;return o},n.text.match=function(t,o){var e=!1;if(t=t||"",o=n.text.is(o)?{"=":o}:o||{},n.obj.has(o,"~")&&(t=t.toLowerCase(),o["="]=(o["="]||o["~"]).toLowerCase()),n.obj.has(o,"="))return t===o["="];if(n.obj.has(o,"*")){if(t.slice(0,o["*"].length)!==o["*"])return!1;e=!0,t=t.slice(o["*"].length)}if(n.obj.has(o,"!")){if(t.slice(-o["!"].length)!==o["!"])return!1;e=!0}if(n.obj.has(o,"+")&&n.list.map(n.list.is(o["+"])?o["+"]:[o["+"]],function(n){if(!(t.indexOf(n)>=0))return!0;e=!0}))return!1;if(n.obj.has(o,"-")&&n.list.map(n.list.is(o["-"])?o["-"]:[o["-"]],function(n){if(!(t.indexOf(n)<0))return!0;e=!0}))return!1;if(n.obj.has(o,">")){if(!(t>o[">"]))return!1;e=!0}if(n.obj.has(o,"<")){if(!(to?1:0):0}},n.list.map=function(t,n,o){return a(t,n,o)},n.list.index=1,n.obj={is:function(t){return!!t&&(t instanceof Object&&t.constructor===Object||"Object"===Object.prototype.toString.call(t).match(/^\[object (\w+)\]$/)[1])}},n.obj.put=function(t,n,o){return(t||{})[n]=o,t},n.obj.has=function(t,n){return t&&Object.prototype.hasOwnProperty.call(t,n)},n.obj.del=function(t,n){if(t)return t[n]=null,delete t[n],t},n.obj.as=function(t,n,o,e){return t[n]=t[n]||(e===o?{}:o)},n.obj.ify=function(t){if(r(t))return t;try{t=JSON.parse(t)}catch(n){t={}}return t},function(){function t(t,n){u(this,n)&&o!==this[n]||(this[n]=t)}var o;n.obj.to=function(n,o){return o=o||{},a(n,t,o),o}}(),n.obj.copy=function(t){return t?JSON.parse(JSON.stringify(t)):t},function(){function t(t,n){var o=this.n;if(!o||!(n===o||r(o)&&u(o,n)))return!!n||void 0}n.obj.empty=function(n,o){return!n||!a(n,t,{n:o})}}(),function(){function t(n,o){if(2===arguments.length)return t.r=t.r||{},void(t.r[n]=o);t.r=t.r||[],t.r.push(n)}var i=Object.keys;n.obj.map=function(a,s,f){var c,l,p,g,d,h=0,v=o(s);if(t.r=null,i&&r(a)&&(g=i(a),d=!0),e(a)||g)for(l=(g||a).length;h",o.drift=0,o.is=function(t,n,e){var i=n&&t&&t[k]&&t[k][o._]||e;if(i)return b(i=i[n])?i:-1/0},o.lex=function(){return o().toString(36).replace(".","")},o.ify=function(t,n,e,r,u){if(!t||!t[k]){if(!u)return;t=i.soul.ify(t,u)}var a=g(t[k],o._);return l!==n&&n!==k&&(b(e)&&(a[n]=e),l!==r&&(t[n]=r)),t},o.to=function(t,n,e){var r=t[n];return h(r)&&(r=_(r)),o.ify(e,n,o.is(t,n),r,i.soul(t))},function(){function t(t,n){k!==n&&o.ify(this.o,n,this.s)}o.map=function(n,e,i){var r,u=h(u=n||e)?u:null;return n=m(n=n||e)?n:null,u&&!n?(e=b(e)?e:o(),u[k]=u[k]||{},v(u,t,{o:u,s:e}),u):(i=i||h(e)?e:r,e=b(e)?e:o(),function(o,u,a,s){if(!n)return t.call({o:a,s:e},o,u),o;n.call(i||this||{},o,u,a,s),d(a,u)&&r===a[u]||t.call({o:a,s:e},o,u)})}}();var l,p=e.obj,g=p.as,d=p.has,h=p.is,v=p.map,_=p.copy,b=e.num.is,m=e.fn.is,k=i._;n.exports=o})(t,"./state"),t(function(n){var o=t("./type"),e=t("./val"),i=t("./node"),r={};!function(){function t(t,o){if(!t||o!==i.soul(t)||!i.is(t,this.fn,this.as))return!0;this.cb&&(n.n=t,n.as=this.as,this.cb.call(n.as,t,o,n))}function n(t){t&&i.is(n.n,t,n.as)}r.is=function(n,o,e,i){return!(!n||!s(n)||l(n))&&!g(n,t,{cb:o,fn:e,as:i})}}(),function(){function t(t,r){var u;return(u=l(t,r))?u:(r.env=t,r.soul=o,i.ify(r.obj,n,r)&&(t.graph[e.rel.is(r.rel)]=r.node),r)}function n(n,o,r){var s,l,p=this,g=p.env;if(i._===o&&c(n,e.rel._))return r._;if(s=a(n,o,r,p,g)){if(o||(p.node=p.node||r||{},c(n,i._)&&(p.node._=d(n._)),p.node=i.soul.ify(p.node,e.rel.is(p.rel)),p.rel=p.rel||e.rel.ify(i.soul(p.node))),(l=g.map)&&(l.call(g.as||{},n,o,r,p),c(r,o))){if(n=r[o],u===n)return void f(r,o);if(!(s=a(n,o,r,p,g)))return}if(!o)return p.node;if(!0===s)return n;if((l=t(g,{obj:n,path:p.path.concat(o)})).node)return l.rel}}function o(t){var n=this,o=e.rel.is(n.rel),r=n.env.graph;n.rel=n.rel||e.rel.ify(t),n.rel[e.rel._]=t,n.node&&n.node[i._]&&(n.node[i._][e.rel._]=t),c(r,o)&&(r[t]=r[o],f(r,o))}function a(t,n,o,i,r){var u;return!!e.is(t)||(s(t)?1:(u=r.invalid)?(t=u.call(r.as||{},t,n,o),a(t,n,o,i,r)):void(r.err="Invalid value at '"+i.path.concat(n).join(".")+"'!"))}function l(t,n){for(var o,e=t.seen,i=e.length;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}r.ify=function(n,o,i){var r={path:[],obj:n};return o?"string"==typeof o?o={soul:o}:o instanceof Function&&(o.map=o):o={},o.soul&&(r.rel=e.rel.ify(o.soul)),o.graph=o.graph||{},o.seen=o.seen||[],o.as=o.as||i,t(o,r),o.root=r.node,o.graph}}(),r.node=function(t){var n=i.soul(t);if(n)return p({},n,t)},function(){function t(t,n){var o,u;if(i._!==n)(o=e.rel.is(t))?(u=this.opt.seen[o])?this.obj[n]=u:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(l(t,e.rel._))return;this.obj[n]=d(t)}}r.to=function(n,o,e){if(n){var i={};return e=e||{seen:{}},g(n[o],t,{obj:i,graph:n,opt:e}),i}}}();o.fn.is;var u,a=o.obj,s=a.is,f=a.del,c=a.has,l=a.empty,p=a.put,g=a.map,d=a.copy;n.exports=r})(t,"./graph"),t(function(n){t("./onto"),n.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."}),i.off()},(this.opt||{}).lack||9e3),o}}})(t,"./ask"),t(function(n){var o=t("./type"),e=o.time.is;n.exports=function(t){var n={s:{}};return t=t||{max:1e3,age:9e3},n.check=function(t){return!!n.s[t]&&n.track(t)},n.track=function(i){return n.s[i]=e(),n.to||(n.to=setTimeout(function(){o.obj.map(n.s,function(i,r){t.age>e()-i||o.obj.del(n.s,r)}),n.to=null},t.age)),i},n}})(t,"./dup"),t(function(n){function i(t){return t instanceof i?(this._={gun:this}).gun:this instanceof i?i.create(this._={gun:this,opt:t}):new i(t)}i.is=function(t){return t instanceof i},i.version=.9,i.chain=i.prototype,i.chain.toJSON=function(){};var r=t("./type");r.obj.to(r,i),i.HAM=t("./HAM"),i.val=t("./val"),i.node=t("./node"),i.state=t("./state"),i.graph=t("./graph"),i.on=t("./onto"),i.ask=t("./ask"),i.dup=t("./dup"),i._={node:i.node._,soul:i.val.rel._,state:i.state._,field:".",value:"="},function(){function t(t){var n,o=this.as,e=o.gun;(n=t["#"])||(n=t["#"]=c(9)),o.dup.check(n)||(o.dup.track(n),t=d(t),o.ask(t["@"],t)||(t.get&&i.on.get(t,e),t.put&&i.on.put(t,e)),o.on("out",t))}i.create=function(n){n.root=n.root||n.gun,n.graph=n.graph||{},n.on=n.on||i.on,n.ask=n.ask||i.ask,n.dup=n.dup||i.dup();var o=n.gun.opt(n.opt);return n.once||(n.on("in",t,n),n.on("out",t,n)),n.once=1,o}}(),function(){function t(t,n,o,e){var r=this,u=i.state.is(o,n);if(!u)return r.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=r.graph[e]||m,s=i.state.is(a,n,!0),f=a[n],c=i.HAM(r.machine,u,s,t,f);c.incoming?(r.put[e]=i.state.to(o,n,r.put[e]),(r.diff||(r.diff={}))[e]=i.state.to(o,n,r.diff[e]),r.souls[e]=!0):c.defer&&(r.defer=u<(r.defer||1/0)?u:r.defer)}function n(t,n){var i=this,u=i.gun._,a=(u.next||m)[n];if(a){var s=i.map[n]={put:t,get:n,gun:a},f={ctx:i,msg:s};i.async=!!u.tag.node,i.ack&&(s["@"]=i.ack),h(t,o,f),i.async&&(i.and||u.on("node",function(t){this.to.next(t),t===i.map[t.get]&&(i.souls[t.get]=!1,h(t.put,e,t),h(i.souls,function(t){if(t)return t})||i.c||(i.c=1,this.off(),h(i.map,r,i)))}),i.and=!0,u.on("node",s))}}function o(t,n){var o=this.ctx,e=o.graph,r=this.msg,u=r.get,a=r.put,s=r.gun._;e[u]=i.state.to(a,n,e[u]),o.async||(s.put=i.state.to(a,n,s.put))}function e(t,n){var o=this,e=o.put,r=o.gun._;r.put=i.state.to(e,n,r.put)}function r(t,n){t.gun&&(t.gun._.root._.stop={},t.gun._.on("in",t),t.gun._.root._.stop={})}i.on.put=function(o,e){var a=e._,s={gun:e,graph:a.graph,put:{},map:{},souls:{},machine:i.state(),ack:o["@"]};if(i.graph.is(o.put,null,t,s)||(s.err="Error: Invalid graph!"),s.err)return a.on("in",{"@":o["#"],err:i.log(s.err)});h(s.put,n,s),s.async||h(s.map,r,s),u!==s.defer&&setTimeout(function(){i.on.put(o,e)},s.defer-a.machine),s.diff&&a.on("put",d(o,{put:s.diff}))},i.on.get=function(t,n){var o=n._,e=t.get[_],r=o.graph[e],u=t.get[b],a=((o.next||(o.next={}))[e]||m)._;if(!r||!a)return o.on("get",t);if(u){if(!g(r,u))return o.on("get",t);r=i.state.to(r,u)}else r=i.obj.copy(r);r=i.graph.node(r),o.on("in",{"@":t["#"],put:r,gun:n}),o.on("get",t)}}(),i.chain.opt=function(t){t=t||{};var n=this,o=n._,e=t.peers||t;return p(t)||(t={}),p(o.opt)||(o.opt=t),f(e)&&(e=[e]),a(e)&&(e=h(e,function(t,n,o){o(t,{url:t})}),p(o.opt.peers)||(o.opt.peers={}),o.opt.peers=d(e,o.opt.peers)),o.opt.peers=o.opt.peers||{},d(t,o.opt),i.on("opt",o),o.opt.uuid=o.opt.uuid||function(){return v()+c(12)},n};var u,a=i.list.is,s=i.text,f=s.is,c=s.random,l=i.obj,p=l.is,g=l.has,d=l.to,h=l.map,v=(l.copy,i.state.lex),_=i._.soul,b=i._.field,m=(i._.node,i.val.rel.is,{});o.debug=function(t,n){return o.debug.i&&t===o.debug.i&&o.debug.i++&&(o.log.apply(o,arguments)||n)},i.log=function(){return!i.log.off&&o.log.apply(o,arguments),[].slice.call(arguments).join(" ")},i.log.once=function(t,n,o){return(o=i.log.once)[t]=o[t]||0,o[t]++||i.log(n)},i.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=i),void 0!==e&&(e.exports=i),n.exports=i})(t,"./root"),t(function(n){var o=t("./root");o.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root;if(1===t)return this._.back||this;var r=this,u=r._;if("string"==typeof t&&(t=t.split(".")),!(t instanceof Array)){if(t instanceof Function){for(var a,s={back:r};(s=s.back)&&(s=s._)&&!(a=t(s,n)););return a}return o.num.is(t)?u.back.back(t-1):this}var f=0,c=t.length,s=u;for(f;f=(n.batch||1e3))return s();o||(o=setTimeout(s,n.wait||1))}),t.on("get",function(o){this.to.next(o);var e,i,r=o.get;if(r&&(e=r[Gun._.soul])){var a=r["."];(i=u[e]||void 0)&&a&&(i=Gun.state.to(i,a)),(i||Gun.obj.empty(n.peers))&&t.on("in",{"@":o["#"],put:Gun.graph.node(i),how:"lS"})}});var a=function(t,n,o,e){u[e]=Gun.state.to(o,n,u[e])},s=function(){var a;r=0,clearTimeout(o),o=!1;var s=i;i={};try{e.setItem(n.file,JSON.stringify(u))}catch(t){Gun.log(a=t||"localStorage failure")}(a||Gun.obj.empty(n.peers))&&Gun.obj.map(s,function(n,o){t.on("in",{"@":o,err:a,ok:0})})}}})}})(t,"./adapters/localStorage"),t(function(n){var o,e=t("./index");"undefined"!=typeof WebSocket?o=WebSocket:("undefined"!=typeof webkitWebSocket&&(o=webkitWebSocket),"undefined"!=typeof mozWebSocket&&(o=mozWebSocket)),e.on("opt",function(t){function n(t){var n=this,o=c,e=t.wire||u(t,n);e&&(e.readyState!==e.OPEN?(t.queue=t.queue||[]).push(o):e.send(o))}function r(t,n,o){if(o&&t){try{t=JSON.parse(t.data||t)}catch(t){}if(t instanceof Array)for(var e,u=0;e=t[u++];)r(e,n,o);else 1==f.who&&(t.ws=i),o.on("in",t)}}function u(i,u){if(i&&i.url){var s=i.url.replace("http","ws"),f=i.wire=new o(s);return f.onclose=function(){t.on("bye",i),a(i,u)},f.onerror=function(t){a(i,u),t&&t.code},f.onopen=function(){t.on("hi",i);var o=i.queue;i.queue=[],e.obj.map(o,function(t){c=t,n.call(u,i)})},f.onmessage=function(t){r(t,i,u)},f}}function a(t,n){clearTimeout(t.defer),t.defer=setTimeout(function(){u(t,n)},2e3)}this.to.next(t);var s=t.opt;if(!t.once&&o&&!1!==s.WebSocket){var f=s.ws||(s.ws={});if(f.who=0,e.obj.map(s.peers,function(){++f.who}),!t.once){var c;t.on("out",function(o){this.to.next(o),o.ws&&1==f.who||(c=JSON.stringify(o),f.drain?f.drain.push(c):(f.drain=[],setTimeout(function(){if(f.drain){var o=f.drain;f.drain=null,o.length&&(c=JSON.stringify(o),e.obj.map(s.peers,n,t))}},s.wait||1),e.obj.map(s.peers,n,t)))})}}});var i=function(){}})(t,"./adapters/websocket")}(); \ No newline at end of file +!function(){function t(n){function o(t){return t.split("/").slice(-1).toString().replace(".js","")}return n.slice?t[o(n)]:function(e,i){n(e={exports:{}}),t[o(i)]=e.exports}}var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global);var o=(n=n||{}).console||{log:function(){}};if("undefined"!=typeof module)var e=module;t(function(t){var n={};n.fns=n.fn={is:function(t){return!!t&&"function"==typeof t}},n.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},n.num={is:function(t){return!e(t)&&(t-parseFloat(t)+1>=0||1/0===t||-1/0===t)}},n.text={is:function(t){return"string"==typeof t}},n.text.ify=function(t){return n.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},n.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";t>0;)o+=n.charAt(Math.floor(Math.random()*n.length)),t--;return o},n.text.match=function(t,o){var e=!1;if(t=t||"",o=n.text.is(o)?{"=":o}:o||{},n.obj.has(o,"~")&&(t=t.toLowerCase(),o["="]=(o["="]||o["~"]).toLowerCase()),n.obj.has(o,"="))return t===o["="];if(n.obj.has(o,"*")){if(t.slice(0,o["*"].length)!==o["*"])return!1;e=!0,t=t.slice(o["*"].length)}if(n.obj.has(o,"!")){if(t.slice(-o["!"].length)!==o["!"])return!1;e=!0}if(n.obj.has(o,"+")&&n.list.map(n.list.is(o["+"])?o["+"]:[o["+"]],function(n){if(!(t.indexOf(n)>=0))return!0;e=!0}))return!1;if(n.obj.has(o,"-")&&n.list.map(n.list.is(o["-"])?o["-"]:[o["-"]],function(n){if(!(t.indexOf(n)<0))return!0;e=!0}))return!1;if(n.obj.has(o,">")){if(!(t>o[">"]))return!1;e=!0}if(n.obj.has(o,"<")){if(!(to?1:0):0}},n.list.map=function(t,n,o){return a(t,n,o)},n.list.index=1,n.obj={is:function(t){return!!t&&(t instanceof Object&&t.constructor===Object||"Object"===Object.prototype.toString.call(t).match(/^\[object (\w+)\]$/)[1])}},n.obj.put=function(t,n,o){return(t||{})[n]=o,t},n.obj.has=function(t,n){return t&&Object.prototype.hasOwnProperty.call(t,n)},n.obj.del=function(t,n){if(t)return t[n]=null,delete t[n],t},n.obj.as=function(t,n,o,e){return t[n]=t[n]||(e===o?{}:o)},n.obj.ify=function(t){if(r(t))return t;try{t=JSON.parse(t)}catch(n){t={}}return t},function(){function t(t,n){u(this,n)&&o!==this[n]||(this[n]=t)}var o;n.obj.to=function(n,o){return o=o||{},a(n,t,o),o}}(),n.obj.copy=function(t){return t?JSON.parse(JSON.stringify(t)):t},function(){function t(t,n){var o=this.n;if(!o||!(n===o||r(o)&&u(o,n)))return!!n||void 0}n.obj.empty=function(n,o){return!n||!a(n,t,{n:o})}}(),function(){function t(n,o){if(2===arguments.length)return t.r=t.r||{},void(t.r[n]=o);t.r=t.r||[],t.r.push(n)}var i=Object.keys;n.obj.map=function(a,s,f){var c,l,p,g,d,h=0,v=o(s);if(t.r=null,i&&r(a)&&(g=i(a),d=!0),e(a)||g)for(l=(g||a).length;h",o.drift=0,o.is=function(t,n,e){var i=n&&t&&t[k]&&t[k][o._]||e;if(i)return b(i=i[n])?i:-1/0},o.lex=function(){return o().toString(36).replace(".","")},o.ify=function(t,n,e,r,u){if(!t||!t[k]){if(!u)return;t=i.soul.ify(t,u)}var a=g(t[k],o._);return l!==n&&n!==k&&(b(e)&&(a[n]=e),l!==r&&(t[n]=r)),t},o.to=function(t,n,e){var r=t[n];return h(r)&&(r=_(r)),o.ify(e,n,o.is(t,n),r,i.soul(t))},function(){function t(t,n){k!==n&&o.ify(this.o,n,this.s)}o.map=function(n,e,i){var r,u=h(u=n||e)?u:null;return n=m(n=n||e)?n:null,u&&!n?(e=b(e)?e:o(),u[k]=u[k]||{},v(u,t,{o:u,s:e}),u):(i=i||h(e)?e:r,e=b(e)?e:o(),function(o,u,a,s){if(!n)return t.call({o:a,s:e},o,u),o;n.call(i||this||{},o,u,a,s),d(a,u)&&r===a[u]||t.call({o:a,s:e},o,u)})}}();var l,p=e.obj,g=p.as,d=p.has,h=p.is,v=p.map,_=p.copy,b=e.num.is,m=e.fn.is,k=i._;n.exports=o})(t,"./state"),t(function(n){var o=t("./type"),e=t("./val"),i=t("./node"),r={};!function(){function t(t,o){if(!t||o!==i.soul(t)||!i.is(t,this.fn,this.as))return!0;this.cb&&(n.n=t,n.as=this.as,this.cb.call(n.as,t,o,n))}function n(t){t&&i.is(n.n,t,n.as)}r.is=function(n,o,e,i){return!(!n||!s(n)||l(n))&&!g(n,t,{cb:o,fn:e,as:i})}}(),function(){function t(t,r){var u;return(u=l(t,r))?u:(r.env=t,r.soul=o,i.ify(r.obj,n,r)&&(t.graph[e.rel.is(r.rel)]=r.node),r)}function n(n,o,r){var s,l,p=this,g=p.env;if(i._===o&&c(n,e.rel._))return r._;if(s=a(n,o,r,p,g)){if(o||(p.node=p.node||r||{},c(n,i._)&&(p.node._=d(n._)),p.node=i.soul.ify(p.node,e.rel.is(p.rel)),p.rel=p.rel||e.rel.ify(i.soul(p.node))),(l=g.map)&&(l.call(g.as||{},n,o,r,p),c(r,o))){if(n=r[o],u===n)return void f(r,o);if(!(s=a(n,o,r,p,g)))return}if(!o)return p.node;if(!0===s)return n;if((l=t(g,{obj:n,path:p.path.concat(o)})).node)return l.rel}}function o(t){var n=this,o=e.rel.is(n.rel),r=n.env.graph;n.rel=n.rel||e.rel.ify(t),n.rel[e.rel._]=t,n.node&&n.node[i._]&&(n.node[i._][e.rel._]=t),c(r,o)&&(r[t]=r[o],f(r,o))}function a(t,n,o,i,r){var u;return!!e.is(t)||(s(t)?1:(u=r.invalid)?(t=u.call(r.as||{},t,n,o),a(t,n,o,i,r)):void(r.err="Invalid value at '"+i.path.concat(n).join(".")+"'!"))}function l(t,n){for(var o,e=t.seen,i=e.length;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}r.ify=function(n,o,i){var r={path:[],obj:n};return o?"string"==typeof o?o={soul:o}:o instanceof Function&&(o.map=o):o={},o.soul&&(r.rel=e.rel.ify(o.soul)),o.graph=o.graph||{},o.seen=o.seen||[],o.as=o.as||i,t(o,r),o.root=r.node,o.graph}}(),r.node=function(t){var n=i.soul(t);if(n)return p({},n,t)},function(){function t(t,n){var o,u;if(i._!==n)(o=e.rel.is(t))?(u=this.opt.seen[o])?this.obj[n]=u:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(l(t,e.rel._))return;this.obj[n]=d(t)}}r.to=function(n,o,e){if(n){var i={};return e=e||{seen:{}},g(n[o],t,{obj:i,graph:n,opt:e}),i}}}();o.fn.is;var u,a=o.obj,s=a.is,f=a.del,c=a.has,l=a.empty,p=a.put,g=a.map,d=a.copy;n.exports=r})(t,"./graph"),t(function(n){t("./onto"),n.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."}),i.off()},(this.opt||{}).lack||9e3),o}}})(t,"./ask"),t(function(n){var o=t("./type"),e=o.time.is;n.exports=function(t){var n={s:{}};return t=t||{max:1e3,age:9e3},n.check=function(t){return!!n.s[t]&&n.track(t)},n.track=function(i){return n.s[i]=e(),n.to||(n.to=setTimeout(function(){o.obj.map(n.s,function(i,r){t.age>e()-i||o.obj.del(n.s,r)}),n.to=null},t.age)),i},n}})(t,"./dup"),t(function(n){function i(t){return t instanceof i?(this._={gun:this}).gun:this instanceof i?i.create(this._={gun:this,opt:t}):new i(t)}i.is=function(t){return t instanceof i},i.version=.9,i.chain=i.prototype,i.chain.toJSON=function(){};var r=t("./type");r.obj.to(r,i),i.HAM=t("./HAM"),i.val=t("./val"),i.node=t("./node"),i.state=t("./state"),i.graph=t("./graph"),i.on=t("./onto"),i.ask=t("./ask"),i.dup=t("./dup"),i._={node:i.node._,soul:i.val.rel._,state:i.state._,field:".",value:"="},function(){function t(t){var n,o=this.as,e=o.gun;(n=t["#"])||(n=t["#"]=c(9)),o.dup.check(n)||(o.dup.track(n),t=d(t),o.ask(t["@"],t)||(t.get&&i.on.get(t,e),t.put&&i.on.put(t,e)),o.on("out",t))}i.create=function(n){n.root=n.root||n.gun,n.graph=n.graph||{},n.on=n.on||i.on,n.ask=n.ask||i.ask,n.dup=n.dup||i.dup();var o=n.gun.opt(n.opt);return n.once||(n.on("in",t,n),n.on("out",t,n)),n.once=1,o}}(),function(){function t(t,n,o,e){var r=this,u=i.state.is(o,n);if(!u)return r.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=r.graph[e]||m,s=i.state.is(a,n,!0),f=a[n],c=i.HAM(r.machine,u,s,t,f);c.incoming?(r.put[e]=i.state.to(o,n,r.put[e]),(r.diff||(r.diff={}))[e]=i.state.to(o,n,r.diff[e]),r.souls[e]=!0):c.defer&&(r.defer=u<(r.defer||1/0)?u:r.defer)}function n(t,n){var i=this,u=i.gun._,a=(u.next||m)[n];if(a){var s=i.map[n]={put:t,get:n,gun:a},f={ctx:i,msg:s};i.async=!!u.tag.node,i.ack&&(s["@"]=i.ack),h(t,o,f),i.async&&(i.and||u.on("node",function(t){this.to.next(t),t===i.map[t.get]&&(i.souls[t.get]=!1,h(t.put,e,t),h(i.souls,function(t){if(t)return t})||i.c||(i.c=1,this.off(),h(i.map,r,i)))}),i.and=!0,u.on("node",s))}else i.souls[n]=!1}function o(t,n){var o=this.ctx,e=o.graph,r=this.msg,u=r.get,a=r.put,s=r.gun._;e[u]=i.state.to(a,n,e[u]),o.async||(s.put=i.state.to(a,n,s.put))}function e(t,n){var o=this,e=o.put,r=o.gun._;r.put=i.state.to(e,n,r.put)}function r(t,n){t.gun&&(t.gun._.root._.stop={},t.gun._.on("in",t),t.gun._.root._.stop={})}i.on.put=function(o,e){var a=e._,s={gun:e,graph:a.graph,put:{},map:{},souls:{},machine:i.state(),ack:o["@"]};if(i.graph.is(o.put,null,t,s)||(s.err="Error: Invalid graph!"),s.err)return a.on("in",{"@":o["#"],err:i.log(s.err)});h(s.put,n,s),s.async||h(s.map,r,s),u!==s.defer&&setTimeout(function(){i.on.put(o,e)},s.defer-a.machine),s.diff&&a.on("put",d(o,{put:s.diff}))},i.on.get=function(t,n){var o=n._,e=t.get[_],r=o.graph[e],u=t.get[b],a=((o.next||(o.next={}))[e]||m)._;if(!r||!a)return o.on("get",t);if(u){if(!g(r,u))return o.on("get",t);r=i.state.to(r,u)}else r=i.obj.copy(r);r=i.graph.node(r),o.on("in",{"@":t["#"],put:r,gun:n}),o.on("get",t)}}(),i.chain.opt=function(t){t=t||{};var n=this,o=n._,e=t.peers||t;return p(t)||(t={}),p(o.opt)||(o.opt=t),f(e)&&(e=[e]),a(e)&&(e=h(e,function(t,n,o){o(t,{url:t})}),p(o.opt.peers)||(o.opt.peers={}),o.opt.peers=d(e,o.opt.peers)),o.opt.peers=o.opt.peers||{},d(t,o.opt),i.on("opt",o),o.opt.uuid=o.opt.uuid||function(){return v()+c(12)},n};var u,a=i.list.is,s=i.text,f=s.is,c=s.random,l=i.obj,p=l.is,g=l.has,d=l.to,h=l.map,v=(l.copy,i.state.lex),_=i._.soul,b=i._.field,m=(i._.node,i.val.rel.is,{});o.debug=function(t,n){return o.debug.i&&t===o.debug.i&&o.debug.i++&&(o.log.apply(o,arguments)||n)},i.log=function(){return!i.log.off&&o.log.apply(o,arguments),[].slice.call(arguments).join(" ")},i.log.once=function(t,n,o){return(o=i.log.once)[t]=o[t]||0,o[t]++||i.log(n)},i.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=i),void 0!==e&&(e.exports=i),n.exports=i})(t,"./root"),t(function(n){var o=t("./root");o.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root;if(1===t)return this._.back||this;var r=this,u=r._;if("string"==typeof t&&(t=t.split(".")),!(t instanceof Array)){if(t instanceof Function){for(var a,s={back:r};(s=s.back)&&(s=s._)&&!(a=t(s,n)););return a}return o.num.is(t)?u.back.back(t-1):this}var f=0,c=t.length,s=u;for(f;f=(n.batch||1e3))return s();o||(o=setTimeout(s,n.wait||1))}),t.on("get",function(o){this.to.next(o);var e,i,r=o.get;if(r&&(e=r[Gun._.soul])){var a=r["."];(i=u[e]||void 0)&&a&&(i=Gun.state.to(i,a)),(i||Gun.obj.empty(n.peers))&&t.on("in",{"@":o["#"],put:Gun.graph.node(i),how:"lS"})}});var a=function(t,n,o,e){u[e]=Gun.state.to(o,n,u[e])},s=function(){var a;r=0,clearTimeout(o),o=!1;var s=i;i={};try{e.setItem(n.file,JSON.stringify(u))}catch(t){Gun.log(a=t||"localStorage failure")}(a||Gun.obj.empty(n.peers))&&Gun.obj.map(s,function(n,o){t.on("in",{"@":o,err:a,ok:0})})}}})}})(t,"./adapters/localStorage"),t(function(n){var o,e=t("./index");"undefined"!=typeof WebSocket?o=WebSocket:("undefined"!=typeof webkitWebSocket&&(o=webkitWebSocket),"undefined"!=typeof mozWebSocket&&(o=mozWebSocket)),e.on("opt",function(t){function n(t){var n=this,o=c,e=t.wire||u(t,n);e&&(e.readyState!==e.OPEN?(t.queue=t.queue||[]).push(o):e.send(o))}function r(t,n,o){if(o&&t){try{t=JSON.parse(t.data||t)}catch(t){}if(t instanceof Array)for(var e,u=0;e=t[u++];)r(e,n,o);else 1==f.who&&(t.ws=i),o.on("in",t)}}function u(i,u){if(i&&i.url){var s=i.url.replace("http","ws"),f=i.wire=new o(s);return f.onclose=function(){t.on("bye",i),a(i,u)},f.onerror=function(t){a(i,u),t&&t.code},f.onopen=function(){t.on("hi",i);var o=i.queue;i.queue=[],e.obj.map(o,function(t){c=t,n.call(u,i)})},f.onmessage=function(t){r(t,i,u)},f}}function a(t,n){clearTimeout(t.defer),t.defer=setTimeout(function(){u(t,n)},2e3)}this.to.next(t);var s=t.opt;if(!t.once&&o&&!1!==s.WebSocket){var f=s.ws||(s.ws={});if(f.who=0,e.obj.map(s.peers,function(){++f.who}),!t.once){var c;t.on("out",function(o){this.to.next(o),o.ws&&1==f.who||(c=JSON.stringify(o),f.drain?f.drain.push(c):(f.drain=[],setTimeout(function(){if(f.drain){var o=f.drain;f.drain=null,o.length&&(c=JSON.stringify(o),e.obj.map(s.peers,n,t))}},s.wait||1),e.obj.map(s.peers,n,t)))})}}});var i=function(){}})(t,"./adapters/websocket")}(); \ No newline at end of file diff --git a/package.json b/package.json index 601b0838..85d37656 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "ws": "~>4.0.0" }, "devDependencies": { - "spark-md5": "^3.0.0", "@std/esm": "^0.8.3", "@trust/webcrypto": "^0.7.1", "buffer": "^5.0.7", @@ -66,6 +65,8 @@ "node-webcrypto-ossl": "^1.0.31", "panic-manager": "^1.2.0", "panic-server": "^1.1.0", + "safe-buffer": "^5.1.1", + "spark-md5": "^3.0.0", "text-encoding": "^0.6.4", "uglify-js": ">=2.8.22", "uws": "~>0.14.1" diff --git a/sea.js b/sea.js index fd8f9899..ab62ad63 100644 --- a/sea.js +++ b/sea.js @@ -19,7 +19,8 @@ var Buffer = buffer.Buffer; } if(typeof Buffer === 'undefined'){ - var Buffer = require('buffer').Buffer; //eslint-disable-line no-redeclare + var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare + // var Buffer = require('buffer').Buffer; //eslint-disable-line no-redeclare } var subtle, subtleossl, TextEncoder, TextDecoder, getRandomBytes; @@ -136,8 +137,8 @@ root.get(pub).get(function(at, ev){ pub = pub.slice(4); ev.off(); c--; - if(at.put){ - aliases.push({pub: pub, at: at}); + if(at.put){ + aliases.push({pub: pub, at: at}); } if(!c && (c = -1)){ resolve(aliases) } }); @@ -678,8 +679,7 @@ } var doIt = function(resolve, reject){ - // opts = { hook: function({ iat, exp, alias, proof }), - // session: false } // true uses random PIN, no PIN UX error generated + // opts = { hook: function({ iat, exp, alias, proof }) } // iat == Date.now() when issued, exp == seconds to expire from iat // How this works: // called when app bootstraps, with wanted options @@ -1176,7 +1176,7 @@ if(!m){ if(false === p){ return resolve(m) } return resolve(); } - if(!m.slice || 'SEA[' !== m.slice(0,4)){ + if(!m.slice || 'SEA[' !== m.slice(0,4)){ if(false === p){ return resolve(m) } return resolve() } @@ -1185,7 +1185,7 @@ }catch(e){ return reject(e) } m = m || ''; d = m[0]; - try{ d = d.slice ? JSON.parse(d) : d }catch(e){} + try{ d = d.slice ? JSON.parse(d) : d }catch(e){} if(false === p){ resolve(d) } SEA.verify(m[0], p, m[1]).then(function(ok){ if(!ok){ return resolve() } diff --git a/src/chain.js b/src/chain.js index 76f92fa9..2aa1f499 100644 --- a/src/chain.js +++ b/src/chain.js @@ -250,9 +250,9 @@ function ask(at, soul){ Gun.obj.del(at, 'ask'); // TODO: PERFORMANCE? More elegant way? } function ack(msg, ev){ - var as = this.as, get = as.get || empty, at = as.gun._; + var as = this.as, get = as.get || empty, at = as.gun._, tmp = (msg.put||empty)[get['#']]; if(at.ack){ at.ack = (at.ack + 1) || 1 } - if(!msg.put /*|| node_ == get['.']*/ || (get['.'] && !obj_has(msg.put[get['#']], at.get))){ + if(!msg.put /*|| node_ == get['.']*/ || (get['.'] && !obj_has(tmp, at.get))){ if(at.put !== u){ return } //at.ack = 0; at.on('in', { @@ -263,6 +263,10 @@ function ack(msg, ev){ }) return; } + if(node_ == get['.']){ // is this a security concern? + at.on('in', {get: at.get, put: tmp[at.get], gun: at.gun, '@': msg['@']}); + return; + } //if(/*!msg.gun &&*/ !get['.'] && get['#']){ at.ack = (at.ack + 1) || 1 } //msg = obj_to(msg); msg.gun = at.root; diff --git a/src/root.js b/src/root.js index 3a18868c..5df30669 100644 --- a/src/root.js +++ b/src/root.js @@ -103,7 +103,7 @@ Gun._ = { // some reserved key words, these are not the only ones. var ctx = this, cat = ctx.gun._, at = (cat.next || empty)[soul]; if(!at){ ctx.souls[soul] = false; - return + return } var msg = ctx.map[soul] = { put: node, @@ -224,3 +224,4 @@ module.exports = Gun; },1); }); });*/ + \ No newline at end of file From 21c49b41a047e23040abf97b96365f9038eefc9f Mon Sep 17 00:00:00 2001 From: mhelander Date: Mon, 15 Jan 2018 23:05:14 +0200 Subject: [PATCH 18/29] Removed Spark-MD5 and replaced MD5 key hashing with SHA-256 from Web Crypto subtle --- package.json | 1 - sea.js | 37 ++++++++++++++++--------------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 85d37656..2711f277 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "panic-manager": "^1.2.0", "panic-server": "^1.1.0", "safe-buffer": "^5.1.1", - "spark-md5": "^3.0.0", "text-encoding": "^0.6.4", "uglify-js": ">=2.8.22", "uws": "~>0.14.1" diff --git a/sea.js b/sea.js index ab62ad63..823383dd 100644 --- a/sea.js +++ b/sea.js @@ -27,9 +27,6 @@ var sessionStorage, localStorage, indexedDB; if(typeof window !== 'undefined'){ - if(typeof window.SparkMD5 !== 'undefined'){ - var SparkMD5 = window.SparkMD5; - } var wc = window.crypto || window.msCrypto; // STD or M$ subtle = wc.subtle || wc.webkitSubtle; // STD or iSafari getRandomBytes = function(len){ return wc.getRandomValues(new Buffer(len)) }; @@ -58,10 +55,6 @@ } } - if(typeof SparkMD5 === 'undefined'){ - var SparkMD5 = require('spark-md5'); //eslint-disable-line no-redeclare - } - // Encryption parameters - TODO: maybe to be changed via init? var pbkdf2 = { hash: 'SHA-256', @@ -167,7 +160,7 @@ .catch(function(e){ reject({err: 'Failed to create proof!'}) }) .then(function(proof){ var user = {pub: pub, proof: proof, at: at}; - // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. + // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. /* MARK TO @mhelander : pub vs epub!??? */ @@ -456,13 +449,18 @@ // This recalls Web Cryptography API CryptoKeys from IndexedDB or creates & stores function recallCryptoKey(p,s,o){ // {pub, key}|proof, salt, optional:['sign'] o = o || ['encrypt', 'decrypt']; // Default operations - var importKey = function(key){ return subtle.importKey( - 'raw', - makeKey((Gun.obj.has(key, 'key') && key.key) || key, s || getRandomBytes(8)), - 'AES-CBC', - false, - o - ); }; + var importKey = function(key){ + return makeKey((Gun.obj.has(key, 'key') && key.key) || key, s || getRandomBytes(8)) + .then(function(hashedKey){ + return subtle.importKey( + 'raw', + hashedKey, + 'AES-CBC', + false, + o + ); + }); + }; return new Promise(function(resolve){ if(authsettings.validity && typeof window !== 'undefined' && Gun.obj.has(p, 'pub') && Gun.obj.has(p, 'key')){ @@ -957,14 +955,11 @@ to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). } - // Does enc/dec key like OpenSSL - works with CryptoJS encryption/decryption function makeKey(p,s){ var ps = Buffer.concat([new Buffer(p, 'utf8'), s]); - var h128 = new Buffer((new SparkMD5()).appendBinary(ps).end(true), 'binary'); - return Buffer.concat([ - h128, - new Buffer((new SparkMD5()).appendBinary(Buffer.concat([h128, ps])).end(true), 'binary') - ]); + return sha256hash(ps.toString('utf8')).then(function(s){ + return new Buffer(s, 'binary'); + }); } // These SEA functions support both callback AND Promises From 0b76a6cffb4edf543f1fe1e96795029c297a6806 Mon Sep 17 00:00:00 2001 From: mhelander Date: Mon, 15 Jan 2018 23:33:44 +0200 Subject: [PATCH 19/29] Fixed safe-buffer usage --- sea.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/sea.js b/sea.js index 823383dd..0bc9cf99 100644 --- a/sea.js +++ b/sea.js @@ -15,9 +15,6 @@ var Gun = (typeof window !== 'undefined' ? window : global).Gun || require('./gun'); - if(typeof buffer !== 'undefined'){ - var Buffer = buffer.Buffer; - } if(typeof Buffer === 'undefined'){ var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare // var Buffer = require('buffer').Buffer; //eslint-disable-line no-redeclare From 8f5c67a9e676b49e0a94c16bca43ec7333c75692 Mon Sep 17 00:00:00 2001 From: mhelander Date: Tue, 16 Jan 2018 01:06:20 +0200 Subject: [PATCH 20/29] Fixed safe-buffer usage again --- sea.js | 1 - 1 file changed, 1 deletion(-) diff --git a/sea.js b/sea.js index 0bc9cf99..31a3559e 100644 --- a/sea.js +++ b/sea.js @@ -17,7 +17,6 @@ if(typeof Buffer === 'undefined'){ var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare - // var Buffer = require('buffer').Buffer; //eslint-disable-line no-redeclare } var subtle, subtleossl, TextEncoder, TextDecoder, getRandomBytes; From 071381acad825b9d3a749f85c0af4d6078e0da7c Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Tue, 16 Jan 2018 12:10:01 -0800 Subject: [PATCH 21/29] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2711f277..d8a1e8d4 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/amark/gun#readme", "engines": { - "node": ">=8.6.0" + "node": ">=0.8.4" }, "dependencies": { "aws-sdk": ">=2.153.0", From 1de6e4bdc2c5183ed1ecb1b2b0a7f12f6dcd6c3f Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Tue, 16 Jan 2018 12:11:27 -0800 Subject: [PATCH 22/29] Update .travis.yml --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 476ba329..8d6c79bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ language: node_js node_js: - - 8.6.0 + - 4.0 + - 4.2 + - 5.0 - 6.8 + - 7.9 + - 8.6 From eccc9449f7fdc4b24ccd00bcb70919b7034f1681 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Tue, 16 Jan 2018 12:24:54 -0800 Subject: [PATCH 23/29] Update sea.js --- sea.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sea.js b/sea.js index 31a3559e..61198efe 100644 --- a/sea.js +++ b/sea.js @@ -15,6 +15,9 @@ var Gun = (typeof window !== 'undefined' ? window : global).Gun || require('./gun'); + if(typeof buffer !== 'undefined'){ // polyfill from cryptomodules for now, not needed after safe-buffer! + var Buffer = buffer.Buffer; + } if(typeof Buffer === 'undefined'){ var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare } From 4a8423ea15d613c3af1e16ae5bd1d92ea44d232b Mon Sep 17 00:00:00 2001 From: mhelander Date: Wed, 17 Jan 2018 01:40:45 +0200 Subject: [PATCH 24/29] SEA has work-in-progress SafeBuffer but not in use yet --- sea.js | 149 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/sea.js b/sea.js index 31a3559e..48565867 100644 --- a/sea.js +++ b/sea.js @@ -15,6 +15,113 @@ var Gun = (typeof window !== 'undefined' ? window : global).Gun || require('./gun'); + function SafeBuffer(...props) { + console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()') + return this.from(...props) + } + Object.assign(SafeBuffer, { + // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64' + from() { + if (!Object.keys(arguments).length) { + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') + } + const input = arguments[0] + let buf + if (typeof input === 'string') { + const enc = arguments[1] || 'utf8' + if (enc === 'hex') { + const bytes = input.match(/([\da-fA-F]{2})/g) + if (!bytes || !bytes.length) { + throw new TypeError('Invalid first argument for type \'hex\'.') + } + buf = new ArrayBuffer(input.length / 2) + const bufView = new Uint8Array(buf) + bytes.map((byte, index) => bufView[index] = parseInt(byte, 16)) + } else if (enc === 'utf8') { + const { length } = input + buf = new ArrayBuffer(length * 2) + const bufView = new Uint16Array(buf) + Array.from({ length }).map((_, i) => bufView[i] = input.charCodeAt(i)) + } else if (enc === 'base64') { + const bytes = atob(input) + const { length } = bytes + buf = new ArrayBuffer(length) + const bufView = new Uint8Array(buf) + Array.from({ length }).map((_, i) => bufView[i] = bytes.charCodeAt(i)) + } else { + console.info(`SafeBuffer.from unknown encoding: '${enc}'`) + } + return buf + } + if (Array.isArray(input)) { + const { length } = input + buf = new ArrayBuffer(length) + const bufView = new Uint8Array(buf) + input.map((byte, i) => bufView[i] = byte) + return buf + } + if (input instanceof ArrayBuffer) { + const { byteLength, length = byteLength } = input + buf = new ArrayBuffer(length) + const bufView = new Uint8Array(buf) + const itmView = new Uint8Array(input) + Array.from({ length }).map((_, i) => bufView[i] = itmView[i]) + return buf + } + if (input instanceof Uint8Array) { + const { byteLength, length = byteLength } = input + buf = new ArrayBuffer(length) + const bufView = new Uint8Array(buf) + Array.from({ length }).map((_, i) => bufView[i] = input[i]) + return buf + } + }, + alloc(length, fill = 0 /*, enc*/ ) { + buf = new ArrayBuffer(length) + const bufView = new Uint8Array(buf) + Array.from({ length }).map((_, i) => bufView[i] = fill) + return buf + }, + concat(arr) { // octet array + if (!Array.isArray(arr)) { + throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.') + } + const length = arr.reduce((sum, item) => { + const { byteLength, length = byteLength } = item + return sum + length + }, 0) + buf = new ArrayBuffer(length) + const bufView = new Uint8Array(buf) + arr.reduce((index, item) => { + const { byteLength, length = byteLength } = item + const itmView = new Uint8Array(item) + Array.from({ length }).map((_, i) => bufView[index + i] = itmView[i]) + return index + length + }, 0) + return buf + } + }) + SafeBuffer.prototype.from = SafeBuffer.from + ArrayBuffer.prototype.toString = function(enc = 'utf8', start, end) { + if (enc === 'hex') { + const { byteLength: length } = this + const bufView = new Uint8Array(this) + return Array.from({ length }) + .map((_, i) => bufView[i].toString(16)) + .map((byte) => byte.toString(16).padStart(2, '0')).join('') + } + if (enc === 'utf8') { + const { byteLength, length = byteLength } = this + const bufView = new Uint16Array(this) + return Array.from({ length }) + .map((_, i) => String.fromCharCode(bufView[i])).join('') + } + if (enc === 'base64') { + return btoa(this) + } + } + + // var Buffer = SafeBuffer; if(typeof Buffer === 'undefined'){ var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare } @@ -25,7 +132,7 @@ if(typeof window !== 'undefined'){ var wc = window.crypto || window.msCrypto; // STD or M$ subtle = wc.subtle || wc.webkitSubtle; // STD or iSafari - getRandomBytes = function(len){ return wc.getRandomValues(new Buffer(len)) }; + getRandomBytes = function(len){ return wc.getRandomValues(Buffer.alloc(len)) }; TextEncoder = window.TextEncoder; TextDecoder = window.TextDecoder; sessionStorage = window.sessionStorage; @@ -74,7 +181,7 @@ }; // This creates Web Cryptography API compliant JWK for sign/verify purposes function keystoecdsajwk(pub,priv){ - var pubkey = (new Buffer(pub, 'base64')).toString('utf8').split(':'); + var pubkey = Buffer.from(pub, 'base64').toString('utf8').split(':'); var jwk = priv ? {d: priv, key_ops: ['sign']} : {key_ops: ['verify']}; return Object.assign(jwk, { kty: 'EC', @@ -265,7 +372,7 @@ // IF authsettings.validity === 0 THEN no remember-me, ever // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB var pin = (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10); - pin = new Buffer(pin, 'utf8').toString('base64'); + pin = Buffer.from(pin, 'utf8').toString('base64'); if(proof && user && user.alias && authsettings.validity){ var args = {alias: user.alias}; @@ -293,7 +400,7 @@ var alias = Gun.obj.has(authprops, 'alias') && authprops.alias || sessionStorage.getItem('user'); var pin = Gun.obj.has(authprops, 'pin') - && new Buffer(authprops.pin, 'utf8').toString('base64'); + && Buffer.from(authprops.pin, 'utf8').toString('base64'); var checkRememberData = function(decr){ if(Gun.obj.has(decr, 'proof') @@ -952,9 +1059,9 @@ } function makeKey(p,s){ - var ps = Buffer.concat([new Buffer(p, 'utf8'), s]); + var ps = Buffer.concat([Buffer.from(p, 'utf8'), s]); return sha256hash(ps.toString('utf8')).then(function(s){ - return new Buffer(s, 'binary'); + return Buffer.from(s, 'binary'); }); } @@ -975,13 +1082,13 @@ }, key, pbkdf2.ks*8); }).then(function(result){ pass = getRandomBytes(pass.length); - return new Buffer(result, 'binary').toString('base64'); + return Buffer.from(result, 'binary').toString('base64'); }).then(resolve).catch(function(e){ Gun.log(e); reject(e) }); }) || function(resolve, reject){ // For NodeJS crypto.pkdf2 rocks try{ var hash = crypto.pbkdf2Sync( pass, - new Buffer(salt, 'utf8'), + Buffer.from(salt, 'utf8'), pbkdf2.iter, pbkdf2.ks, pbkdf2.hash.replace('-', '').toLowerCase() @@ -996,12 +1103,12 @@ SEA.keyid = function(p,cb){ var doIt = function(resolve, reject){ // base64('base64(x):base64(y)') => Buffer(xy) - var pb = Buffer.concat((new Buffer(p, 'base64')).toString('utf8').split(':') - .map(function(t){ return new Buffer(t, 'base64') })); + var pb = Buffer.concat(Buffer.from(p, 'base64').toString('utf8').split(':') + .map(function(t){ return Buffer.from(t, 'base64') })); // id is PGPv4 compliant raw key - var id = Buffer.concat([new Buffer([0x99, pb.length/0x100, pb.length%0x100]), pb]); + var id = Buffer.concat([Buffer.from([0x99, pb.length/0x100, pb.length%0x100]), pb]); sha1hash(id).then(function(sha1){ - var hash = new Buffer(sha1, 'binary'); + var hash = Buffer.from(sha1, 'binary'); resolve(hash.slice(hash.length-8).toString('hex')); // 16-bit ID as hex }); }; @@ -1017,7 +1124,7 @@ return {priv: k.d}; }).then(function(keys){ return subtle.exportKey('jwk', pubkey).then(function(k){ - keys.pub = (new Buffer([k.x, k.y].join(':'))).toString('base64'); + keys.pub = Buffer.from([k.x, k.y].join(':')).toString('base64'); // return SEA.keyid(keys.pub).then(function(id){ // keys.pubId = id; // return keys; @@ -1036,7 +1143,7 @@ return keys; }).then(function(keys){ return ecdhSubtle.exportKey('jwk', pubkey).then(function(k){ - keys.epub = (new Buffer([k.x, k.y].join(':'))).toString('base64'); + keys.epub = Buffer.from([k.x, k.y].join(':')).toString('base64'); return keys; }); }).catch(function(e){ Gun.log(e); reject(e) }); @@ -1049,7 +1156,7 @@ SEA.derive = function(m,p,cb){ var ecdhSubtle = subtleossl || subtle; var keystoecdhjwk = function(pub, priv){ - var pubkey = (new Buffer(pub, 'base64')).toString('utf8').split(':'); + var pubkey = Buffer.from(pub, 'base64').toString('utf8').split(':'); var jwk = priv ? {d: priv, key_ops: ['decrypt']} : {key_ops: ['encrypt']}; var ret = Object.assign(jwk, { kty: 'EC', @@ -1087,7 +1194,7 @@ sha256hash(m.slice ? m : JSON.stringify(m)).then(function(mm){ subtle.importKey('jwk', jwk, ecdsakeyprops, false, ['sign']).then(function(key){ subtle.sign(ecdsasignprops, key, mm) - .then(function(s){ resolve(new Buffer(s, 'binary').toString('base64')) }) + .then(function(s){ resolve(Buffer.from(s, 'binary').toString('base64')) }) .catch(function(e){ Gun.log(e); reject(e) }); }).catch(function(e){ Gun.log(e); reject(e) }); }); @@ -1099,7 +1206,7 @@ subtle.importKey('jwk', keystoecdsajwk(p), ecdsakeyprops, false, ['verify']) .then(function(key){ sha256hash(m).then(function(mm){ - subtle.verify(ecdsasignprops, key, new Buffer(s, 'base64'), mm) + subtle.verify(ecdsasignprops, key, Buffer.from(s, 'base64'), mm) .then(function(v){ resolve(v) }) .catch(function(e){ Gun.log(e); reject(e) }); }); @@ -1118,7 +1225,7 @@ name: 'AES-CBC', iv: iv }, aesKey, new TextEncoder().encode(m)).then(function(ct){ aesKey = getRandomBytes(32); - r.ct = new Buffer(ct, 'binary').toString('base64'); + r.ct = Buffer.from(ct, 'binary').toString('base64'); return JSON.stringify(r); }).then(resolve).catch(function(e){ Gun.log(e); reject(e) }); }).catch(function(e){ Gun.log(e); reject(e)} ); @@ -1128,12 +1235,12 @@ SEA.dec = function(m,p,cb){ var doIt = function(resolve, reject){ try{ m = m.slice ? JSON.parse(m) : m }catch(e){} //eslint-disable-line no-empty - var iv = new Buffer(m.iv, 'hex'); - var s = new Buffer(m.s, 'hex'); + var iv = Buffer.from(m.iv, 'hex'); + var s = Buffer.from(m.s, 'hex'); recallCryptoKey(p, s).then(function(aesKey){ subtle.decrypt({ name: 'AES-CBC', iv: iv - }, aesKey, new Buffer(m.ct, 'base64')).then(function(ct){ + }, aesKey, Buffer.from(m.ct, 'base64')).then(function(ct){ aesKey = getRandomBytes(32); var ctUtf8 = new TextDecoder('utf8').decode(ct); try{ return ctUtf8.slice ? JSON.parse(ctUtf8) : ctUtf8; From 68d8da1fdd8dbbc01460d60d1ce289d6cc6a1cf7 Mon Sep 17 00:00:00 2001 From: mhelander Date: Thu, 18 Jan 2018 01:17:13 +0200 Subject: [PATCH 25/29] Refactored SafeBuffer & fixed two failing tests --- sea.js | 193 +++++++++++++++++++++------------------------------- test/sea.js | 8 +-- 2 files changed, 83 insertions(+), 118 deletions(-) diff --git a/sea.js b/sea.js index 48565867..f00acece 100644 --- a/sea.js +++ b/sea.js @@ -15,117 +15,6 @@ var Gun = (typeof window !== 'undefined' ? window : global).Gun || require('./gun'); - function SafeBuffer(...props) { - console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()') - return this.from(...props) - } - Object.assign(SafeBuffer, { - // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64' - from() { - if (!Object.keys(arguments).length) { - throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') - } - const input = arguments[0] - let buf - if (typeof input === 'string') { - const enc = arguments[1] || 'utf8' - if (enc === 'hex') { - const bytes = input.match(/([\da-fA-F]{2})/g) - if (!bytes || !bytes.length) { - throw new TypeError('Invalid first argument for type \'hex\'.') - } - buf = new ArrayBuffer(input.length / 2) - const bufView = new Uint8Array(buf) - bytes.map((byte, index) => bufView[index] = parseInt(byte, 16)) - } else if (enc === 'utf8') { - const { length } = input - buf = new ArrayBuffer(length * 2) - const bufView = new Uint16Array(buf) - Array.from({ length }).map((_, i) => bufView[i] = input.charCodeAt(i)) - } else if (enc === 'base64') { - const bytes = atob(input) - const { length } = bytes - buf = new ArrayBuffer(length) - const bufView = new Uint8Array(buf) - Array.from({ length }).map((_, i) => bufView[i] = bytes.charCodeAt(i)) - } else { - console.info(`SafeBuffer.from unknown encoding: '${enc}'`) - } - return buf - } - if (Array.isArray(input)) { - const { length } = input - buf = new ArrayBuffer(length) - const bufView = new Uint8Array(buf) - input.map((byte, i) => bufView[i] = byte) - return buf - } - if (input instanceof ArrayBuffer) { - const { byteLength, length = byteLength } = input - buf = new ArrayBuffer(length) - const bufView = new Uint8Array(buf) - const itmView = new Uint8Array(input) - Array.from({ length }).map((_, i) => bufView[i] = itmView[i]) - return buf - } - if (input instanceof Uint8Array) { - const { byteLength, length = byteLength } = input - buf = new ArrayBuffer(length) - const bufView = new Uint8Array(buf) - Array.from({ length }).map((_, i) => bufView[i] = input[i]) - return buf - } - }, - alloc(length, fill = 0 /*, enc*/ ) { - buf = new ArrayBuffer(length) - const bufView = new Uint8Array(buf) - Array.from({ length }).map((_, i) => bufView[i] = fill) - return buf - }, - concat(arr) { // octet array - if (!Array.isArray(arr)) { - throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.') - } - const length = arr.reduce((sum, item) => { - const { byteLength, length = byteLength } = item - return sum + length - }, 0) - buf = new ArrayBuffer(length) - const bufView = new Uint8Array(buf) - arr.reduce((index, item) => { - const { byteLength, length = byteLength } = item - const itmView = new Uint8Array(item) - Array.from({ length }).map((_, i) => bufView[index + i] = itmView[i]) - return index + length - }, 0) - return buf - } - }) - SafeBuffer.prototype.from = SafeBuffer.from - ArrayBuffer.prototype.toString = function(enc = 'utf8', start, end) { - if (enc === 'hex') { - const { byteLength: length } = this - const bufView = new Uint8Array(this) - return Array.from({ length }) - .map((_, i) => bufView[i].toString(16)) - .map((byte) => byte.toString(16).padStart(2, '0')).join('') - } - if (enc === 'utf8') { - const { byteLength, length = byteLength } = this - const bufView = new Uint16Array(this) - return Array.from({ length }) - .map((_, i) => String.fromCharCode(bufView[i])).join('') - } - if (enc === 'base64') { - return btoa(this) - } - } - - // var Buffer = SafeBuffer; - if(typeof Buffer === 'undefined'){ - var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare - } - var subtle, subtleossl, TextEncoder, TextDecoder, getRandomBytes; var sessionStorage, localStorage, indexedDB; @@ -158,6 +47,78 @@ } } + if(typeof Buffer === 'undefined'){ + var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare + } + + // This is Buffer implementation used in SEA: + function SafeBuffer(...props) { + console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()') + return this.from(...props) + } + Object.assign(SafeBuffer, { + // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64' + from() { + if (!Object.keys(arguments).length) { + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') + } + const input = arguments[0] + let buf + if (typeof input === 'string') { + const enc = arguments[1] || 'utf8' + if (enc === 'hex') { + const bytes = input.match(/([\da-fA-F]{2})/g) + .map((byte) => parseInt(byte, 16)) + if (!bytes || !bytes.length) { + throw new TypeError('Invalid first argument for type \'hex\'.') + } + buf = new Uint8Array(bytes) + } else if (enc === 'utf8') { + const src = new TextEncoder().encode(input) + buf = new Uint8Array(src) + } else if (enc === 'base64') { + const bytes = atob(input) + buf = new Uint8Array(bytes) + } else { + console.info(`SafeBuffer.from unknown encoding: '${enc}'`) + } + return buf + } + if (Array.isArray(input) + || input instanceof ArrayBuffer + || input instanceof Uint8Array) { + return new Uint8Array(input) + } + }, + alloc(length, fill = 0 /*, enc*/ ) { + return new Uint8Array(Array.from({ length }, () => fill)) + }, + concat(arr) { // octet array + if (!Array.isArray(arr)) { + throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.') + } + return new Uint8Array(arr.reduce((ret, item) => ret.concat(Array.from(item)), [])) + } + }) + SafeBuffer.prototype.from = SafeBuffer.from + Uint8Array.prototype.toString = function(enc = 'utf8', start, end) { + if (enc === 'hex') { + const { byteLength: length } = this + return Array.from({ length }) + .map((_, i) => this[i].toString(16)) + .map((byte) => byte.toString(16).padStart(2, '0')).join('') + } + if (enc === 'utf8') { + const { byteLength, length = byteLength } = this + return Array.from({ length }) + .map((_, i) => String.fromCharCode(this[i])).join('') + } + if (enc === 'base64') { + return btoa(this) + } + } + // var Buffer = SafeBuffer; + // Encryption parameters - TODO: maybe to be changed via init? var pbkdf2 = { hash: 'SHA-256', @@ -587,7 +548,8 @@ function sha256hash(m){ var hashSubtle = subtleossl || subtle; try{ m = m.slice ? m : JSON.stringify(m) }catch(e){} //eslint-disable-line no-empty - return hashSubtle.digest(pbkdf2.hash, new TextEncoder("utf-8").encode(m)); + return hashSubtle.digest(pbkdf2.hash, new TextEncoder().encode(m)) + .then(function(hash){ return Buffer.from(hash) }); } // This internal func returns SHA-1 hashed data for KeyID generation function sha1hash(b){ @@ -1065,8 +1027,11 @@ }); } - // These SEA functions support both callback AND Promises var SEA = {}; + // This is Buffer used in SEA and usable from Gun/SEA application also. + // For documentation see https://nodejs.org/api/buffer.html + SEA.Buffer = SafeBuffer; + // These SEA functions support both callback AND Promises // create a wrapper library around Web Crypto API. // now wrap the various AES, ECDSA, PBKDF2 functions we called above. SEA.proof = function(pass,salt,cb){ @@ -1088,7 +1053,7 @@ try{ var hash = crypto.pbkdf2Sync( pass, - Buffer.from(salt, 'utf8'), + new TextEncoder().encode(salt), pbkdf2.iter, pbkdf2.ks, pbkdf2.hash.replace('-', '').toLowerCase() diff --git a/test/sea.js b/test/sea.js index aa9d66c6..92e409da 100644 --- a/test/sea.js +++ b/test/sea.js @@ -67,9 +67,9 @@ Gun.SEA && describe('SEA', function(){ // proof - generates PBKDF2 hash from user's alias and password // which is then used to decrypt user's auth record if(type === 'callback'){ - Gun.SEA.proof(alias, pass, check); + Gun.SEA.proof(pass, Gun.text.random(64), check); } else { - Gun.SEA.proof(alias, pass).then(check).catch(done); + Gun.SEA.proof(pass, Gun.text.random(64)).then(check).catch(done); } }); @@ -509,8 +509,8 @@ Gun().user && describe('Gun', function(){ expect(ack).to.not.be(''); expect(ack).to.not.have.key('err'); expect(ack).to.have.key('ok'); - //expect(gun.back(-1)._.user).to.not.have.keys([ 'sea', 'pub' ]); - expect(gun.back(-1)._.user).to.not.be.ok(); + expect(gun.back(-1)._.user._).to.not.have.keys([ 'sea', 'pub' ]); + // expect(gun.back(-1)._.user).to.not.be.ok(); }catch(e){ done(e); return } done(); }; From 2cdf5340d47b0ec3d39fdf058af586f71e0aacfa Mon Sep 17 00:00:00 2001 From: mhelander Date: Fri, 19 Jan 2018 02:31:28 +0200 Subject: [PATCH 26/29] Finally SEA has built-in Buffer : no module dependencies for browsers! --- package.json | 2 -- sea.js | 97 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 2711f277..29879c73 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "devDependencies": { "@std/esm": "^0.8.3", "@trust/webcrypto": "^0.7.1", - "buffer": "^5.0.7", "express": ">=4.15.2", "fake-indexeddb": "^2.0.3", "hapi": "^16.1.1", @@ -65,7 +64,6 @@ "node-webcrypto-ossl": "^1.0.31", "panic-manager": "^1.2.0", "panic-server": "^1.1.0", - "safe-buffer": "^5.1.1", "text-encoding": "^0.6.4", "uglify-js": ">=2.8.22", "uws": "~>0.14.1" diff --git a/sea.js b/sea.js index f00acece..13035810 100644 --- a/sea.js +++ b/sea.js @@ -47,15 +47,34 @@ } } - if(typeof Buffer === 'undefined'){ - var Buffer = require('safe-buffer').Buffer; //eslint-disable-line no-redeclare + // This is Array extended to have .toString(['utf8'|'hex'|'base64']) + function SeaArray() {} + Object.assign(SeaArray, { from: Array.from }) + SeaArray.prototype = Object.create(Array.prototype) + SeaArray.prototype.toString = function(enc = 'utf8', start = 0, end) { + const { length } = this + if (enc === 'hex') { + const buf = new Uint8Array(this) + return [ ...Array(((end && (end + 1)) || length) - start).keys()] + .map((i) => buf[ i + start ].toString(16).padStart(2, '0')).join('') + } + if (enc === 'utf8') { + return Array.from( + { length: (end || length) - start }, + (_, i) => String.fromCharCode(this[ i + start]) + ).join('') + } + if (enc === 'base64') { + return btoa(this) + } } // This is Buffer implementation used in SEA: function SafeBuffer(...props) { console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()') - return this.from(...props) + return SafeBuffer.from(...props) } + SafeBuffer.prototype = Object.create(Array.prototype) Object.assign(SafeBuffer, { // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64' from() { @@ -72,54 +91,56 @@ if (!bytes || !bytes.length) { throw new TypeError('Invalid first argument for type \'hex\'.') } - buf = new Uint8Array(bytes) + buf = SeaArray.from(bytes) } else if (enc === 'utf8') { - const src = new TextEncoder().encode(input) - buf = new Uint8Array(src) + const { length } = input + const words = new Uint16Array(length) + Array.from({ length }, (_, i) => words[i] = input.charCodeAt(i)) + buf = SeaArray.from(words) } else if (enc === 'base64') { - const bytes = atob(input) - buf = new Uint8Array(bytes) + const dec = atob(input) + const { length } = dec + const bytes = new Uint8Array(length) + Array.from({ length }, (_, i) => bytes[i] = dec.charCodeAt(i)) + buf = SeaArray.from(bytes) + } else if (enc === 'binary') { + buf = SeaArray.from(input) } else { console.info(`SafeBuffer.from unknown encoding: '${enc}'`) } return buf } - if (Array.isArray(input) - || input instanceof ArrayBuffer - || input instanceof Uint8Array) { - return new Uint8Array(input) + const { byteLength, length = byteLength } = input + if (length) { + let buf + if (input instanceof ArrayBuffer) { + buf = new Uint8Array(input) + } + return SeaArray.from(buf || input) } }, + alloc(length, fill = 0 /*, enc*/ ) { - return new Uint8Array(Array.from({ length }, () => fill)) + return SeaArray.from(new Uint8Array(Array.from({ length }, () => fill))) }, + + allocUnsafe(length) { + return SeaArray.from(new Uint8Array(Array.from({ length }))) + }, + concat(arr) { // octet array if (!Array.isArray(arr)) { throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.') } - return new Uint8Array(arr.reduce((ret, item) => ret.concat(Array.from(item)), [])) + return SeaArray.from(arr.reduce((ret, item) => ret.concat(Array.from(item)), [])) } }) SafeBuffer.prototype.from = SafeBuffer.from - Uint8Array.prototype.toString = function(enc = 'utf8', start, end) { - if (enc === 'hex') { - const { byteLength: length } = this - return Array.from({ length }) - .map((_, i) => this[i].toString(16)) - .map((byte) => byte.toString(16).padStart(2, '0')).join('') - } - if (enc === 'utf8') { - const { byteLength, length = byteLength } = this - return Array.from({ length }) - .map((_, i) => String.fromCharCode(this[i])).join('') - } - if (enc === 'base64') { - return btoa(this) - } - } - // var Buffer = SafeBuffer; + SafeBuffer.prototype.toString = SeaArray.prototype.toString - // Encryption parameters - TODO: maybe to be changed via init? + const Buffer = SafeBuffer + + // Encryption parameters var pbkdf2 = { hash: 'SHA-256', iter: 50000, @@ -554,7 +575,7 @@ // This internal func returns SHA-1 hashed data for KeyID generation function sha1hash(b){ var hashSubtle = subtleossl || subtle; - return hashSubtle.digest('SHA-1', b); + return hashSubtle.digest('SHA-1', new ArrayBuffer(b)); } // How does it work? @@ -1074,7 +1095,7 @@ var id = Buffer.concat([Buffer.from([0x99, pb.length/0x100, pb.length%0x100]), pb]); sha1hash(id).then(function(sha1){ var hash = Buffer.from(sha1, 'binary'); - resolve(hash.slice(hash.length-8).toString('hex')); // 16-bit ID as hex + resolve(hash.toString('hex', hash.length-8)); // 16-bit ID as hex }); }; if(cb){ doIt(cb, function(){cb()}) } else { return new Promise(doIt) } @@ -1158,7 +1179,7 @@ var jwk = keystoecdsajwk(p.pub, p.priv); sha256hash(m.slice ? m : JSON.stringify(m)).then(function(mm){ subtle.importKey('jwk', jwk, ecdsakeyprops, false, ['sign']).then(function(key){ - subtle.sign(ecdsasignprops, key, mm) + subtle.sign(ecdsasignprops, key, new Uint8Array(mm)) .then(function(s){ resolve(Buffer.from(s, 'binary').toString('base64')) }) .catch(function(e){ Gun.log(e); reject(e) }); }).catch(function(e){ Gun.log(e); reject(e) }); @@ -1200,12 +1221,12 @@ SEA.dec = function(m,p,cb){ var doIt = function(resolve, reject){ try{ m = m.slice ? JSON.parse(m) : m }catch(e){} //eslint-disable-line no-empty - var iv = Buffer.from(m.iv, 'hex'); - var s = Buffer.from(m.s, 'hex'); + var iv = new Uint8Array(Buffer.from(m.iv, 'hex')); + var s = new Uint8Array(Buffer.from(m.s, 'hex')); recallCryptoKey(p, s).then(function(aesKey){ subtle.decrypt({ name: 'AES-CBC', iv: iv - }, aesKey, Buffer.from(m.ct, 'base64')).then(function(ct){ + }, aesKey, new Uint8Array(Buffer.from(m.ct, 'base64'))).then(function(ct){ aesKey = getRandomBytes(32); var ctUtf8 = new TextDecoder('utf8').decode(ct); try{ return ctUtf8.slice ? JSON.parse(ctUtf8) : ctUtf8; From 25f7582a5e7847da18a371fe7c43770a85e879c8 Mon Sep 17 00:00:00 2001 From: mhelander Date: Fri, 19 Jan 2018 08:00:54 +0200 Subject: [PATCH 27/29] Added Uint8Array conversion to subtle calls to beat the darn stupid API's --- sea.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sea.js b/sea.js index 13035810..0af26a13 100644 --- a/sea.js +++ b/sea.js @@ -21,7 +21,9 @@ if(typeof window !== 'undefined'){ var wc = window.crypto || window.msCrypto; // STD or M$ subtle = wc.subtle || wc.webkitSubtle; // STD or iSafari - getRandomBytes = function(len){ return wc.getRandomValues(Buffer.alloc(len)) }; + getRandomBytes = function(len){ + return wc.getRandomValues(new UInt8Array(Buffer.alloc(len))); + }; TextEncoder = window.TextEncoder; TextDecoder = window.TextDecoder; sessionStorage = window.sessionStorage; @@ -1192,7 +1194,7 @@ subtle.importKey('jwk', keystoecdsajwk(p), ecdsakeyprops, false, ['verify']) .then(function(key){ sha256hash(m).then(function(mm){ - subtle.verify(ecdsasignprops, key, Buffer.from(s, 'base64'), mm) + subtle.verify(ecdsasignprops, key, new Uint8Array(Buffer.from(s, 'base64')), mm) .then(function(v){ resolve(v) }) .catch(function(e){ Gun.log(e); reject(e) }); }); From 5b655ffbfbfd9f7dfaec4008f6cb5eb4d21b9800 Mon Sep 17 00:00:00 2001 From: mhelander Date: Fri, 19 Jan 2018 21:47:13 +0200 Subject: [PATCH 28/29] More Uint8Array conversions to subtle calls & fix one UInt8Array typo --- sea.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sea.js b/sea.js index 0af26a13..3c8e25a5 100644 --- a/sea.js +++ b/sea.js @@ -22,7 +22,7 @@ var wc = window.crypto || window.msCrypto; // STD or M$ subtle = wc.subtle || wc.webkitSubtle; // STD or iSafari getRandomBytes = function(len){ - return wc.getRandomValues(new UInt8Array(Buffer.alloc(len))); + return wc.getRandomValues(new Uint8Array(Buffer.alloc(len))); }; TextEncoder = window.TextEncoder; TextDecoder = window.TextDecoder; @@ -541,7 +541,7 @@ .then(function(hashedKey){ return subtle.importKey( 'raw', - hashedKey, + new Uint8Array(hashedKey), 'AES-CBC', false, o @@ -1194,7 +1194,7 @@ subtle.importKey('jwk', keystoecdsajwk(p), ecdsakeyprops, false, ['verify']) .then(function(key){ sha256hash(m).then(function(mm){ - subtle.verify(ecdsasignprops, key, new Uint8Array(Buffer.from(s, 'base64')), mm) + subtle.verify(ecdsasignprops, key, new Uint8Array(Buffer.from(s, 'base64')), new Uint8Array(mm)) .then(function(v){ resolve(v) }) .catch(function(e){ Gun.log(e); reject(e) }); }); From a4cdf4ab532715e3efb9cd289b8144e5932833a1 Mon Sep 17 00:00:00 2001 From: mhelander Date: Fri, 19 Jan 2018 23:00:47 +0200 Subject: [PATCH 29/29] Fixed some more Buffer/Uint8Array issues --- sea.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sea.js b/sea.js index 3c8e25a5..a7c4b976 100644 --- a/sea.js +++ b/sea.js @@ -22,7 +22,7 @@ var wc = window.crypto || window.msCrypto; // STD or M$ subtle = wc.subtle || wc.webkitSubtle; // STD or iSafari getRandomBytes = function(len){ - return wc.getRandomValues(new Uint8Array(Buffer.alloc(len))); + return Buffer.from(wc.getRandomValues(new Uint8Array(Buffer.alloc(len)))); }; TextEncoder = window.TextEncoder; TextDecoder = window.TextDecoder; @@ -36,7 +36,7 @@ var webcrypto = new WebCrypto({directory: 'key_storage'}); subtleossl = webcrypto.subtle; subtle = require('@trust/webcrypto').subtle; // All but ECDH - getRandomBytes = function(len){ return crypto.randomBytes(len) }; + getRandomBytes = function(len){ return Buffer.from(crypto.randomBytes(len)) }; TextEncoder = require('text-encoding').TextEncoder; TextDecoder = require('text-encoding').TextDecoder; // Let's have Storage for NodeJS / testing @@ -1210,7 +1210,7 @@ m = (m.slice && m) || JSON.stringify(m); recallCryptoKey(p, s).then(function(aesKey){ subtle.encrypt({ - name: 'AES-CBC', iv: iv + name: 'AES-CBC', iv: new Uint8Array(iv) }, aesKey, new TextEncoder().encode(m)).then(function(ct){ aesKey = getRandomBytes(32); r.ct = Buffer.from(ct, 'binary').toString('base64');