diff --git a/axe.js b/axe.js
new file mode 100644
index 00000000..40b3121c
--- /dev/null
+++ b/axe.js
@@ -0,0 +1,80 @@
+;(function(){
+
+ /* UNBUILD */
+ var root;
+ if(typeof window !== "undefined"){ root = window }
+ if(typeof global !== "undefined"){ root = global }
+ root = root || {};
+ var console = root.console || {log: function(){}};
+ function USE(arg, req){
+ return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
+ arg(mod = {exports: {}});
+ USE[R(path)] = mod.exports;
+ }
+ function R(p){
+ return p.split('/').slice(-1).toString().replace('.js','');
+ }
+ }
+ if(typeof module !== "undefined"){ var common = module }
+ /* UNBUILD */
+
+ ;USE(function(module){
+ if(typeof window !== "undefined"){ module.window = window }
+ var tmp = module.window || module;
+ var AXE = tmp.AXE || function(){};
+
+ if(AXE.window = module.window){ try{
+ AXE.window.AXE = AXE;
+ tmp = document.createEvent('CustomEvent');
+ tmp.initCustomEvent('extension', false, false, {type: "AXE"});
+ (window.dispatchEvent || window.fireEvent)(tmp);
+ window.postMessage({type: "AXE"}, '*');
+ } catch(e){} }
+
+ try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
+ module.exports = AXE;
+ })(USE, './root');
+
+ ;USE(function(module){
+
+ var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
+ (Gun.AXE = AXE).GUN = AXE.Gun = Gun;
+
+ Gun.on('opt', function(at){
+ if(!at.axe){
+ at.axe = {};
+ var p = at.opt.peers, tmp;
+ // 1. If any remembered peers or from last cache or extension
+ // 2. Fallback to use hard coded peers from dApp
+ // 3. Or any offered peers.
+ if(Gun.obj.empty(p)){
+ Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){
+ p[url] = {url: url, axe: {}};
+ });
+ }
+ // Our current hypothesis is that it is most optimal
+ // to take peers in a common network, and align
+ // them in a line, where you only have left and right
+ // peers, so messages propagate left and right in
+ // a linear manner with reduced overlap, and
+ // with one common superpeer (with ready failovers)
+ // in case the p2p linear latency is high.
+ // Or there could be plenty of other better options.
+ console.log("axe", at.opt);
+ if(at.opt.super){
+ at.on('in', USE('./lib/super', 1), at);
+ } else {
+ //at.on('in', input, at);
+ }
+ }
+ this.to.next(at); // make sure to call the "next" middleware adapter.
+ });
+
+ function input(msg){
+ var at = this.as, to = this.to;
+ }
+
+ module.exports = AXE;
+ })(USE, './axe');
+
+}());
\ No newline at end of file
diff --git a/examples/basic/user.html b/examples/basic/user.html
index c736c67b..9ae9b009 100644
--- a/examples/basic/user.html
+++ b/examples/basic/user.html
@@ -5,6 +5,7 @@
+
@@ -23,13 +24,14 @@ var gun = Gun(); //Gun(['http://localhost:8765/gun', 'https://guntest.herokuapp.
var user = gun.user().recall({sessionStorage: true});
$('#up').on('click', function(e){
- user.create($('#alias').val(), $('#pass').val());
+ user.create($('#alias').val(), $('#pass').val(), login);
});
-
-$('#sign').on('submit', function(e){
- e.preventDefault();
+function login(e){
user.auth($('#alias').val(), $('#pass').val());
-});
+ return false; // e.preventDefault();
+};
+$('#sign').on('submit', login);
+$('#mask').on('click', login);
gun.on('auth', function(){
$('#sign').hide();
@@ -38,7 +40,7 @@ gun.on('auth', function(){
$('#said').on('submit', function(e){
e.preventDefault();
- if(!user.is){ return }
+ //if(!user.is){ return }
user.get('said').set($('#say').val());
$('#say').val("");
});
diff --git a/examples/move/index.html b/examples/move/index.html
new file mode 100644
index 00000000..ff51f0bb
--- /dev/null
+++ b/examples/move/index.html
@@ -0,0 +1,203 @@
+
+
+
+
+ Move by Neon ERA
+
+
+
+
+
+
+
+ Share
+
+
Copy and Paste this URL to your friends to share your location:
+
+ Location sharing not available!
+
+
Note: Location may not sync when your device's screen is off or the tab is out of focus. You'd need to install this as an app for that to work.
+
+ Close
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gun.min.js b/gun.min.js
index d30b4be2..d701be45 100644
--- a/gun.min.js
+++ b/gun.min.js
@@ -1,2 +1,2 @@
-!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._;return(i=r.soul)?(n(i,e,r),t):(i=r.link)?(n(i,e,r),t):(t.get(function(t,o){o.rid(t);var r=(r=t.$)&&r._||{};i=r.link||r.soul||c.is(t.put)||l(t.put),n(i,e,t,o)},{out:{get:{".":!0}}}),t)}function e(t){var n,o=this,e=o.as,i=e.at,r=i.root,u=t.$,f=(u||{})._||{},l=t.put||f.put;if((n=r.now)&&o!==n[e.now])return o.to.next(t);if(o.seen&&f.id&&o.seen[f.id])return o.to.next(t);if((n=l)&&n[c._]&&(n=c.is(n))&&(n=(t.$$=f.root.gun.get(n))._,a!==n.put&&(t=s(t,{put:l=n.put}))),(n=r.mum)&&f.id){if(n[f.id])return;a===l||c.is(l)||(n[f.id]=!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,l;if("string"!=typeof t){if(t instanceof Function){if(!0===a)return o(this,t,a,u);s=this;var d,h=s._,v=h.root,l=v.now;u=a||{},u.at=h,u.use=t,u.out=u.out||{},u.out.get=u.out.get||{},(d=h.on("in",e,u)).rid=i,(v.now={$:1})[u.now=h.id]=d;var g=v.mum;return v.mum={},h.on("out",u.out),v.mum=g,v.now=l,s}return f(t)?this.get(""+t,a,u):(l=c.is(t))?this.get(l,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||p;return(s=k[t])||(s=n(t,m)),s=s.$,(l=b.stun)&&(s._.stun=s._.stun||l),a&&a instanceof Function&&s.get(a,u),s};var a,u=r.obj,s=(u.has,r.obj.to),f=r.num.is,c=r.val.link,l=r.node.soul,p=(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.get,a=a.root.$.get(r)._,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.$=a=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,e){var r,u,s=this.as,f=(s.at,t.$),c=f._,l=c.put||t.put;if(u=t.$$){if(r=u=t.$$._,i===u.put)return;l=u.put}return(u=n.wait)&&(u=u[c.id])&&clearTimeout(u),!e&&(i===l||c.soul||c.link||r&&!(0 .once, apologies unexpected."),this.once(t,n)},e.chain.once=function(t,n){var r=this,a=r._,u=a.put;if(0=(n.batch||1e3)?s():void(e||(e=setTimeout(s,n.wait||1)))}),t.on("get",function(o){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(n.peers))&&t.on("in",{"@":o["#"],put:Gun.graph.node(r),how:"lS",lS:o.I})}}this.to.next(o);var i,r,u,s=o.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{o.setItem(n.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 ."),t.on("localStorage:error",{err:f,file:n.prefix,flush:a,retry:s})}(f||Gun.obj.empty(n.peers))&&Gun.obj.map(c,function(n,o){t.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(){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._;return(i=r.soul)?(n(i,e,r),t):(i=r.link)?(n(i,e,r),t):(t.get(function(t,o){o.rid(t);var r=(r=t.$)&&r._||{};i=r.link||r.soul||c.is(t.put)||l(t.put),n(i,e,t,o)},{out:{get:{".":!0}}}),t)}function e(t){var n,o=this,e=o.as,i=e.at,r=i.root,u=t.$,f=(u||{})._||{},l=t.put||f.put;if((n=r.now)&&o!==n[e.now])return o.to.next(t);if(o.seen&&f.id&&o.seen[f.id])return o.to.next(t);if((n=l)&&n[c._]&&(n=c.is(n))&&(n=(t.$$=f.root.gun.get(n))._,a!==n.put&&(t=s(t,{put:l=n.put}))),(n=r.mum)&&f.id){if(n[f.id])return;a===l||c.is(l)||(n[f.id]=!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,l;if("string"!=typeof t){if(t instanceof Function){if(!0===a)return o(this,t,a,u);s=this;var d,h=s._,v=h.root,l=v.now;u=a||{},u.at=h,u.use=t,u.out=u.out||{},u.out.get=u.out.get||{},(d=h.on("in",e,u)).rid=i,(v.now={$:1})[u.now=h.id]=d;var g=v.mum;return v.mum={},h.on("out",u.out),v.mum=g,v.now=l,s}return f(t)?this.get(""+t,a,u):(l=c.is(t))?this.get(l,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||p;return(s=k[t])||(s=n(t,m)),s=s.$,(l=b.stun)&&(s._.stun=s._.stun||l),a&&a instanceof Function&&s.get(a,u),s};var a,u=r.obj,s=(u.has,r.obj.to),f=r.num.is,c=r.val.link,l=r.node.soul,p=(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.get,a=a.root.$.get(r)._,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.$=a=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
diff --git a/lib/normalize.js b/lib/normalize.js
new file mode 100644
index 00000000..901f1671
--- /dev/null
+++ b/lib/normalize.js
@@ -0,0 +1,139 @@
+;(function(){
+ function normalize(opt){
+ var el = $(this);
+ opt = opt || $.extend(true, normalize.opt, opt||{});
+ el.children().each(function(){
+ var a = {$: $(this), opt: opt};
+ a.tag = normalize.tag(a.$);
+ $(a.opt.mutate).each(function(i,fn){
+ fn && fn(a);
+ });
+ })
+ };
+ var n = normalize, u;
+ n.get = function(o, p){
+ p = p.split('.');
+ var i = 0, l = p.length, u;
+ while((o = o[p[i++]]) != null && i < l){};
+ return i < l ? u : o;
+ }
+ n.has = function(o,p){
+ return Object.prototype.hasOwnProperty.call(o, p);
+ }
+ n.tag = function(e){
+ return (($(e)[0]||{}).nodeName||'').toLowerCase();
+ }
+ n.attrs = function(e, cb){
+ var attr = {};
+ (e = $(e)) && e.length && $(e[0].attributes||[]).each(function(v,n){
+ n = n.nodeName||n.name;
+ v = e.attr(n);
+ v = cb? cb(v,n,e) : v;
+ if(v !== u && v !== false){ attr[n] = v }
+ });
+ return attr;
+ }
+ n.joint = function(e, d){
+ d = (d? 'next' : 'previous') + 'Sibling'
+ return $(($(e)[0]||{})[d]);
+ }
+ var h = {
+ attr: function(a$, av, al){
+ var l = function(i,v){
+ var t = v;
+ i = al? v : i;
+ v = al? av[v.toLowerCase()] : t;
+ a$.attr(i, v);
+ }
+ al? $(al.sort()).each(l) : $.each(av,l);
+ }
+ }
+ n.opt = { // some reasonable defaults, limited to content alone.
+ tags: {
+ 'a': {attrs:{'src':1}, exclude:{'a':1}},
+ 'b': {exclude:{'b':1}},
+ //'blockquote':1,
+ 'br': {empty: 1},
+ 'div': 1,
+ //'code':1,
+ 'i': {exclude:{'i':1}},
+ 'img': {attrs:{'src':1}, empty: 1},
+ 'li':1, 'ol':1,
+ 'p': {exclude:{'p':1,'div':1}},
+ //'pre':1,
+ 's': {exclude:{'s':1}},
+ 'sub':1, 'sup':1,
+ 'span': {exclude:{'p':1,'ul':1,'ol':1,'li':1,'br':1}},
+ 'u': {exclude:{'u':1,'p':1}},
+ 'ul':1
+ }
+ // a, audio, b, br, div, i, img, li, ol, p, s, span, sub, sup, u, ul, video
+ // button, canvas, embed, form, iframe, input, style, svg, table,
+ // Text: bold, italics, underline, align, bullet, list,
+ ,convert: {
+ 'em': 'i', 'strong': 'b'
+ }
+ ,attrs: {
+ 'id':1
+ ,'class':1
+ ,'style':1
+ }
+ ,mutate: [
+ function(a){ // attr
+ a.attrs = [];
+ a.attr = $.extend(a.opt.attrs, n.get(a.opt,'tags.'+ a.tag +'attrs'));
+ a.attr = n.attrs(a.$, function(v,i){
+ a.$.removeAttr(i);
+ if(a.attr[i.toLowerCase()]){
+ a.attrs.push(i)
+ return v;
+ }
+ });
+ // if this tag is gonna get converted, wait to add attr back till after the convert
+ if(a.attrs && !n.get(a.opt, 'convert.' + a.tag)){
+ h.attr(a.$, a.attr, a.attrs);
+ }
+ }
+ ,function(a, tmp){ // convert
+ if(!(tmp = n.get(a.opt,'convert.' + a.tag))){ return }
+ a.attr = a.attr || n.attrs(a.$);
+ a.$.replaceWith(a.$ = $('<'+ (a.tag = t.toLowerCase()) +'>').append(a.$.contents()));
+ h.attr(a.$, a.attr, a.attrs);
+ }
+ ,function(a, tmp){ // lookahead
+ if((tmp = n.joint(a.$,1)) && (t = t.contents()).length === 1 && a.tag === n.tag(t = t.first())){
+ a.$.append(t.parent()); // no need to unwrap the child, since the recursion will do it for us
+ }
+ }
+ ,function(a){ // recurse
+ // this needs to precede the exclusion and empty.
+ normalize(a);
+ }
+ ,function(a){ // exclude
+ var t;
+ if(!n.get(a.opt,'tags.' + a.tag)
+ || ((t = n.get(a.opt,'tags.'+ a.tag +'.exclude'))
+ && a.$.parents($.map(t,function(i,v){return v})+' ').length)
+ ){
+ a.$.replaceWith(a.$.contents());
+ }
+ }
+ ,function(a){ // prior
+ var t;
+ if((t = n.joint(a.$)).length && a.tag === n.tag(t)){
+ t.append(a.$.contents());
+ }
+ }
+ ,function(a){ // empty
+ // should always go last, since the element will be removed!
+ if(a.opt.empty || !n.has(a.opt,'empty')){
+ if(!n.get(a.opt,'tags.'+ a.tag +'.empty')
+ && !a.$.contents().length){
+ a.$.remove();
+ }
+ }
+ }
+ ]
+ }
+ $.fn.normalize = normalize;
+}());
\ No newline at end of file
diff --git a/lib/reboot.js b/lib/reboot.js
new file mode 100644
index 00000000..9a2ba612
--- /dev/null
+++ b/lib/reboot.js
@@ -0,0 +1,19 @@
+;(function(){
+ var exec = require('child_process').execSync;
+ var dir = __dirname, tmp;
+
+ try{exec("crontab -l");
+ }catch(e){tmp = e}
+ if(0 > tmp.toString().indexOf('no')){ return }
+
+ try{tmp = exec('which node').toString();
+ }catch(e){console.log(e);return}
+
+ try{tmp = exec('echo "@reboot '+tmp+' '+dir+'/../examples/http.js" > '+dir+'/reboot.cron');
+ }catch(e){console.log(e);return}
+
+ try{tmp = exec('crontab '+dir+'/reboot.cron');
+ }catch(e){console.log(e);return}
+ console.log(tmp.toString());
+
+}());
\ No newline at end of file
diff --git a/lib/server.js b/lib/server.js
index a893322e..0c698be1 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -3,15 +3,16 @@
Gun.serve = require('./serve');
//process.env.GUN_ENV = process.env.GUN_ENV || 'debug';
Gun.on('opt', function(root){
+ if(u === root.opt.super){
+ root.opt.super = true;
+ }
this.to.next(root);
- if(root.once){ return }
- if(u !== root.opt.super){ return }
- root.opt.super = true;
})
require('../nts');
require('./store');
require('./rs3');
require('./wire');
+ //try{require('../axe');}catch(e){}
require('./file');
require('./evict');
if('debug' === process.env.GUN_ENV){ require('./debug') }
diff --git a/package.json b/package.json
index 47c7d85f..a8f8ebfa 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gun",
- "version": "0.9.99997",
+ "version": "0.9.99998",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"main": "index.js",
"browser": "gun.min.js",
diff --git a/sea.js b/sea.js
index 7c08fc81..ca47037a 100644
--- a/sea.js
+++ b/sea.js
@@ -27,15 +27,9 @@
if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module;
- var SEA = tmp.SEA || function(){};
+ var SEA = tmp.SEA || {};
- if(SEA.window = module.window){ try{
- SEA.window.SEA = SEA;
- tmp = document.createEvent('CustomEvent');
- tmp.initCustomEvent('extension', false, false, {type: "SEA"});
- (window.dispatchEvent || window.fireEvent)(tmp);
- window.postMessage({type: "SEA"}, '*');
- } catch(e){} }
+ if(SEA.window = module.window){ SEA.window.SEA = SEA }
try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
module.exports = SEA;
@@ -320,8 +314,18 @@
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)} }
+ return;
+ } catch(e) {
+ console.log(e);
+ SEA.err = e;
+ if(cb){ cb() }
+ return;
+ }});
+
//SEA.pair = async (data, proof, cb) => { try {
- SEA.pair = SEA.pair || (async (cb) => { try {
+ SEA.pair = SEA.pair || (async (cb, opt) => { try {
const ecdhSubtle = shim.ossl || shim.subtle
// First: ECDSA keys for signing/verifying...
@@ -383,7 +387,7 @@
var S = USE('./settings');
var sha256hash = USE('./sha256');
- SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
+ SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
if(data && data.slice
&& 'SEA{' === data.slice(0,4)
&& '"m":' === data.slice(4,8)){
@@ -393,6 +397,10 @@
if(cb){ try{ cb(data) }catch(e){console.log(e)} }
return data;
}
+ opt = opt || {};
+ 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)
@@ -422,7 +430,7 @@
var parse = USE('./parse');
var u;
- SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
+ SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
const json = parse(data)
if(false === pair){ // don't verify!
const raw = (json !== data)?
@@ -431,6 +439,8 @@
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)
@@ -474,8 +484,12 @@
var aeskey = USE('./aeskey');
SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
- var opt = opt || {};
- const key = pair.epriv || pair;
+ opt = opt || {};
+ var key = (pair||opt).epriv || pair;
+ 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)
@@ -507,8 +521,12 @@
var parse = USE('./parse');
SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
- var opt = opt || {};
- const key = pair.epriv || pair;
+ opt = opt || {};
+ var key = (pair||opt).epriv || pair;
+ if(!key){
+ pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
+ key = pair.epriv || pair;
+ }
const json = parse(data)
const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
.then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible...
@@ -530,8 +548,12 @@
var SEA = USE('./root');
var shim = USE('./shim');
var S = USE('./settings');
- // Derive shared secret from other's pub and my epub/epriv
- SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
+ // Derive shared secret from other's pub and my epub/epriv
+ SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try {
+ opt = opt || {};
+ 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
@@ -576,6 +598,7 @@
;USE(function(module){
// Old Code...
+ try{
const __gky10 = USE('./shim')
const crypto = __gky10.crypto
const subtle = __gky10.subtle
@@ -594,8 +617,8 @@
const keysToEcdsaJwk = __gky11.jwk
const sha1hash = USE('./sha1')
const sha256hash = USE('./sha256')
- const recallCryptoKey = USE('./remember')
const parseProps = USE('./parse')
+ }catch(e){}
// Practical examples about usage found from ./test/common.js
const SEA = USE('./root');
@@ -609,7 +632,7 @@
// This is easy way to use IndexedDB, all methods are Promises
// Note: Not all SEA interfaces have to support this.
- SEA.EasyIndexedDB = EasyIndexedDB;
+ try{SEA.EasyIndexedDB = EasyIndexedDB;}catch(e){}
// This is Buffer used in SEA and usable from Gun/SEA application also.
// For documentation see https://nodejs.org/api/buffer.html
@@ -656,419 +679,6 @@
module.exports = SEA
})(USE, './sea');
- ;USE(function(module){
- var SEA = USE('./sea');
- var Gun = SEA.Gun;
- // This is internal func queries public key(s) for alias.
- const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
- // load all public keys associated with the username alias we want to log in with.
- gunRoot.get('~@'+alias).once((data, key) => {
- //rev.off();
- if (!data) {
- // if no user, don't do anything.
- const err = 'No user!'
- Gun.log(err)
- return reject({ err })
- }
- // then figuring out all possible candidates having matching username
- const aliases = []
- let c = 0
- // TODO: how about having real chainable map without callback ?
- Gun.obj.map(data, (at, pub) => {
- if (!pub.slice || '~' !== pub.slice(0, 1)) {
- // TODO: ... this would then be .filter((at, pub))
- return
- }
- ++c
- // grab the account associated with this public key.
- gunRoot.get(pub).once(data => {
- pub = pub.slice(1)
- --c
- if (data){
- aliases.push({ pub, put: data })
- }
- if (!c && (c = -1)) {
- resolve(aliases)
- }
- })
- })
- if (!c) {
- reject({ err: 'Public key does not exist!' })
- }
- })
- })
- module.exports = queryGunAliases
- })(USE, './query');
-
- ;USE(function(module){
- var SEA = USE('./sea');
- var Gun = SEA.Gun;
- const queryGunAliases = USE('./query')
- const parseProps = USE('./parse')
- // This is internal User authentication func.
- const authenticate = async (alias, pass, gunRoot) => {
- // load all public keys associated with the username alias we want to log in with.
- const aliases = (await queryGunAliases(alias, gunRoot))
- .filter(a => !!a.pub && !!a.put)
- // Got any?
- if (!aliases.length) {
- throw { err: 'Public key does not exist!' }
- }
- let err
- // then attempt to log into each one until we find ours!
- // (if two users have the same username AND the same password... that would be bad)
- const users = await Promise.all(aliases.map(async (a, i) => {
- // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
- const auth = parseProps(a.put.auth)
- // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
- // SEA.verify(at.put.auth, pub).then(function(auth){
- try {
- const proof = await SEA.work(pass, auth.s)
- //const props = { pub: pub, proof: proof, at: at }
- // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
- /*
- MARK TO @mhelander : pub vs epub!???
- */
- const salt = auth.salt
- const sea = await SEA.decrypt(auth.ek, proof)
- if (!sea) {
- err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
- return
- }
- // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
- // if we were successful, then that meanswe're logged in!
- const priv = sea.priv
- const epriv = sea.epriv
- const epub = a.put.epub
- // TODO: 'salt' needed?
- err = null
- if(SEA.window){
- var tmp; try{tmp = window.sessionStorage}catch(e){}
- if(tmp && gunRoot._.opt.remember){ // TODO: Bug! This needs to be moved to finalize?
- tmp.alias = alias;
- tmp.tmp = pass;
- }
- }
- return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
- } catch (e) {
- err = 'Failed to decrypt secret!'
- throw { err }
- }
- }))
- var user = Gun.list.map(users, function(acc){ if(acc){ return acc } })
- if (!user) {
- throw { err: err || 'Public key does not exist!' }
- }
- return user
- }
- module.exports = authenticate;
- })(USE, './authenticate');
-
- ;USE(function(module){
- const authsettings = USE('./settings')
- const SEA = USE('./sea');
- const Gun = SEA.Gun;
- //const { scope: seaIndexedDb } = USE('./indexed')
- // This updates sessionStorage & IndexedDB to persist authenticated "session"
- const updateStorage = (proof, key, pin) => async (props) => {
- if (!Gun.obj.has(props, 'alias')) {
- return // No 'alias' - we're done.
- }
- if (authsettings.validity && proof && Gun.obj.has(props, 'iat')) {
- props.proof = proof
- delete props.remember // Not stored if present
-
- const alias = props.alias
- const id = props.alias
- const remember = { alias: alias, pin: pin }
-
- try {
- const signed = await SEA.sign(JSON.stringify(remember), key)
-
- sessionStorage.setItem('user', alias)
- sessionStorage.setItem('remember', signed)
-
- const encrypted = await SEA.encrypt(props, pin)
-
- if (encrypted) {
- const auth = await SEA.sign(encrypted, key)
- await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
- await seaIndexedDb.put(id, { auth: auth })
- }
-
- return props
- } catch (err) {
- throw { err: 'Session persisting failed!' }
- }
- }
-
- // Wiping IndexedDB completely when using random PIN
- await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
- // And remove sessionStorage data
- sessionStorage.removeItem('user')
- sessionStorage.removeItem('remember')
-
- return props
- }
- module.exports = updateStorage
- })(USE, './update');
-
- ;USE(function(module){
- const SEA = USE('./sea');
- const Gun = SEA.Gun;
- const Buffer = USE('./buffer')
- const authsettings = USE('./settings')
- const updateStorage = USE('./update')
- // This internal func persists User authentication if so configured
- const authPersist = async (user, proof, opts) => {
- // opts = { pin: 'string' }
- // no opts.pin then uses random PIN
- // How this works:
- // called when app bootstraps, with wanted options
- // IF authsettings.validity === 0 THEN no remember-me, ever
- // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
- const pin = Buffer.from(
- (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10),
- 'utf8'
- ).toString('base64')
-
- const alias = user.alias
- const exp = authsettings.validity // seconds // @mhelander what is `exp`???
-
- if (proof && alias && exp) {
- const iat = Math.ceil(Date.now() / 1000) // seconds
- const remember = Gun.obj.has(opts, 'pin') || undefined // for hook - not stored
- const props = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
- const pub = user.pub
- const epub = user.epub
- const priv = user.sea.priv
- const epriv = user.sea.epriv
- const key = { pub: pub, priv: priv, epub: epub, epriv: epriv }
- if (props instanceof Promise) {
- const asyncProps = await props.then()
- return await updateStorage(proof, key, pin)(asyncProps)
- }
- return await updateStorage(proof, key, pin)(props)
- }
- return await updateStorage()({ alias: 'delete' })
- }
- module.exports = authPersist
- })(USE, './persist');
-
- ;USE(function(module){
- const authPersist = USE('./persist')
- // This internal func finalizes User authentication
- const finalizeLogin = async (alias, key, gunRoot, opts) => {
- const user = gunRoot._.user
- // add our credentials in-memory only to our root gun instance
- var tmp = user._.tag;
- var opt = user._.opt;
- user._ = gunRoot.get('~'+key.pub)._;
- user._.opt = opt;
- var tags = user._.tag;
- /*Object.values && Object.values(tmp).forEach(function(tag){
- // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
- var t = tags[tag.tag];
- console.log("hm??", tag, t);
- if(!t){
- tags[tag.tag] = tag;
- return;
- }
- if(tag.last){
- tag.last.to = t.to;
- t.last = tag.last = t.last || tag.last;
- }
- t.to = tag.to;
- })*/
- //user._.tag = tmp || user._.tag;
- // so that way we can use the credentials to encrypt/decrypt data
- // that is input/output through gun (see below)
- const pub = key.pub
- const priv = key.priv
- const epub = key.epub
- const epriv = key.epriv
- user._.is = user.is = {alias: alias, pub: pub};
- Object.assign(user._, { alias: alias, pub: pub, epub: epub, sea: { pub: pub, priv: priv, epub: epub, epriv: epriv } })
- //console.log("authorized", user._);
- // persist authentication
- //await authPersist(user._, key.proof, opts) // temporarily disabled
- // emit an auth event, useful for page redirects and stuff.
- try {
- gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
- //user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
- } catch (e) {
- console.log('Your \'auth\' callback crashed with:', e)
- }
- // returns success with the user data credentials.
- return user._
- }
- module.exports = finalizeLogin
- })(USE, './login');
-
- ;USE(function(module){
- const Buffer = USE('./buffer')
- const authsettings = USE('./settings')
- //const { scope: seaIndexedDb } = USE('./indexed')
- const queryGunAliases = USE('./query')
- const parseProps = USE('./parse')
- const updateStorage = USE('./update')
- const SEA = USE('./sea')
- const Gun = SEA.Gun;
- const finalizeLogin = USE('./login')
-
- // This internal func recalls persisted User authentication if so configured
- const authRecall = async (gunRoot, authprops) => {
- // window.sessionStorage only holds signed { alias, pin } !!!
- const remember = authprops || sessionStorage.getItem('remember')
- const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn?
- const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64')
- // Checks for existing proof, matching alias and expiration:
- const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => {
- if (!!proof && alias === aLias) {
- const checkNotExpired = (args) => {
- if (Math.floor(Date.now() / 1000) < (iat + args.exp)) {
- // No way hook to update 'iat'
- return Object.assign(args, { iat: iat, proof: proof })
- } else {
- Gun.log('Authentication expired!')
- }
- }
- // We're not gonna give proof to hook!
- const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
- return ((hooked instanceof Promise)
- && await hooked.then(checkNotExpired)) || checkNotExpired(hooked)
- }
- }
- const readAndDecrypt = async (data, pub, key) =>
- parseProps(await SEA.decrypt(await SEA.verify(data, pub), key))
-
- // Already authenticated?
- if (gunRoot._.user
- && Gun.obj.has(gunRoot._.user._, 'pub')
- && Gun.obj.has(gunRoot._.user._, 'sea')) {
- return gunRoot._.user._ // Yes, we're done here.
- }
- // No, got persisted 'alias'?
- if (!alias) {
- throw { err: 'No authentication session found!' }
- }
- // Yes, got persisted 'remember'?
- if (!remember) {
- throw { // And return proof if for matching alias
- err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity
- && 'Missing PIN and alias!') || 'No authentication session found!'
- }
- }
- // Yes, let's get (all?) matching aliases
- const aliases = (await queryGunAliases(alias, gunRoot))
- .filter(({ pub } = {}) => !!pub)
- // Got any?
- if (!aliases.length) {
- throw { err: 'Public key does not exist!' }
- }
- let err
- // Yes, then attempt to log into each one until we find ours!
- // (if two users have the same username AND the same password... that would be bad)
- const [ { key, at, proof, pin: newPin } = {} ] = await Promise
- .all(aliases.filter(({ at: { put } = {} }) => !!put)
- .map(async ({ at: at, pub: pub }) => {
- const readStorageData = async (args) => {
- const props = args || parseProps(await SEA.verify(remember, pub, true))
- let pin = props.pin
- let aLias = props.alias
-
- const data = (!pin && alias === aLias)
- // No PIN, let's try short-term proof if for matching alias
- ? await checkRememberData(props)
- // Got PIN so get IndexedDB secret if signature is ok
- : await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin))
- pin = pin || data.pin
- delete data.pin
- return { pin: pin, data: data }
- }
- // got pub, try auth with pin & alias :: or unwrap Storage data...
- const __gky20 = await readStorageData(pin && { pin, alias })
- const data = __gky20.data
- const newPin = __gky20.pin
- const proof = data.proof
-
- if (!proof) {
- if (!data) {
- err = 'No valid authentication session found!'
- return
- }
- try { // Wipes IndexedDB silently
- await updateStorage()(data)
- } catch (e) {} //eslint-disable-line no-empty
- err = 'Expired session!'
- return
- }
-
- try { // auth parsing or decryption fails or returns empty - silently done
- const auth= at.put.auth.auth
- const sea = await SEA.decrypt(auth, proof)
- if (!sea) {
- err = 'Failed to decrypt private key!'
- return
- }
- const priv = sea.priv
- const epriv = sea.epriv
- const epub = at.put.epub
- // Success! we've found our private data!
- err = null
- return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } }
- } catch (e) {
- err = 'Failed to decrypt private key!'
- return
- }
- }).filter((props) => !!props))
-
- if (!key) {
- throw { err: err || 'Public key does not exist!' }
- }
-
- // now we have AES decrypted the private key,
- // if we were successful, then that means we're logged in!
- try {
- await updateStorage(proof, key, newPin || pin)(key)
-
- const user = Object.assign(key, { at: at, proof: proof })
- const pIN = newPin || pin
-
- const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') }
-
- return await finalizeLogin(alias, user, gunRoot, pinProp)
- } catch (e) { // TODO: right log message ?
- Gun.log('Failed to finalize login with new password!')
- const { err = '' } = e || {}
- throw { err: 'Finalizing new password login failed! Reason: '+err }
- }
- }
- module.exports = authRecall
- })(USE, './recall');
-
- ;USE(function(module){
- const authPersist = USE('./persist')
- const authsettings = USE('./settings')
- //const { scope: seaIndexedDb } = USE('./indexed')
- // This internal func executes logout actions
- const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => {
- var user = gunRoot._.user._ || {};
- [ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key])
- if(user.$){
- delete user.$.is;
- }
- // Let's use default
- gunRoot.user();
- // Removes persisted authentication & CryptoKeys
- try {
- await authPersist({ alias: alias })
- } catch (e) {} //eslint-disable-line no-empty
- return { ok: 0 }
- }
- module.exports = authLeave
- })(USE, './leave');
-
;USE(function(module){
var Gun = USE('./sea').Gun;
Gun.chain.then = function(cb){
@@ -1100,7 +710,7 @@
(at = (user = at.user = gun.chain(new User))._).opt = {};
at.opt.uuid = function(cb){
var id = uuid(), pub = root.user;
- if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id }
+ if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
id = id + '~' + pub + '.';
if(cb && cb.call){ cb(null, id) }
return id;
@@ -1115,22 +725,75 @@
// TODO: This needs to be split into all separate functions.
// Not just everything thrown into 'create'.
- const SEA = USE('./sea')
- const User = USE('./user')
- const authRecall = USE('./recall')
- const authsettings = USE('./settings')
- const authenticate = USE('./authenticate')
- const finalizeLogin = USE('./login')
- const authLeave = USE('./leave')
- const _initial_authsettings = USE('./settings').recall
- const Gun = SEA.Gun;
+ var SEA = USE('./sea');
+ var User = USE('./user');
+ var authsettings = USE('./settings');
+ var Gun = SEA.Gun;
+
+ var noop = function(){};
- var u;
// Well first we have to actually create a user. That is what this function does.
- User.prototype.create = function(username, pass, cb, opt){
- // TODO: Needs to be cleaned up!!!
- const gunRoot = this.back(-1)
- var gun = this, cat = (gun._);
+ User.prototype.create = function(alias, pass, cb, opt){
+ var gun = this, cat = (gun._), root = gun.back(-1);
+ cb = cb || noop;
+ if(cat.ing){
+ cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
+ return gun;
+ }
+ cat.ing = true;
+ opt = opt || {};
+ var act = {}, u;
+ act.a = function(pubs){
+ act.pubs = pubs;
+ if(pubs && !opt.already){
+ // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
+ var ack = {err: Gun.log('User already created!')};
+ cat.ing = false;
+ cb(ack);
+ gun.leave();
+ return;
+ }
+ act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
+ SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
+ }
+ act.b = function(proof){
+ act.proof = proof;
+ SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account.
+ }
+ act.c = function(pair){
+ act.pair = pair || {};
+ // the user's public key doesn't need to be signed. But everything else needs to be signed with it!
+ act.data = {pub: pair.pub};
+ SEA.sign(alias, pair, act.d);
+ }
+ act.d = function(alias){
+ act.data.alias = alias;
+ SEA.sign(act.pair.epub, act.pair, act.e);
+ }
+ act.e = function(epub){
+ act.data.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.f = function(auth){
+ act.data.auth = auth;
+ SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
+ }
+ act.g = function(auth){ var tmp;
+ act.data.auth = auth;
+ root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID.
+ root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list.
+ setTimeout(function(){ // we should be able to delete this now, right?
+ cat.ing = false;
+ cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
+ if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
+ },10);
+ }
+ root.get('~@'+alias).once(act.a);
+ return gun;
+ }
+ // now that we have created a user, we want to authenticate them!
+ User.prototype.auth = function(alias, pass, cb, opt){
+ var gun = this, cat = (gun._), root = gun.back(-1);
cb = cb || function(){};
if(cat.ing){
cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
@@ -1138,160 +801,116 @@
}
cat.ing = true;
opt = opt || {};
- var resolve = function(){}, reject = resolve;
- // Because more than 1 user might have the same username, we treat the alias as a list of those users.
- if(cb){ resolve = reject = cb }
- gunRoot.get('~@'+username).get(async (at, ev) => {
- ev.off()
- if (at.put && !opt.already) {
- // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
- const err = 'User already created!'
- Gun.log(err)
- cat.ing = false;
- gun.leave();
- return reject({ err: err })
+ var pair = (alias.pub || alias.epub)? alias : (pass.pub || pass.epub)? pass : null;
+ var act = {}, u;
+ act.a = function(data){
+ if(!data){ return act.b() }
+ if(!data.pub){
+ var tmp = [];
+ Gun.node.is(data, function(v){ tmp.push(v) })
+ return act.b(tmp);
}
- const salt = Gun.text.random(64)
- // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
- try {
- const proof = await SEA.work(pass, salt)
- // this will take some short amount of time to produce a proof, which slows brute force attacks.
- const pairs = await SEA.pair()
- // now we have generated a brand new ECDSA key pair for the user account.
- const pub = pairs.pub
- const priv = pairs.priv
- const epriv = pairs.epriv
- // the user's public key doesn't need to be signed. But everything else needs to be signed with it!
- const alias = await SEA.sign(username, pairs)
- if(u === alias){ throw SEA.err }
- const epub = await SEA.sign(pairs.epub, pairs)
- if(u === epub){ throw SEA.err }
- // to keep the private key safe, we AES encrypt it with the proof of work!
- const auth = await SEA.encrypt({ priv: priv, epriv: epriv }, proof)
- .then((auth) => // TODO: So signedsalt isn't needed?
- // SEA.sign(salt, pairs).then((signedsalt) =>
- SEA.sign({ek: auth, s: salt}, pairs)
- // )
- ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) })
- const user = { alias: alias, pub: pub, epub: epub, auth: auth }
- const tmp = '~'+pairs.pub;
- // awesome, now we can actually save the user with their public key as their ID.
- try{
-
- gunRoot.get(tmp).put(user)
- }catch(e){console.log(e)}
- // next up, we want to associate the alias with the public key. So we add it to the alias list.
- gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp)))
- // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
- setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it.
- } catch (e) {
- Gun.log('SEA.create failed!')
- cat.ing = false;
- gun.leave();
- reject(e)
+ if(act.name){ return act.f(data) }
+ act.c((act.data = data).auth);
+ }
+ act.b = function(list){
+ var get = (act.list = (act.list||[]).concat(list||[])).shift();
+ if(u === get){
+ if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
+ return act.err('Wrong user or password.')
}
- })
- return gun; // gun chain commands must return gun chains!
- }
- // now that we have created a user, we want to authenticate them!
- User.prototype.auth = function(alias, pass, cb, opt){
- // TODO: Needs to be cleaned up!!!!
- const opts = opt || (typeof cb !== 'function' && cb)
- let pin = opts && opts.pin
- let newpass = opts && opts.newpass
- const gunRoot = this.back(-1)
- cb = typeof cb === 'function' ? cb : () => {}
- newpass = newpass || (opts||{}).change;
- var gun = this, cat = (gun._);
- if(cat.ing){
- cb({err: "User is already being created or authenticated!", wait: true});
- return gun;
+ root.get(get).once(act.a);
}
- cat.ing = true;
-
- const putErr = (msg) => (e) => {
- const { message, err = message || '' } = e
- Gun.log(msg)
- var error = { err: msg+' Reason: '+err }
- return cat.ing = false, gun.leave(), cb(error), gun;
+ act.c = function(auth){
+ if(u === auth){ return act.b() }
+ SEA.work(pass, (act.auth = auth).s, act.d); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
}
-
- var key = (alias.pub || alias.epub)? alias : (pass.pub || pass.epub)? pass : null;
- if(key){
- (async function(){ try {
- alias = (typeof alias === 'string')? alias : null;
- const login = finalizeLogin(alias, key, gunRoot, { pin: pin })
- login.catch(putErr('Finalizing login failed!'))
- return cat.ing = false, cb(await login), gun;
- } catch(e){
- return cat.ing = false, gun.leave(), cb(e), gun;
- }}())
- return gun;
+ act.d = function(proof){
+ if(u === proof){ return act.b() }
+ SEA.decrypt(act.auth.ek, proof, act.e);
}
-
- if (!pass && pin) { (async function(){
- try {
- var r = await authRecall(gunRoot, { alias: alias, pin: pin })
- return cat.ing = false, cb(r), gun;
- } catch (e) {
- var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' }
- return cat.ing = false, gun.leave(), cb(err), gun;
- }}())
- return gun;
+ act.e = function(half){
+ if(u === half){ return act.b() }
+ act.half = half;
+ act.f(act.data);
}
-
- (async function(){ try {
- const keys = await authenticate(alias, pass, gunRoot)
- if (!keys) {
- return putErr('Auth attempt failed!')({ message: 'No keys' })
+ act.f = function(data){
+ if(!data || !data.pub){ return act.b() }
+ var tmp = act.half || {};
+ act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv});
+ }
+ act.g = function(pair){
+ act.pair = pair;
+ var user = (root._).user, at = (user._);
+ var tmp = at.tag;
+ var upt = at.opt;
+ at = user._ = root.get('~'+pair.pub)._;
+ at.opt = upt;
+ // add our credentials in-memory only to our root user instance
+ user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
+ at.sea = act.pair;
+ cat.ing = false;
+ opt.change? act.z() : cb(at);
+ if(SEA.window && ((gun.back('user')._).opt||opt).remember){
+ // TODO: this needs to be modular.
+ var sS = {}; try{sS = window.sessionStorage}catch(e){}
+ sS.recall = true;
+ sS.alias = alias;
+ sS.tmp = pass;
}
- const pub = keys.pub
- const priv = keys.priv
- const epub = keys.epub
- const epriv = keys.epriv
- // we're logged in!
- if (newpass) {
- // password update so encrypt private key using new pwd + salt
- try {
- const salt = Gun.text.random(64);
- const encSigAuth = await SEA.work(newpass, salt)
- .then((key) =>
- SEA.encrypt({ priv: priv, epriv: epriv }, key)
- .then((auth) => SEA.sign({ek: auth, s: salt}, keys))
- )
- const signedEpub = await SEA.sign(epub, keys)
- const signedAlias = await SEA.sign(alias, keys)
- const user = {
- pub: pub,
- alias: signedAlias,
- auth: encSigAuth,
- epub: signedEpub
- }
- // awesome, now we can update the user using public key ID.
- gunRoot.get('~'+user.pub).put(user)
- // then we're done
- const login = finalizeLogin(alias, keys, gunRoot, { pin })
- login.catch(putErr('Failed to finalize login with new password!'))
- return cat.ing = false, cb(await login), gun
- } catch (e) {
- return putErr('Password set attempt failed!')(e)
- }
- } else {
- const login = finalizeLogin(alias, keys, gunRoot, { pin: pin })
- login.catch(putErr('Finalizing login failed!'))
- return cat.ing = false, cb(await login), gun;
+ try{
+ (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do.
+ //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
+ }catch(e){
+ Gun.log("Your 'auth' callback crashed with:", e);
}
- } catch (e) {
- return putErr('Auth attempt failed!')(e)
- } }());
+ }
+ act.z = function(){
+ // password update so encrypt private key using new pwd + salt
+ act.salt = Gun.text.random(64); // pseudo-random
+ 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);
+ }
+ act.x = function(auth){
+ SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
+ }
+ act.w = function(auth){
+ root.get('~'+act.pair.pub).get('auth').put(auth, cb);
+ }
+ act.err = function(e){
+ var ack = {err: Gun.log(e || 'User cannot be found!')};
+ cat.ing = false;
+ cb(ack);
+ }
+ act.plugin = function(name){
+ if(!(act.name = name)){ return act.err() }
+ var tmp = [name];
+ if('~' !== name[0]){
+ tmp[1] = '~'+name;
+ tmp[2] = '~@'+name;
+ }
+ act.b(tmp);
+ }
+ if(pair){
+ act.g(pair);
+ } else
+ if(alias){
+ root.get('~@'+alias).once(act.a);
+ } else
+ if(!alias && !pass){
+ SEA.name(act.plugin);
+ }
return gun;
}
User.prototype.pair = function(){
+ console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!");
var user = this;
if(!user.is){ return false }
return user._.sea;
}
- User.prototype.leave = async function(){
+ User.prototype.leave = function(opt, cb){
var gun = this, user = (gun.back(-1)._).user;
if(user){
delete user.is;
@@ -1299,86 +918,52 @@
delete user._.sea;
}
if(SEA.window){
- var tmp; try{tmp = window.sessionStorage}catch(e){}; tmp = tmp || {};
- delete tmp.alias;
- delete tmp.tmp;
+ var sS = {}; try{sS = window.sessionStorage}catch(e){};
+ delete sS.alias;
+ delete sS.tmp;
+ delete sS.recall;
}
- return await authLeave(this.back(-1))
+ return gun;
}
// If authenticated user wants to delete his/her account, let's support it!
- User.prototype.delete = async function(alias, pass){
- const gunRoot = this.back(-1)
+ User.prototype.delete = async function(alias, pass, cb){
+ var gun = this, root = gun.back(-1), user = gun.back('user');
try {
- const __gky40 = await authenticate(alias, pass, gunRoot)
- const pub = __gky40.pub
- await authLeave(gunRoot, alias)
- // Delete user data
- gunRoot.get('~'+pub).put(null)
- // Wipe user data from memory
- const { user = { _: {} } } = gunRoot._;
- // TODO: is this correct way to 'logout' user from Gun.User ?
- [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
- user._.is = user.is = {}
- gunRoot.user()
- return { ok: 0 } // TODO: proper return codes???
+ user.auth(alias, pass, function(ack){
+ var pub = (user.is||{}).pub;
+ // Delete user data
+ user.map().once(function(){ this.put(null) });
+ // Wipe user data from memory
+ user.leave();
+ (cb || noop)({ok: 0});
+ });
} catch (e) {
- Gun.log('User.delete failed! Error:', e)
- throw e // TODO: proper error codes???
+ Gun.log('User.delete failed! Error:', e);
}
+ return gun;
}
- // If authentication is to be remembered over reloads or browser closing,
- // set validity time in minutes.
- User.prototype.recall = function(setvalidity, options){
- var gun = this;
- const gunRoot = this.back(-1)
-
- let validity
- let opts
-
- var o = setvalidity, tmp;
- if(o && o.sessionStorage){
+ User.prototype.recall = function(opt, cb){
+ var gun = this, root = gun.back(-1), tmp;
+ opt = opt || {};
+ if(opt && opt.sessionStorage){
if(SEA.window){
- try{tmp = window.sessionStorage}catch(e){}
- if(tmp){
- gunRoot._.opt.remember = true;
- if(tmp.alias && tmp.tmp){
- gunRoot.user().auth(tmp.alias, tmp.tmp);
+ var sS = {}; try{sS = window.sessionStorage}catch(e){}
+ if(sS){
+ (root._).opt.remember = true;
+ ((gun.back('user')._).opt||opt).remember = true;
+ if(sS.recall || (sS.alias && sS.tmp)){
+ root.user().auth(sS.alias, sS.tmp, cb);
}
}
}
return gun;
}
-
- if (!Gun.val.is(setvalidity)) {
- opts = setvalidity
- validity = _initial_authsettings.validity
- } else {
- opts = options
- validity = setvalidity * 60 // minutes to seconds
- }
-
- try {
- // opts = { hook: function({ iat, exp, alias, proof }) }
- // iat == Date.now() when issued, exp == seconds to expire from iat
- // How this works:
- // called when app bootstraps, with wanted options
- // IF authsettings.validity === 0 THEN no remember-me, ever
- // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
- authsettings.validity = typeof validity !== 'undefined'
- ? validity : _initial_authsettings.validity
- authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
- ? opts.hook : _initial_authsettings.hook
- // All is good. Should we do something more with actual recalled data?
- (async function(){ await authRecall(gunRoot) }());
- return gun;
- } catch (e) {
- const err = 'No session!'
- Gun.log(err)
- // NOTE! It's fine to resolve recall with reason why not successful
- // instead of rejecting...
- //return { err: (e && e.err) || err }
- return gun;
- }
+ /*
+ TODO: copy mhelander's expiry code back in.
+ Although, we should check with community,
+ should expiry be core or a plugin?
+ */
+ return gun;
}
User.prototype.alive = async function(){
const gunRoot = this.back(-1)
@@ -1404,7 +989,7 @@
User.prototype.grant = function(to, cb){
console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
- gun.back(function(at){ if(at.pub){ return } path += (at.get||'') });
+ gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
(async function(){
var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
sec = await SEA.decrypt(sec, pair);
@@ -1425,7 +1010,7 @@
User.prototype.secret = function(data, cb){
console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
- gun.back(function(at){ if(at.pub){ return } path += (at.get||'') });
+ gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
(async function(){
var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
sec = await SEA.decrypt(sec, pair);
@@ -1555,9 +1140,9 @@
return each.end({err: "Account must match!"});
}
check['user'+soul+key] = 1;
- if(user && (user = user._) && user.sea && pub === user.pub){
+ if(user && user.is && pub === user.is.pub){
//var id = Gun.text.random(3);
- SEA.sign(val, user.sea, function(data){ var rel;
+ SEA.sign(val, (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;
@@ -1590,7 +1175,7 @@
return s;
}
each.any = function(val, key, node, soul, user){ var tmp, pub;
- if(!user || !(user = user._) || !(user = user.sea)){
+ if(!user || !user.is){
if(tmp = relpub(soul)){
check['any'+soul+key] = 1;
SEA.verify(val, pub = tmp, function(data){ var rel;
@@ -1631,20 +1216,19 @@
//});
return;
}
- var pub = tmp;
- if(pub !== user.pub){
+ if((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.pub !== p){ return p }
+ if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}*/
check['any'+soul+key] = 1;
- SEA.sign(val, user, function(data){
+ SEA.sign(val, (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = data;
check['any'+soul+key] = 0;
@@ -1669,6 +1253,7 @@
}
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
}
+ var noop = {};
})(USE, './index');
}());
\ No newline at end of file
diff --git a/sea/authenticate.js b/sea/authenticate.js
deleted file mode 100644
index 91bc0d8e..00000000
--- a/sea/authenticate.js
+++ /dev/null
@@ -1,63 +0,0 @@
-
- var SEA = require('./sea');
- var Gun = SEA.Gun;
- const queryGunAliases = require('./query')
- const parseProps = require('./parse')
- // This is internal User authentication func.
- const authenticate = async (alias, pass, gunRoot) => {
- // load all public keys associated with the username alias we want to log in with.
- const aliases = (await queryGunAliases(alias, gunRoot))
- .filter(a => !!a.pub && !!a.put)
- // Got any?
- if (!aliases.length) {
- throw { err: 'Public key does not exist!' }
- }
- let err
- // then attempt to log into each one until we find ours!
- // (if two users have the same username AND the same password... that would be bad)
- const users = await Promise.all(aliases.map(async (a, i) => {
- // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
- const auth = parseProps(a.put.auth)
- // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
- // SEA.verify(at.put.auth, pub).then(function(auth){
- try {
- const proof = await SEA.work(pass, auth.s)
- //const props = { pub: pub, proof: proof, at: at }
- // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
- /*
- MARK TO @mhelander : pub vs epub!???
- */
- const salt = auth.salt
- const sea = await SEA.decrypt(auth.ek, proof)
- if (!sea) {
- err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
- return
- }
- // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
- // if we were successful, then that meanswe're logged in!
- const priv = sea.priv
- const epriv = sea.epriv
- const epub = a.put.epub
- // TODO: 'salt' needed?
- err = null
- if(SEA.window){
- var tmp = SEA.window.sessionStorage;
- if(tmp && gunRoot._.opt.remember){
- SEA.window.sessionStorage.alias = alias;
- SEA.window.sessionStorage.tmp = pass;
- }
- }
- return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
- } catch (e) {
- err = 'Failed to decrypt secret!'
- throw { err }
- }
- }))
- var user = Gun.list.map(users, function(acc){ if(acc){ return acc } })
- if (!user) {
- throw { err: err || 'Public key does not exist!' }
- }
- return user
- }
- module.exports = authenticate;
-
\ No newline at end of file
diff --git a/sea/buffer.js b/sea/buffer.js
index c17702b7..1854bd1c 100644
--- a/sea/buffer.js
+++ b/sea/buffer.js
@@ -45,7 +45,7 @@
}
return buf
}
- const byteLength = input.byteLength
+ const byteLength = input.byteLength // what is going on here? FOR MARTTI
const length = input.byteLength ? input.byteLength : input.length
if (length) {
let buf
diff --git a/sea/create.js b/sea/create.js
index a6c00cfd..96048918 100644
--- a/sea/create.js
+++ b/sea/create.js
@@ -2,22 +2,75 @@
// TODO: This needs to be split into all separate functions.
// Not just everything thrown into 'create'.
- const SEA = require('./sea')
- const User = require('./user')
- const authRecall = require('./recall')
- const authsettings = require('./settings')
- const authenticate = require('./authenticate')
- const finalizeLogin = require('./login')
- const authLeave = require('./leave')
- const _initial_authsettings = require('./settings').recall
- const Gun = SEA.Gun;
+ var SEA = require('./sea');
+ var User = require('./user');
+ var authsettings = require('./settings');
+ var Gun = SEA.Gun;
+
+ var noop = function(){};
- var u;
// Well first we have to actually create a user. That is what this function does.
- User.prototype.create = function(username, pass, cb, opt){
- // TODO: Needs to be cleaned up!!!
- const gunRoot = this.back(-1)
- var gun = this, cat = (gun._);
+ User.prototype.create = function(alias, pass, cb, opt){
+ var gun = this, cat = (gun._), root = gun.back(-1);
+ cb = cb || noop;
+ if(cat.ing){
+ cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
+ return gun;
+ }
+ cat.ing = true;
+ opt = opt || {};
+ var act = {}, u;
+ act.a = function(pubs){
+ act.pubs = pubs;
+ if(pubs && !opt.already){
+ // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
+ var ack = {err: Gun.log('User already created!')};
+ cat.ing = false;
+ cb(ack);
+ gun.leave();
+ return;
+ }
+ act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
+ SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
+ }
+ act.b = function(proof){
+ act.proof = proof;
+ SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account.
+ }
+ act.c = function(pair){
+ act.pair = pair || {};
+ // the user's public key doesn't need to be signed. But everything else needs to be signed with it!
+ act.data = {pub: pair.pub};
+ SEA.sign(alias, pair, act.d);
+ }
+ act.d = function(alias){
+ act.data.alias = alias;
+ SEA.sign(act.pair.epub, act.pair, act.e);
+ }
+ act.e = function(epub){
+ act.data.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.f = function(auth){
+ act.data.auth = auth;
+ SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
+ }
+ act.g = function(auth){ var tmp;
+ act.data.auth = auth;
+ root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID.
+ root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list.
+ setTimeout(function(){ // we should be able to delete this now, right?
+ cat.ing = false;
+ cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
+ if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
+ },10);
+ }
+ root.get('~@'+alias).once(act.a);
+ return gun;
+ }
+ // now that we have created a user, we want to authenticate them!
+ User.prototype.auth = function(alias, pass, cb, opt){
+ var gun = this, cat = (gun._), root = gun.back(-1);
cb = cb || function(){};
if(cat.ing){
cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
@@ -25,234 +78,169 @@
}
cat.ing = true;
opt = opt || {};
- var resolve = function(){}, reject = resolve;
- // Because more than 1 user might have the same username, we treat the alias as a list of those users.
- if(cb){ resolve = reject = cb }
- gunRoot.get('~@'+username).get(async (at, ev) => {
- ev.off()
- if (at.put && !opt.already) {
- // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
- const err = 'User already created!'
- Gun.log(err)
- cat.ing = false;
- gun.leave();
- return reject({ err: err })
+ var pair = (alias.pub || alias.epub)? alias : (pass.pub || pass.epub)? pass : null;
+ var act = {}, u;
+ act.a = function(data){
+ if(!data){ return act.b() }
+ if(!data.pub){
+ var tmp = [];
+ Gun.node.is(data, function(v){ tmp.push(v) })
+ return act.b(tmp);
}
- const salt = Gun.text.random(64)
- // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
- try {
- const proof = await SEA.work(pass, salt)
- // this will take some short amount of time to produce a proof, which slows brute force attacks.
- const pairs = await SEA.pair()
- // now we have generated a brand new ECDSA key pair for the user account.
- const pub = pairs.pub
- const priv = pairs.priv
- const epriv = pairs.epriv
- // the user's public key doesn't need to be signed. But everything else needs to be signed with it!
- const alias = await SEA.sign(username, pairs)
- if(u === alias){ throw SEA.err }
- const epub = await SEA.sign(pairs.epub, pairs)
- if(u === epub){ throw SEA.err }
- // to keep the private key safe, we AES encrypt it with the proof of work!
- const auth = await SEA.encrypt({ priv: priv, epriv: epriv }, proof)
- .then((auth) => // TODO: So signedsalt isn't needed?
- // SEA.sign(salt, pairs).then((signedsalt) =>
- SEA.sign({ek: auth, s: salt}, pairs)
- // )
- ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) })
- const user = { alias: alias, pub: pub, epub: epub, auth: auth }
- const tmp = '~'+pairs.pub;
- // awesome, now we can actually save the user with their public key as their ID.
- try{
-
- gunRoot.get(tmp).put(user)
- }catch(e){console.log(e)}
- // next up, we want to associate the alias with the public key. So we add it to the alias list.
- gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp)))
- // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
- setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it.
- } catch (e) {
- Gun.log('SEA.create failed!')
- cat.ing = false;
- gun.leave();
- reject(e)
- }
- })
- return gun; // gun chain commands must return gun chains!
- }
- // now that we have created a user, we want to authenticate them!
- User.prototype.auth = function(alias, pass, cb, opt){
- // TODO: Needs to be cleaned up!!!!
- const opts = opt || (typeof cb !== 'function' && cb)
- let pin = opts && opts.pin
- let newpass = opts && opts.newpass
- const gunRoot = this.back(-1)
- cb = typeof cb === 'function' ? cb : () => {}
- newpass = newpass || (opts||{}).change;
- var gun = this, cat = (gun._);
- if(cat.ing){
- cb({err: "User is already being created or authenticated!", wait: true});
- return gun;
+ if(act.name){ return act.f(data) }
+ act.c((act.data = data).auth);
}
- cat.ing = true;
-
- if (!pass && pin) { (async function(){
- try {
- var r = await authRecall(gunRoot, { alias: alias, pin: pin })
- return cat.ing = false, cb(r), gun;
- } catch (e) {
- var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' }
- return cat.ing = false, gun.leave(), cb(err), gun;
- }}())
- return gun;
- }
-
- const putErr = (msg) => (e) => {
- const { message, err = message || '' } = e
- Gun.log(msg)
- var error = { err: msg+' Reason: '+err }
- return cat.ing = false, gun.leave(), cb(error), gun;
- }
-
- (async function(){ try {
- const keys = await authenticate(alias, pass, gunRoot)
- if (!keys) {
- return putErr('Auth attempt failed!')({ message: 'No keys' })
+ act.b = function(list){
+ var get = (act.list = (act.list||[]).concat(list||[])).shift();
+ if(u === get){
+ if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
+ return act.err('Wrong user or password.')
}
- const pub = keys.pub
- const priv = keys.priv
- const epub = keys.epub
- const epriv = keys.epriv
- // we're logged in!
- if (newpass) {
- // password update so encrypt private key using new pwd + salt
- try {
- const salt = Gun.text.random(64);
- const encSigAuth = await SEA.work(newpass, salt)
- .then((key) =>
- SEA.encrypt({ priv: priv, epriv: epriv }, key)
- .then((auth) => SEA.sign({ek: auth, s: salt}, keys))
- )
- const signedEpub = await SEA.sign(epub, keys)
- const signedAlias = await SEA.sign(alias, keys)
- const user = {
- pub: pub,
- alias: signedAlias,
- auth: encSigAuth,
- epub: signedEpub
- }
- // awesome, now we can update the user using public key ID.
- gunRoot.get('~'+user.pub).put(user)
- // then we're done
- const login = finalizeLogin(alias, keys, gunRoot, { pin })
- login.catch(putErr('Failed to finalize login with new password!'))
- return cat.ing = false, cb(await login), gun
- } catch (e) {
- return putErr('Password set attempt failed!')(e)
- }
- } else {
- const login = finalizeLogin(alias, keys, gunRoot, { pin: pin })
- login.catch(putErr('Finalizing login failed!'))
- return cat.ing = false, cb(await login), gun;
+ root.get(get).once(act.a);
+ }
+ act.c = function(auth){
+ if(u === auth){ return act.b() }
+ SEA.work(pass, (act.auth = auth).s, act.d); // 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){
+ if(u === proof){ return act.b() }
+ SEA.decrypt(act.auth.ek, proof, act.e);
+ }
+ act.e = function(half){
+ if(u === half){ return act.b() }
+ act.half = half;
+ act.f(act.data);
+ }
+ act.f = function(data){
+ if(!data || !data.pub){ return act.b() }
+ var tmp = act.half || {};
+ act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv});
+ }
+ act.g = function(pair){
+ act.pair = pair;
+ var user = (root._).user, at = (user._);
+ var tmp = at.tag;
+ var upt = at.opt;
+ at = user._ = root.get('~'+pair.pub)._;
+ at.opt = upt;
+ // add our credentials in-memory only to our root user instance
+ user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
+ at.sea = act.pair;
+ cat.ing = false;
+ opt.change? act.z() : cb(at);
+ if(SEA.window && ((gun.back('user')._).opt||opt).remember){
+ // TODO: this needs to be modular.
+ var sS = {}; try{sS = window.sessionStorage}catch(e){}
+ sS.recall = true;
+ sS.alias = alias;
+ sS.tmp = pass;
}
- } catch (e) {
- return putErr('Auth attempt failed!')(e)
- } }());
+ try{
+ (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do.
+ //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
+ }catch(e){
+ Gun.log("Your 'auth' callback crashed with:", e);
+ }
+ }
+ act.z = function(){
+ // password update so encrypt private key using new pwd + salt
+ act.salt = Gun.text.random(64); // pseudo-random
+ 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);
+ }
+ act.x = function(auth){
+ SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
+ }
+ act.w = function(auth){
+ root.get('~'+act.pair.pub).get('auth').put(auth, cb);
+ }
+ act.err = function(e){
+ var ack = {err: Gun.log(e || 'User cannot be found!')};
+ cat.ing = false;
+ cb(ack);
+ }
+ act.plugin = function(name){
+ if(!(act.name = name)){ return act.err() }
+ var tmp = [name];
+ if('~' !== name[0]){
+ tmp[1] = '~'+name;
+ tmp[2] = '~@'+name;
+ }
+ act.b(tmp);
+ }
+ if(pair){
+ act.g(pair);
+ } else
+ if(alias){
+ root.get('~@'+alias).once(act.a);
+ } else
+ if(!alias && !pass){
+ SEA.name(act.plugin);
+ }
return gun;
}
User.prototype.pair = function(){
+ console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!");
var user = this;
if(!user.is){ return false }
return user._.sea;
}
- User.prototype.leave = async function(){
+ User.prototype.leave = function(opt, cb){
var gun = this, user = (gun.back(-1)._).user;
if(user){
delete user.is;
delete user._.is;
delete user._.sea;
}
- if(typeof window !== 'undefined'){
- var tmp = window.sessionStorage;
- delete tmp.alias;
- delete tmp.tmp;
+ if(SEA.window){
+ var sS = {}; try{sS = window.sessionStorage}catch(e){};
+ delete sS.alias;
+ delete sS.tmp;
+ delete sS.recall;
}
- return await authLeave(this.back(-1))
+ return gun;
}
// If authenticated user wants to delete his/her account, let's support it!
- User.prototype.delete = async function(alias, pass){
- const gunRoot = this.back(-1)
+ User.prototype.delete = async function(alias, pass, cb){
+ var gun = this, root = gun.back(-1), user = gun.back('user');
try {
- const __gky40 = await authenticate(alias, pass, gunRoot)
- const pub = __gky40.pub
- await authLeave(gunRoot, alias)
- // Delete user data
- gunRoot.get('~'+pub).put(null)
- // Wipe user data from memory
- const { user = { _: {} } } = gunRoot._;
- // TODO: is this correct way to 'logout' user from Gun.User ?
- [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
- user._.is = user.is = {}
- gunRoot.user()
- return { ok: 0 } // TODO: proper return codes???
+ user.auth(alias, pass, function(ack){
+ var pub = (user.is||{}).pub;
+ // Delete user data
+ user.map().once(function(){ this.put(null) });
+ // Wipe user data from memory
+ user.leave();
+ (cb || noop)({ok: 0});
+ });
} catch (e) {
- Gun.log('User.delete failed! Error:', e)
- throw e // TODO: proper error codes???
+ Gun.log('User.delete failed! Error:', e);
}
+ return gun;
}
- // If authentication is to be remembered over reloads or browser closing,
- // set validity time in minutes.
- User.prototype.recall = function(setvalidity, options){
- var gun = this;
- const gunRoot = this.back(-1)
-
- let validity
- let opts
-
- var o = setvalidity;
- if(o && o.sessionStorage){
- if(typeof window !== 'undefined'){
- var tmp = window.sessionStorage;
- if(tmp){
- gunRoot._.opt.remember = true;
- if(tmp.alias && tmp.tmp){
- gunRoot.user().auth(tmp.alias, tmp.tmp);
+ User.prototype.recall = function(opt, cb){
+ var gun = this, root = gun.back(-1), tmp;
+ opt = opt || {};
+ if(opt && opt.sessionStorage){
+ if(SEA.window){
+ var sS = {}; try{sS = window.sessionStorage}catch(e){}
+ if(sS){
+ (root._).opt.remember = true;
+ ((gun.back('user')._).opt||opt).remember = true;
+ if(sS.recall || (sS.alias && sS.tmp)){
+ root.user().auth(sS.alias, sS.tmp, cb);
}
}
}
return gun;
}
-
- if (!Gun.val.is(setvalidity)) {
- opts = setvalidity
- validity = _initial_authsettings.validity
- } else {
- opts = options
- validity = setvalidity * 60 // minutes to seconds
- }
-
- try {
- // opts = { hook: function({ iat, exp, alias, proof }) }
- // iat == Date.now() when issued, exp == seconds to expire from iat
- // How this works:
- // called when app bootstraps, with wanted options
- // IF authsettings.validity === 0 THEN no remember-me, ever
- // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
- authsettings.validity = typeof validity !== 'undefined'
- ? validity : _initial_authsettings.validity
- authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
- ? opts.hook : _initial_authsettings.hook
- // All is good. Should we do something more with actual recalled data?
- (async function(){ await authRecall(gunRoot) }());
- return gun;
- } catch (e) {
- const err = 'No session!'
- Gun.log(err)
- // NOTE! It's fine to resolve recall with reason why not successful
- // instead of rejecting...
- //return { err: (e && e.err) || err }
- return gun;
- }
+ /*
+ TODO: copy mhelander's expiry code back in.
+ Although, we should check with community,
+ should expiry be core or a plugin?
+ */
+ return gun;
}
User.prototype.alive = async function(){
const gunRoot = this.back(-1)
@@ -278,7 +266,7 @@
User.prototype.grant = function(to, cb){
console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
- gun.back(function(at){ if(at.pub){ return } path += (at.get||'') });
+ gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
(async function(){
var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
sec = await SEA.decrypt(sec, pair);
@@ -299,7 +287,7 @@
User.prototype.secret = function(data, cb){
console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
- gun.back(function(at){ if(at.pub){ return } path += (at.get||'') });
+ gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
(async function(){
var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
sec = await SEA.decrypt(sec, pair);
diff --git a/sea/decrypt.js b/sea/decrypt.js
index d82a7f9f..94b5cdf5 100644
--- a/sea/decrypt.js
+++ b/sea/decrypt.js
@@ -6,8 +6,12 @@
var parse = require('./parse');
SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
- var opt = opt || {};
- const key = pair.epriv || pair;
+ opt = opt || {};
+ var key = (pair||opt).epriv || pair;
+ if(!key){
+ pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
+ key = pair.epriv || pair;
+ }
const json = parse(data)
const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
.then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible...
diff --git a/sea/encrypt.js b/sea/encrypt.js
index 6816efb3..c03a037b 100644
--- a/sea/encrypt.js
+++ b/sea/encrypt.js
@@ -5,8 +5,12 @@
var aeskey = require('./aeskey');
SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
- var opt = opt || {};
- const key = pair.epriv || pair;
+ opt = opt || {};
+ var key = (pair||opt).epriv || pair;
+ 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)
diff --git a/sea/index.js b/sea/index.js
index 80a5d26f..a70f45e5 100644
--- a/sea/index.js
+++ b/sea/index.js
@@ -62,7 +62,7 @@
// if there is a request to read data from us, then...
var soul = msg.get['#'];
if(soul){ // for now, only allow direct IDs to be read.
- if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
+ if(soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
return to.next(msg); // yes.
} else
@@ -111,9 +111,9 @@
return each.end({err: "Account must match!"});
}
check['user'+soul+key] = 1;
- if(user && (user = user._) && user.sea && pub === user.pub){
+ if(user && user.is && pub === user.is.pub){
//var id = Gun.text.random(3);
- SEA.sign(val, user.sea, function(data){ var rel;
+ SEA.sign(val, (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;
@@ -146,7 +146,7 @@
return s;
}
each.any = function(val, key, node, soul, user){ var tmp, pub;
- if(!user || !(user = user._) || !(user = user.sea)){
+ if(!user || !user.is){
if(tmp = relpub(soul)){
check['any'+soul+key] = 1;
SEA.verify(val, pub = tmp, function(data){ var rel;
@@ -187,20 +187,19 @@
//});
return;
}
- var pub = tmp;
- if(pub !== user.pub){
+ if((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.pub !== p){ return p }
+ if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}*/
check['any'+soul+key] = 1;
- SEA.sign(val, user, function(data){
+ SEA.sign(val, (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = data;
check['any'+soul+key] = 0;
@@ -210,7 +209,7 @@
each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
if(each.err){ return }
if((each.err = ctx.err) || ctx.no){
- console.log('NO!', each.err, msg.put);
+ console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI
return;
}
if(!each.end.ed){ return }
@@ -225,5 +224,6 @@
}
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
}
+ var noop = {};
-
+
\ No newline at end of file
diff --git a/sea/leave.js b/sea/leave.js
deleted file mode 100644
index df497b8f..00000000
--- a/sea/leave.js
+++ /dev/null
@@ -1,21 +0,0 @@
-
- const authPersist = require('./persist')
- const authsettings = require('./settings')
- //const { scope: seaIndexedDb } = require('./indexed')
- // This internal func executes logout actions
- const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => {
- var user = gunRoot._.user._ || {};
- [ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key])
- if(user.$){
- delete user.$.is;
- }
- // Let's use default
- gunRoot.user();
- // Removes persisted authentication & CryptoKeys
- try {
- await authPersist({ alias: alias })
- } catch (e) {} //eslint-disable-line no-empty
- return { ok: 0 }
- }
- module.exports = authLeave
-
\ No newline at end of file
diff --git a/sea/login.js b/sea/login.js
deleted file mode 100644
index 1288aebf..00000000
--- a/sea/login.js
+++ /dev/null
@@ -1,49 +0,0 @@
-
- const authPersist = require('./persist')
- // This internal func finalizes User authentication
- const finalizeLogin = async (alias, key, gunRoot, opts) => {
- const user = gunRoot._.user
- // add our credentials in-memory only to our root gun instance
- var tmp = user._.tag;
- var opt = user._.opt;
- user._ = gunRoot.get('~'+key.pub)._;
- user._.opt = opt;
- var tags = user._.tag;
- /*Object.values && Object.values(tmp).forEach(function(tag){
- // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
- var t = tags[tag.tag];
- console.log("hm??", tag, t);
- if(!t){
- tags[tag.tag] = tag;
- return;
- }
- if(tag.last){
- tag.last.to = t.to;
- t.last = tag.last = t.last || tag.last;
- }
- t.to = tag.to;
- })*/
- //user._.tag = tmp || user._.tag;
- // so that way we can use the credentials to encrypt/decrypt data
- // that is input/output through gun (see below)
- const pub = key.pub
- const priv = key.priv
- const epub = key.epub
- const epriv = key.epriv
- user._.is = user.is = {alias: alias, pub: pub};
- Object.assign(user._, { alias: alias, pub: pub, epub: epub, sea: { pub: pub, priv: priv, epub: epub, epriv: epriv } })
- //console.log("authorized", user._);
- // persist authentication
- //await authPersist(user._, key.proof, opts) // temporarily disabled
- // emit an auth event, useful for page redirects and stuff.
- try {
- gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
- //user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
- } catch (e) {
- console.log('Your \'auth\' callback crashed with:', e)
- }
- // returns success with the user data credentials.
- return user._
- }
- module.exports = finalizeLogin
-
\ No newline at end of file
diff --git a/sea/pair.js b/sea/pair.js
index 9ff4651e..dd02e9d6 100644
--- a/sea/pair.js
+++ b/sea/pair.js
@@ -4,8 +4,18 @@
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)} }
+ return;
+ } catch(e) {
+ console.log(e);
+ SEA.err = e;
+ if(cb){ cb() }
+ return;
+ }});
+
//SEA.pair = async (data, proof, cb) => { try {
- SEA.pair = SEA.pair || (async (cb) => { try {
+ SEA.pair = SEA.pair || (async (cb, opt) => { try {
const ecdhSubtle = shim.ossl || shim.subtle
// First: ECDSA keys for signing/verifying...
diff --git a/sea/persist.js b/sea/persist.js
deleted file mode 100644
index 511decc8..00000000
--- a/sea/persist.js
+++ /dev/null
@@ -1,41 +0,0 @@
-
- const SEA = require('./sea');
- const Gun = SEA.Gun;
- const Buffer = require('./buffer')
- const authsettings = require('./settings')
- const updateStorage = require('./update')
- // This internal func persists User authentication if so configured
- const authPersist = async (user, proof, opts) => {
- // opts = { pin: 'string' }
- // no opts.pin then uses random PIN
- // How this works:
- // called when app bootstraps, with wanted options
- // IF authsettings.validity === 0 THEN no remember-me, ever
- // IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
- const pin = Buffer.from(
- (Gun.obj.has(opts, 'pin') && opts.pin) || Gun.text.random(10),
- 'utf8'
- ).toString('base64')
-
- const alias = user.alias
- const exp = authsettings.validity // seconds // @mhelander what is `exp`???
-
- if (proof && alias && exp) {
- const iat = Math.ceil(Date.now() / 1000) // seconds
- const remember = Gun.obj.has(opts, 'pin') || undefined // for hook - not stored
- const props = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
- const pub = user.pub
- const epub = user.epub
- const priv = user.sea.priv
- const epriv = user.sea.epriv
- const key = { pub: pub, priv: priv, epub: epub, epriv: epriv }
- if (props instanceof Promise) {
- const asyncProps = await props.then()
- return await updateStorage(proof, key, pin)(asyncProps)
- }
- return await updateStorage(proof, key, pin)(props)
- }
- return await updateStorage()({ alias: 'delete' })
- }
- module.exports = authPersist
-
\ No newline at end of file
diff --git a/sea/query.js b/sea/query.js
deleted file mode 100644
index f256c8e0..00000000
--- a/sea/query.js
+++ /dev/null
@@ -1,43 +0,0 @@
-
- var SEA = require('./sea');
- var Gun = SEA.Gun;
- // This is internal func queries public key(s) for alias.
- const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
- // load all public keys associated with the username alias we want to log in with.
- gunRoot.get('~@'+alias).once((data, key) => {
- //rev.off();
- if (!data) {
- // if no user, don't do anything.
- const err = 'No user!'
- Gun.log(err)
- return reject({ err })
- }
- // then figuring out all possible candidates having matching username
- const aliases = []
- let c = 0
- // TODO: how about having real chainable map without callback ?
- Gun.obj.map(data, (at, pub) => {
- if (!pub.slice || '~' !== pub.slice(0, 1)) {
- // TODO: ... this would then be .filter((at, pub))
- return
- }
- ++c
- // grab the account associated with this public key.
- gunRoot.get(pub).once(data => {
- pub = pub.slice(1)
- --c
- if (data){
- aliases.push({ pub, put: data })
- }
- if (!c && (c = -1)) {
- resolve(aliases)
- }
- })
- })
- if (!c) {
- reject({ err: 'Public key does not exist!' })
- }
- })
- })
- module.exports = queryGunAliases
-
\ No newline at end of file
diff --git a/sea/recall.js b/sea/recall.js
deleted file mode 100644
index 998f39de..00000000
--- a/sea/recall.js
+++ /dev/null
@@ -1,141 +0,0 @@
-
- const Buffer = require('./buffer')
- const authsettings = require('./settings')
- //const { scope: seaIndexedDb } = require('./indexed')
- const queryGunAliases = require('./query')
- const parseProps = require('./parse')
- const updateStorage = require('./update')
- const SEA = require('./sea')
- const Gun = SEA.Gun;
- const finalizeLogin = require('./login')
-
- // This internal func recalls persisted User authentication if so configured
- const authRecall = async (gunRoot, authprops) => {
- // window.sessionStorage only holds signed { alias, pin } !!!
- const remember = authprops || sessionStorage.getItem('remember')
- const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn?
- const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64')
- // Checks for existing proof, matching alias and expiration:
- const checkRememberData = async ({ proof, alias: aLias, iat, exp, remember }) => {
- if (!!proof && alias === aLias) {
- const checkNotExpired = (args) => {
- if (Math.floor(Date.now() / 1000) < (iat + args.exp)) {
- // No way hook to update 'iat'
- return Object.assign(args, { iat: iat, proof: proof })
- } else {
- Gun.log('Authentication expired!')
- }
- }
- // We're not gonna give proof to hook!
- const hooked = authsettings.hook({ alias: alias, iat: iat, exp: exp, remember: remember })
- return ((hooked instanceof Promise)
- && await hooked.then(checkNotExpired)) || checkNotExpired(hooked)
- }
- }
- const readAndDecrypt = async (data, pub, key) =>
- parseProps(await SEA.decrypt(await SEA.verify(data, pub), key))
-
- // Already authenticated?
- if (gunRoot._.user
- && Gun.obj.has(gunRoot._.user._, 'pub')
- && Gun.obj.has(gunRoot._.user._, 'sea')) {
- return gunRoot._.user._ // Yes, we're done here.
- }
- // No, got persisted 'alias'?
- if (!alias) {
- throw { err: 'No authentication session found!' }
- }
- // Yes, got persisted 'remember'?
- if (!remember) {
- throw { // And return proof if for matching alias
- err: (await seaIndexedDb.get(alias, 'auth') && authsettings.validity
- && 'Missing PIN and alias!') || 'No authentication session found!'
- }
- }
- // Yes, let's get (all?) matching aliases
- const aliases = (await queryGunAliases(alias, gunRoot))
- .filter(({ pub } = {}) => !!pub)
- // Got any?
- if (!aliases.length) {
- throw { err: 'Public key does not exist!' }
- }
- let err
- // Yes, then attempt to log into each one until we find ours!
- // (if two users have the same username AND the same password... that would be bad)
- const [ { key, at, proof, pin: newPin } = {} ] = await Promise
- .all(aliases.filter(({ at: { put } = {} }) => !!put)
- .map(async ({ at: at, pub: pub }) => {
- const readStorageData = async (args) => {
- const props = args || parseProps(await SEA.verify(remember, pub, true))
- let pin = props.pin
- let aLias = props.alias
-
- const data = (!pin && alias === aLias)
- // No PIN, let's try short-term proof if for matching alias
- ? await checkRememberData(props)
- // Got PIN so get IndexedDB secret if signature is ok
- : await checkRememberData(await readAndDecrypt(await seaIndexedDb.get(alias, 'auth'), pub, pin))
- pin = pin || data.pin
- delete data.pin
- return { pin: pin, data: data }
- }
- // got pub, try auth with pin & alias :: or unwrap Storage data...
- const __gky20 = await readStorageData(pin && { pin, alias })
- const data = __gky20.data
- const newPin = __gky20.pin
- const proof = data.proof
-
- if (!proof) {
- if (!data) {
- err = 'No valid authentication session found!'
- return
- }
- try { // Wipes IndexedDB silently
- await updateStorage()(data)
- } catch (e) {} //eslint-disable-line no-empty
- err = 'Expired session!'
- return
- }
-
- try { // auth parsing or decryption fails or returns empty - silently done
- const auth= at.put.auth.auth
- const sea = await SEA.decrypt(auth, proof)
- if (!sea) {
- err = 'Failed to decrypt private key!'
- return
- }
- const priv = sea.priv
- const epriv = sea.epriv
- const epub = at.put.epub
- // Success! we've found our private data!
- err = null
- return { proof: proof, at: at, pin: newPin, key: { pub: pub, priv: priv, epriv: epriv, epub: epub } }
- } catch (e) {
- err = 'Failed to decrypt private key!'
- return
- }
- }).filter((props) => !!props))
-
- if (!key) {
- throw { err: err || 'Public key does not exist!' }
- }
-
- // now we have AES decrypted the private key,
- // if we were successful, then that means we're logged in!
- try {
- await updateStorage(proof, key, newPin || pin)(key)
-
- const user = Object.assign(key, { at: at, proof: proof })
- const pIN = newPin || pin
-
- const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') }
-
- return await finalizeLogin(alias, user, gunRoot, pinProp)
- } catch (e) { // TODO: right log message ?
- Gun.log('Failed to finalize login with new password!')
- const { err = '' } = e || {}
- throw { err: 'Finalizing new password login failed! Reason: '+err }
- }
- }
- module.exports = authRecall
-
\ No newline at end of file
diff --git a/sea/root.js b/sea/root.js
index 7095ad34..7b99eb97 100644
--- a/sea/root.js
+++ b/sea/root.js
@@ -7,15 +7,9 @@
if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module;
- var SEA = tmp.SEA || function(){};
+ var SEA = tmp.SEA || {};
- if(SEA.window = module.window){ try{
- SEA.window.SEA = SEA;
- tmp = document.createEvent('CustomEvent');
- tmp.initCustomEvent('extension', false, false, {type: "SEA"});
- (window.dispatchEvent || window.fireEvent)(tmp);
- window.postMessage({type: "SEA"}, '*');
- } catch(e){} }
+ if(SEA.window = module.window){ SEA.window.SEA = SEA }
try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
module.exports = SEA;
diff --git a/sea/sea.js b/sea/sea.js
index 77032638..7eab76a2 100644
--- a/sea/sea.js
+++ b/sea/sea.js
@@ -1,5 +1,6 @@
// Old Code...
+ try{
const __gky10 = require('./shim')
const crypto = __gky10.crypto
const subtle = __gky10.subtle
@@ -18,8 +19,8 @@
const keysToEcdsaJwk = __gky11.jwk
const sha1hash = require('./sha1')
const sha256hash = require('./sha256')
- const recallCryptoKey = require('./remember')
const parseProps = require('./parse')
+ }catch(e){}
// Practical examples about usage found from ./test/common.js
const SEA = require('./root');
@@ -75,7 +76,7 @@
// Cheers! Tell me what you think.
var Gun = (SEA.window||{}).Gun || require('./gun', 1);
Gun.SEA = SEA;
- SEA.Gun = Gun;
+ SEA.GUN = SEA.Gun = Gun;
module.exports = SEA
\ No newline at end of file
diff --git a/sea/secret.js b/sea/secret.js
index 3d38ba18..cf832004 100644
--- a/sea/secret.js
+++ b/sea/secret.js
@@ -2,8 +2,12 @@
var SEA = require('./root');
var shim = require('./shim');
var S = require('./settings');
- // Derive shared secret from other's pub and my epub/epriv
- SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
+ // Derive shared secret from other's pub and my epub/epriv
+ SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try {
+ opt = opt || {};
+ 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
diff --git a/sea/shim.js b/sea/shim.js
index 82dccd8a..28594a8b 100644
--- a/sea/shim.js
+++ b/sea/shim.js
@@ -1,43 +1,38 @@
+ const SEA = require('./root')
const Buffer = require('./buffer')
const api = {Buffer: Buffer}
+ var o = {};
- if (typeof window !== 'undefined') {
- var crypto = window.crypto || window.msCrypto;
- var subtle = crypto.subtle || crypto.webkitSubtle;
- const TextEncoder = window.TextEncoder
- const TextDecoder = window.TextDecoder
+ if(SEA.window){
+ api.crypto = window.crypto || window.msCrypto;
+ api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
+ api.TextEncoder = window.TextEncoder;
+ api.TextDecoder = window.TextDecoder;
+ api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
+ }
+ 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,
TextEncoder,
TextDecoder,
- random: (len) => Buffer.from(crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
- })
- } else {
- 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,
- 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
- //}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;
- }
- }
+ random: (len) => Buffer.from(crypto.randomBytes(len))
+ });
+ //try{
+ const WebCrypto = require('node-webcrypto-ossl', 1)
+ api.ossl = 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;
+ }}
module.exports = api
\ No newline at end of file
diff --git a/sea/sign.js b/sea/sign.js
index 399df922..05688cce 100644
--- a/sea/sign.js
+++ b/sea/sign.js
@@ -4,7 +4,7 @@
var S = require('./settings');
var sha256hash = require('./sha256');
- SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
+ SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
if(data && data.slice
&& 'SEA{' === data.slice(0,4)
&& '"m":' === data.slice(4,8)){
@@ -14,6 +14,10 @@
if(cb){ try{ cb(data) }catch(e){console.log(e)} }
return data;
}
+ opt = opt || {};
+ 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)
diff --git a/sea/update.js b/sea/update.js
deleted file mode 100644
index a312d63f..00000000
--- a/sea/update.js
+++ /dev/null
@@ -1,48 +0,0 @@
-
- const authsettings = require('./settings')
- const SEA = require('./sea');
- const Gun = SEA.Gun;
- //const { scope: seaIndexedDb } = require('./indexed')
- // This updates sessionStorage & IndexedDB to persist authenticated "session"
- const updateStorage = (proof, key, pin) => async (props) => {
- if (!Gun.obj.has(props, 'alias')) {
- return // No 'alias' - we're done.
- }
- if (authsettings.validity && proof && Gun.obj.has(props, 'iat')) {
- props.proof = proof
- delete props.remember // Not stored if present
-
- const alias = props.alias
- const id = props.alias
- const remember = { alias: alias, pin: pin }
-
- try {
- const signed = await SEA.sign(JSON.stringify(remember), key)
-
- sessionStorage.setItem('user', alias)
- sessionStorage.setItem('remember', signed)
-
- const encrypted = await SEA.encrypt(props, pin)
-
- if (encrypted) {
- const auth = await SEA.sign(encrypted, key)
- await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
- await seaIndexedDb.put(id, { auth: auth })
- }
-
- return props
- } catch (err) {
- throw { err: 'Session persisting failed!' }
- }
- }
-
- // Wiping IndexedDB completely when using random PIN
- await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out.
- // And remove sessionStorage data
- sessionStorage.removeItem('user')
- sessionStorage.removeItem('remember')
-
- return props
- }
- module.exports = updateStorage
-
\ No newline at end of file
diff --git a/sea/user.js b/sea/user.js
index 7c0fadcd..daab6da3 100644
--- a/sea/user.js
+++ b/sea/user.js
@@ -19,7 +19,7 @@
(at = (user = at.user = gun.chain(new User))._).opt = {};
at.opt.uuid = function(cb){
var id = uuid(), pub = root.user;
- if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id }
+ if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
id = id + '~' + pub + '.';
if(cb && cb.call){ cb(null, id) }
return id;
diff --git a/sea/verify.js b/sea/verify.js
index 128aa928..1946f6b5 100644
--- a/sea/verify.js
+++ b/sea/verify.js
@@ -6,7 +6,7 @@
var parse = require('./parse');
var u;
- SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
+ SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
const json = parse(data)
if(false === pair){ // don't verify!
const raw = (json !== data)?
@@ -15,6 +15,9 @@
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'])
@@ -27,7 +30,7 @@
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
} catch(e) {
- console.log(e);
+ console.log(e); // mismatched owner FOR MARTTI
SEA.err = e;
if(cb){ cb() }
return;
diff --git a/src/adapters/localStorage.js b/src/adapters/localStorage.js
index 33361ab5..38aac62b 100644
--- a/src/adapters/localStorage.js
+++ b/src/adapters/localStorage.js
@@ -1,10 +1,12 @@
if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs?
-var root, noop = function(){}, u;
-if(typeof window !== 'undefined'){ root = window }
-var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop};
-
+var root, noop = function(){}, store, u;
+try{store = (Gun.window||noop).localStorage}catch(e){}
+if(!store){
+ console.log("Warning: No localStorage exists to persist data to!");
+ store = {setItem: noop, removeItem: noop, getItem: noop};
+}
/*
NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design!
If you update anything here, consider updating the other adapters as well.
diff --git a/src/on.js b/src/on.js
index 4121852e..f9b3998a 100644
--- a/src/on.js
+++ b/src/on.js
@@ -78,19 +78,19 @@ function val(msg, eve, to){
var opt = this.as, cat = opt.at, gun = msg.$, at = gun._, data = at.put || msg.put, link, tmp;
if(tmp = msg.$$){
link = tmp = (msg.$$._);
- if(u === tmp.put){
- return;
+ if(u !== link.put){
+ data = link.put;
}
- data = tmp.put;
}
if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) }
if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack))))
- || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (link||at).ack <= tmp)){
+ || (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (!to && (link||at).ack <= tmp))){
tmp = (eve.wait = {})[at.id] = setTimeout(function(){
val.call({as:opt}, msg, eve, tmp || 1);
}, opt.wait || 99);
return;
}
+ if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) }
eve.rid(msg);
opt.ok.call(gun || opt.$, data, msg.get);
}