diff --git a/.travis.yml b/.travis.yml index 93f7a625..4cf6f163 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ branches: except: - debug node_js: - - 4 - - 6 - 8 - 10 cache: diff --git a/gun.js b/gun.js index cb8c132d..c0f42f12 100644 --- a/gun.js +++ b/gun.js @@ -959,7 +959,6 @@ function output(msg){ var put, get, at = this.as, back = at.back, root = at.root, tmp; - if(!msg.I){ msg.I = at.$ } if(!msg.$){ msg.$ = at.$ } this.to.next(msg); if(get = msg.get){ @@ -1215,7 +1214,6 @@ at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']}); return; } - msg.$ = at.root.$; Gun.on.put(msg, at.root.$); } var empty = {}, u; @@ -1312,7 +1310,7 @@ //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - //root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2)); + //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? @@ -1806,13 +1804,13 @@ }); }); setTimeout(function(){ - root.on('out', {put: send, '#': root.ask(ack), I: root.$}); + root.on('out', {put: send, '#': root.ask(ack)}); },1); } root.on('out', function(msg){ if(msg.lS){ return } - if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){ + if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){ id = msg['#']; Gun.graph.is(msg.put, null, map); if(!to){ to = setTimeout(flush, opt.wait || 1) } @@ -1888,7 +1886,7 @@ return; // Hmm, what if we have peers but we are disconnected? } //console.log("lS get", lex, data); - root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I}); + root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$}); }; Gun.debug? setTimeout(to,1) : to(); }); @@ -1938,8 +1936,8 @@ if((tmp = msg['@']) && (tmp = ctx.dup.s[tmp]) && (tmp = tmp.it) - && tmp.mesh){ - mesh.say(msg, tmp.mesh.via, 1); + && tmp._){ + mesh.say(msg, (tmp._).via, 1); tmp['##'] = msg['##']; return; } @@ -1971,9 +1969,9 @@ (tmp = dup.s)[hash] = tmp[id]; } } - (msg.mesh = function(){}).via = peer; + (msg._ = function(){}).via = peer; if((tmp = msg['><'])){ - msg.mesh.to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); + (msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); } if(msg.dam){ if(tmp = mesh.hear[msg.dam]){ @@ -1981,7 +1979,6 @@ } return; } - ctx.on('in', msg); return; @@ -2012,7 +2009,7 @@ } var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! if(!wire){ return } - msh = msg.mesh || empty; + msh = (msg._) || empty; if(peer === msh.via){ return } if(!(raw = msh.raw)){ raw = mesh.raw(msg) } if((tmp = msg['@']) @@ -2063,7 +2060,7 @@ mesh.raw = function(msg){ if(!msg){ return '' } - var dup = ctx.dup, msh = msg.mesh || {}, put, hash, tmp; + var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp; if(tmp = msh.raw){ return tmp } if(typeof msg === 'string'){ return msg } if(msg['@'] && (tmp = msg.put)){ diff --git a/gun.min.js b/gun.min.js index 2ec9c2fa..2a6b24fd 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1,2 +1 @@ -!function(){function t(n,o){function e(t){return t.split("/").slice(-1).toString().replace(".js","")}return o?require(n):n.slice?t[e(n)]:function(o,i){n(o={exports:{}}),t[e(i)]=o.exports}}var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var o=n.console||{log:function(){}};if("undefined"!=typeof module)var e=module;t(function(t){var n={};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){function e(t,n){for(var o,e=-1,i=0;o=n[i++];)if(!~(e=t.indexOf(o,e+1)))return!1;return!0}var i=!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;i=!0,t=t.slice(o["*"].length)}if(n.obj.has(o,"!")){if(t.slice(-o["!"].length)!==o["!"])return!1;i=!0}if(n.obj.has(o,"+")&&n.list.map(n.list.is(o["+"])?o["+"]:[o["+"]],function(n){return t.indexOf(n)>=0?void(i=!0):!0}))return!1;if(n.obj.has(o,"-")&&n.list.map(n.list.is(o["-"])?o["-"]:[o["-"]],function(n){return t.indexOf(n)<0?void(i=!0):!0}))return!1;if(n.obj.has(o,">")){if(!(t>o[">"]))return!1;i=!0}if(n.obj.has(o,"<")){if(!(tn?-1:n>o?1:0):0}},n.list.map=function(t,n,o){return u(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]:!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){return t?(t[n]=null,delete t[n],t):void 0},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){a(this,n)&&o!==this[n]||(this[n]=t)}var o;n.obj.to=function(n,o){return o=o||{},u(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)&&a(o,n)))return n?!0:void 0}n.obj.empty=function(n,o){return n&&u(n,t,{n:o})?!1:!0}}(),function(){function t(n,o){return 2===arguments.length?(t.r=t.r||{},void(t.r[n]=o)):(t.r=t.r||[],void t.r.push(n))}var i=Object.keys;n.obj.map=function(u,s,f){var c,l,p,d,h,v=0,g=o(s);if(t.r=null,i&&r(u)&&(d=i(u),h=!0),e(u)||d)for(l=(d||u).length;l>v;v++){var m=v+n.list.index;if(g){if(p=h?s.call(f||this,u[d[v]],d[v],t):s.call(f||this,u[v],m,t),p!==c)return p}else if(s===u[h?d[v]:v])return d?d[v]:m}else for(v in u)if(g){if(a(u,v)&&(p=f?s.call(f,u[v],v,t):s(u[v],v,t),p!==c))return p}else if(s===u[v])return v;return g?t.r:n.list.index?0:-1}}(),n.time={},n.time.is=function(t){return t?t instanceof Date:+(new Date).getTime()};var o=n.fn.is,e=n.list.is,i=n.obj,r=i.is,a=i.has,u=i.map;t.exports=n})(t,"./type"),t(function(t){t.exports=function n(t,o,e){if(!t)return{to:n};var i,t=(this.tag||(this.tag={}))[t]||(this.tag[t]={tag:t,to:n._={next:function(t){var n;(n=this.to)&&n.next(t)}}});if(o instanceof Function){var r={off:n.off||(n.off=function(){return this.next===n._.next?!0:(this===this.the.last&&(this.the.last=this.back),this.to.back=this.back,this.next=n._.next,this.back.to=this.to,void(this.the.last===this.the&&delete this.on.tag[this.the.tag]))}),to:n._,next:o,the:t,on:this,as:e};return(r.back=t.last||t).to=r,t.last=r}return(t=t.to)&&i!==o&&t.next(o),t}})(t,"./onto"),t(function(t){function n(t,n,e,i,r){if(n>t)return{defer:!0};if(e>n)return{historical:!0};if(n>e)return{converge:!0,incoming:!0};if(n===e){if(i=o(i)||"",r=o(r)||"",i===r)return{state:!0};if(r>i)return{converge:!0,current:!0};if(i>r)return{converge:!0,incoming:!0}}return{err:"Invalid CRDT Data: "+i+" to "+r+" at "+n+" to "+e+"!"}}if("undefined"==typeof JSON)throw new Error("JSON is not included in this browser. Please load it first: ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js");var o=JSON.stringify;t.exports=n})(t,"./HAM"),t(function(n){var o=t("./type"),e={};e.is=function(t){return t===i?!1:null===t?!0:t===1/0?!1:s(t)||a(t)||u(t)?!0:e.rel.is(t)||!1},e.link=e.rel={_:"#"},function(){function t(t,n){var o=this;return o.id?o.id=!1:n==r&&s(t)?void(o.id=t):o.id=!1}e.rel.is=function(n){if(n&&n[r]&&!n._&&c(n)){var o={};if(p(n,t,o),o.id)return o.id}return!1}}(),e.rel.ify=function(t){return l({},r,t)},o.obj.has._=".";var i,r=e.link._,a=o.bi.is,u=o.num.is,s=o.text.is,f=o.obj,c=f.is,l=f.put,p=f.map;n.exports=e})(t,"./val"),t(function(n){var o=t("./type"),e=t("./val"),i={_:"_"};i.soul=function(t,n){return t&&t._&&t._[n||p]},i.soul.ify=function(t,n){return n="string"==typeof n?{soul:n}:n||{},t=t||{},t._=t._||{},t._[p]=n.soul||t._[p]||l(),t},i.soul._=e.link._,function(){function t(t,n){return n!==i._?e.is(t)?void(this.cb&&this.cb.call(this.as,t,n,this.n,this.s)):!0:void 0}i.is=function(n,o,e){var r;return u(n)&&(r=i.soul(n))?!f(n,t,{as:e,cb:o,s:r,n:n}):!1}}(),function(){function t(t,n){var o,i,r=this.o;return r.map?(o=r.map.call(this.as,t,""+n,r.node),void(i===o?s(r.node,n):r.node&&(r.node[n]=o))):void(e.is(t)&&(r.node[n]=t))}i.ify=function(n,o,e){return o?"string"==typeof o?o={soul:o}:o instanceof Function&&(o={map:o}):o={},o.map&&(o.node=o.map.call(e,n,r,o.node||{})),(o.node=i.soul.ify(o.node||{},o))&&f(n,t,{o:o,as:e}),o.node}}();var r,a=o.obj,u=a.is,s=a.del,f=a.map,c=o.text,l=c.random,p=i.soul._;n.exports=i})(t,"./node"),t(function(n){function o(){var t;return t=r(),t>a?(u=0,a=t+o.drift):a=t+(u+=1)/s+o.drift}{var e=t("./type"),i=t("./node"),r=e.time.is,a=-(1/0),u=0,s=1e3,f="undefined"!=typeof performance?performance.timing&&performance:!1;f&&f.timing&&f.timing.navigationStart||(f=!1)}o._=">",o.drift=0,o.is=function(t,n,e){var i=n&&t&&t[x]&&t[x][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,a){if(!t||!t[x]){if(!a)return;t=i.soul.ify(t,a)}var u=p(t[x],o._);return c!==n&&n!==x&&(b(e)&&(u[n]=e),c!==r&&(t[n]=r)),t},o.to=function(t,n,e){var r=(t||{})[n];return h(r)&&(r=g(r)),o.ify(e,n,o.is(t,n),r,i.soul(t))},function(){function t(t,n){x!==n&&o.ify(this.o,n,this.s)}o.map=function(n,e,i){var r,a=h(a=n||e)?a:null;return n=y(n=n||e)?n:null,a&&!n?(e=b(e)?e:o(),a[x]=a[x]||{},v(a,t,{o:a,s:e}),a):(i=i||h(e)?e:r,e=b(e)?e:o(),function(o,a,u,s){return n?(n.call(i||this||{},o,a,u,s),void(d(u,a)&&r===u[a]||t.call({o:u,s:e},o,a))):(t.call({o:u,s:e},o,a),o)})}}();var c,l=e.obj,p=l.as,d=l.has,h=l.is,v=l.map,g=l.copy,m=e.num,b=m.is,k=e.fn,y=k.is,x=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){return t&&o===i.soul(t)&&i.is(t,this.fn,this.as)?void(this.cb&&(n.n=t,n.as=this.as,this.cb.call(n.as,t,o,n))):!0}function n(t){t&&i.is(n.n,t,n.as)}r.is=function(n,o,e,i){return n&&s(n)&&!l(n)?!d(n,t,{cb:o,fn:e,as:i}):!1}}(),function(){function t(t,o){var r;return(r=p(t,o))?r:(o.env=t,o.soul=u,i.ify(o.obj,n,o)&&(o.rel=o.rel||e.rel.ify(i.soul(o.node)),o.obj!==t.shell&&(t.graph[e.rel.is(o.rel)]=o.node)),o)}function n(n,o,r){var u,s,p=this,d=p.env;if(i._===o&&c(n,e.rel._))return r._;if(u=l(n,o,r,p,d)){if(o||(p.node=p.node||r||{},c(n,i._)&&i.soul(n)&&(p.node._=h(n._)),p.node=i.soul.ify(p.node,e.rel.is(p.rel)),p.rel=p.rel||e.rel.ify(i.soul(p.node))),(s=d.map)&&(s.call(d.as||{},n,o,r,p),c(r,o))){if(n=r[o],a===n)return void f(r,o);if(!(u=l(n,o,r,p,d)))return}if(!o)return p.node;if(!0===u)return n;if(s=t(d,{obj:n,path:p.path.concat(o)}),s.node)return s.rel}}function u(t){var n=this,o=e.link.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 l(t,n,i,r,a){var u;return e.is(t)?!0:s(t)?1:(u=a.invalid)?(t=u.call(a.as||{},t,n,i),l(t,n,i,r,a)):(a.err="Invalid value at '"+r.path.concat(n).join(".")+"'!",void(o.list.is(t)&&(a.err+=" Use `.set(item)` instead of an Array.")))}function p(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.shell=(i||{}).shell,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,a;if(i._===n){if(l(t,e.rel._))return;return void(this.obj[n]=h(t))}return(o=e.rel.is(t))?(a=this.opt.seen[o])?void(this.obj[n]=a):void(this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt)):void(this.obj[n]=t)}r.to=function(n,o,e){if(n){var i={};return e=e||{seen:{}},d(n[o],t,{obj:i,graph:n,opt:e}),i}}}();var a,u=(o.fn.is,o.obj),s=u.is,f=u.del,c=u.has,l=u.empty,p=u.put,d=u.map,h=u.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}var o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),o}}})(t,"./ask"),t(function(n){function o(t){var n={s:{}};return t=t||{max:1e3,age:9e3},n.check=function(t){var o;return(o=n.s[t])?o.pass?o.pass=!1:n.track(t):!1},n.track=function(o,r){var a=n.s[o]||(n.s[o]={});return a.was=i(),r&&(a.pass=!0),n.to||(n.to=setTimeout(function(){var o=i();e.obj.map(n.s,function(i,r){i&&t.age>o-i.was||e.obj.del(n.s,r)}),n.to=null},t.age+9)),a},n}var e=t("./type"),i=e.time.is;n.exports=o})(t,"./dup"),t(function(n){function i(t){return t instanceof i?(this._={gun:this,$:this}).$:this instanceof i?i.create(this._={gun:this,$:this,opt:t}):new i(t)}i.is=function(t){return t instanceof i||t&&t._&&t===t._.$||!1},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"),function(){function t(n){var o,e,r=this,u=r.as,s=u.at||u,f=s.$;return(e=n["#"])||(e=n["#"]=c(9)),(o=s.dup).check(e)?void(u.out===n.out&&(n.out=a,r.to.next(n))):(o.track(e),s.ask(n["@"],n)||(n.get&&i.on.get(n,f),n.put&&i.on.put(n,f)),r.to.next(n),void(u.out||(n.out=t,s.on("out",n))))}i.create=function(n){n.root=n.root||n,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.$.opt(n.opt);return n.once||(n.on("in",t,n),n.on("out",t,{at:n,out:t}),i.on("create",n),n.on("create",n)),n.once=1,o}}(),function(){function t(t,n,o,e){var r=this,a=i.state.is(o,n);if(!a)return r.err="Error: No state on '"+n+"' in node '"+e+"'!";var u=r.graph[e]||k,s=i.state.is(u,n,!0),f=u[n],c=i.HAM(r.machine,a,s,t,f);return 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]),void(r.souls[e]=!0)):void(c.defer&&(r.defer=a<(r.defer||1/0)?a:r.defer))}function n(t,n){var i=this,a=i.$._,u=(a.next||k)[n];if(!u){if(!(a.opt||k)["super"])return void(i.souls[n]=!1);u=i.$.get(n)._}var s=i.map[n]={put:t,get:n,$:u.$},f={ctx:i,msg:s};i.async=!!a.tag.node,i.ack&&(s["@"]=i.ack),v(t,o,f),i.async&&(i.and||a.on("node",function(t){this.to.next(t),t===i.map[t.get]&&(i.souls[t.get]=!1,v(t.put,e,t),v(i.souls,function(t){return t?t:void 0})||i.c||(i.c=1,this.off(),v(i.map,r,i)))}),i.and=!0,a.on("node",s))}function o(t,n){var o=this.ctx,e=o.graph,r=this.msg,a=r.get,u=r.put,s=r.$._;e[a]=i.state.to(u,n,e[a]),o.async||(s.put=i.state.to(u,n,s.put))}function e(t,n){var o=this,e=o.put,r=o.$._;r.put=i.state.to(e,n,r.put)}function r(t){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}i.on.put=function(o,e){var u=e._,s={$:e,graph:u.graph,put:{},map:{},souls:{},machine:i.state(),ack:o["@"],cat:u,stop:{}};return i.graph.is(o.put,null,t,s)||(s.err="Error: Invalid graph!"),s.err?u.on("in",{"@":o["#"],err:i.log(s.err)}):(v(s.put,n,s),s.async||v(s.map,r,s),a!==s.defer&&setTimeout(function(){i.on.put(o,e)},s.defer-s.machine),void(s.diff&&u.on("put",h(o,{put:s.diff}))))},i.on.get=function(t,n){var o,e=n._,r=t.get,a=r[m],u=e.graph[a],s=r[b],f=e.next||(e.next={}),c=f[a];if(d(a,"*")){var l={};i.obj.map(e.graph,function(t,n){i.text.match(n,a)&&(l[n]=i.obj.copy(t))}),i.obj.empty(l)||e.on("in",{"@":t["#"],how:"*",put:l,$:n})}if(!u)return e.on("get",t);if(s){if(!d(u,s))return e.on("get",t);u=i.state.to(u,s)}else u=i.obj.copy(u);u=i.graph.node(u),o=(c||k).ack,e.on("in",{"@":t["#"],how:"mem",put:u,$:n}),e.on("get",t)}}(),function(){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]),u(e)&&(e=v(e,function(t,n,o){o(t,{url:t})}),p(o.opt.peers)||(o.opt.peers={}),o.opt.peers=h(e,o.opt.peers)),o.opt.peers=o.opt.peers||{},h(t,o.opt),i.on("opt",o),o.opt.uuid=o.opt.uuid||function(){return g()+c(12)},n}}();var a,u=i.list.is,s=i.text,f=s.is,c=s.random,l=i.obj,p=l.is,d=l.has,h=l.to,v=l.map,g=(l.copy,i.state.lex),m=i.val.rel._,b=".",k=(i.node._,i.val.link.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=window.Gun=i).window=window);try{"undefined"!=typeof e&&(e.exports=i)}catch(y){}n.exports=i})(t,"./root"),t(function(){var n=t("./root");n.chain.back=function(t,i){var r;if(t=t||1,-1===t||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var a=this,u=a._;if("string"==typeof t&&(t=t.split(".")),!(t instanceof Array)){if(t instanceof Function){for(var s,r={back:u};(r=r.back)&&o===(s=t(r,i)););return s}return n.num.is(t)?(u.back||u).$.back(t-1):this}var f=0,c=t.length,r=u;for(f;c>f;f++)r=(r||e)[t[f]];return o!==r?i?a:r:(r=u.back)?r.$.back(t,i):void 0};var o,e={}})(t,"./back"),t(function(){function n(t){var n,o,e,i=this.as,r=i.back,a=i.root;if(t.I||(t.I=i.$),t.$||(t.$=i.$),this.to.next(t),o=t.get){if(o["#"]||i.soul){if(o["#"]=o["#"]||i.soul,t["#"]||(t["#"]=b(9)),r=a.$.get(o["#"])._,o=o["."]){if(h(r.put,o)&&(n=r.$.get(o)._,(e=n.ack)||(n.ack=-1),r.on("in",{$:r.$,put:c.state.to(r.put,o),get:r.get}),e))return}else{if(e=r.ack,e||(r.ack=-1),h(r,"put")&&r.on("in",r),e)return;t.$=r.$}return a.ask(f,t),a.on("in",t)}if(a.now&&(a.now[i.id]=a.now[i.id]||!0,i.pass={}),o["."])return i.get?(t={get:{".":i.get},$:i.$},r.ask||(r.ask={}),r.ask[i.get]=t.$._,r.on("out",t)):(t={get:{},$:i.$},r.on("out",t));if(i.ack=i.ack||-1,i.get)return t.$=i.$,o["."]=i.get,(r.ask||(r.ask={}))[i.get]=t.$._,r.on("out",t)}return r.on("out",t)}function o(t){var n,o,r=this,s=r.as,f=s.root,d=t.$,b=(d||p)._||p,k=t.put;if(s.get&&t.get!==s.get&&(t=g(t,{get:s.get})),s.has&&b!==s&&(t=g(t,{$:s.$}),b.ack&&(s.ack=b.ack)),l===k){if(o=b.put,r.to.next(t),s.soul)return;if(l===o&&l!==b.put)return;return i(s,t,r),s.has&&u(s,t),v(b.echo,s.id),void v(s.map,b.id)}if(s.soul)return r.to.next(t),i(s,t,r),void(s.next&&m(k,a,{msg:t,cat:s}));if(!(n=c.val.link.is(k)))return c.val.is(k)?(s.has||s.soul?u(s,t):(b.has||b.soul)&&((b.echo||(b.echo={}))[s.id]=b.echo[b.id]||s,(s.map||(s.map={}))[b.id]=s.map[b.id]||{at:b}),r.to.next(t),void i(s,t,r)):(s.has&&b!==s&&h(b,"put")&&(s.put=b.put),(n=c.node.soul(k))&&b.has&&(b.put=s.root.$.get(n)._.put),o=(f.stop||{})[b.id],r.to.next(t),e(s,t,b,n),i(s,t,r),void(s.next&&m(k,a,{msg:t,cat:s})));f.stop;o=f.stop||{},o=o[b.id]||(o[b.id]={}),o.is=o.is||b.put,o[s.id]=b.put||!0,r.to.next(t),e(s,t,b,n),i(s,t,r)}function e(t,n,o,i){if(i&&k!==t.get){var r=t.root.$.get(i)._;t.has?o=r:o.has&&e(o,n,o,i),o!==t&&(o.$||(o={}),(o.echo||(o.echo={}))[t.id]=o.echo[t.id]||t,t.has&&!(t.map||p)[o.id]&&u(t,n),r=o.id?(t.map||(t.map={}))[o.id]=t.map[o.id]||{at:o}:{},(i!==r.link||r.pass||t.pass)&&(t.pass&&(c.obj.map(t.map,function(t){t.pass=!0}),v(t,"pass")),r.pass&&v(r,"pass"),t.has&&(t.link=i),s(t,r.link=i)))}}function i(t,n){t.echo&&m(t.echo,r,n)}function r(t){t&&t.on&&t.on("in",this)}function a(t,n){var o,e,i,r=this.cat,a=r.next||p,u=this.msg;(k!==n||a[n])&&(e=a[n])&&(e.has?(l!==e.put&&c.val.link.is(t)||(e.put=t),o=e.$):(i=u.$)&&(i=(o=u.$.get(n))._,l!==i.put&&c.val.link.is(t)||(i.put=t)),e.on("in",{put:t,get:n,$:o,via:u}))}function u(t,n){if(t.has||t.soul){{var o=t.map;t.root}t.map=null,t.has&&(t.link=null),(t.pass||n["@"]||null!==o)&&(l===o&&c.val.link.is(t.put)||(m(o,function(n){(n=n.at)&&v(n.echo,t.id)}),o=t.put,m(t.next,function(n,e){return l===o&&l!==t.put?!0:(n.put=l,n.ack&&(n.ack=-1),void n.on("in",{get:e,$:n.$,put:l}))})))}}function s(t,n){var o=t.root.$.get(n)._;(!t.ack||(o.on("out",{get:{"#":n}}),t.ask))&&(o=t.ask,c.obj.del(t,"ask"),m(o||t.next,function(t,o){t.on("out",{get:{"#":n,".":o}})}),c.obj.del(t,"ask"))}function f(t){var n=this.as,o=n.get||p,e=n.$._,i=(t.put||p)[o["#"]];if(e.ack&&(e.ack=e.ack+1||1),!t.put||o["."]&&!h(i,e.get)){if(e.put!==l)return;return void e.on("in",{get:e.get,put:e.put=l,$:e.$,"@":t["@"]})}return k==o["."]?void e.on("in",{get:e.get,put:c.val.link.ify(o["#"]),$:e.$,"@":t["@"]}):(t.$=e.root.$,void c.on.put(t,e.root.$))}var c=t("./root");c.chain.chain=function(t){var e,i=this,r=i._,a=new(t||i).constructor(i),u=a._;return u.root=e=r.root,u.id=++e.once,u.back=i._,u.on=c.on,u.on("in",o,u),u.on("out",n,u),a};var l,p={},d=c.obj,h=d.has,v=(d.put,d.del),g=d.to,m=d.map,b=c.text.random,k=(c.val.rel._,c.node._)})(t,"./chain"),t(function(){function n(t,n){var o=n._,e=o.next,i=n.chain(),r=i._;return e||(e=o.next={}),e[r.get=t]=r,n===o.root.$?r.soul=t:(o.soul||o.has)&&(r.has=t),r}function o(t,n,o,e){var i,r=t._,u=0;return(i=r.soul)?(n(i,e,r),t):(i=r.link)?(n(i,e,r),t):(t.get(function(t,o){if(!(a===t.put&&(i=(s(r.root.opt.peers,function(t,n,o){o(n)})||[]).length)&&u++<=i)){o.rid(t);var f=(f=t.$)&&f._||{};i=f.link||f.soul||l.is(t.put)||p(t.put)||f.dub,n(i,e,t,o)}},{out:{get:{".":!0}}}),t)}function e(t){var n,o=this,e=o.as,i=e.at,u=i.root,s=t.$,c=(s||{})._||{},p=t.put||c.put;if((n=u.now)&&o!==n[e.now])return o.to.next(t);if(o.seen&&c.id&&o.seen[c.id])return o.to.next(t);if((n=p)&&n[l._]&&(n=l.is(n))&&(n=(t.$$=c.root.gun.get(n))._,a!==n.put&&(t=f(t,{put:p=n.put}))),(n=u.mum)&&c.id){var d=c.id+(o.id||(o.id=r.text.random(9)));if(n[d])return;a===p||l.is(p)||(n[d]=!0)}return e.use(t,o),o.stun?void(o.stun=null):void o.to.next(t)}function i(t){var n=this.on;if(!t||n.soul||n.has)return this.off();if(t=(t=(t=t.$||t)._||t).id){{var o,e;n.map}return(o=(e=this.seen||(this.seen={}))[t])?!0:void(e[t]=!0)}}var r=t("./root");r.chain.get=function(t,a,u){var s,f;if("string"!=typeof t){if(t instanceof Function){if(!0===a)return o(this,t,a,u);s=this;var p,h=s._,v=h.root,f=v.now;u=a||{},u.at=h,u.use=t,u.out=u.out||{},u.out.get=u.out.get||{},(p=h.on("in",e,u)).rid=i,(v.now={$:1})[u.now=h.id]=p;var g=v.mum;return v.mum={},h.on("out",u.out),v.mum=g,v.now=f,s}return c(t)?this.get(""+t,a,u):(f=l.is(t))?this.get(f,a,u):((u=this.chain())._.err={err:r.log("Invalid get request!",t)},a&&a.call(u,u._.err),u)}var m=this,b=m._,k=b.next||d;return(s=k[t])||(s=n(t,m)),s=s.$,(f=b.stun)&&(s._.stun=s._.stun||f),a&&a instanceof Function&&s.get(a,u),s};var a,u=r.obj,s=u.map,f=(u.has,r.obj.to),c=r.num.is,l=r.val.link,p=r.node.soul,d=(r.node._,{})})(t,"./get"),t(function(){function n(t){t.batch=i;var n=t.opt||{},o=t.env=c.state.map(a,n.state);return o.soul=t.soul,t.graph=c.graph.ify(t.data,o,t),o.err?((t.ack||m).call(t,t.out={err:c.log(o.err)}),void(t.res&&t.res())):void t.batch()}function e(t){return void(t&&t())}function i(){var t=this;t.graph&&!v(t.stun,r)&&(t.res=t.res||function(t){t&&t()},t.res(function(){var n=t.$.back(-1)._,o=n.ask(function(o){n.root.on("ack",o),o.err&&c.log(o),o.lack||this.off(),t.ack&&t.ack(o,this)},t.opt),e=n.root.now;p.del(n.root,"now");var i=n.root.mum;n.root.mum={},t.ref._.on("out",{$:t.ref,put:t.out=t.env.graph,opt:t.opt,"#":o}),n.root.mum=i?p.to(i,n.root.mum):i,n.root.now=e},t),t.res&&t.res())}function r(t){return t?!0:void 0}function a(t,n,o,e){var i=this,r=c.is(t);!n&&e.path.length&&(i.res||b)(function(){var n=e.path,o=i.ref,a=(i.opt,0),s=n.length;for(a;s>a;a++)o=o.get(n[a]);r&&(o=t);var f=o._.dub;return f||(f=c.node.soul(e.obj))?(o.back(-1).get(f),void e.soul(f)):((i.stun=i.stun||{})[n]=!0,void o.get(u,!0,{as:{at:e,as:i,p:n}}))},{as:i,at:e})}function u(t,n,o,e){var n=n.as,i=n.at;n=n.as;var r=((o||{}).$||{})._||{};return t=r.dub=r.dub||t||c.node.soul(i.obj)||c.node.soul(o.put||r.put)||c.val.rel.is(o.put||r.put)||(n.via.back("opt.uuid")||c.text.random)(),e&&(e.stun=!0),t?void s(r,r.dub=t,i,n):void r.via.back("opt.uuid")(function(t,o){return t?c.log(t):void s(r,r.dub=r.dub||o,i,n)})}function s(t,n,o,e){t.$.back(-1).get(n),o.soul(n),e.stun[o.path]=!1,e.batch()}function f(t,n,e,i){if(n=n.as,e.$&&e.$._){if(e.err)return void o.log("Please report this as an issue! Put.any.err");var r,a=e.$._,u=a.put,s=n.opt||{};if(!(r=n.ref)||!r._.now){if(i&&(i.stun=!0),n.ref!==n.$){if(r=n.$._.get||a.get,!r)return void o.log("Please report this as an issue! Put.no.get");n.data=h({},r,n.data),r=null}if(l===u){if(!a.get)return;t||(r=a.$.back(function(t){return t.link||t.soul?t.link||t.soul:void(n.data=h({},t.get,n.data))})),r=r||a.soul||a.link||a.dub,a=r?a.root.$.get(r)._:a,n.soul=r,u=n.data}return n.not||(n.soul=n.soul||t)||(n.path&&d(n.data)?n.soul=(s.uuid||n.via.back("opt.uuid")||c.text.random)():(k==a.get&&(n.soul=(a.put||g)["#"]||a.dub),n.soul=n.soul||a.soul||a.soul||(s.uuid||n.via.back("opt.uuid")||c.text.random)()),n.soul)?void n.ref.put(n.data,n.soul,n):void n.via.back("opt.uuid")(function(t,o){return t?c.log(t):void n.ref.put(n.data,n.soul=o,n)})}}}var c=t("./root");c.chain.put=function(t,o,i){var r,a=this,u=a._,s=u.root.$;return i=i||{},i.data=t,i.via=i.$=i.via||i.$||a,"string"==typeof o?i.soul=o:i.ack=i.ack||o,u.soul&&(i.soul=u.soul),i.soul||s===a?d(i.data)?(i.soul=i.soul||(i.not=c.node.soul(i.data)||(i.via.back("opt.uuid")||c.text.random)()),i.soul?(i.$=s.get(i.soul),i.ref=i.$,n(i),a):(i.via.back("opt.uuid")(function(t,n){return t?c.log(t):void(i.ref||i.$).put(i.data,i.soul=n,i)}),a)):((i.ack||m).call(i,i.out={err:c.log("Data saved to the root level of the graph must be a node (an object), not a",typeof i.data,'of "'+i.data+'"!')}),i.res&&i.res(),a):c.is(t)?(t.get(function(t,n,e){return!t&&c.val.is(e.put)?c.log("The reference you are saving is a",typeof e.put,'"'+e.put+'", not a node (object)!'):void a.put(c.val.rel.ify(t),o,i)},!0),a):(i.ref=i.ref||s._===(r=u.back)?a:r.$,i.ref._.soul&&c.val.is(i.data)&&u.get?(i.data=h({},u.get,i.data),i.ref.put(i.data,i.soul,i),a):(i.ref.get(f,!0,{as:i}),i.out||(i.res=i.res||e,i.$._.stun=i.ref._.stun),a))};var l,p=c.obj,d=p.is,h=p.put,v=p.map,g={},m=function(){},b=function(t,n){t.call(n||g)},k=c.node._})(t,"./put"),t(function(n){var o=t("./root");t("./chain"),t("./back"),t("./put"),t("./get"),n.exports=o})(t,"./index"),t(function(){function n(t,n){{var o,e=this,r=t.$,a=(r||{})._||{},u=a.put||t.put;e.at}if(i!==u){if(o=t.$$){if(o=t.$$._,i===o.put)return;u=o.put}e.change&&(u=t.put),e.as?e.ok.call(e.as,t,n):e.ok.call(r,u,t.get,t,n)}}function o(t,n,r){var u,f,c=this.as,l=(c.at,t.$),p=l._,d=p.put||t.put;return(f=t.$$)&&(u=f=t.$$._,i!==u.put&&(d=u.put)),(f=n.wait)&&(f=f[p.id])&&clearTimeout(f),!r&&(i===d||p.soul||p.link||u&&!(0 .once, apologies unexpected."),this.once(t,n)},e.chain.once=function(t,n){var r=this,a=r._,u=a.put;if(0=(o.batch||1e3)?s():void(e||(e=setTimeout(s,o.wait||1)))}),n.on("get",function(t){function e(){if(s&&(i=s["#"])){var e=s["."];r=a[i]||u,r&&e&&(r=Gun.state.to(r,e)),(r||Gun.obj.empty(o.peers))&&n.on("in",{"@":t["#"],put:Gun.graph.node(r),how:"lS",lS:t.I})}}this.to.next(t);var i,r,u,s=t.get;Gun.debug?setTimeout(e,1):e()});var u=function(t,n,o,e){a[e]=Gun.state.to(o,n,a[e])},s=function(u){var f;r=0,clearTimeout(e),e=!1;var c=i;i={},u&&(a=u);try{t.setItem(o.prefix,JSON.stringify(a))}catch(l){Gun.log(f=(l||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."),n.on("localStorage:error",{err:f,file:o.prefix,flush:a,retry:s})}(f||Gun.obj.empty(o.peers))&&Gun.obj.map(c,function(t,o){n.on("in",{"@":o,err:f,ok:0})})}}})}})(t,"./adapters/localStorage"),t(function(n){function e(t){var n=function(){},u=t.opt||{};return u.log=u.log||o.log,u.gap=u.gap||u.wait||1,u.pack=u.pack||.3*(u.memory?1e3*u.memory*1e3:1399e6),n.out=function(o){var e;return this.to&&this.to.next(o),(e=o["@"])&&(e=t.dup.s[e])&&(e=e.it)&&e.mesh?(n.say(o,e.mesh.via,1),void(e["##"]=o["##"])):void n.say(o)},t.on("create",function(o){o.opt.pid=o.opt.pid||i.text.random(9),this.to.next(o),t.on("out",n.out)}),n.hear=function(o,e){if(o){var r,a,s,f=t.dup,c=o[0];if(u.pack<=o.length)return n.say({dam:"!",err:"Message too big!"},e);try{s=JSON.parse(o)}catch(l){u.log("DAM JSON parse error",l)}if("{"===c){if(!s)return;if(f.check(r=s["#"]))return;if(f.track(r,!0).it=s,(c=s["@"])&&s.put&&(a=s["##"]||(s["##"]=n.hash(s)),(c+=a)!=r)){if(f.check(c))return;(c=f.s)[a]=c[r]}return(s.mesh=function(){}).via=e,(c=s["><"])&&(s.mesh.to=i.obj.map(c.split(","),function(t,n,o){o(t,!0)})),s.dam?void((c=n.hear[s.dam])&&c(s,e,t)):void t.on("in",s)}if("["!==c);else{if(!s)return;for(var p,d=0;p=s[d++];)n.hear(p,e)}}},function(){function o(t){var n=t.batch;if(n&&(t.batch=t.tail=null,n.length))try{e(JSON.stringify(n),t)}catch(o){u.log("DAM JSON stringify error",o)}}function e(t,n){var o=n.wire;try{o.send?o.send(t):n.say&&n.say(t)}catch(e){(n.queue=n.queue||[]).push(t)}}n.say=function(r,s,f){if(!s)return void i.obj.map(u.peers,function(t){n.say(r,t)});var c,l,p,d=s.wire||u.wire&&u.wire(s);if(d&&(l=r.mesh||a,s!==l.via&&((p=l.raw)||(p=n.raw(r)),!((c=r["@"])&&(c=t.dup.s[c])&&(c=c.it)&&c.get&&c["##"]&&c["##"]===r["##"]||(c=l.to)&&(c[s.url]||c[s.id])&&!f)))){if(s.batch){if(s.tail=(s.tail||0)+p.length,s.tail<=u.pack)return void s.batch.push(p);o(s)}s.batch=[],setTimeout(function(){o(s)},u.gap),e(p,s)}}}(),function(){function o(t,n){var o;return n instanceof Object?(i.obj.map(Object.keys(n).sort(),a,{to:o={},on:n}),o):n}function a(t){this.to[t]=this.on[t]}n.raw=function(e){if(!e)return"";var a,c,l,p=t.dup,d=e.mesh||{};if(l=d.raw)return l;if("string"==typeof e)return e;e["@"]&&(l=e.put)&&((c=e["##"])||(a=s(l,o)||"",c=n.hash(e,a),e["##"]=c),(l=p.s)[c=e["@"]+c]=l[e["#"]],e["#"]=c||e["#"],a&&((e=i.obj.to(e)).put=f));var h=0,v=[];i.obj.map(u.peers,function(t){return v.push(t.url||t.id),++h>9?!0:void 0}),e["><"]=v.join();var g=s(e);return r!==a&&(l=g.indexOf(f,g.indexOf("put")),g=g.slice(0,l-1)+a+g.slice(l+f.length+1)),d&&(d.raw=g),g},n.hash=function(t,n){return e.hash(n||s(t.put,o)||"")||t["#"]||i.text.random(9)};var s=JSON.stringify,f=":])([:"}(),n.hi=function(o){var e=o.wire||{};o.id||o.url?(u.peers[o.url||o.id]=o,i.obj.del(u.peers,e.id)):(e=e.id=e.id||i.text.random(9),n.say({dam:"?"},u.peers[e]=o)),e.hied||t.on(e.hied="hi",o),e=o.queue,o.queue=[],i.obj.map(e,function(t){n.say(t,o)})},n.bye=function(n){ -i.obj.del(u.peers,n.id),t.on("bye",n)},n.hear["!"]=function(t){u.log("Error:",t.err)},n.hear["?"]=function(t,o){return t.pid?(o.id=o.id||t.pid,void n.hi(o)):n.say({dam:"?",pid:u.pid,"@":t["#"]},o)},n}var i=t("../type");e.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o,e=0,i=t.length;i>e;++e)o=t.charCodeAt(e),n=(n<<5)-n+o,n|=0;return n};var r,a={};Object.keys=Object.keys||function(t){return map(t,function(t,n,o){o(n)})};try{n.exports=e}catch(u){}})(t,"./adapters/mesh"),t(function(){var n=t("../index");n.Mesh=t("./mesh"),n.on("opt",function(t){function o(t){try{if(!t||!t.url)return o&&o(t);var n=t.url.replace("http","ws"),o=t.wire=new i.WebSocket(n);return o.onclose=function(){i.mesh.bye(t),e(t)},o.onerror=function(n){e(t),n&&"ECONNREFUSED"===n.code},o.onopen=function(){i.mesh.hi(t)},o.onmessage=function(n){n&&i.mesh.hear(n.data||n,t)},o}catch(r){}}function e(t){clearTimeout(t.defer),t.defer=setTimeout(function(){o(t)},2e3)}this.to.next(t);var i=t.opt;if(!t.once&&!1!==i.WebSocket){var r;"undefined"!=typeof window&&(r=window),"undefined"!=typeof global&&(r=global),r=r||{};var a=i.WebSocket||r.WebSocket||r.webkitWebSocket||r.mozWebSocket;if(a){i.WebSocket=a;{i.mesh=i.mesh||n.Mesh(t),i.wire}i.wire=o}}})})(t,"./adapters/websocket")}(); \ No newline at end of file +!function(){var t;"undefined"!=typeof window&&(t=window),"undefined"!=typeof global&&(t=global);var b=(t=t||{}).console||{log:function(){}};function _(o,t){return t?require(o):o.slice?_[e(o)]:function(t,n){o(t={exports:{}}),_[e(n)]=t.exports};function e(t){return t.split("/").slice(-1).toString().replace(".js","")}}if("undefined"!=typeof module)var f=module;_(function(t){var p={fn:{is:function(t){return!!t&&"function"==typeof t}}};p.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},p.num={is:function(t){return!d(t)&&(0<=t-parseFloat(t)+1||1/0===t||-1/0===t)}},p.text={is:function(t){return"string"==typeof t}},p.text.ify=function(t){return p.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},p.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0")){if(!(n>t[">"]))return!1;o=!0}if(p.obj.has(t,"<")){if(!(n",s.drift=0,s.is=function(t,n,o){var e=n&&t&&t[m]&&t[m][s._]||o;if(e)return g(e=e[n])?e:-1/0},s.lex=function(){return s().toString(36).replace(".","")},s.ify=function(t,n,o,e,i){if(!t||!t[m]){if(!i)return;t=a.soul.ify(t,i)}var r=c(t[m],s._);return void 0!==n&&n!==m&&(g(o)&&(r[n]=o),void 0!==e&&(t[n]=e)),t},s.to=function(t,n,o){var e=(t||{})[n];return p(e)&&(e=d(e)),s.ify(o,n,s.is(t,n),e,a.soul(t))},function(){function u(t,n){m!==n&&s.ify(this.o,n,this.s)}s.map=function(i,r,a){var t=p(t=i||r)?t:null;return i=v(i=i||r)?i:null,t&&!i?(r=g(r)?r:s(),t[m]=t[m]||{},h(t,u,{o:t,s:r}),t):(a=a||p(r)?r:void 0,r=g(r)?r:s(),function(t,n,o,e){if(!i)return u.call({o:o,s:r},t,n),t;i.call(a||this||{},t,n,o,e),l(o,n)&&void 0===o[n]||u.call({o:o,s:r},t,n)})}}();var f=n.obj,c=f.as,l=f.has,p=f.is,h=f.map,d=f.copy,g=n.num.is,v=n.fn.is,m=a._;t.exports=s})(_,"./state"),_(function(t){var a=_("./type"),f=_("./val"),c=_("./node"),r={};!function(){function i(t,n){if(!t||n!==c.soul(t)||!c.is(t,this.fn,this.as))return!0;this.cb&&(o.n=t,o.as=this.as,this.cb.call(o.as,t,n,o))}function o(t){t&&c.is(o.n,t,o.as)}r.is=function(t,n,o,e){return!(!t||!l(t)||u(t))&&!s(t,i,{cb:n,fn:o,as:e})}}(),function(){function u(t,n){var o;return(o=function(t,n){var o,e=t.seen,i=e.length;for(;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}(t,n))?o:(n.env=t,n.soul=i,c.ify(n.obj,e,n)&&(n.rel=n.rel||f.rel.ify(c.soul(n.node)),n.obj!==t.shell&&(t.graph[f.rel.is(n.rel)]=n.node)),n)}function e(t,n,o){var e,i,r=this,a=r.env;if(c._===n&&h(t,f.rel._))return o._;if(e=s(t,n,o,r,a)){if(n||(r.node=r.node||o||{},h(t,c._)&&c.soul(t)&&(r.node._=d(t._)),r.node=c.soul.ify(r.node,f.rel.is(r.rel)),r.rel=r.rel||f.rel.ify(c.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,o,r),h(o,n))){if(void 0===(t=o[n]))return void p(o,n);if(!(e=s(t,n,o,r,a)))return}if(!n)return r.node;if(!0===e)return t;if((i=u(a,{obj:t,path:r.path.concat(n)})).node)return i.rel}}function i(t){var n=this,o=f.link.is(n.rel),e=n.env.graph;n.rel=n.rel||f.rel.ify(t),n.rel[f.rel._]=t,n.node&&n.node[c._]&&(n.node[c._][f.rel._]=t),h(e,o)&&(e[t]=e[o],p(e,o))}function s(t,n,o,e,i){var r;return!!f.is(t)||(l(t)?1:(r=i.invalid)?s(t=r.call(i.as||{},t,n,o),n,o,e,i):(i.err="Invalid value at '"+e.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}r.ify=function(t,n,o){var e={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(e.rel=f.rel.ify(n.soul)),n.shell=(o||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||o,u(n,e),n.root=e.node,n.graph}}(),r.node=function(t){var n=c.soul(t);if(n)return o({},n,t)},function(){function i(t,n){var o,e;if(c._!==n)(o=f.rel.is(t))?(e=this.opt.seen[o])?this.obj[n]=e:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(u(t,f.rel._))return;this.obj[n]=d(t)}}r.to=function(t,n,o){if(t){var e={};return o=o||{seen:{}},s(t[n],i,{obj:e,graph:t,opt:o}),e}}}();a.fn.is;var n=a.obj,l=n.is,p=n.del,h=n.has,u=n.empty,o=n.put,s=n.map,d=n.copy;t.exports=r})(_,"./graph"),_(function(t){_("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),o}}})(_,"./ask"),_(function(t){var r=_("./type");var a=r.time.is;t.exports=function(e){var i={s:{}};return e=e||{max:1e3,age:9e3},i.check=function(t){var n;return!!(n=i.s[t])&&(n.pass?n.pass=!1:i.track(t))},i.track=function(t,n){var o=i.s[t]||(i.s[t]={});return o.was=a(),n&&(o.pass=!0),i.to||(i.to=setTimeout(function(){var o=a();r.obj.map(i.s,function(t,n){t&&e.age>o-t.was||r.obj.del(i.s,n)}),i.to=null},e.age+9)),o},i}})(_,"./dup"),_(function(t){function c(t){return t instanceof c?(this._={gun:this,$:this}).$:this instanceof c?c.create(this._={gun:this,$:this,opt:t}):new c(t)}c.is=function(t){return t instanceof c||t&&t._&&t===t._.$||!1},c.version=.9,(c.chain=c.prototype).toJSON=function(){};var n=_("./type");n.obj.to(n,c),c.HAM=_("./HAM"),c.val=_("./val"),c.node=_("./node"),c.state=_("./state"),c.graph=_("./graph"),c.on=_("./onto"),c.ask=_("./ask"),c.dup=_("./dup"),function(){function a(t){var n,o,e=this.as,i=e.at||e,r=i.$;(o=t["#"])||(o=t["#"]=u(9)),(n=i.dup).check(o)?e.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(o),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),e.out||(t.out=a,i.on("out",t)))}c.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||c.on,t.ask=t.ask||c.ask,t.dup=t.dup||c.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),c.on("create",t),t.on("create",t)),t.once=1,n}}(),function(){function i(t,n,o,e){var i=this,r=c.state.is(o,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=i.graph[e]||v,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[e]=c.state.to(o,n,i.put[e]),(i.diff||(i.diff={}))[e]=c.state.to(o,n,i.diff[e]),i.souls[e]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var o=this,e=o.$._,i=(e.next||v)[n];if(!i){if(!(e.opt||v).super)return void(o.souls[n]=!1);i=o.$.get(n)._}var r=o.map[n]={put:t,get:n,$:i.$},a={ctx:o,msg:r};o.async=!!e.tag.node,o.ack&&(r["@"]=o.ack),h(t,u,a),o.async&&(o.and||e.on("node",function(t){this.to.next(t),t===o.map[t.get]&&(o.souls[t.get]=!1,h(t.put,s,t),h(o.souls,function(t){if(t)return t})||o.c||(o.c=1,this.off(),h(o.map,f,o)))}),o.and=!0,e.on("node",r))}function u(t,n){var o=this.ctx,e=o.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;e[r]=c.state.to(a,n,e[r]),o.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var o=this.put,e=this.$._;e.put=c.state.to(o,n,e.put)}function f(t,n){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}c.on.put=function(t,n){var o=n._,e={$:n,graph:o.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:o,stop:{}};if(c.graph.is(t.put,null,i,e)||(e.err="Error: Invalid graph!"),e.err)return o.on("in",{"@":t["#"],err:c.log(e.err)});h(e.put,r,e),e.async||h(e.map,f,e),void 0!==e.defer&&setTimeout(function(){c.on.put(t,n)},e.defer-e.machine),e.diff&&o.on("put",p(t,{put:e.diff}))},c.on.get=function(t,n){var o=n._,e=t.get,i=e[d],r=o.graph[i],a=e[g],u=(o.next||(o.next={}))[i];if(l(i,"*")){var s={};c.obj.map(o.graph,function(t,n){c.text.match(n,i)&&(s[n]=c.obj.copy(t))}),c.obj.empty(s)||o.on("in",{"@":t["#"],how:"*",put:s,$:n})}if(!r)return o.on("get",t);if(a){if(!l(r,a))return o.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||v).ack,o.on("in",{"@":t["#"],how:"mem",put:r,$:n}),o.on("get",t)}}(),c.chain.opt=function(t){t=t||{};var n=this._,o=t.peers||t;return a(t)||(t={}),a(n.opt)||(n.opt=t),i(o)&&(o=[o]),e(o)&&(o=h(o,function(t,n,o){o(t,{url:t})}),a(n.opt.peers)||(n.opt.peers={}),n.opt.peers=p(o,n.opt.peers)),n.opt.peers=n.opt.peers||{},p(t,n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return s()+u(12)},this};var e=c.list.is,o=c.text,i=o.is,u=o.random,r=c.obj,a=r.is,l=r.has,p=r.to,h=r.map,s=(r.copy,c.state.lex),d=c.val.rel._,g=".",v=(c.node._,c.val.link.is,{});b.debug=function(t,n){return b.debug.i&&t===b.debug.i&&b.debug.i++&&(b.log.apply(b,arguments)||n)},(c.log=function(){return!c.log.off&&b.log.apply(b,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,o){return(o=c.log.once)[t]=o[t]||0,o[t]++||c.log(n)},c.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&((window.GUN=window.Gun=c).window=window);try{void 0!==f&&(f.exports=c)}catch(t){}t.exports=c})(_,"./root"),_(function(t){var u=_("./root");u.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var o=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var e=0,i=t.length,r=o;e .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var o=this,e=o._,i=e.put;if(0=(a.batch||1e3))return f();e||(e=setTimeout(f,a.wait||1))}),r.on("get",function(n){this.to.next(n);var o,e,i=n.get;function t(){if(i&&(o=i["#"])){var t=i["."];(e=s[o]||void 0)&&t&&(e=Gun.state.to(e,t)),(e||Gun.obj.empty(a.peers))&&r.on("in",{"@":n["#"],put:Gun.graph.node(e),how:"lS",lS:n.$||r.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,o,e){s[e]=Gun.state.to(o,n,s[e])},f=function(t){var o;u=0,clearTimeout(e),e=!1;var n=i;i={},t&&(s=t);try{p.setItem(a.prefix,JSON.stringify(s))}catch(t){Gun.log(o=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."),r.on("localStorage:error",{err:o,file:a.prefix,flush:s,retry:f})}(o||Gun.obj.empty(a.peers))&&Gun.obj.map(n,function(t,n){r.on("in",{"@":n,err:o,ok:0})})}}})}})(_,"./adapters/localStorage"),_(function(t){var g=_("../type");function o(p){var h=function(){},d=p.opt||{};return d.log=d.log||b.log,d.gap=d.gap||d.wait||1,d.pack=d.pack||.3*(d.memory?1e3*d.memory*1e3:1399e6),h.out=function(t){var n;if(this.to&&this.to.next(t),(n=t["@"])&&(n=p.dup.s[n])&&(n=n.it)&&n._)return h.say(t,n._.via,1),void(n["##"]=t["##"]);h.say(t)},p.on("create",function(t){t.opt.pid=t.opt.pid||g.text.random(9),this.to.next(t),p.on("out",h.out)}),h.hear=function(t,n){if(t){var o,e,i,r=p.dup,a=t[0];if(d.pack<=t.length)return h.say({dam:"!",err:"Message too big!"},n);try{i=JSON.parse(t)}catch(t){d.log("DAM JSON parse error",t)}if("{"===a){if(!i)return;if(r.check(o=i["#"]))return;if((a=(r.track(o,!0).it=i)["@"])&&i.put&&(a+=e=i["##"]||(i["##"]=h.hash(i)))!=o){if(r.check(a))return;(a=r.s)[e]=a[o]}return(i._=function(){}).via=n,(a=i["><"])&&(i._.to=g.obj.map(a.split(","),function(t,n,o){o(t,!0)})),i.dam?void((a=h.hear[i.dam])&&a(i,n,p)):void p.on("in",i)}if("["!==a);else{if(!i)return;for(var u,s=0;u=i[s++];)h.hear(u,n)}}},function(){function a(t){var n=t.batch;if(n&&(t.batch=t.tail=null,n.length))try{u(JSON.stringify(n),t)}catch(t){d.log("DAM JSON stringify error",t)}}function u(n,o){var t=o.wire;try{t.send?t.send(n):o.say&&o.say(n)}catch(t){(o.queue=o.queue||[]).push(n)}}h.say=function(n,t,o){var e,i,r;if(t){if((t.wire||d.wire&&d.wire(t))&&(i=n._||s,t!==i.via&&((r=i.raw)||(r=h.raw(n)),!((e=n["@"])&&(e=p.dup.s[e])&&(e=e.it)&&e.get&&e["##"]&&e["##"]===n["##"])&&(!(e=i.to)||!e[t.url]&&!e[t.id]||o)))){if(t.batch){if(t.tail=(t.tail||0)+r.length,t.tail<=d.pack)return void t.batch.push(r);a(t)}t.batch=[],setTimeout(function(){a(t)},d.gap),u(r,t)}}else g.obj.map(d.peers,function(t){h.say(n,t)})}}(),function(){function f(t,n){var o;return n instanceof Object?(g.obj.map(Object.keys(n).sort(),e,{to:o={},on:n}),o):n}function e(t){this.to[t]=this.on[t]}h.raw=function(t){if(!t)return"";var n,o,e,i=p.dup,r=t._||{};if(e=r.raw)return e;if("string"==typeof t)return t;t["@"]&&(e=t.put)&&((o=t["##"])||(n=c(e,f)||"",o=h.hash(t,n),t["##"]=o),(e=i.s)[o=t["@"]+o]=e[t["#"]],t["#"]=o||t["#"],n&&((t=g.obj.to(t)).put=l));var a=0,u=[];g.obj.map(d.peers,function(t){if(u.push(t.url||t.id),9<++a)return!0}),t["><"]=u.join();var s=c(t);return v!==n&&(e=s.indexOf(l,s.indexOf("put")),s=s.slice(0,e-1)+n+s.slice(e+l.length+1)),r&&(r.raw=s),s},h.hash=function(t,n){return o.hash(n||c(t.put,f)||"")||t["#"]||g.text.random(9)};var c=JSON.stringify,l=":])([:"}(),h.hi=function(n){var t=n.wire||{};n.id||n.url?(d.peers[n.url||n.id]=n,g.obj.del(d.peers,t.id)):(t=t.id=t.id||g.text.random(9),h.say({dam:"?"},d.peers[t]=n)),t.hied||p.on(t.hied="hi",n),t=n.queue,n.queue=[],g.obj.map(t,function(t){h.say(t,n)})},h.bye=function(t){g.obj.del(d.peers,t.id),p.on("bye",t)},h.hear["!"]=function(t,n){d.log("Error:",t.err)},h.hear["?"]=function(t,n){if(!t.pid)return h.say({dam:"?",pid:d.pid,"@":t["#"]},n);n.id=n.id||t.pid,h.hi(n)},h}o.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o=0.8.4" }, "dependencies": { - "@trust/webcrypto": "^0.9.2", - "text-encoding": "^0.6.4", - "node-webcrypto-ossl": "^1.0.38", + "text-encoding": "^0.7.0", + "node-webcrypto-ossl": "^1.0.39", "ws": "~>5.2.0" }, "devDependencies": { @@ -58,7 +57,7 @@ "concat-map": "^0.0.1", "express": ">=4.15.2", "ip": "^1.1.5", - "mocha": ">=3.2.0", + "mocha": "^5.2.0", "panic-manager": "^1.2.0", "panic-server": "^1.1.1", "uglify-js": ">=2.8.22" diff --git a/sea.js b/sea.js index 6a053585..28264b4f 100644 --- a/sea.js +++ b/sea.js @@ -165,96 +165,73 @@ } if(!api.crypto){try{ var crypto = USE('crypto', 1); - const { subtle } = USE('@trust/webcrypto', 1) // All but ECDH const { TextEncoder, TextDecoder } = USE('text-encoding', 1) Object.assign(api, { crypto, - subtle, + //subtle, TextEncoder, TextDecoder, random: (len) => Buffer.from(crypto.randomBytes(len)) }); //try{ - const WebCrypto = USE('node-webcrypto-ossl', 1) - api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH + const WebCrypto = USE('node-webcrypto-ossl', 1); + api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH //}catch(e){ //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); //} }catch(e){ - console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!"); - console.log("node-webcrypto-ossl is temporarily needed for ECDSA signature verification, and optionally needed for ECDH, please install if needed (currently necessary so add them to your package.json for now)."); - TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; + console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!"); + OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; }} module.exports = api })(USE, './shim'); ;USE(function(module){ - const SEA = USE('./root'); - const Buffer = USE('./buffer') - const settings = {} - // Encryption parameters - const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } + var SEA = USE('./root'); + var Buffer = USE('./buffer'); + var s = {}; + s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64}; + s.ecdsa = { + pair: {name: 'ECDSA', namedCurve: 'P-256'}, + sign: {name: 'ECDSA', hash: {name: 'SHA-256'}} + }; + s.ecdh = {name: 'ECDH', namedCurve: 'P-256'}; - const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } - const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } - const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' } - - const _initial_authsettings = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - hook: (props) => props // { iat, exp, alias, remember } - // or return new Promise((resolve, reject) => resolve(props) - } - // These are used to persist user's authentication "session" - const authsettings = Object.assign({}, _initial_authsettings) // This creates Web Cryptography API compliant JWK for sign/verify purposes - const keysToEcdsaJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + s.jwk = function(pub, d){ // d === priv + pub = pub.split('.'); + var x = pub[0], y = pub[1]; + var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true}; jwk.key_ops = d ? ['sign'] : ['verify']; if(d){ jwk.d = d } return jwk; + }; + s.recall = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props) + }; + + s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) } + s.parse = function p(t){ try { + var yes = (typeof t == 'string'); + if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) } + return yes ? JSON.parse(t) : t; + } catch (e) {} + return t; } - Object.assign(settings, { - pbkdf2: pbkdf2, - ecdsa: { - pair: ecdsaKeyProps, - sign: ecdsaSignProps - }, - ecdh: ecdhKeyProps, - jwk: keysToEcdsaJwk, - recall: authsettings - }) - SEA.opt = settings; - module.exports = settings + SEA.opt = s; + module.exports = s })(USE, './settings'); ;USE(function(module){ - module.exports = (props) => { - try { - if(props.slice && 'SEA{' === props.slice(0,4)){ - props = props.slice(3); - } - return props.slice ? JSON.parse(props) : props - } catch (e) {} //eslint-disable-line no-empty - return props + var shim = USE('./shim'); + module.exports = async function(d, o){ + var t = (typeof d == 'string')? d : JSON.stringify(d); + var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t)); + return shim.Buffer.from(hash); } - })(USE, './parse'); - - ;USE(function(module){ - const shim = USE('./shim'); - const Buffer = USE('./buffer') - const parse = USE('./parse') - const { pbkdf2 } = USE('./settings') - // This internal func returns SHA-256 hashed data for signing - const sha256hash = async (mm) => { - const m = parse(mm) - const hash = await shim.subtle.digest({name: pbkdf2.hash}, new shim.TextEncoder().encode(m)) - return Buffer.from(hash) - } - module.exports = sha256hash })(USE, './sha256'); ;USE(function(module){ @@ -281,25 +258,25 @@ salt = u; } salt = salt || shim.random(9); - if('SHA-256' === opt.name){ - var rsha = shim.Buffer.from(await sha(data), 'binary').toString(opt.encode || 'base64') + data = (typeof data == 'string')? data : JSON.stringify(data); + if('sha' === (opt.name||'').toLowerCase().slice(0,3)){ + var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } return rsha; } - const key = await (shim.ossl || shim.subtle).importKey( - 'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits'] - ) - const result = await (shim.ossl || shim.subtle).deriveBits({ + var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']); + var work = await (shim.ossl || shim.subtle).deriveBits({ name: opt.name || 'PBKDF2', iterations: opt.iterations || S.pbkdf2.iter, salt: new shim.TextEncoder().encode(opt.salt || salt), hash: opt.hash || S.pbkdf2.hash, }, key, opt.length || (S.pbkdf2.ks * 8)) data = shim.random(data.length) // Erase data in case of passphrase - const r = shim.Buffer.from(result, 'binary').toString(opt.encode || 'base64') + var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -313,7 +290,6 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; SEA.name = SEA.name || (async (cb, opt) => { try { if(cb){ try{ cb() }catch(e){console.log(e)} } @@ -329,17 +305,17 @@ //SEA.pair = async (data, proof, cb) => { try { SEA.pair = SEA.pair || (async (cb, opt) => { try { - const ecdhSubtle = shim.ossl || shim.subtle + var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - const key = {}; + var key = {}; key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - const pub = await shim.subtle.exportKey('jwk', keys.publicKey) + var pub = await shim.subtle.exportKey('jwk', keys.publicKey); //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y // new + key.pub = pub.x+'.'+pub.y; // new // x and y are already base64 // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -354,11 +330,11 @@ var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! - const key = {}; + var key = {}; key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey) + var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y // new + key.epub = pub.x+'.'+pub.y; // new // ex and ey are already base64 // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -370,7 +346,7 @@ else { throw e } } dh = dh || {}; - const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { @@ -388,7 +364,7 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var sha256hash = USE('./sha256'); + var sha = USE('./sha256'); var u; SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { @@ -396,13 +372,24 @@ if(!(pair||opt).priv){ pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - const pub = pair.pub - const priv = pair.priv - const jwk = S.jwk(pub, priv) - const hash = await sha256hash(JSON.stringify(data)) - const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + if(u === data){ throw '`undefined` not allowed.' } + var json = S.parse(data); + var check = opt.check = opt.check || json; + if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) + && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. + var r = S.parse(check); + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + var pub = pair.pub; + var priv = pair.priv; + var jwk = S.jwk(pub, priv); + var hash = await sha(json); + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! - const r = 'SEA'+JSON.stringify({m: data, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}); + var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; @@ -421,38 +408,32 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var sha256hash = USE('./sha256'); - var parse = USE('./parse'); + var sha = USE('./sha256'); var u; SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { - const json = parse(data) + var json = S.parse(data); if(false === pair){ // don't verify! - const raw = (json !== data)? - (json.s && json.m)? parse(json.m) : data - : json; + var raw = S.parse(json.m); if(cb){ try{ cb(raw) }catch(e){console.log(e)} } return raw; } opt = opt || {}; // SEA.I // verify is free! Requires no user permission. - if(json === data){ throw "No signature on data." } - const pub = pair.pub || pair - const jwk = S.jwk(pub) - const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) - const hash = await sha256hash(json.m) - var buf; var sig; var check; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + var pub = pair.pub || pair; + var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']); + var hash = await sha(json.m); + var buf, sig, check, tmp; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); if(!check){ throw "Signature did not match." } }catch(e){ - buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } + if(SEA.opt.fallback){ + return await SEA.opt.fall_verify(data, pair, cb, opt); + } } - const r = check? parse(json.m) : u; + var r = check? S.parse(json.m) : u; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; @@ -465,6 +446,38 @@ }}); module.exports = SEA.verify; + // legacy & ossl leak mitigation: + + var knownKeys = {}; + var keyForPair = SEA.opt.slow_leak = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + var jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + return knownKeys[pair]; + }; + + + SEA.opt.fall_verify = async function(data, pair, cb, opt, f){ + if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1; + var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub); + var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility. + var buf; var sig; var check; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + }catch(e){ + buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + } + var r = check? S.parse(json.m) : u; + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + SEA.opt.fallback = 2; + })(USE, './verify'); ;USE(function(module){ @@ -486,29 +499,32 @@ var shim = USE('./shim'); var S = USE('./settings'); var aeskey = USE('./aeskey'); + var u; SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { opt = opt || {}; var key = (pair||opt).epriv || pair; + if(u === data){ throw '`undefined` not allowed.' } if(!key){ pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); key = pair.epriv || pair; } - const msg = JSON.stringify(data) - const rand = {s: shim.random(8), iv: shim.random(16)}; - const ct = await aeskey(key, rand.s, opt) - .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... + var msg = (typeof data == 'string')? data : JSON.stringify(data); + var rand = {s: shim.random(8), iv: shim.random(16)}; + var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) - }, aes, new shim.TextEncoder().encode(msg))) - const r = 'SEA'+JSON.stringify({ + }, aes, new shim.TextEncoder().encode(msg))); + var r = { ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'), iv: rand.iv.toString(opt.encode || 'base64'), s: rand.s.toString(opt.encode || 'base64') - }); + } + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -523,7 +539,6 @@ var shim = USE('./shim'); var S = USE('./settings'); var aeskey = USE('./aeskey'); - var parse = USE('./parse'); SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { opt = opt || {}; @@ -532,23 +547,26 @@ pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); key = pair.epriv || pair; } - const json = parse(data) - - var buf; try{buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - }catch(e){buf = shim.Buffer.from(json.s, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - var bufiv; try{bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64') // NEW DEFAULT! - }catch(e){bufiv = shim.Buffer.from(json.iv, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - var bufct; try{bufct = shim.Buffer.from(json.ct, opt.encode || 'base64') // NEW DEFAULT! - }catch(e){bufct = shim.Buffer.from(json.ct, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - - const ct = await aeskey(key, buf, opt) - .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... - name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) - }, aes, new Uint8Array(bufct))) - const r = parse(new shim.TextDecoder('utf8').decode(ct)) + var json = S.parse(data); + var buf, bufiv, bufct; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); + bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64'); + bufct = shim.Buffer.from(json.ct, opt.encode || 'base64'); + var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... + name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) + }, aes, new Uint8Array(bufct))); + }catch(e){ + if('utf8' === opt.encode){ throw "Could not decrypt" } + if(SEA.opt.fallback){ + opt.encode = 'utf8'; + return await SEA.decrypt(data, pair, cb, opt); + } + } + var r = S.parse(new shim.TextDecoder('utf8').decode(ct)); if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -568,36 +586,34 @@ if(!pair || !pair.epriv || !pair.epub){ pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); } - const pub = key.epub || key - const epub = pair.epub - const epriv = pair.epriv - const ecdhSubtle = shim.ossl || shim.subtle - const pubKeyData = keysToEcdhJwk(pub) - const props = Object.assign( - S.ecdh, - { public: await ecdhSubtle.importKey(...pubKeyData, true, []) } - ) - const privKeyData = keysToEcdhJwk(epub, epriv) - const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) - .then(async (privKey) => { + var pub = key.epub || key; + var epub = pair.epub; + var epriv = pair.epriv; + var ecdhSubtle = shim.ossl || shim.subtle; + var pubKeyData = keysToEcdhJwk(pub); + var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var privKeyData = keysToEcdhJwk(epub, epriv); + var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! - const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]) - return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]); + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k); }) - const r = derived; + var r = derived; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } return; }}); - const keysToEcdhJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - const jwk = d ? { d: d } : {} + // can this be replaced with settings.jwk? + var keysToEcdhJwk = (pub, d) => { // d === priv + //var [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + var [ x, y ] = pub.split('.') // new + var jwk = d ? { d: d } : {} return [ // Use with spread returned value... 'jwk', Object.assign( @@ -757,19 +773,19 @@ } // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now! act.data = {pub: pair.pub}; - SEA.sign(alias, pair, act.d); + act.d(); } - act.d = function(sig){ - act.data.alias = alias || sig; - SEA.sign(act.pair.epub, act.pair, act.e); + act.d = function(){ + act.data.alias = alias; + act.e(); } - act.e = function(epub){ - act.data.epub = act.pair.epub || epub; - SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f); // to keep the private key safe, we AES encrypt it with the proof of work! + act.e = function(){ + act.data.epub = act.pair.epub; + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f, {raw:1}); // to keep the private key safe, we AES encrypt it with the proof of work! } act.f = function(auth){ act.data.auth = JSON.stringify({ek: auth, s: act.salt}); - SEA.sign({ek: auth, s: act.salt}, act.pair, act.g); + act.g(act.data.auth); } act.g = function(auth){ var tmp; act.data.auth = act.data.auth || auth; @@ -816,7 +832,7 @@ } act.c = function(auth){ if(u === auth){ return act.b() } - if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // new format + if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. } act.d = function(proof){ @@ -849,7 +865,7 @@ user.is = {pub: pair.pub, epub: pair.epub, alias: alias}; at.sea = act.pair; cat.ing = false; - if(pass && !Gun.text.is(act.data.auth)){ opt.shuffle = opt.change = pass; } // migrate UTF8 + Shuffle! Test against NAB alias test_sea_shuffle + passw0rd + try{if(pass && !Gun.obj.has(Gun.obj.ify(cat.root.graph['~'+pair.pub].auth), ':')){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle! opt.change? act.z() : cb(at); if(SEA.window && ((gun.back('user')._).opt||opt).remember){ // TODO: this needs to be modular. @@ -873,18 +889,17 @@ SEA.work(opt.change, act.salt, act.y); } act.y = function(proof){ - SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x); + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1}); } act.x = function(auth){ act.w(JSON.stringify({ek: auth, s: act.salt})); - //SEA.sign({ek: auth, s: act.salt}, act.pair, act.w); } act.w = function(auth){ if(opt.shuffle){ // delete in future! + console.log('migrate core account from UTF8 & shuffle'); var tmp = Gun.obj.to(act.data); Gun.obj.del(tmp, '_'); tmp.auth = auth; - console.log('migrate core account from UTF8 & shuffle', tmp); root.get('~'+act.pair.pub).put(tmp); } // end delete root.get('~'+act.pair.pub).get('auth').put(auth, cb); @@ -1074,27 +1089,30 @@ // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!! // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT. var to = this.to, vertex = (msg.$._).put, c = 0, d; - Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node - // TODO: consider async/await use here... + Gun.node.is(msg.put, function(val, key, node){ + // only process if SEA formatted? + var tmp = Gun.obj.ify(val) || noop; + if(u !== tmp[':']){ + node[key] = SEA.opt.unpack(tmp); + return; + } + if(!SEA.opt.check(val)){ return } + c++; // for each property on the node SEA.verify(val, false, function(data){ c--; // false just extracts the plain data. - var tmp = data; - data = SEA.opt.unpack(data, key, node); - node[key] = val = data; // transform to plain value. + node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value. if(d && !c && (c = -1)){ to.next(msg) } }); }); - d = true; - if(d && !c){ to.next(msg) } - return; + if((d = true) && !c){ to.next(msg) } } // signature handles data output, it is a proxy to the security function. function signature(msg){ - if(msg.user){ + if((msg._||noop).user){ return this.to.next(msg); } var ctx = this.as; - msg.user = ctx.user; + (msg._||(msg._=function(){})).user = ctx.user; security.call(this, msg); } @@ -1135,9 +1153,9 @@ each.pubs(val, key, node, soul); return; } if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key. - each.pub(val, key, node, soul, tmp, msg.user); return; + each.pub(val, key, node, soul, tmp, (msg._||noop).user); return; } - each.any(val, key, node, soul, msg.user); return; + each.any(val, key, node, soul, (msg._||noop).user); return; return each.end({err: "No other data allowed!"}); }; each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}} @@ -1150,62 +1168,43 @@ if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys. }; - each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}} + each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}} if('pub' === key){ if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key. return each.end({err: "Account must match!"}); } check['user'+soul+key] = 1; - if(user && user.is && pub === user.is.pub){ - //var id = Gun.text.random(3); - SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ var rel; + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel; if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) } if(rel = Gun.val.link.is(val)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } - node[key] = data; + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['user'+soul+key] = 0; each.end({ok: 1}); - }); - // TODO: Handle error!!!! + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - SEA.verify(val, pub, function(data){ var rel, tmp; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp; data = SEA.opt.unpack(data, key, node); if(u === data){ // make sure the signature matches the account it claims to be on. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account. } - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } check['user'+soul+key] = 0; each.end({ok: 1}); }); }; - function relpub(s){ - if(!s){ return } - s = s.split('~'); - if(!s || !(s = s[1])){ return } - s = s.split('.'); - if(!s || 2 > s.length){ return } - s = s.slice(0,2).join('.'); - return s; - } each.any = function(val, key, node, soul, user){ var tmp, pub; - if(!user || !user.is){ - if(tmp = relpub(soul)){ - check['any'+soul+key] = 1; - SEA.verify(val, pub = tmp, function(data){ var rel; - data = SEA.opt.unpack(data, key, node); - if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ - (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; - } - check['any'+soul+key] = 0; - each.end({ok: 1}); - }); + if(!(pub = SEA.opt.pub(soul))){ + if(at.opt.secure){ + each.end({err: "Soul is missing public key at '" + key + "'."}); return; } + // TODO: Ask community if should auto-sign non user-graph data. check['any'+soul+key] = 1; at.on('secure', function(msg){ this.off(); check['any'+soul+key] = 0; @@ -1215,40 +1214,30 @@ //each.end({err: "Data cannot be modified."}); return; } - if(!(tmp = relpub(soul))){ - if(at.opt.secure){ - each.end({err: "Soul is missing public key at '" + key + "'."}); + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ + if((user.is||{}).pub !== p){ return p } + }); + if(other){ + each.any(val, key, node, soul); return; - } - if(val && val.slice && 'SEA{' === (val).slice(0,4)){ + }*/ + check['any'+soul+key] = 1; + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ + if(u === data){ return each.end({err: 'My signature fail.'}) } + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['any'+soul+key] = 0; each.end({ok: 1}); - return; - } - //check['any'+soul+key] = 1; - //SEA.sign(val, user, function(data){ - // if(u === data){ return each.end({err: 'Any signature failed.'}) } - // node[key] = data; - check['any'+soul+key] = 0; - each.end({ok: 1}); - //}); + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - if(!msg.I || (pub = tmp) !== (user.is||noop).pub){ - each.any(val, key, node, soul); - return; - } - /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ - if((user.is||{}).pub !== p){ return p } - }); - if(other){ - each.any(val, key, node, soul); - return; - }*/ check['any'+soul+key] = 1; - SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ - if(u === data){ return each.end({err: 'My signature fail.'}) } - node[key] = data; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel; + data = SEA.opt.unpack(data, key, node); + if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } check['any'+soul+key] = 0; each.end({ok: 1}); }); @@ -1263,6 +1252,7 @@ if(Gun.obj.map(check, function(no){ if(no){ return true } })){ return } + (msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out. to.next(msg); }; Gun.obj.map(msg.put, each.node); @@ -1271,18 +1261,42 @@ } to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). } - SEA.opt.unpack = function(data, key, node){ - if(u === data){ return } - var tmp = data, soul = Gun.node.soul(node), s = Gun.state.is(node, key); - if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && s === tmp[3]){ - return tmp[2]; + SEA.opt.pub = function(s){ + if(!s){ return } + s = s.split('~'); + if(!s || !(s = s[1])){ return } + s = s.split('.'); + if(!s || 2 > s.length){ return } + s = s.slice(0,2).join('.'); + return s; + } + SEA.opt.prep = function(d,k, n,s){ // prep for signing + return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)}; + } + SEA.opt.pack = function(d,k, n,s){ // pack for verifying + if(SEA.opt.check(d)){ return d } + var meta = (Gun.obj.ify(d)||noop), sig = meta['~']; + return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d; + } + SEA.opt.unpack = function(d, k, n){ var tmp; + if(u === d){ return } + if(d && (u !== (tmp = d[':']))){ return tmp } + if(!k || !n){ return } + if(d === n[k]){ return d } + if(!SEA.opt.check(n[k])){ return d } + var soul = Gun.node.soul(n), s = Gun.state.is(n, k); + if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){ + return d[2]; } if(s < SEA.opt.shuffle_attack){ - return data; + return d; } } SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 - var noop = {}, u; + var noop = function(){}, u; + var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. + var rel_is = Gun.val.rel.is; + // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. })(USE, './index'); -}()); +}()); \ No newline at end of file diff --git a/sea/create.js b/sea/create.js index 44a6a524..e0946b40 100644 --- a/sea/create.js +++ b/sea/create.js @@ -45,19 +45,19 @@ } // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now! act.data = {pub: pair.pub}; - SEA.sign(alias, pair, act.d); + act.d(); } - act.d = function(sig){ - act.data.alias = alias || sig; - SEA.sign(act.pair.epub, act.pair, act.e); + act.d = function(){ + act.data.alias = alias; + act.e(); } - act.e = function(epub){ - act.data.epub = act.pair.epub || epub; - SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f); // to keep the private key safe, we AES encrypt it with the proof of work! + act.e = function(){ + act.data.epub = act.pair.epub; + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f, {raw:1}); // to keep the private key safe, we AES encrypt it with the proof of work! } act.f = function(auth){ act.data.auth = JSON.stringify({ek: auth, s: act.salt}); - SEA.sign({ek: auth, s: act.salt}, act.pair, act.g); + act.g(act.data.auth); } act.g = function(auth){ var tmp; act.data.auth = act.data.auth || auth; @@ -104,7 +104,7 @@ } act.c = function(auth){ if(u === auth){ return act.b() } - if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // new format + if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. } act.d = function(proof){ @@ -137,7 +137,7 @@ user.is = {pub: pair.pub, epub: pair.epub, alias: alias}; at.sea = act.pair; cat.ing = false; - if(pass && !Gun.text.is(act.data.auth)){ opt.shuffle = opt.change = pass; } // migrate UTF8 + Shuffle! Test against NAB alias test_sea_shuffle + passw0rd + try{if(pass && !Gun.obj.has(Gun.obj.ify(cat.root.graph['~'+pair.pub].auth), ':')){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle! opt.change? act.z() : cb(at); if(SEA.window && ((gun.back('user')._).opt||opt).remember){ // TODO: this needs to be modular. @@ -161,18 +161,17 @@ SEA.work(opt.change, act.salt, act.y); } act.y = function(proof){ - SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x); + SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1}); } act.x = function(auth){ act.w(JSON.stringify({ek: auth, s: act.salt})); - //SEA.sign({ek: auth, s: act.salt}, act.pair, act.w); } act.w = function(auth){ if(opt.shuffle){ // delete in future! + console.log('migrate core account from UTF8 & shuffle'); var tmp = Gun.obj.to(act.data); Gun.obj.del(tmp, '_'); tmp.auth = auth; - console.log('migrate core account from UTF8 & shuffle', tmp); root.get('~'+act.pair.pub).put(tmp); } // end delete root.get('~'+act.pair.pub).get('auth').put(auth, cb); diff --git a/sea/decrypt.js b/sea/decrypt.js index c815e3d3..736fd4cc 100644 --- a/sea/decrypt.js +++ b/sea/decrypt.js @@ -3,7 +3,6 @@ var shim = require('./shim'); var S = require('./settings'); var aeskey = require('./aeskey'); - var parse = require('./parse'); SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { opt = opt || {}; @@ -12,23 +11,26 @@ pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); key = pair.epriv || pair; } - const json = parse(data) - - var buf; try{buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - }catch(e){buf = shim.Buffer.from(json.s, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - var bufiv; try{bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64') // NEW DEFAULT! - }catch(e){bufiv = shim.Buffer.from(json.iv, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - var bufct; try{bufct = shim.Buffer.from(json.ct, opt.encode || 'base64') // NEW DEFAULT! - }catch(e){bufct = shim.Buffer.from(json.ct, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - - const ct = await aeskey(key, buf, opt) - .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... - name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) - }, aes, new Uint8Array(bufct))) - const r = parse(new shim.TextDecoder('utf8').decode(ct)) + var json = S.parse(data); + var buf, bufiv, bufct; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); + bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64'); + bufct = shim.Buffer.from(json.ct, opt.encode || 'base64'); + var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... + name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) + }, aes, new Uint8Array(bufct))); + }catch(e){ + if('utf8' === opt.encode){ throw "Could not decrypt" } + if(SEA.opt.fallback){ + opt.encode = 'utf8'; + return await SEA.decrypt(data, pair, cb, opt); + } + } + var r = S.parse(new shim.TextDecoder('utf8').decode(ct)); if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } diff --git a/sea/encrypt.js b/sea/encrypt.js index 0977f02f..dc31cc04 100644 --- a/sea/encrypt.js +++ b/sea/encrypt.js @@ -3,29 +3,32 @@ var shim = require('./shim'); var S = require('./settings'); var aeskey = require('./aeskey'); + var u; SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { opt = opt || {}; var key = (pair||opt).epriv || pair; + if(u === data){ throw '`undefined` not allowed.' } if(!key){ pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); key = pair.epriv || pair; } - const msg = JSON.stringify(data) - const rand = {s: shim.random(8), iv: shim.random(16)}; - const ct = await aeskey(key, rand.s, opt) - .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... + var msg = (typeof data == 'string')? data : JSON.stringify(data); + var rand = {s: shim.random(8), iv: shim.random(16)}; + var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) - }, aes, new shim.TextEncoder().encode(msg))) - const r = 'SEA'+JSON.stringify({ + }, aes, new shim.TextEncoder().encode(msg))); + var r = { ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'), iv: rand.iv.toString(opt.encode || 'base64'), s: rand.s.toString(opt.encode || 'base64') - }); + } + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } diff --git a/sea/index.js b/sea/index.js index a2434594..a1d880af 100644 --- a/sea/index.js +++ b/sea/index.js @@ -31,27 +31,30 @@ // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!! // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT. var to = this.to, vertex = (msg.$._).put, c = 0, d; - Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node - // TODO: consider async/await use here... + Gun.node.is(msg.put, function(val, key, node){ + // only process if SEA formatted? + var tmp = Gun.obj.ify(val) || noop; + if(u !== tmp[':']){ + node[key] = SEA.opt.unpack(tmp); + return; + } + if(!SEA.opt.check(val)){ return } + c++; // for each property on the node SEA.verify(val, false, function(data){ c--; // false just extracts the plain data. - var tmp = data; - data = SEA.opt.unpack(data, key, node); - node[key] = val = data; // transform to plain value. + node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value. if(d && !c && (c = -1)){ to.next(msg) } }); }); - d = true; - if(d && !c){ to.next(msg) } - return; + if((d = true) && !c){ to.next(msg) } } // signature handles data output, it is a proxy to the security function. function signature(msg){ - if(msg.user){ + if((msg._||noop).user){ return this.to.next(msg); } var ctx = this.as; - msg.user = ctx.user; + (msg._||(msg._=function(){})).user = ctx.user; security.call(this, msg); } @@ -92,9 +95,9 @@ each.pubs(val, key, node, soul); return; } if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key. - each.pub(val, key, node, soul, tmp, msg.user); return; + each.pub(val, key, node, soul, tmp, (msg._||noop).user); return; } - each.any(val, key, node, soul, msg.user); return; + each.any(val, key, node, soul, (msg._||noop).user); return; return each.end({err: "No other data allowed!"}); }; each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}} @@ -107,62 +110,43 @@ if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys. }; - each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}} + each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}} if('pub' === key){ if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key. return each.end({err: "Account must match!"}); } check['user'+soul+key] = 1; - if(user && user.is && pub === user.is.pub){ - //var id = Gun.text.random(3); - SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ var rel; + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel; if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) } if(rel = Gun.val.link.is(val)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } - node[key] = data; + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['user'+soul+key] = 0; each.end({ok: 1}); - }); - // TODO: Handle error!!!! + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - SEA.verify(val, pub, function(data){ var rel, tmp; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp; data = SEA.opt.unpack(data, key, node); if(u === data){ // make sure the signature matches the account it claims to be on. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account. } - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } check['user'+soul+key] = 0; each.end({ok: 1}); }); }; - function relpub(s){ - if(!s){ return } - s = s.split('~'); - if(!s || !(s = s[1])){ return } - s = s.split('.'); - if(!s || 2 > s.length){ return } - s = s.slice(0,2).join('.'); - return s; - } each.any = function(val, key, node, soul, user){ var tmp, pub; - if(!user || !user.is){ - if(tmp = relpub(soul)){ - check['any'+soul+key] = 1; - SEA.verify(val, pub = tmp, function(data){ var rel; - data = SEA.opt.unpack(data, key, node); - if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! - if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){ - (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; - } - check['any'+soul+key] = 0; - each.end({ok: 1}); - }); + if(!(pub = SEA.opt.pub(soul))){ + if(at.opt.secure){ + each.end({err: "Soul is missing public key at '" + key + "'."}); return; } + // TODO: Ask community if should auto-sign non user-graph data. check['any'+soul+key] = 1; at.on('secure', function(msg){ this.off(); check['any'+soul+key] = 0; @@ -172,40 +156,30 @@ //each.end({err: "Data cannot be modified."}); return; } - if(!(tmp = relpub(soul))){ - if(at.opt.secure){ - each.end({err: "Soul is missing public key at '" + key + "'."}); + if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){ + /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ + if((user.is||{}).pub !== p){ return p } + }); + if(other){ + each.any(val, key, node, soul); return; - } - if(val && val.slice && 'SEA{' === (val).slice(0,4)){ + }*/ + check['any'+soul+key] = 1; + SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ + if(u === data){ return each.end({err: 'My signature fail.'}) } + node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s}); check['any'+soul+key] = 0; each.end({ok: 1}); - return; - } - //check['any'+soul+key] = 1; - //SEA.sign(val, user, function(data){ - // if(u === data){ return each.end({err: 'Any signature failed.'}) } - // node[key] = data; - check['any'+soul+key] = 0; - each.end({ok: 1}); - //}); + }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1}); return; } - if(!msg.I || (pub = tmp) !== (user.is||noop).pub){ - each.any(val, key, node, soul); - return; - } - /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ - if((user.is||{}).pub !== p){ return p } - }); - if(other){ - each.any(val, key, node, soul); - return; - }*/ check['any'+soul+key] = 1; - SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ - if(u === data){ return each.end({err: 'My signature fail.'}) } - node[key] = data; + SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel; + data = SEA.opt.unpack(data, key, node); + if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski ! + if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } check['any'+soul+key] = 0; each.end({ok: 1}); }); @@ -220,6 +194,7 @@ if(Gun.obj.map(check, function(no){ if(no){ return true } })){ return } + (msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out. to.next(msg); }; Gun.obj.map(msg.put, each.node); @@ -228,17 +203,41 @@ } to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols). } - SEA.opt.unpack = function(data, key, node){ - if(u === data){ return } - var tmp = data, soul = Gun.node.soul(node), s = Gun.state.is(node, key); - if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && s === tmp[3]){ - return tmp[2]; + SEA.opt.pub = function(s){ + if(!s){ return } + s = s.split('~'); + if(!s || !(s = s[1])){ return } + s = s.split('.'); + if(!s || 2 > s.length){ return } + s = s.slice(0,2).join('.'); + return s; + } + SEA.opt.prep = function(d,k, n,s){ // prep for signing + return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)}; + } + SEA.opt.pack = function(d,k, n,s){ // pack for verifying + if(SEA.opt.check(d)){ return d } + var meta = (Gun.obj.ify(d)||noop), sig = meta['~']; + return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d; + } + SEA.opt.unpack = function(d, k, n){ var tmp; + if(u === d){ return } + if(d && (u !== (tmp = d[':']))){ return tmp } + if(!k || !n){ return } + if(d === n[k]){ return d } + if(!SEA.opt.check(n[k])){ return d } + var soul = Gun.node.soul(n), s = Gun.state.is(n, k); + if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){ + return d[2]; } if(s < SEA.opt.shuffle_attack){ - return data; + return d; } } SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 - var noop = {}, u; + var noop = function(){}, u; + var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. + var rel_is = Gun.val.rel.is; + // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. \ No newline at end of file diff --git a/sea/pair.js b/sea/pair.js index 0005db4a..3a59e177 100644 --- a/sea/pair.js +++ b/sea/pair.js @@ -2,7 +2,6 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; SEA.name = SEA.name || (async (cb, opt) => { try { if(cb){ try{ cb() }catch(e){console.log(e)} } @@ -18,17 +17,17 @@ //SEA.pair = async (data, proof, cb) => { try { SEA.pair = SEA.pair || (async (cb, opt) => { try { - const ecdhSubtle = shim.ossl || shim.subtle + var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - const key = {}; + var key = {}; key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - const pub = await shim.subtle.exportKey('jwk', keys.publicKey) + var pub = await shim.subtle.exportKey('jwk', keys.publicKey); //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y // new + key.pub = pub.x+'.'+pub.y; // new // x and y are already base64 // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -43,11 +42,11 @@ var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! - const key = {}; + var key = {}; key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey) + var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y // new + key.epub = pub.x+'.'+pub.y; // new // ex and ey are already base64 // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -59,7 +58,7 @@ else { throw e } } dh = dh || {}; - const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { diff --git a/sea/parse.js b/sea/parse.js deleted file mode 100644 index 7372bc1f..00000000 --- a/sea/parse.js +++ /dev/null @@ -1,11 +0,0 @@ - - module.exports = (props) => { - try { - if(props.slice && 'SEA{' === props.slice(0,4)){ - props = props.slice(3); - } - return props.slice ? JSON.parse(props) : props - } catch (e) {} //eslint-disable-line no-empty - return props - } - \ No newline at end of file diff --git a/sea/secret.js b/sea/secret.js index 787ccecb..4009ee31 100644 --- a/sea/secret.js +++ b/sea/secret.js @@ -8,36 +8,34 @@ if(!pair || !pair.epriv || !pair.epub){ pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); } - const pub = key.epub || key - const epub = pair.epub - const epriv = pair.epriv - const ecdhSubtle = shim.ossl || shim.subtle - const pubKeyData = keysToEcdhJwk(pub) - const props = Object.assign( - S.ecdh, - { public: await ecdhSubtle.importKey(...pubKeyData, true, []) } - ) - const privKeyData = keysToEcdhJwk(epub, epriv) - const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) - .then(async (privKey) => { + var pub = key.epub || key; + var epub = pair.epub; + var epriv = pair.epriv; + var ecdhSubtle = shim.ossl || shim.subtle; + var pubKeyData = keysToEcdhJwk(pub); + var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var privKeyData = keysToEcdhJwk(epub, epriv); + var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! - const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]) - return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]); + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k); }) - const r = derived; + var r = derived; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } return; }}); - const keysToEcdhJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - const jwk = d ? { d: d } : {} + // can this be replaced with settings.jwk? + var keysToEcdhJwk = (pub, d) => { // d === priv + //var [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + var [ x, y ] = pub.split('.') // new + var jwk = d ? { d: d } : {} return [ // Use with spread returned value... 'jwk', Object.assign( diff --git a/sea/settings.js b/sea/settings.js index ac32f729..8e68e8c5 100644 --- a/sea/settings.js +++ b/sea/settings.js @@ -1,41 +1,37 @@ - const SEA = require('./root'); - const Buffer = require('./buffer') - const settings = {} - // Encryption parameters - const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } + var SEA = require('./root'); + var Buffer = require('./buffer'); + var s = {}; + s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64}; + s.ecdsa = { + pair: {name: 'ECDSA', namedCurve: 'P-256'}, + sign: {name: 'ECDSA', hash: {name: 'SHA-256'}} + }; + s.ecdh = {name: 'ECDH', namedCurve: 'P-256'}; - const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } - const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } - const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' } - - const _initial_authsettings = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - hook: (props) => props // { iat, exp, alias, remember } - // or return new Promise((resolve, reject) => resolve(props) - } - // These are used to persist user's authentication "session" - const authsettings = Object.assign({}, _initial_authsettings) // This creates Web Cryptography API compliant JWK for sign/verify purposes - const keysToEcdsaJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + s.jwk = function(pub, d){ // d === priv + pub = pub.split('.'); + var x = pub[0], y = pub[1]; + var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true}; jwk.key_ops = d ? ['sign'] : ['verify']; if(d){ jwk.d = d } return jwk; + }; + s.recall = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props) + }; + + s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) } + s.parse = function p(t){ try { + var yes = (typeof t == 'string'); + if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) } + return yes ? JSON.parse(t) : t; + } catch (e) {} + return t; } - Object.assign(settings, { - pbkdf2: pbkdf2, - ecdsa: { - pair: ecdsaKeyProps, - sign: ecdsaSignProps - }, - ecdh: ecdhKeyProps, - jwk: keysToEcdsaJwk, - recall: authsettings - }) - SEA.opt = settings; - module.exports = settings + SEA.opt = s; + module.exports = s \ No newline at end of file diff --git a/sea/sha256.js b/sea/sha256.js index a3ff728a..c3182317 100644 --- a/sea/sha256.js +++ b/sea/sha256.js @@ -1,13 +1,8 @@ - const shim = require('./shim'); - const Buffer = require('./buffer') - const parse = require('./parse') - const { pbkdf2 } = require('./settings') - // This internal func returns SHA-256 hashed data for signing - const sha256hash = async (mm) => { - const m = parse(mm) - const hash = await shim.subtle.digest({name: pbkdf2.hash}, new shim.TextEncoder().encode(m)) - return Buffer.from(hash) + var shim = require('./shim'); + module.exports = async function(d, o){ + var t = (typeof d == 'string')? d : JSON.stringify(d); + var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t)); + return shim.Buffer.from(hash); } - module.exports = sha256hash \ No newline at end of file diff --git a/sea/shim.js b/sea/shim.js index 28594a8b..b6026e8c 100644 --- a/sea/shim.js +++ b/sea/shim.js @@ -13,25 +13,23 @@ } if(!api.crypto){try{ var crypto = require('crypto', 1); - const { subtle } = require('@trust/webcrypto', 1) // All but ECDH const { TextEncoder, TextDecoder } = require('text-encoding', 1) Object.assign(api, { crypto, - subtle, + //subtle, TextEncoder, TextDecoder, random: (len) => Buffer.from(crypto.randomBytes(len)) }); //try{ - const WebCrypto = require('node-webcrypto-ossl', 1) - api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH + const WebCrypto = require('node-webcrypto-ossl', 1); + api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH //}catch(e){ //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed."); //} }catch(e){ - console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!"); - console.log("node-webcrypto-ossl is temporarily needed for ECDSA signature verification, and optionally needed for ECDH, please install if needed (currently necessary so add them to your package.json for now)."); - TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; + console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!"); + OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; }} module.exports = api diff --git a/sea/sign.js b/sea/sign.js index f8ca43f1..4efd2e5f 100644 --- a/sea/sign.js +++ b/sea/sign.js @@ -2,7 +2,7 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - var sha256hash = require('./sha256'); + var sha = require('./sha256'); var u; SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { @@ -10,13 +10,24 @@ if(!(pair||opt).priv){ pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - const pub = pair.pub - const priv = pair.priv - const jwk = S.jwk(pub, priv) - const hash = await sha256hash(JSON.stringify(data)) - const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + if(u === data){ throw '`undefined` not allowed.' } + var json = S.parse(data); + var check = opt.check = opt.check || json; + if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) + && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. + var r = S.parse(check); + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + var pub = pair.pub; + var priv = pair.priv; + var jwk = S.jwk(pub, priv); + var hash = await sha(json); + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! - const r = 'SEA'+JSON.stringify({m: data, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}); + var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} + if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; diff --git a/sea/verify.js b/sea/verify.js index 909bed30..06286e8c 100644 --- a/sea/verify.js +++ b/sea/verify.js @@ -2,38 +2,32 @@ var SEA = require('./root'); var shim = require('./shim'); var S = require('./settings'); - var sha256hash = require('./sha256'); - var parse = require('./parse'); + var sha = require('./sha256'); var u; SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { - const json = parse(data) + var json = S.parse(data); if(false === pair){ // don't verify! - const raw = (json !== data)? - (json.s && json.m)? parse(json.m) : data - : json; + var raw = S.parse(json.m); if(cb){ try{ cb(raw) }catch(e){console.log(e)} } return raw; } opt = opt || {}; // SEA.I // verify is free! Requires no user permission. - if(json === data){ throw "No signature on data." } - const pub = pair.pub || pair - const jwk = S.jwk(pub) - const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) - const hash = await sha256hash(json.m) - var buf; var sig; var check; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + var pub = pair.pub || pair; + var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']); + var hash = await sha(json.m); + var buf, sig, check, tmp; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); if(!check){ throw "Signature did not match." } }catch(e){ - buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } + if(SEA.opt.fallback){ + return await SEA.opt.fall_verify(data, pair, cb, opt); + } } - const r = check? parse(json.m) : u; + var r = check? S.parse(json.m) : u; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; @@ -46,4 +40,36 @@ }}); module.exports = SEA.verify; + // legacy & ossl leak mitigation: + + var knownKeys = {}; + var keyForPair = SEA.opt.slow_leak = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + var jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + return knownKeys[pair]; + }; + + + SEA.opt.fall_verify = async function(data, pair, cb, opt, f){ + if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1; + var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub); + var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility. + var buf; var sig; var check; try{ + buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + }catch(e){ + buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! + sig = new Uint8Array(buf) + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + } + var r = check? S.parse(json.m) : u; + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + SEA.opt.fallback = 2; + \ No newline at end of file diff --git a/sea/work.js b/sea/work.js index b3be649a..37a6c043 100644 --- a/sea/work.js +++ b/sea/work.js @@ -13,25 +13,25 @@ salt = u; } salt = salt || shim.random(9); - if('SHA-256' === opt.name){ - var rsha = shim.Buffer.from(await sha(data), 'binary').toString(opt.encode || 'base64') + data = (typeof data == 'string')? data : JSON.stringify(data); + if('sha' === (opt.name||'').toLowerCase().slice(0,3)){ + var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } return rsha; } - const key = await (shim.ossl || shim.subtle).importKey( - 'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits'] - ) - const result = await (shim.ossl || shim.subtle).deriveBits({ + var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']); + var work = await (shim.ossl || shim.subtle).deriveBits({ name: opt.name || 'PBKDF2', iterations: opt.iterations || S.pbkdf2.iter, salt: new shim.TextEncoder().encode(opt.salt || salt), hash: opt.hash || S.pbkdf2.hash, }, key, opt.length || (S.pbkdf2.ks * 8)) data = shim.random(data.length) // Erase data in case of passphrase - const r = shim.Buffer.from(result, 'binary').toString(opt.encode || 'base64') + var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } diff --git a/src/adapters/localStorage.js b/src/adapters/localStorage.js index 5c8f0302..0945b99d 100644 --- a/src/adapters/localStorage.js +++ b/src/adapters/localStorage.js @@ -36,7 +36,7 @@ Gun.on('create', function(root){ root.on('out', function(msg){ if(msg.lS){ return } - if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){ + if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){ id = msg['#']; Gun.graph.is(msg.put, null, map); if(!to){ to = setTimeout(flush, opt.wait || 1) } @@ -84,7 +84,7 @@ Gun.on('create', function(root){ var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; var lS = function(){}, u; root.on('localStorage', disk); // NON-STANDARD EVENT! - + root.on('put', function(at){ this.to.next(at); Gun.graph.is(at.put, null, map); @@ -112,7 +112,7 @@ Gun.on('create', function(root){ return; // Hmm, what if we have peers but we are disconnected? } //console.log("lS get", lex, data); - root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I}); + root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$}); }; Gun.debug? setTimeout(to,1) : to(); }); @@ -130,7 +130,7 @@ Gun.on('create', function(root){ acks = {}; if(data){ disk = data } try{store.setItem(opt.prefix, JSON.stringify(disk)); - }catch(e){ + }catch(e){ Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."); root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); } diff --git a/src/adapters/mesh.js b/src/adapters/mesh.js index 790beaed..b4401b46 100644 --- a/src/adapters/mesh.js +++ b/src/adapters/mesh.js @@ -14,8 +14,8 @@ function Mesh(ctx){ if((tmp = msg['@']) && (tmp = ctx.dup.s[tmp]) && (tmp = tmp.it) - && tmp.mesh){ - mesh.say(msg, tmp.mesh.via, 1); + && tmp._){ + mesh.say(msg, (tmp._).via, 1); tmp['##'] = msg['##']; return; } @@ -47,9 +47,9 @@ function Mesh(ctx){ (tmp = dup.s)[hash] = tmp[id]; } } - (msg.mesh = function(){}).via = peer; + (msg._ = function(){}).via = peer; if((tmp = msg['><'])){ - msg.mesh.to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); + (msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)}); } if(msg.dam){ if(tmp = mesh.hear[msg.dam]){ @@ -58,7 +58,7 @@ function Mesh(ctx){ return; } ctx.on('in', msg); - + return; } else if('[' === tmp){ @@ -87,7 +87,7 @@ function Mesh(ctx){ } var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen! if(!wire){ return } - msh = msg.mesh || empty; + msh = (msg._) || empty; if(peer === msh.via){ return } if(!(raw = msh.raw)){ raw = mesh.raw(msg) } if((tmp = msg['@']) @@ -138,7 +138,7 @@ function Mesh(ctx){ mesh.raw = function(msg){ if(!msg){ return '' } - var dup = ctx.dup, msh = msg.mesh || {}, put, hash, tmp; + var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp; if(tmp = msh.raw){ return tmp } if(typeof msg === 'string'){ return msg } if(msg['@'] && (tmp = msg.put)){ diff --git a/src/chain.js b/src/chain.js index c2a1d20d..a707a940 100644 --- a/src/chain.js +++ b/src/chain.js @@ -16,7 +16,6 @@ Gun.chain.chain = function(sub){ function output(msg){ var put, get, at = this.as, back = at.back, root = at.root, tmp; - if(!msg.I){ msg.I = at.$ } if(!msg.$){ msg.$ = at.$ } this.to.next(msg); if(get = msg.get){ @@ -139,7 +138,7 @@ function input(msg){ //if(tmp[cat.id]){ return } tmp.is = tmp.is || at.put; tmp[cat.id] = at.put || true; - //if(root.stop){ + //if(root.stop){ eve.to.next(msg) //} relate(cat, msg, at, rel); @@ -152,7 +151,7 @@ function relate(at, msg, from, rel){ var tmp = (at.root.$.get(rel)._); if(at.has){ from = tmp; - } else + } else if(from.has){ relate(from, msg, from, rel); } @@ -204,7 +203,7 @@ function map(data, key){ // Map over only the changes on every update. if(tmp = via.$){ tmp = (chain = via.$.get(key))._; if(u === tmp.put || !Gun.val.link.is(data)){ - tmp.put = data; + tmp.put = data; } } at.on('in', { @@ -272,7 +271,6 @@ function ack(msg, ev){ at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']}); return; } - msg.$ = at.root.$; Gun.on.put(msg, at.root.$); } var empty = {}, u; diff --git a/src/get.js b/src/get.js index 6f5ab51d..57d13aa1 100644 --- a/src/get.js +++ b/src/get.js @@ -86,9 +86,9 @@ function use(msg){ //else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); - //root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2)); + //root.stop && (root.stop.id = root.stop.id || Gun.text.random(2)); //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); - if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } + if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ tmp = ((msg.$$ = at.root.gun.get(tmp))._); diff --git a/src/onto.js b/src/onto.js index 78827257..4851d32b 100644 --- a/src/onto.js +++ b/src/onto.js @@ -5,13 +5,13 @@ module.exports = function onto(tag, arg, as){ var u, tag = (this.tag || (this.tag = {}))[tag] || (this.tag[tag] = {tag: tag, to: onto._ = { next: function(arg){ var tmp; - if((tmp = this.to)){ + if((tmp = this.to)){ tmp.next(arg); }} }}); if(arg instanceof Function){ var be = { - off: onto.off || + off: onto.off || (onto.off = function(){ if(this.next === onto._.next){ return !0 } if(this === this.the.last){ diff --git a/src/root.js b/src/root.js index 242f53ee..642a96b7 100644 --- a/src/root.js +++ b/src/root.js @@ -104,7 +104,7 @@ Gun.dup = require('./dup'); if(!at){ if(!(cat.opt||empty).super){ ctx.souls[soul] = false; - return; + return; } at = (ctx.$.get(soul)._); } diff --git a/src/state.js b/src/state.js index 16ef2ca6..1cddd3f1 100644 --- a/src/state.js +++ b/src/state.js @@ -26,10 +26,10 @@ State.lex = function(){ return State().toString(36).replace('.','') } State.ify = function(n, k, s, v, soul){ // put a key's state on a node. if(!n || !n[N_]){ // reject if it is not node-like. if(!soul){ // unless they passed a soul - return; + return; } n = Node.soul.ify(n, soul); // then make it so! - } + } var tmp = obj_as(n[N_], State._); // grab the states data. if(u !== k && k !== N_){ if(num_is(s)){ diff --git a/src/val.js b/src/val.js index fa4ac8f7..fb7cf4ee 100644 --- a/src/val.js +++ b/src/val.js @@ -7,7 +7,7 @@ Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. if(text_is(v) // by "text" we mean strings. || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. + || num_is(v)){ // by "number" we mean integers or decimals. return true; // simple values are valid. } return Val.rel.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. diff --git a/test/common.js b/test/common.js index 30b97063..fd4a9d53 100644 --- a/test/common.js +++ b/test/common.js @@ -19,6 +19,7 @@ describe('Gun', function(){ //require('../lib/file'); require('../lib/store'); require('../lib/rfs'); + require('./sea/sea.js'); } }(this)); //Gun.log.squelch = true; @@ -2835,7 +2836,7 @@ describe('Gun', function(){ cat.slave = bob; gun.on('put', {$: gun, put: Gun.graph.ify(user, s)}); //console.debug.i=1;console.log("-------------"); - gun.get(s.soul).get('bob').get('pet').get('slave').val(function(data){ + gun.get(s.soul).get('bob').get('pet').get('slave').once(function(data){ //clearTimeout(done.to); //setTimeout(function(){ //console.log("*****************", data);return; @@ -2879,7 +2880,7 @@ describe('Gun', function(){ it('empty val followed', function(done){ var gun = Gun(); - gun.get('val/follow').val(function(data){ + gun.get('val/follow').once(function(data){ //console.log("val", data); }).get(function(at){ //console.log("?????", at); @@ -2928,7 +2929,7 @@ describe('Gun', function(){ list.set(gun.get('dave').put({name: "Dave", group: "awesome", married: true})); var check = {}, count = {}; - list.map().val(function(data, id){ + list.map().once(function(data, id){ //console.log("***************", id, data); check[id] = data; count[id] = (count[id] || 0) + 1; @@ -3050,7 +3051,7 @@ describe('Gun', function(){ setTimeout(function(){ var gun2 = Gun(); //console.log(require('fs').readFileSync('./radata/!').toString()); - gun2.get('stef').get('address').val(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" + gun2.get('stef').get('address').once(function(data){ // Object {_: Object, country: "Netherlands", zip: "1766KP"} "adress" //console.log("******", data); done.a = true; expect(data.country).to.be('Netherlands'); @@ -3059,7 +3060,7 @@ describe('Gun', function(){ if(done.c){ return } done.c = 1; done(); }); - gun2.get('stef').val(function(data){ //Object {_: Object, address: Object} "stef" + gun2.get('stef').once(function(data){ //Object {_: Object, address: Object} "stef" //console.log("**************", data); //return; done.s = true; @@ -3237,14 +3238,14 @@ describe('Gun', function(){ var check = {A: {}, B: {}}; setTimeout(function(){ - gun.get('usersMM').map().map().val(function(data){ + gun.get('usersMM').map().map().once(function(data){ //console.log('A', data); check.A[data.pub] = true; }) }, 900); setTimeout(function(){ - gun.get('usersMM').map().map().val(function(data){ + gun.get('usersMM').map().map().once(function(data){ //console.log('B', data, check); check.B[data.pub] = true; if(check.A['asdf'] && check.A['fdsa'] && check.B['asdf'] && check.B['fdsa']){ @@ -3302,7 +3303,7 @@ describe('Gun', function(){ var app = gun.get(s.soul); //console.debug.i=1;console.log("==================="); - app.get('alias').get('mark').map().val(function(alias){ + app.get('alias').get('mark').map().once(function(alias){ //console.log("***", alias); done.alias = alias; }); @@ -3480,19 +3481,19 @@ describe('Gun', function(){ 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){ + gun.get('nv/foo').get('bar').get('baz').once(function(val, key){ //console.log('*******', key, val); expect(val).to.be(undefined); done.fbb = true; }); - gun.get('nv/totesnothing').val(function(val, key){ + gun.get('nv/totesnothing').once(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){ + gun.get('nv/bz').get('lul').once(function(val, key){ //console.log('*****************', key, val); expect(val).to.be(undefined); done.bzl = true; @@ -3514,7 +3515,7 @@ describe('Gun', function(){ data.b = 2; }); - gun.get('ds/safe').val(function(data){ + gun.get('ds/safe').once(function(data){ expect(gun.back(-1)._.graph['ds/safe'].b).to.not.be.ok(); if(done.c){ return } done.c = 1; done(); @@ -3588,7 +3589,7 @@ describe('Gun', function(){ setTimeout(function(){ a.get('profile').get('said').get('asdf').put('yes'); setTimeout(function(){ - a.val(function(data){ + a.once(function(data){ expect(data.profile).to.be.eql({'#': 'sabnbprofile'}); if(done.c){ return } done.c = 1; done(); @@ -3784,7 +3785,7 @@ describe('Gun', function(){ var chain = this.chain(); var context = this; var _tags; - context.val(function(obj, key){ + context.once(function(obj, key){ if(!obj.tags){ console.warn('Not tagged to anything!'); context._.valid = false; @@ -3822,7 +3823,7 @@ describe('Gun', function(){ 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)}) + .once(function(yes){console.log("YES!", yes)}) }); it.only('Check that events are called with multiple instances', function(done){ @@ -4670,7 +4671,7 @@ describe('Gun', function(){ 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){ + data.once(function(val, field){ expect(val.foo).to.be('epic'); expect(Gun.node.soul(val)).to.be('key/path/put'); done(); @@ -4794,7 +4795,7 @@ describe('Gun', function(){ var get = gun.get('shallow/path'); var path = get.path('one'); var put = path.put('good'); - put.val(function(val, field){ + put.once(function(val, field){ expect(val).to.be('good'); expect(field).to.be('one'); done(); @@ -4806,7 +4807,7 @@ describe('Gun', function(){ 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){ + put.once(function(val, field){ //console.log('***********', field, val); expect(val.you).to.be('are'); expect(val.here).to.be(1); @@ -4862,7 +4863,7 @@ describe('Gun', function(){ },100); }); - it('any any not', function(done){ + it('get get not', function(done){ var s = Gun.state.map(); s.soul = 'a'; Gun.on('put', {$: gun, put: Gun.graph.ify({b: 1, c: 2}, s)}); @@ -4882,9 +4883,9 @@ describe('Gun', function(){ 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); + gun.get('a').path('b').get(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('a').path('c').get(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('a').path('d').get(cb);//.err(cb).not(cb).on(cb).once(cb); }); it('any not any not any not', function(done){ @@ -4902,16 +4903,16 @@ describe('Gun', function(){ 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); + gun.get('x').path('b').any(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('x').path('c').any(cb);//.err(cb).not(cb).on(cb).once(cb); + gun.get('x').path('d').any(cb);//.err(cb).not(cb).on(cb).once(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. + get.once(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.link.is(data.very)).to.be.ok(); @@ -4926,12 +4927,12 @@ describe('Gun', function(){ } } }); - get.val(function(data){ + get.once(function(data){ //console.log("...2", data); expect(Gun.val.link.is(data.very)).to.be.ok(); }); setTimeout(function(){ - put.val(function(data){ + put.once(function(data){ //console.log("...3", data); expect(Gun.val.link.is(data.very)).to.be.ok(); done.val = true; @@ -4939,7 +4940,7 @@ describe('Gun', function(){ var p = put.path('very'); p.put({we: 'have gone!'}); setTimeout(function(){ - p.val(function(data){ + p.once(function(data){ //console.log("...4", data); expect(data.we).to.be('have gone!'); expect(Gun.val.link.is(data.deep)).to.be.ok(); @@ -4959,7 +4960,7 @@ describe('Gun', function(){ 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){ + put.once(function(val, field){ //console.log("****", field, val); expect(val.oh).to.be('okay'); expect(field).to.be('two'); @@ -4973,13 +4974,13 @@ describe('Gun', function(){ var path = get.path('one.two'); var path3 = path.path('three'); var put = path3.put({you: 'found', the: 'bottom!'}); - put.val(function(val, field){ + put.once(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){ + gun.get('deep/path').path('one.two.three.you').put('are').once(function(val, field){ //console.log("********2*********", field, val);return; expect(val).to.be('are'); expect(field).to.be('you'); @@ -5099,13 +5100,13 @@ describe('Gun', function(){ mark.path('wife').put(amber, function(err){ expect(err).to.not.be.ok(); }); - mark.path('wife.name').val(function(val){ + mark.path('wife.name').once(function(val){ expect(val).to.be("Amber Nadal"); }); }); it('put val', function(done){ - gun.put({hello: "world"}).val(function(val){ + gun.put({hello: "world"}).once(function(val){ expect(val.hello).to.be('world'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5117,7 +5118,7 @@ describe('Gun', function(){ }); it('put key val', function(done){ - gun.put({hello: "world"}).key('hello/world').val(function(val, field){ + gun.put({hello: "world"}).key('hello/world').once(function(val, field){ if(done.c){ return } expect(val.hello).to.be('world'); expect(done.c).to.not.be.ok(); @@ -5130,7 +5131,7 @@ describe('Gun', function(){ }); it('get val', function(done){ - gun.get('hello/world').val(function(val, field){ + gun.get('hello/world').once(function(val, field){ expect(val.hello).to.be('world'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5142,7 +5143,7 @@ describe('Gun', function(){ }); it('get path', function(done){ - gun.get('hello/world').path('hello').val(function(val){ + gun.get('hello/world').path('hello').once(function(val){ //console.log("**************", val); expect(val).to.be('world'); expect(done.c).to.not.be.ok(); @@ -5155,7 +5156,7 @@ describe('Gun', function(){ }); it('get put path', function(done){ - gun.get('hello/world').put({hello: 'Mark'}).path('hello').val(function(val, field){ + gun.get('hello/world').put({hello: 'Mark'}).path('hello').once(function(val, field){ expect(val).to.be('Mark'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5167,7 +5168,7 @@ describe('Gun', function(){ }); it('get path put', function(done){ - gun.get('hello/world').path('hello').put('World').val(function(val){ + gun.get('hello/world').path('hello').put('World').once(function(val){ expect(val).to.be('World'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5195,7 +5196,7 @@ describe('Gun', function(){ 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){ + gun.get('hello/world/not').path('earth').put('mars').once(function(val){ done.c = 1; }); setTimeout(function(){ @@ -5208,7 +5209,7 @@ describe('Gun', function(){ var gun = Gun(); var get = gun.get('hello/imp/world'); var put = get.put({planet: 'the earth'}); - put.val(function(val){ + put.once(function(val){ expect(val.planet).to.be('the earth'); done(); }); @@ -5219,7 +5220,7 @@ describe('Gun', function(){ 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){ + var val = put.once(function(val, field){ expect(field).to.be('where'); expect(val).to.be('the mars'); done(); @@ -5227,7 +5228,7 @@ describe('Gun', function(){ }); it('get path empty put val implicit', function(done){ - gun.get('hello/world').path('earth').put('mars').val(function(val, field){ + gun.get('hello/world').path('earth').put('mars').once(function(val, field){ expect(val).to.be('mars'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5241,7 +5242,7 @@ describe('Gun', function(){ 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){ + gun.get('hello/world/not').path('earth').once(function(val){ expect(val).to.be('mars'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5253,7 +5254,7 @@ describe('Gun', function(){ }); it('get path val implicit', function(done){ - gun.get('hello/world').path('earth').val(function(val){ + gun.get('hello/world').path('earth').once(function(val){ expect(val).to.be('mars'); expect(done.c).to.not.be.ok(); done.c = 1; @@ -5268,7 +5269,7 @@ describe('Gun', 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. + }).once(function(val){ // and THEN still retrieve it. expect(val.now).to.be('exists'); done(); }); @@ -5280,7 +5281,7 @@ describe('Gun', function(){ foo.not(function(key, kick){ done.not = true; this.put({now: 'THIS SHOULD NOT HAPPEN'}).key(key); - }).val(function(val){ + }).once(function(val){ expect(val.now).to.be('exists'); expect(done.not).to.not.be.ok(); done(); @@ -5289,7 +5290,7 @@ describe('Gun', function(){ }); it('put path val sub', function(done){ - gun.put({last: {some: 'object'}}).path('last').val(function(val){ + gun.put({last: {some: 'object'}}).path('last').once(function(val){ expect(val.some).to.be('object'); done(); }); @@ -5326,11 +5327,11 @@ describe('Gun', function(){ }); it('get put null', function(done){ - gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ + gun.put({last: {some: 'object'}}).path('last').once(function(val, field){ //console.log("**", field, val); expect(field).to.be('last'); expect(val.some).to.be('object'); - }).put(null).val(function(val, field){ + }).put(null).once(function(val, field){ //console.log("******", field, val); expect(field).to.be('last'); expect(val).to.be(null); @@ -5340,11 +5341,11 @@ describe('Gun', function(){ it('Gun get put null', function(done){ // flip flop bug var gun = Gun(); - gun.put({last: {some: 'object'}}).path('last').val(function(val, field){ + gun.put({last: {some: 'object'}}).path('last').once(function(val, field){ //console.log("**", field, val); done.some = true; expect(val.some).to.be('object'); - }).put(null).val(function(val, field){ + }).put(null).once(function(val, field){ //console.log("********", field, val); expect(val).to.be(null); expect(done.some).to.be.ok(); @@ -5356,7 +5357,7 @@ describe('Gun', function(){ 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 + foo.path('foo').once(function(val){ // and then the original should be able to be reused later expect(val).to.be('bar'); // this should work done(); }); @@ -5367,7 +5368,7 @@ describe('Gun', function(){ 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 + foo.path('foo').once(function(val){ // and then the original should be able to be reused later expect(val).to.be('bar'); // this should work done(); }); @@ -5381,10 +5382,10 @@ describe('Gun', function(){ title: 'awesome title', todos: {} }).key(key); - }).val(function(data){ + }).once(function(data){ expect(data.id).to.be('foobar'); - //}).path('todos').val(function(todos, field){ - }).path('todos').val(function(todos, field){ + //}).path('todos').once(function(todos, field){ + }).path('todos').once(function(todos, field){ expect(field).to.be('todos'); expect(todos).to.not.have.property('id'); done(); @@ -5400,7 +5401,7 @@ describe('Gun', function(){ data.b.parent = data.a; gun.put(data, function(err, ok){ expect(err).to.not.be.ok(); - }).val(function(val){ + }).once(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.link.is(val.a)]; var b = gun.back(-1)._.graph[Gun.val.link.is(val.b)]; @@ -5464,7 +5465,7 @@ describe('Gun', function(){ lol: true } } - }).path('foo.bar.lol').val(function(val){ + }).path('foo.bar.lol').once(function(val){ expect(val).to.be(true); done(); }); @@ -5477,7 +5478,7 @@ describe('Gun', function(){ lol: {ok: true} } } - }).path('foo.bar.lol').val(function(val){ + }).path('foo.bar.lol').once(function(val){ expect(val.ok).to.be(true); done(); }); @@ -5506,15 +5507,15 @@ describe('Gun', function(){ //console.debug.i=1;console.log("------------"); gun.put(mark, function(err, ok){ expect(err).to.not.be.ok(); - }).val(function(val){ + }).once(function(val){ expect(val.age).to.be(23); expect(val.name).to.be("Mark Nadal"); expect(Gun.val.link.is(val.wife)).to.be.ok(); expect(Gun.val.link.is(val.pet)).to.be.ok(); - }).path('wife.pet.name').val(function(val){ + }).path('wife.pet.name').once(function(val){ //console.debug(1, "*****************", val); expect(val).to.be('Hobbes'); - }).back().path('pet.master').val(function(val){ + }).back().path('pet.master').once(function(val){ //console.log("*****************", val); expect(val.name).to.be("Amber Nadal"); expect(val.phd).to.be.ok(); @@ -5536,14 +5537,14 @@ describe('Gun', function(){ 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){ + var mark = gun.put({name: "Mark", wife: { name: "Amber" }}).key('person/mark').once(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){ + mark.put({citizen: "USA", wife: {citizen: "USA"}}).once(function(mark){ //console.log("VAL2", mark, gun); expect(mark.name).to.be("Mark"); expect(mark.age).to.be(23); @@ -5563,10 +5564,10 @@ describe('Gun', function(){ it('path path', function(done){ var deep = gun.put({some: {deeply: {nested: 'value'}}}); - deep.path('some.deeply.nested').val(function(val){ + deep.path('some.deeply.nested').once(function(val){ expect(val).to.be('value'); }); - deep.path('some').path('deeply').path('nested').val(function(val){ + deep.path('some').path('deeply').path('nested').once(function(val){ expect(val).to.be('value'); done(); }); @@ -5584,7 +5585,7 @@ describe('Gun', function(){ var gun = Gun(); var fo = gun.put({fo: 'bar'}); Gun.log.ba = 1; - fo.put({ba: {}}).val(function(obj, field){ + fo.put({ba: {}}).once(function(obj, field){ c += 1; expect(c).to.be(1); done(); @@ -5597,7 +5598,7 @@ describe('Gun', function(){ describe('random', function(){ var foo; it('context null put node', function(done){ - foo = gun.put({foo: 'bar'}).val(function(obj){ + foo = gun.put({foo: 'bar'}).once(function(obj){ expect(obj.foo).to.be('bar'); done(); //setTimeout(function(){ done() },1); }); @@ -5613,7 +5614,7 @@ describe('Gun', function(){ 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){ + foo.put({bar: {zoo: 'who'}}).once(function(obj, field){ //console.log("terribly terrilby unpleasant", field, obj); expect(obj.foo).to.be('bar'); expect(Gun.val.link.is(obj.bar)).to.ok(); @@ -5626,7 +5627,7 @@ describe('Gun', function(){ // 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){ + bar.put({combo: 'double'}).once(function(obj, field){ //expect(obj.zoo).to.be('who'); expect(obj.combo).to.be('double'); done(); //setTimeout(function(){ done() },1); @@ -5636,7 +5637,7 @@ describe('Gun', function(){ 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){ + tar.put('zebra').once(function(val){ expect(val).to.be('zebra'); done(); //setTimeout(function(){ done() },1); }); @@ -5644,10 +5645,10 @@ describe('Gun', function(){ 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){ + bar.path('combo').put({another: 'node'}).once(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){ + bar.once(function(node){ expect(Gun.val.link.is(node.combo)).to.be.ok(); expect(Gun.val.link.is(node.combo)).to.be(Gun.node.soul(obj)); done(); //setTimeout(function(){ done() },1); @@ -5662,10 +5663,10 @@ describe('Gun', function(){ 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){ + al.once(function(a){ beth.put({friend: a}, function(err, ok){ expect(err).to.not.be.ok(); - }).path('friend').val(function(aa){ + }).path('friend').once(function(aa){ expect(Gun.node.soul(a)).to.be(Gun.node.soul(aa)); done(); }); @@ -5682,7 +5683,7 @@ describe('Gun', function(){ 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){ + gun.get('user/alfred').once(function(a){ //console.log("*****", a); //expect(a['_']['key']).to.be.ok(); gun.get('user/beth').put({friend: a}, function(err, ok){ // b - friend_of -> a @@ -5691,13 +5692,13 @@ describe('Gun', function(){ var c = soulnode(gun, keynode), soul = c[0]; expect(c.length).to.be(1); }); - gun.get('user/beth').val(function(b){ + gun.get('user/beth').once(function(b){ //console.log("beth", b); - gun.get('user/alfred').put({friend: b}).val(function(al){ // a - friend_of -> b + gun.get('user/alfred').put({friend: b}).once(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/beth').put({cat: {name: "fluffy", age: 3, coat: "tabby"}}).once(function(bet){ gun.get('user/alfred').path('friend.cat').key('the/cat'); - gun.get('the/cat').val(function(c){ + gun.get('the/cat').once(function(c){ //console.log("cat!!!", c); expect(c.name).to.be('fluffy'); expect(c.age).to.be(3); @@ -5761,7 +5762,7 @@ describe('Gun', function(){ }}}), soul = Gun.text.random(); gun.get(soul).not(function(err, ok){ done.fail = true; - }).val(function(val){ + }).once(function(val){ setTimeout(function(){ expect(val.a).to.be('b'); expect(val.c).to.be('d'); @@ -5792,7 +5793,7 @@ describe('Gun', function(){ gun.get('me', function(err, data){ - }).val(function(val){ + }).once(function(val){ done.count = (done.count || 0) + 1; setTimeout(function(){ expect(val.a).to.be('b'); @@ -5820,7 +5821,7 @@ describe('Gun', function(){ cb(null, n); }}}), soul = Gun.text.random(); - gun.get(soul).path('a').val(function(val){ + gun.get(soul).path('a').once(function(val){ done.count = (done.count || 0) + 1; setTimeout(function(){ expect(val).to.be('b'); @@ -5838,7 +5839,7 @@ describe('Gun', function(){ setTimeout(function(){ gun.not(function(){ done.not = true; - }).val(function(){ + }).once(function(){ expect(done.not).to.not.be.ok(); done(); }, {empty: true}); @@ -5960,7 +5961,7 @@ describe('Gun', function(){ }); it('instance.val', function(done){ - Gun().val(); + Gun().once(); done(); }); }); @@ -6123,7 +6124,7 @@ describe('Gun', function(){ it('set', function(done){ done.c = 0; var u, gun = Gun(); - gun.get('set').set().set().val(function(val){ + gun.get('set').set().set().once(function(val){ var keynode = gun.__.graph['set']; expect(Gun.node.soul.ify(keynode, '.')).to.be.ok(); Gun.is.node(keynode, function(rel, soul){ @@ -6149,7 +6150,7 @@ describe('Gun', function(){ // 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){ + gun.once(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'); @@ -6158,7 +6159,7 @@ describe('Gun', function(){ }); 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! + //gun.map().once(function(val, field){ // TODO: SEAN! DON'T LET ME FORGET! console.log("\n TEST 2+", field, val); return; i += 1; @@ -6182,7 +6183,7 @@ describe('Gun', function(){ users.path(Gun.text.random()).put('bob'); users.path(Gun.text.random()).put('sam'); setTimeout(function(){ - users.val(function(v){ + users.once(function(v){ expect(Gun.val.link.is(v)).to.not.be.ok(); expect(Object.keys(v).length).to.be(3); done(); @@ -6217,7 +6218,7 @@ describe('Gun', function(){ gun.put({a: 1, z: -1}).key('pseudo'); gun.put({b: 2, z: 0}).key('pseudo'); - gun.get('pseudo').val(function(val){ + gun.get('pseudo').once(function(val){ expect(val.a).to.be(1); expect(val.b).to.be(2); expect(val.z).to.be(0); @@ -6275,14 +6276,14 @@ describe('Gun', function(){ 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){ + gun1.once(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){ + gun2.once(function(val){ expect(val.hi).to.be('mars!'); }); setTimeout(function(){ @@ -6293,13 +6294,13 @@ describe('Gun', function(){ //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){ + gun1.once(function(val){ expect(val.hello).to.be('world!'); expect(val.hi).to.be('mars!'); done.g1 = true; }); //return; - gun2.val(function(val){ + gun2.once(function(val){ expect(val.hello).to.be('world!'); expect(val.hi).to.be('mars!'); expect(done.g1).to.be.ok(); @@ -6325,8 +6326,8 @@ describe('Gun', function(){ }}).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){ + passengers.map().once(function(passenger, id){ + this.map().once(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){ @@ -6367,7 +6368,7 @@ describe('Gun', function(){ list.put({a: {x:1}, b: {y: 1}}); list.path('a').path('w').put(2); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; console.log("*************************", f,v); if(check.a && check.b){ @@ -6383,7 +6384,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/sub/val/after'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; if(check.a && check.b){ setTimeout(function(){ @@ -6405,7 +6406,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/sub/val/after/to'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ //console.log("*************", f,v);return; check[f] = v; if(check.a && check.b){ @@ -6426,7 +6427,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/simple/after'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; if(check.a && check.b){ setTimeout(function(){ @@ -6445,7 +6446,7 @@ describe('Gun', function(){ var g = Gun(); var list = gun.get('map/simple/after/to'); var check = {}; - list.map().val(function(v,f){ + list.map().once(function(v,f){ check[f] = v; if(check.a && check.b){ setTimeout(function(){ @@ -6502,7 +6503,7 @@ describe('Gun', function(){ }}).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){ + passengers.map().map().once(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){ @@ -6537,7 +6538,7 @@ describe('Gun', function(){ }).key('n/b/l/a/c'); }); var check = {a:{},b:{}}, F = 'a'; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ var c = check[F]; c[f] = v; if(check.b && check.b.x && check.b.y){ @@ -6566,7 +6567,7 @@ describe('Gun', function(){ }).key('n/b/l/a'); }); var check = {}; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ check[f] = v; if(check.x && check.y && check.w && check.u){ expect(check.x).to.be(1); @@ -6586,7 +6587,7 @@ describe('Gun', function(){ var g = gun.get('b/l/a'); g.put({a: {x:1,y:1}}); var check = {}; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ check[f] = v; if(check.x && check.y && check.w && check.u && check.z){ expect(check.x).to.be(1); @@ -6607,7 +6608,7 @@ describe('Gun', function(){ var g = gun.get('b/d/l/a'); g.put({a: {x:1,y:1}}); var check = {}; - g.map().map().val(function(v,f){ + g.map().map().once(function(v,f){ check[f] = v; if(check.x && check.y && check.w && check.u){ expect(check.x).to.be(1); @@ -6647,7 +6648,7 @@ describe('Gun', function(){ } }); var check = {}; - g.map().map().map().map().val(function(v,f){ + g.map().map().map().map().once(function(v,f){ check[f] = (check[f] || 0) + 1; if(check.d === 2 && check.e === 2 && check.f === 2){ done(); @@ -6824,7 +6825,7 @@ describe('Gun', function(){ direction: '128.2' }}).key(key); }); - passengers.map().path('location.lng').val(function(val, field){ + passengers.map().path('location.lng').once(function(val, field){ //passengers.map().path('location.lng').on(function(val, field){ console.log("******", field, val); expect(field).to.be('lng'); @@ -6902,9 +6903,9 @@ describe('Gun', function(){ 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){ + gun.put({you: {have: {got: {to: {be: {kidding: "me!"}}}}}}).path('you.have.got.to.be').once(function(val, field){ expect(val.kidding).to.be('me!'); - this.path('kidding').val(function(val){ + this.path('kidding').once(function(val){ expect(val).to.be('me!'); done(); }); @@ -6918,10 +6919,10 @@ describe('Gun', function(){ 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){ + passengers.map().path('direction.lol').once(function(val){ + this.path('just').once(function(val){ expect(val).to.be('kidding'); - }).back().path('dude').val(function(val){ + }).back().path('dude').once(function(val){ expect(val).to.be('!'); done(); }); @@ -7218,7 +7219,7 @@ describe('Gun', function(){ if(call[hash]){ return } gun.__.meta($.soul).put = true; call[hash] = true; - if(Gun.is.val(obj)){ + if(Gun.is.once(obj)){ if($.from && $.at){ $.soul = $.from; $.field = $.at; @@ -7434,7 +7435,7 @@ describe('Gun', function(){ } }, function(err,ok){ expect(done.c++).to.be(0); - }).val(function(p){ + }).once(function(p){ done.p = Gun.node.soul(p); done.m = Gun.val.link.is(p[0]); expect(Gun.val.link.is(p[0])).to.be.ok(); @@ -7480,7 +7481,7 @@ describe('Gun', function(){ var u; var gun = Gun(gopt); var game = gun.get('game1/players'); - var me = game.path('player1').val(function(val){ + var me = game.path('player1').once(function(val){ if(!done.c){ done.fail = true } expect(val).to.not.be(u); expect(val.x).to.be(0); @@ -7532,7 +7533,7 @@ describe('Gun', function(){ 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){ + var me = game.path('player4').init().path('alias').init().put({oh: 'awesome'}).once(function(val, field){ expect(val.oh).to.be('awesome'); expect(field).to.be('alias'); done(); @@ -7559,7 +7560,7 @@ describe('Gun', function(){ 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){ + chat.map().once(function renderToDo(val, field){ expect(field).to.be.ok(); expect(val.who).to.be.ok(); expect(val.when).to.be.ok(); @@ -7670,7 +7671,7 @@ describe('Gun', function(){ 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){ + chat.map().once(function renderToDo(val, field){ //console.log("ALICE", field, val); expect(field).to.be.ok(); expect(val.who).to.be.ok(); @@ -7684,7 +7685,7 @@ describe('Gun', function(){ //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){ + chat2.map().once(function renderToDo(val, field){ //console.log("BOB", field, val); expect(field).to.be.ok(); expect(val.who).to.be.ok(); @@ -7712,7 +7713,7 @@ describe('Gun', function(){ // Test set with new object var alan = users.set({name: 'alan', birth: Math.random()}).key('person/alan'); - alan.val(function(alan) { + alan.once(function(alan) { // Test set with node dave.path('friends').set(alan); }); @@ -7734,7 +7735,7 @@ describe('Gun', function(){ alice.path('team').put(team); bob.path('team').put(team); - dave.path('friends').map().path('team.members').map().val(function(member){ + dave.path('friends').map().path('team.members').map().once(function(member){ //console.log("Dave's friend is on a team that has", member.name, "on it."); if('alice' === member.name){ done.alice = true; @@ -7775,14 +7776,14 @@ describe('Gun', function(){ 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){ + gun.get('ctx/lol').once(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){ + gun.get('ctx/foo').once(function(data){ expect(data.hello).to.be('world'); expect(done.fail).to.not.be.ok(); expect(done.please).to.be.ok(); @@ -7794,7 +7795,7 @@ describe('Gun', function(){ var gun = Gun(); gun.get('users/cv').set(gun.put({name: 'alice'})); gun.get('users/cv').set(gun.put({name: 'bob'}));; - gun.get('users/cv').val().map(function(person){ + gun.get('users/cv').once().map(function(person){ if(person.name === 'alice'){ done.alice = true; } @@ -7861,12 +7862,12 @@ describe('Gun', function(){ }); setTimeout(function(){ - //list.path('next').val('wat'); + //list.path('next').once('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. + list.path('next.to').once(function () { // TODO: BUG! If this is 'next.next' as with the data, then it fails. done(); }); },100); @@ -7920,7 +7921,7 @@ describe('Gun', function(){ BSMI.path(path).put({status:false}); }); setTimeout(function(){ - BSMI.path(allPaths[0]).val(function(a,b,c){ + BSMI.path(allPaths[0]).once(function(a,b,c){ expect(a.a).to.be(1); expect(a.b).to.be(2); expect(a.c).to.be(3); @@ -7950,7 +7951,7 @@ describe('Gun', function(){ 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){ + test.once(function(node,b){ delete node._; expect(Gun.obj.empty(node, 'try')).to.be.ok(); node = Gun.obj.copy(gun.__.graph[Gun.val.link.is(node.try)]); @@ -7986,7 +7987,7 @@ describe('Gun', function(){ var world = 0; player.path("id").put(id); player.path("world_id").put(world); - }).val(function(data){ + }).once(function(data){ //console.log("we have value!", data); expect(done.not).to.be.ok(); expect(data).to.be('fluffy'); @@ -8010,7 +8011,7 @@ describe('Gun', function(){ // 3: bacon // 9: `.not` - depp.path('spouse.pet.name').val().on(log); + depp.path('spouse.pet.name').once().on(log); // 0: fluffy // 1: fluff */ @@ -8051,7 +8052,7 @@ describe('Gun', function(){ ctx.length = i; } ctx.get.fake = Gun.is.node.ify(ctx.get.fake, 'big'); - var big = peer.put(ctx.get.fake).val(function(val){ + var big = peer.put(ctx.get.fake).once(function(val){ ref = val; ctx.get({'#': 'big'}, function(err, graph){ if(Gun.obj.empty(graph)){ done() } @@ -8062,7 +8063,7 @@ describe('Gun', function(){ it('map chain', function(done){ var set = gun.put({a: {here: 'you'}, b: {go: 'dear'}, c: {sir: '!'} }); - set.map().val(function(obj, field){ + set.map().once(function(obj, field){ if(obj.here){ done.a = obj.here; expect(obj.here).to.be('you'); @@ -8091,7 +8092,7 @@ describe('Gun', function(){ pet: {coat: "tux", name: "Casper"} } }); - set.map().path('pet').val(function(obj, field){ + set.map().path('pet').once(function(obj, field){ if(obj.name === 'Hobbes'){ done.hobbes = obj.name; expect(obj.name).to.be('Hobbes'); @@ -8154,7 +8155,7 @@ describe('Gun', function(){ it('get val', function(done){ this.timeout(ctx.gen * ctx.extra); - g().get('big').val(function(obj){ + g().get('big').once(function(obj){ delete obj._; expect(obj.f1).to.be(1); expect(obj['f' + ctx.length]).to.be(ctx.length); @@ -8169,7 +8170,7 @@ describe('Gun', function(){ 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){ + g().get('big').map().once(function(val, field){ if(test.seen[field]){ return } test.seen[field] = true; delete val._; @@ -8192,7 +8193,7 @@ describe('Gun', function(){ 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){ + chat.map(function(m){ }).once(function(msg, field){ var msg = Gun.obj.copy(msg); if(msg.what){ expect(msg.what).to.be.ok(); diff --git a/test/mocha.html b/test/mocha.html index 86e4d79e..db231f5a 100644 --- a/test/mocha.html +++ b/test/mocha.html @@ -17,13 +17,21 @@ + + +