From 026b2783118321b9869e74caa7ae9f32e76ab6d8 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Fri, 25 Oct 2019 15:44:07 -0700 Subject: [PATCH 01/28] temporary push for @Dletta to update stats --- examples/docs.html | 69 ++++++++++++++ gun.js | 43 ++++----- gun.min.js | 2 +- lib/meta.js | 4 +- lib/radisk.js | 33 +++---- lib/server.js | 1 + lib/stats.js | 12 +++ lib/store.js | 13 ++- src/adapters/localStorage.js | 3 +- src/adapters/mesh.js | 31 ++++--- src/get.js | 1 - src/put.js | 4 +- src/root.js | 4 - test/panic/livestream.js | 171 +++++++++++++++++++++++++++++++++++ 14 files changed, 317 insertions(+), 74 deletions(-) create mode 100644 examples/docs.html create mode 100644 test/panic/livestream.js diff --git a/examples/docs.html b/examples/docs.html new file mode 100644 index 00000000..73389007 --- /dev/null +++ b/examples/docs.html @@ -0,0 +1,69 @@ + + + + + + + + +Docs + + + + +
+
+

Docs

+

The contemporary tendency in our society is to base our distribution on scarcity, which has vanished, and to compress our abundance into the overfed mouths of the middle and upper classes until they gag with superfluity. If democracy is to have breadth of meaning, it is necessary to adjust this inequity. It is not only moral, but it is also intelligent. We are wasting and degrading human life by clinging to archaic thinking.

+
+
+ + + + + \ No newline at end of file diff --git a/gun.js b/gun.js index cb02e16f..45156557 100644 --- a/gun.js +++ b/gun.js @@ -811,20 +811,16 @@ // Maybe... in case the in-memory key we have is a local write // we still need to trigger a pull/merge from peers. } else { - //var S = +new Date; node = Gun.obj.copy(node); - //console.log(+new Date - S, 'copy node'); } node = Gun.graph.node(node); tmp = (at||empty).ack; - //var S = +new Date; root.on('in', { '@': msg['#'], how: 'mem', put: node, $: gun }); - //console.log(+new Date - S, 'root got send'); //if(0 < tmp){ return } root.on('get', msg); } @@ -1317,7 +1313,6 @@ function use(msg){ var eve = this, as = eve.as, cat = as.at, root = cat.root, gun = msg.$, at = (gun||{})._ || {}, data = msg.put || at.put, tmp; if((tmp = root.now) && eve !== tmp[as.now]){ return eve.to.next(msg) } - //console.log("USE:", cat.id, cat.soul, cat.has, cat.get, msg, root.mum); //if(at.async && msg.root){ return } //if(at.async === 1 && cat.async !== true){ return } //if(root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); @@ -1545,7 +1540,7 @@ as = as.as; if(!msg.$ || !msg.$._){ return } // TODO: Handle if(msg.err){ // TODO: Handle - console.log("Please report this as an issue! Put.any.err"); + Gun.log("Please report this as an issue! Put.any.err"); return; } var at = (msg.$._), data = at.put, opt = as.opt||{}, root, tmp; @@ -1554,7 +1549,7 @@ if(as.ref !== as.$){ tmp = (as.$._).get || at.get; if(!tmp){ // TODO: Handle - console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? + Gun.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? return; } as.data = obj_put({}, tmp, as.data); @@ -1814,7 +1809,7 @@ 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!"); + Gun.log("Warning: No localStorage exists to persist data to!"); store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; } /* @@ -1920,7 +1915,6 @@ data = Gun.state.to(data, has); } //if(!data && !Gun.obj.empty(opt.peers)){ return } // if data not found, don't ack if there are peers. // Hmm, what if we have peers but we are disconnected? - //console.log("lS get", lex, data); root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$});// || root.$}); }; Gun.debug? setTimeout(to,1) : to(); @@ -1976,15 +1970,14 @@ if('[' === tmp){ try{msg = JSON.parse(raw);}catch(e){opt.log('DAM JSON parse error', e)} if(!msg){ return } - //console.log('hear batch length of', msg.length); + LOG && opt.log(+new Date, msg.length, 'in hear batch'); (function go(){ - var S = +new Date; // STATS! + var S; LOG && (S = +new Date); // STATS! var m, c = 100; // hardcoded for now? while(c-- && (m = msg.shift())){ mesh.hear(m, peer); } - //console.log(+new Date - S, 'hear batch'); - (mesh.hear.long || (mesh.hear.long = [])).push(+new Date - S); + LOG && opt.log(S, +new Date - S, 'batch heard'); if(!msg.length){ return } puff(go, 0); }()); @@ -1995,7 +1988,7 @@ }catch(e){return opt.log('DAM JSON parse error', e)} if(!msg){ return } if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } - if(msg.DBG_s){ console.log(+new Date - msg.DBG_s, 'to hear', id) } + if(msg.DBG_s){ opt.log(+new Date - msg.DBG_s, 'to hear', id) } if(dup.check(id)){ return } dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore? if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } @@ -2011,9 +2004,9 @@ } return; } - //var S = +new Date; + var S; LOG && (S = +new Date); root.on('in', msg); - //!msg.nts && console.log(+new Date - S, 'msg', msg['#']); + LOG && !msg.nts && opt.log(S, +new Date - S, 'msg', msg['#']); return; } } @@ -2027,7 +2020,7 @@ if(this.to){ this.to.next(msg) } // compatible with middleware adapters. if(!msg){ return false } var id, hash, tmp, raw; - //var S = +new Date; //msg.DBG_s = msg.DBG_s || +new Date; + var S; LOG && (S = +new Date); //msg.DBG_s = msg.DBG_s || +new Date; var meta = msg._||(msg._=function(){}); if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } @@ -2041,15 +2034,15 @@ } } } - //console.log(+new Date - S, 'mesh say prep'); + LOG && opt.log(S, +new Date - S, 'say prep'); dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more! if(!peer){ peer = (tmp = dup.s[msg['@']]) && (tmp = tmp.it) && (tmp = tmp._) && (tmp = tmp.via) } if(!peer && mesh.way){ return mesh.way(msg) } if(!peer || !peer.id){ message = msg; if(!Type.obj.is(peer || opt.peers)){ return false } - //var S = +new Date; + var S; LOG && (S = +new Date); Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. - //console.log(+new Date - S, 'mesh say loop'); + LOG && opt.log(S, +new Date - S, 'say loop'); return; } if(!peer.wire && mesh.wire){ mesh.wire(peer) } @@ -2073,10 +2066,10 @@ peer.batch = peer.tail = null; if(!tmp){ return } if(!tmp.length){ return } // if(3 > tmp.length){ return } // TODO: ^ - //var S = +new Date; + var S; LOG && (S = +new Date); try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp)); }catch(e){return opt.log('DAM JSON stringify error', e)} - //console.log(+new Date - S, 'mesh flush', tmp.length); + LOG && opt.log(S, +new Date - S, 'say stringify', tmp.length); if(!tmp){ return } send(tmp, peer); } @@ -2086,14 +2079,14 @@ // for now - find better place later. function send(raw, peer){ try{ var wire = peer.wire; - //var S = +new Date; + var S; LOG && (S = +new Date); if(peer.say){ peer.say(raw); } else if(wire.send){ wire.send(raw); } - //console.log(+new Date - S, 'wire send', raw.length); + LOG && opt.log(S, +new Date - S, 'wire send', raw.length); mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! }catch(e){ (peer.queue = peer.queue || []).push(raw); @@ -2143,6 +2136,7 @@ root.on('bye', peer); var tmp = +(new Date); tmp = (tmp - (peer.met||tmp)); mesh.bye.time = ((mesh.bye.time || tmp) + tmp) / 2; + LOG = console.LOG; // dirty place to cheaply update LOG settings over time. } mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } mesh.hear['?'] = function(msg, peer){ @@ -2223,6 +2217,7 @@ }()); var empty = {}, ok = true, u; + var LOG = console.LOG; try{ module.exports = Mesh }catch(e){} diff --git a/gun.min.js b/gun.min.js index 4b43f0e5..1eb09d40 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1 +1 @@ -!function(){var t;"undefined"!=typeof window&&(t=window),"undefined"!=typeof global&&(t=global);var j=(t=t||{}).console||{log:function(){}};function $(o,t){return t?require(o):o.slice?$[e(o)]:function(t,n){o(t={exports:{}}),$[e(n)]=t.exports};function e(t){return t.split("/").slice(-1).toString().replace(".js","")}}if("undefined"!=typeof module)var o=module;$(function(t){var n,c,l={};function o(t,n){v(this,n)&&void 0!==this[n]||(this[n]=t)}function e(t,n){var o=this.n;if(!o||!(n===o||g(o)&&v(o,n)))return!!n||void 0}function p(t,n){2!==arguments.length?(p.r=p.r||[]).push(t):(p.r=p.r||{})[t]=n}l.fn={is:function(t){return!!t&&"function"==typeof t}},l.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},l.num={is:function(t){return!d(t)&&(0<=t-parseFloat(t)+1||1/0===t||-1/0===t)}},l.text={is:function(t){return"string"==typeof t}},l.text.ify=function(t){return l.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},l.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0"]||n["<"])||e===n["="]&&(o=n["*"]||n[">"]||n["<"],t.slice(0,(o||"").length)===o||e===n["*"]&&(e!==n[">"]&&e!==n["<"]?t>=n[">"]&&t<=n["<"]:e!==n[">"]&&t>=n[">"]||e!==n["<"]&&t<=n["<"])))},l.list={is:function(t){return t instanceof Array}},l.list.slit=Array.prototype.slice,l.list.sort=function(o){return function(t,n){return t&&n?(t=t[o])<(n=n[o])?-1:n",u.drift=0,u.is=function(t,n,o){var e=n&&t&&t[k]&&t[k][u._]||o;if(e)return v(e=e[n])?e:-1/0},u.lex=function(){return u().toString(36).replace(".","")},u.ify=function(t,n,o,e,i){if(!t||!t[k]){if(!i)return;t=a.soul.ify(t,i)}var r=l(t[k],u._);return void 0!==n&&n!==k&&(v(o)&&(r[n]=o),void 0!==e&&(t[n]=e)),t},u.to=function(t,n,o){var e=(t||{})[n];return h(e)&&(e=g(e)),u.ify(o,n,u.is(t,n),e,a.soul(t))},u.map=function(i,r,a){var t=h(t=i||r)?t:null;return i=m(i=i||r)?i:null,t&&!i?(r=v(r)?r:u(),t[k]=t[k]||{},d(t,f,{o:t,s:r}),t):(a=a||h(r)?r:void 0,r=v(r)?r:u(),function(t,n,o,e){if(!i)return f.call({o:o,s:r},t,n),t;i.call(a||this||{},t,n,o,e),p(o,n)&&void 0===o[n]||f.call({o:o,s:r},t,n)})};var c=n.obj,l=c.as,p=c.has,h=c.is,d=c.map,g=c.copy,v=n.num.is,m=n.fn.is,k=a._;t.exports=u})($,"./state"),$(function(t){var a=$("./type"),u=$("./val"),s=$("./node"),i={};function r(t,n){if(!t||n!==s.soul(t)||!s.is(t,this.fn,this.as))return!0;this.cb&&(o.n=t,o.as=this.as,this.cb.call(o.as,t,n,o))}function o(t){t&&s.is(o.n,t,o.as)}function f(t,n){var o;return(o=function(t,n){var o,e=t.seen,i=e.length;for(;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}(t,n))?o:(n.env=t,n.soul=c,s.ify(n.obj,e,n)&&(n.link=n.link||u.link.ify(s.soul(n.node)),n.obj!==t.shell&&(t.graph[u.link.is(n.link)]=n.node)),n)}function e(t,n,o){var e,i,r=this,a=r.env;if(s._===n&&g(t,u.link._))return o._;if(e=l(t,n,o,r,a)){if(n||(r.node=r.node||o||{},g(t,s._)&&s.soul(t)&&(r.node._=b(t._)),r.node=s.soul.ify(r.node,u.link.is(r.link)),r.link=r.link||u.link.ify(s.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,o,r),g(o,n))){if(void 0===(t=o[n]))return void d(o,n);if(!(e=l(t,n,o,r,a)))return}if(!n)return r.node;if(!0===e)return t;if((i=f(a,{obj:t,path:r.path.concat(n)})).node)return i.link}}function c(t){var n=this,o=u.link.is(n.link),e=n.env.graph;n.link=n.link||u.link.ify(t),n.link[u.link._]=t,n.node&&n.node[s._]&&(n.node[s._][u.link._]=t),g(e,o)&&(e[t]=e[o],d(e,o))}function l(t,n,o,e,i){var r;return!!u.is(t)||(h(t)?1:(r=i.invalid)?l(t=r.call(i.as||{},t,n,o),n,o,e,i):(i.err="Invalid value at '"+e.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}function p(t,n){var o,e;if(s._!==n)(o=u.link.is(t))?(e=this.opt.seen[o])?this.obj[n]=e:this.obj[n]=this.opt.seen[o]=i.to(this.graph,o,this.opt):this.obj[n]=t;else{if(v(t,u.link._))return;this.obj[n]=b(t)}}i.is=function(t,n,o,e){return!(!t||!h(t)||v(t)||k(t,r,{cb:n,fn:o,as:e}))},i.ify=function(t,n,o){var e={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(e.link=u.link.ify(n.soul)),n.shell=(o||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||o,f(n,e),n.root=e.node,n.graph},i.node=function(t){var n=s.soul(t);if(n)return m({},n,t)},i.to=function(t,n,o){if(t){var e={};return o=o||{seen:{}},k(t[n],p,{obj:e,graph:t,opt:o}),e}};a.fn.is;var n=a.obj,h=n.is,d=n.del,g=n.has,v=n.empty,m=n.put,k=n.map,b=n.copy;t.exports=i})($,"./graph"),$(function(t){$("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet.",lack:!0}),i.off()},(this.opt||{}).lack||9e3),o}}})($,"./ask"),$(function(t){var r=$("./type");var a=r.time.is;t.exports=function(e){var i={s:{}};return e=e||{max:1e3,age:9e3},i.check=function(t){var n;return!!(n=i.s[t])&&(n.pass?n.pass=!1:i.track(t))},i.track=function(t,n){var o=i.s[t]||(i.s[t]={});return o.was=a(),n&&(o.pass=!0),i.to||(i.to=setTimeout(function(){var o=a();r.obj.map(i.s,function(t,n){t&&e.age>o-t.was||r.obj.del(i.s,n)}),i.to=null},e.age+9)),o},i}})($,"./dup"),$(function(t){function c(t){return t instanceof c?(this._={gun:this,$:this}).$:this instanceof c?c.create(this._={gun:this,$:this,opt:t}):new c(t)}c.is=function(t){return t instanceof c||t&&t._&&t===t._.$||!1},c.version=.9,(c.chain=c.prototype).toJSON=function(){};var n=$("./type");function a(t){var n,o,e=this.as,i=e.at||e,r=i.$;(o=t["#"])||(o=t["#"]=h(9)),(n=i.dup).check(o)?e.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(o),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),e.out||(t.out=a,i.on("out",t)))}function i(t,n,o,e){var i=this,r=c.state.is(o,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=i.graph[e]||x,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[e]=c.state.to(o,n,i.put[e]),(i.diff||(i.diff={}))[e]=c.state.to(o,n,i.diff[e]),i.souls[e]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var o=this,e=o.$._,i=(e.next||x)[n];if(!i){if(!(e.opt||x).super)return void(o.souls[n]=!1);i=o.$.get(n)._}var r=o.map[n]={put:t,get:n,$:i.$},a={ctx:o,msg:r};o.async=!!e.tag.node,o.ack&&(r["@"]=o.ack),k(t,u,a),o.async&&(o.and||e.on("node",function(t){this.to.next(t),t===o.map[t.get]&&(o.souls[t.get]=!1,k(t.put,s,t),k(o.souls,function(t){if(t)return t})||o.c||(o.c=1,this.off(),k(o.map,f,o)))}),o.and=!0,e.on("node",r))}function u(t,n){var o=this.ctx,e=o.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;e[r]=c.state.to(a,n,e[r]),o.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var o=this.put,e=this.$._;e.put=c.state.to(o,n,e.put)}function f(t,n){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}n.obj.to(n,c),c.HAM=$("./HAM"),c.val=$("./val"),c.node=$("./node"),c.state=$("./state"),c.graph=$("./graph"),c.on=$("./onto"),c.ask=$("./ask"),c.dup=$("./dup"),c.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||c.on,t.ask=t.ask||c.ask,t.dup=t.dup||c.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),c.on("create",t),t.on("create",t)),t.once=1,n},c.on.put=function(t,n){var o=n._,e={$:n,graph:o.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:o,stop:{}};if(c.graph.is(t.put,null,i,e)||(e.err="Error: Invalid graph!"),e.err)return o.on("in",{"@":t["#"],err:c.log(e.err)});k(e.put,r,e),e.async||k(e.map,f,e),void 0!==e.defer&&setTimeout(function(){c.on.put(t,n)},e.defer-e.machine),e.diff&&o.on("put",m(t,{put:e.diff}))},c.on.get=function(t,n){var o=n._,e=t.get,i=e[y],r=o.graph[i],a=e[_],u=(o.next||(o.next={}))[i];if(!r)return o.on("get",t);if(a){if("string"!=typeof a||!v(r,a))return o.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||x).ack,o.on("in",{"@":t["#"],how:"mem",put:r,$:n}),o.on("get",t)},c.chain.opt=function(t){t=t||{};var n=this._,o=t.peers||t;return g(t)||(t={}),g(n.opt)||(n.opt=t),p(o)&&(o=[o]),e(o)&&(o=k(o,function(t,n,o){(n={}).id=n.url=t,o(t,n)}),g(n.opt.peers)||(n.opt.peers={}),n.opt.peers=m(o,n.opt.peers)),n.opt.peers=n.opt.peers||{},k(t,function t(n,o){!v(this,o)||l.is(n)||d.empty(n)?this[o]=n:n&&n.constructor!==Object&&!e(n)||k(n,t,this[o])},n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return b()+h(12)},this};var e=c.list.is,l=c.text,p=l.is,h=l.random,d=c.obj,g=d.is,v=d.has,m=d.to,k=d.map,b=(d.copy,c.state.lex),y=c.val.link._,_=".",x=(c.node._,c.val.link.is,{});j.only=function(t,n){return j.only.i&&t===j.only.i&&j.only.i++&&(j.log.apply(j,arguments)||n)},(c.log=function(){return c.log.off||j.log.apply(j,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,o){return(o=c.log.once)[t]=o[t]||0,o[t]++||c.log(n)},c.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&((window.GUN=window.Gun=c).window=window);try{void 0!==o&&(o.exports=c)}catch(t){}t.exports=c})($,"./root"),$(function(t){var u=$("./root");u.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var o=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var e=0,i=t.length,r=o;e(r.acks||0)&&this.off(),r.ack&&r.ack(t,this)},r.opt),o=0,e=n.root.now;h.del(n.root,"now");var i=n.root.mum;n.root.mum={},r.ref._.on("out",{$:r.ref,put:r.out=r.env.graph,opt:r.opt,"#":t}),n.root.mum=i?h.to(i,n.root.mum):i,n.root.now=e},r),r.res&&r.res())}function n(t,n){if(t)return!0}function c(r,t,n,a){var u=this,s=f.is(r);!t&&a.path.length&&(u.res||e)(function(){for(var t=a.path,n=u.ref,o=(u.opt,0),e=t.length;o .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var o=this,e=o._,i=e.put;if(0=(e.batch||1e3))return f();i=i||setTimeout(f,e.wait||1)}),a.on("get",function(n){this.to.next(n);var o,e,i,r=n.get;function t(){if(r&&(o=r["#"])){var t=r["."];(e=s[o]||i)&&t&&(e=Gun.state.to(e,t)),a.on("in",{"@":n["#"],put:Gun.graph.node(e),how:"lS",lS:n.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,o,e){s[e]=Gun.state.to(o,n,s[e])},f=function(t){var o;u=0,clearTimeout(i),i=!1;var n=r;r={},t&&(s=t);try{p.setItem(e.prefix,JSON.stringify(s))}catch(t){Gun.log(o=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"),a.on("localStorage:error",{err:o,file:e.prefix,flush:s,retry:f})}(o||Gun.obj.empty(e.peers))&&Gun.obj.map(n,function(t,n){a.on("in",{"@":n,err:o,ok:0})})}}})}})($,"./adapters/localStorage"),$(function(t){var g=$("../type"),v="undefined"!=typeof setImmediate?setImmediate:setTimeout;!function(){g.text.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o<"])&&(r._.to=g.obj.map(e.split(","),l)),r.dam?void((e=u.hear[r.dam])&&e(r,i,a)):void a.on("in",r)}}else{try{r=JSON.parse(t)}catch(t){s.log("DAM JSON parse error",t)}if(!r)return;(function t(){for(var n,o=+new Date,e=100;e--&&(n=r.shift());)u.hear(n,i);(u.hear.long||(u.hear.long=[])).push(+new Date-o),r.length&&v(t,0)})()}}};var c,l=function(t,n,o){o(t,!0)};function p(t){u.say(c,t)}function h(t){var n=t.batch;if(t.batch=t.tail=null,n&&n.length){try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return s.log("DAM JSON stringify error",t)}n&&d(n,t)}}function d(n,o){try{var t=o.wire;o.say?o.say(n):t.send&&t.send(n),u.say.d+=n.length||0,++u.say.c}catch(t){(o.queue=o.queue||[]).push(n)}}u.hear.c=u.hear.d=0,u.say=function(t,n){if(this.to&&this.to.next(t),!t)return!1;var o,e,i,r,a=t._||(t._=function(){});if((o=t["#"])||(o=t["#"]=g.text.random(9)),(e=t["##"])||void 0===t.put||(e=t["##"]=g.obj.hash(t.put)),!(r=a.raw)&&(r=a.raw=u.raw(t),e&&(i=t["@"])&&(f.track(i+e).it=t,i=(f.s[i]||!0).it))){if(e===i["##"])return!1;i["##"]=e}if(f.track(o).it=t,!(n=n||(i=f.s[t["@"]])&&(i=i.it)&&(i=i._)&&(i=i.via))&&u.way)return u.way(t);if(!n||!n.id)return c=t,!!g.obj.is(n||s.peers)&&void g.obj.map(n||s.peers,p);if(!n.wire&&u.wire&&u.wire(n),o!==n.last){if(n.last=o,n===a.via)return!1;if((i=a.to)&&(i[n.url]||i[n.pid]||i[n.id]))return!1;if(n.batch){if(n.tail=(i=n.tail||0)+r.length,n.tail<=s.pack)return void n.batch.push(r);h(n)}n.batch=[],setTimeout(function(){h(n)},s.gap),d(r,n)}},u.say.c=u.say.d=0,function(){u.raw=function(t){if(!t)return"";var n,o=t._||{};if(n=o.raw)return n;if("string"==typeof t)return t;if(!t.dam){var e=0,i=[];g.obj.map(s.peers,function(t){if(i.push(t.url||t.pid||t.id),9<++e)return!0}),1<"]=i.join())}var r=a(t);return o&&(o.raw=r),r};var a=JSON.stringify}(),u.hi=function(n){var t=n.wire||{};n.id?s.peers[n.url||n.id]=n:(t=n.id=n.id||g.text.random(9),u.say({dam:"?"},s.peers[t]=n)),n.met=n.met||+new Date,t.hied||a.on(t.hied="hi",n),t=n.queue,n.queue=[],g.obj.map(t,function(t){d(t,n)})},u.bye=function(t){a.on("bye",t);var n=+new Date;n-=t.met||n,u.bye.time=((u.bye.time||n)+n)/2},u.hear["!"]=function(t,n){s.log("Error:",t.err)},u.hear["?"]=function(t,n){t.pid?n.pid||(n.pid=t.pid):u.say({dam:"?",pid:s.pid,"@":t["#"]},n)},a.on("create",function(t){t.opt.pid=t.opt.pid||g.text.random(9),this.to.next(t),t.on("out",u.say)}),a.on("bye",function(t,n){t=s.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),g.obj.del(s.peers,t.id),t.wire=null});var i={};return a.on("bye",function(t,n){this.to.next(t),(n=t.url)&&(i[n]=!0,setTimeout(function(){delete i[n]},s.lack||9e3))}),a.on("hi",function(o,e){this.to.next(o),(e=o.url)&&i[e]&&(delete i[e],g.obj.map(a.next,function(t,n){(e={})[n]=a.graph[n],u.say({"##":g.obj.hash(e),get:{"#":n}},o)}))}),u}}catch(t){}})($,"./adapters/mesh"),$(function(t){var f=$("../index");f.Mesh=$("./mesh"),f.on("opt",function(t){this.to.next(t);var e=t.opt;if(!t.once&&!1!==e.WebSocket){var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var o=e.WebSocket||n.WebSocket||n.webkitWebSocket||n.mozWebSocket;if(o){e.WebSocket=o;var i=e.mesh=e.mesh||f.Mesh(t);i.wire||e.wire;i.wire=e.wire=u,setTimeout(function(){t.on("out",{dam:"hi"})},1);var r=2e3,a="undefined"!=typeof document&&document}}function u(n){try{if(!n||!n.url)return o&&o(n);var t=n.url.replace("http","ws"),o=n.wire=new e.WebSocket(t);return o.onclose=function(){e.mesh.bye(n),s(n)},o.onerror=function(t){s(n)},o.onopen=function(){e.mesh.hi(n)},o.onmessage=function(t){t&&e.mesh.hear(t.data||t,n)},o}catch(t){}}function s(n){clearTimeout(n.defer),a&&n.retry<=0||(n.retry=(n.retry||e.retry||60)-1,n.defer=setTimeout(function t(){if(a&&a.hidden)return setTimeout(t,r);u(n)},r))}})})($,"./adapters/websocket")}(); \ No newline at end of file +!function(){var t;"undefined"!=typeof window&&(t=window),"undefined"!=typeof global&&(t=global);var e=(t=t||{}).console||{log:function(){}};function j(e,t){return t?require(e):e.slice?j[o(e)]:function(t,n){e(t={exports:{}}),j[o(n)]=t.exports};function o(t){return t.split("/").slice(-1).toString().replace(".js","")}}if("undefined"!=typeof module)var $=module;j(function(t){var n,c,l={};function e(t,n){v(this,n)&&void 0!==this[n]||(this[n]=t)}function o(t,n){var e=this.n;if(!e||!(n===e||g(e)&&v(e,n)))return!!n||void 0}function p(t,n){2!==arguments.length?(p.r=p.r||[]).push(t):(p.r=p.r||{})[t]=n}l.fn={is:function(t){return!!t&&"function"==typeof t}},l.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},l.num={is:function(t){return!d(t)&&(0<=t-parseFloat(t)+1||1/0===t||-1/0===t)}},l.text={is:function(t){return"string"==typeof t}},l.text.ify=function(t){return l.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},l.text.random=function(t,n){var e="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"]||n["<"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},l.list={is:function(t){return t instanceof Array}},l.list.slit=Array.prototype.slice,l.list.sort=function(e){return function(t,n){return t&&n?(t=t[e])<(n=n[e])?-1:n",u.drift=0,u.is=function(t,n,e){var o=n&&t&&t[k]&&t[k][u._]||e;if(o)return v(o=o[n])?o:-1/0},u.lex=function(){return u().toString(36).replace(".","")},u.ify=function(t,n,e,o,i){if(!t||!t[k]){if(!i)return;t=a.soul.ify(t,i)}var r=l(t[k],u._);return void 0!==n&&n!==k&&(v(e)&&(r[n]=e),void 0!==o&&(t[n]=o)),t},u.to=function(t,n,e){var o=(t||{})[n];return h(o)&&(o=g(o)),u.ify(e,n,u.is(t,n),o,a.soul(t))},u.map=function(i,r,a){var t=h(t=i||r)?t:null;return i=m(i=i||r)?i:null,t&&!i?(r=v(r)?r:u(),t[k]=t[k]||{},d(t,f,{o:t,s:r}),t):(a=a||h(r)?r:void 0,r=v(r)?r:u(),function(t,n,e,o){if(!i)return f.call({o:e,s:r},t,n),t;i.call(a||this||{},t,n,e,o),p(e,n)&&void 0===e[n]||f.call({o:e,s:r},t,n)})};var c=n.obj,l=c.as,p=c.has,h=c.is,d=c.map,g=c.copy,v=n.num.is,m=n.fn.is,k=a._;t.exports=u})(j,"./state"),j(function(t){var a=j("./type"),u=j("./val"),s=j("./node"),i={};function r(t,n){if(!t||n!==s.soul(t)||!s.is(t,this.fn,this.as))return!0;this.cb&&(e.n=t,e.as=this.as,this.cb.call(e.as,t,n,e))}function e(t){t&&s.is(e.n,t,e.as)}function f(t,n){var e;return(e=function(t,n){var e,o=t.seen,i=o.length;for(;i--;)if(e=o[i],n.obj===e.obj)return e;o.push(n)}(t,n))?e:(n.env=t,n.soul=c,s.ify(n.obj,o,n)&&(n.link=n.link||u.link.ify(s.soul(n.node)),n.obj!==t.shell&&(t.graph[u.link.is(n.link)]=n.node)),n)}function o(t,n,e){var o,i,r=this,a=r.env;if(s._===n&&g(t,u.link._))return e._;if(o=l(t,n,e,r,a)){if(n||(r.node=r.node||e||{},g(t,s._)&&s.soul(t)&&(r.node._=b(t._)),r.node=s.soul.ify(r.node,u.link.is(r.link)),r.link=r.link||u.link.ify(s.soul(r.node))),(i=a.map)&&(i.call(a.as||{},t,n,e,r),g(e,n))){if(void 0===(t=e[n]))return void d(e,n);if(!(o=l(t,n,e,r,a)))return}if(!n)return r.node;if(!0===o)return t;if((i=f(a,{obj:t,path:r.path.concat(n)})).node)return i.link}}function c(t){var n=this,e=u.link.is(n.link),o=n.env.graph;n.link=n.link||u.link.ify(t),n.link[u.link._]=t,n.node&&n.node[s._]&&(n.node[s._][u.link._]=t),g(o,e)&&(o[t]=o[e],d(o,e))}function l(t,n,e,o,i){var r;return!!u.is(t)||(h(t)?1:(r=i.invalid)?l(t=r.call(i.as||{},t,n,e),n,e,o,i):(i.err="Invalid value at '"+o.path.concat(n).join(".")+"'!",void(a.list.is(t)&&(i.err+=" Use `.set(item)` instead of an Array."))))}function p(t,n){var e,o;if(s._!==n)(e=u.link.is(t))?(o=this.opt.seen[e])?this.obj[n]=o:this.obj[n]=this.opt.seen[e]=i.to(this.graph,e,this.opt):this.obj[n]=t;else{if(v(t,u.link._))return;this.obj[n]=b(t)}}i.is=function(t,n,e,o){return!(!t||!h(t)||v(t)||k(t,r,{cb:n,fn:e,as:o}))},i.ify=function(t,n,e){var o={path:[],obj:t};return n?"string"==typeof n?n={soul:n}:n instanceof Function&&(n.map=n):n={},n.soul&&(o.link=u.link.ify(n.soul)),n.shell=(e||{}).shell,n.graph=n.graph||{},n.seen=n.seen||[],n.as=n.as||e,f(n,o),n.root=o.node,n.graph},i.node=function(t){var n=s.soul(t);if(n)return m({},n,t)},i.to=function(t,n,e){if(t){var o={};return e=e||{seen:{}},k(t[n],p,{obj:o,graph:t,opt:e}),o}};a.fn.is;var n=a.obj,h=n.is,d=n.del,g=n.has,v=n.empty,m=n.put,k=n.map,b=n.copy;t.exports=i})(j,"./graph"),j(function(t){j("./onto"),t.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var e=t["#"]||t,o=(this.tag||empty)[e];if(!o)return;return o=this.on(e,n),clearTimeout(o.err),!0}e=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return e;var i=this.on(e,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),e}}})(j,"./ask"),j(function(t){var r=j("./type");var a=r.time.is;t.exports=function(o){var i={s:{}};return o=o||{max:1e3,age:9e3},i.check=function(t){var n;return!!(n=i.s[t])&&(n.pass?n.pass=!1:i.track(t))},i.track=function(t,n){var e=i.s[t]||(i.s[t]={});return e.was=a(),n&&(e.pass=!0),i.to||(i.to=setTimeout(function(){var e=a();r.obj.map(i.s,function(t,n){t&&o.age>e-t.was||r.obj.del(i.s,n)}),i.to=null},o.age+9)),e},i}})(j,"./dup"),j(function(t){function c(t){return t instanceof c?(this._={gun:this,$:this}).$:this instanceof c?c.create(this._={gun:this,$:this,opt:t}):new c(t)}c.is=function(t){return t instanceof c||t&&t._&&t===t._.$||!1},c.version=.9,(c.chain=c.prototype).toJSON=function(){};var n=j("./type");function a(t){var n,e,o=this.as,i=o.at||o,r=i.$;(e=t["#"])||(e=t["#"]=h(9)),(n=i.dup).check(e)?o.out===t.out&&(t.out=void 0,this.to.next(t)):(n.track(e),i.ask(t["@"],t)||(t.get&&c.on.get(t,r),t.put&&c.on.put(t,r)),this.to.next(t),o.out||(t.out=a,i.on("out",t)))}function i(t,n,e,o){var i=this,r=c.state.is(e,n);if(!r)return i.err="Error: No state on '"+n+"' in node '"+o+"'!";var a=i.graph[o]||x,u=c.state.is(a,n,!0),s=a[n],f=c.HAM(i.machine,r,u,t,s);f.incoming?(i.put[o]=c.state.to(e,n,i.put[o]),(i.diff||(i.diff={}))[o]=c.state.to(e,n,i.diff[o]),i.souls[o]=!0):f.defer&&(i.defer=r<(i.defer||1/0)?r:i.defer)}function r(t,n){var e=this,o=e.$._,i=(o.next||x)[n];if(!i){if(!(o.opt||x).super)return void(e.souls[n]=!1);i=e.$.get(n)._}var r=e.map[n]={put:t,get:n,$:i.$},a={ctx:e,msg:r};e.async=!!o.tag.node,e.ack&&(r["@"]=e.ack),k(t,u,a),e.async&&(e.and||o.on("node",function(t){this.to.next(t),t===e.map[t.get]&&(e.souls[t.get]=!1,k(t.put,s,t),k(e.souls,function(t){if(t)return t})||e.c||(e.c=1,this.off(),k(e.map,f,e)))}),e.and=!0,o.on("node",r))}function u(t,n){var e=this.ctx,o=e.graph,i=this.msg,r=i.get,a=i.put,u=i.$._;o[r]=c.state.to(a,n,o[r]),e.async||(u.put=c.state.to(a,n,u.put))}function s(t,n){var e=this.put,o=this.$._;o.put=c.state.to(e,n,o.put)}function f(t,n){t.$&&(this.cat.stop=this.stop,t.$._.on("in",t),this.cat.stop=null)}n.obj.to(n,c),c.HAM=j("./HAM"),c.val=j("./val"),c.node=j("./node"),c.state=j("./state"),c.graph=j("./graph"),c.on=j("./onto"),c.ask=j("./ask"),c.dup=j("./dup"),c.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||c.on,t.ask=t.ask||c.ask,t.dup=t.dup||c.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",a,t),t.on("out",a,{at:t,out:a}),c.on("create",t),t.on("create",t)),t.once=1,n},c.on.put=function(t,n){var e=n._,o={$:n,graph:e.graph,put:{},map:{},souls:{},machine:c.state(),ack:t["@"],cat:e,stop:{}};if(c.graph.is(t.put,null,i,o)||(o.err="Error: Invalid graph!"),o.err)return e.on("in",{"@":t["#"],err:c.log(o.err)});k(o.put,r,o),o.async||k(o.map,f,o),void 0!==o.defer&&setTimeout(function(){c.on.put(t,n)},o.defer-o.machine),o.diff&&e.on("put",m(t,{put:o.diff}))},c.on.get=function(t,n){var e=n._,o=t.get,i=o[y],r=e.graph[i],a=o[_],u=(e.next||(e.next={}))[i];if(!r)return e.on("get",t);if(a){if("string"!=typeof a||!v(r,a))return e.on("get",t);r=c.state.to(r,a)}else r=c.obj.copy(r);r=c.graph.node(r),(u||x).ack,e.on("in",{"@":t["#"],how:"mem",put:r,$:n}),e.on("get",t)},c.chain.opt=function(t){t=t||{};var n=this._,e=t.peers||t;return g(t)||(t={}),g(n.opt)||(n.opt=t),p(e)&&(e=[e]),o(e)&&(e=k(e,function(t,n,e){(n={}).id=n.url=t,e(t,n)}),g(n.opt.peers)||(n.opt.peers={}),n.opt.peers=m(e,n.opt.peers)),n.opt.peers=n.opt.peers||{},k(t,function t(n,e){!v(this,e)||l.is(n)||d.empty(n)?this[e]=n:n&&n.constructor!==Object&&!o(n)||k(n,t,this[e])},n.opt),c.on("opt",n),n.opt.uuid=n.opt.uuid||function(){return b()+h(12)},this};var o=c.list.is,l=c.text,p=l.is,h=l.random,d=c.obj,g=d.is,v=d.has,m=d.to,k=d.map,b=(d.copy,c.state.lex),y=c.val.link._,_=".",x=(c.node._,c.val.link.is,{});e.only=function(t,n){return e.only.i&&t===e.only.i&&e.only.i++&&(e.log.apply(e,arguments)||n)},(c.log=function(){return c.log.off||e.log.apply(e,arguments),[].slice.call(arguments).join(" ")}).once=function(t,n,e){return(e=c.log.once)[t]=e[t]||0,e[t]++||c.log(n)},c.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&((window.GUN=window.Gun=c).window=window);try{void 0!==$&&($.exports=c)}catch(t){}t.exports=c})(j,"./root"),j(function(t){var u=j("./root");u.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.$;if(1===t)return(this._.back||this._).$;var e=this._;if("string"==typeof t&&(t=t.split(".")),t instanceof Array){for(var o=0,i=t.length,r=e;o(r.acks||0)&&this.off(),r.ack&&r.ack(t,this)},r.opt),e=0,o=n.root.now;h.del(n.root,"now");var i=n.root.mum;n.root.mum={},r.ref._.on("out",{$:r.ref,put:r.out=r.env.graph,opt:r.opt,"#":t}),n.root.mum=i?h.to(i,n.root.mum):i,n.root.now=o},r),r.res&&r.res())}function n(t,n){if(t)return!0}function c(r,t,n,a){var u=this,s=f.is(r);!t&&a.path.length&&(u.res||o)(function(){for(var t=a.path,n=u.ref,e=(u.opt,0),o=t.length;e .once, apologies unexpected."),this.once(t,n)},f.chain.once=function(t,n){var e=this,o=e._,i=o.put;if(0=(o.batch||1e3))return f();i=i||setTimeout(f,o.wait||1)}),a.on("get",function(n){this.to.next(n);var e,o,i,r=n.get;function t(){if(r&&(e=r["#"])){var t=r["."];(o=s[e]||i)&&t&&(o=Gun.state.to(o,t)),a.on("in",{"@":n["#"],put:Gun.graph.node(o),how:"lS",lS:n.$})}}Gun.debug?setTimeout(t,1):t()});var n=function(t,n,e,o){s[o]=Gun.state.to(e,n,s[o])},f=function(t){var e;u=0,clearTimeout(i),i=!1;var n=r;r={},t&&(s=t);try{p.setItem(o.prefix,JSON.stringify(s))}catch(t){Gun.log(e=(t||"localStorage failure")+" Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"),a.on("localStorage:error",{err:e,file:o.prefix,flush:s,retry:f})}(e||Gun.obj.empty(o.peers))&&Gun.obj.map(n,function(t,n){a.on("in",{"@":n,err:e,ok:0})})}}})}})(j,"./adapters/localStorage"),j(function(t){var v=j("../type"),m="undefined"!=typeof setImmediate?setImmediate:setTimeout;!function(){v.text.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var e=0,o=t.length;e<"])&&(r._.to=v.obj.map(o.split(","),p)),r.dam?void((o=s.hear[r.dam])&&o(r,i,u)):(k&&(a=+new Date),u.on("in",r),void(k&&!r.nts&&f.log(+new Date-a,"msg",r["#"])));var a}}};var l,p=function(t,n,e){e(t,!0)};function h(t){s.say(l,t)}function d(t){var n=t.batch;if(t.batch=t.tail=null,n&&n.length){var e;k&&(e=+new Date);try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return f.log("DAM JSON stringify error",t)}k&&f.log(+new Date-e,"say stringify",n.length),n&&g(n,t)}}function g(n,e){try{var t,o=e.wire;k&&(t=+new Date),e.say?e.say(n):o.send&&o.send(n),k&&f.log(+new Date-t,"wire send",n.length),s.say.d+=n.length||0,++s.say.c}catch(t){(e.queue=e.queue||[]).push(n)}}s.hear.c=s.hear.d=0,s.say=function(t,n){if(this.to&&this.to.next(t),!t)return!1;var e,o,i,r;k&&(a=+new Date);var a,u=t._||(t._=function(){});if((e=t["#"])||(e=t["#"]=v.text.random(9)),(o=t["##"])||void 0===t.put||(o=t["##"]=v.obj.hash(t.put)),!(r=u.raw)&&(r=u.raw=s.raw(t),o&&(i=t["@"])&&(c.track(i+o).it=t,i=(c.s[i]||!0).it))){if(o===i["##"])return!1;i["##"]=o}if(k&&f.log(+new Date-a,"say prep"),c.track(e).it=t,!(n=n||(i=c.s[t["@"]])&&(i=i.it)&&(i=i._)&&(i=i.via))&&s.way)return s.way(t);if(!n||!n.id)return l=t,!!v.obj.is(n||f.peers)&&(k&&(a=+new Date),v.obj.map(n||f.peers,h),void(k&&f.log(+new Date-a,"say loop")));if(!n.wire&&s.wire&&s.wire(n),e!==n.last){if(n.last=e,n===u.via)return!1;if((i=u.to)&&(i[n.url]||i[n.pid]||i[n.id]))return!1;if(n.batch){if(n.tail=(i=n.tail||0)+r.length,n.tail<=f.pack)return void n.batch.push(r);d(n)}n.batch=[],setTimeout(function(){d(n)},f.gap),g(r,n)}},s.say.c=s.say.d=0,function(){s.raw=function(t){if(!t)return"";var n,e=t._||{};if(n=e.raw)return n;if("string"==typeof t)return t;if(!t.dam){var o=0,i=[];v.obj.map(f.peers,function(t){if(i.push(t.url||t.pid||t.id),9<++o)return!0}),1<"]=i.join())}var r=a(t);return e&&(e.raw=r),r};var a=JSON.stringify}(),s.hi=function(n){var t=n.wire||{};n.id?f.peers[n.url||n.id]=n:(t=n.id=n.id||v.text.random(9),s.say({dam:"?"},f.peers[t]=n)),n.met=n.met||+new Date,t.hied||u.on(t.hied="hi",n),t=n.queue,n.queue=[],v.obj.map(t,function(t){g(t,n)})},s.bye=function(t){u.on("bye",t);var n=+new Date;n-=t.met||n,s.bye.time=((s.bye.time||n)+n)/2,k=e.LOG},s.hear["!"]=function(t,n){f.log("Error:",t.err)},s.hear["?"]=function(t,n){t.pid?n.pid||(n.pid=t.pid):s.say({dam:"?",pid:f.pid,"@":t["#"]},n)},u.on("create",function(t){t.opt.pid=t.opt.pid||v.text.random(9),this.to.next(t),t.on("out",s.say)}),u.on("bye",function(t,n){t=f.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),v.obj.del(f.peers,t.id),t.wire=null});var i={};return u.on("bye",function(t,n){this.to.next(t),(n=t.url)&&(i[n]=!0,setTimeout(function(){delete i[n]},f.lack||9e3))}),u.on("hi",function(e,o){this.to.next(e),(o=e.url)&&i[o]&&(delete i[o],v.obj.map(u.next,function(t,n){(o={})[n]=u.graph[n],s.say({"##":v.obj.hash(o),get:{"#":n}},e)}))}),s}}catch(t){}})(j,"./adapters/mesh"),j(function(t){var f=j("../index");f.Mesh=j("./mesh"),f.on("opt",function(t){this.to.next(t);var o=t.opt;if(!t.once&&!1!==o.WebSocket){var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var e=o.WebSocket||n.WebSocket||n.webkitWebSocket||n.mozWebSocket;if(e){o.WebSocket=e;var i=o.mesh=o.mesh||f.Mesh(t);i.wire||o.wire;i.wire=o.wire=u,setTimeout(function(){t.on("out",{dam:"hi"})},1);var r=2e3,a="undefined"!=typeof document&&document}}function u(n){try{if(!n||!n.url)return e&&e(n);var t=n.url.replace("http","ws"),e=n.wire=new o.WebSocket(t);return e.onclose=function(){o.mesh.bye(n),s(n)},e.onerror=function(t){s(n)},e.onopen=function(){o.mesh.hi(n)},e.onmessage=function(t){t&&o.mesh.hear(t.data||t,n)},e}catch(t){}}function s(n){clearTimeout(n.defer),a&&n.retry<=0||(n.retry=(n.retry||o.retry||60)-1,n.defer=setTimeout(function t(){if(a&&a.hidden)return setTimeout(t,r);u(n)},r))}})})(j,"./adapters/websocket")}(); \ No newline at end of file diff --git a/lib/meta.js b/lib/meta.js index c9fa3f9b..5b8b63c9 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -149,7 +149,7 @@ /* UI */ if(meta.css){ return } var $m = $('
').attr('id', 'meta'); - $m.append($('').text('+').addClass('meta-start')); + $m.append($('').html('☰').addClass('meta-start')); $m.append($('
').addClass('meta-menu meta-none').append('
    ')); $(document.body).append($m); css({ @@ -161,7 +161,7 @@ background: 'white', 'font-size': '18pt', 'font-family': 'Tahoma, arial', - 'box-shadow': '0px 0px 1px #000044', + //'box-shadow': '0px 0px 1px #000044', 'border-radius': '1em', 'text-align': 'center', 'z-index': 999999, diff --git a/lib/radisk.js b/lib/radisk.js index 0c1b82d4..03bdcc6a 100644 --- a/lib/radisk.js +++ b/lib/radisk.js @@ -19,7 +19,7 @@ function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') } function atomic(v){ return u !== v && (!v || 'object' != typeof v) } var map = Gun.obj.map; - var LOG = false; + var LOG = console.LOG; if(!opt.store){ return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!"); @@ -46,7 +46,7 @@ cb = val; var S; LOG && (S = +new Date); val = r.batch(key); - LOG && console.log(+new Date - S, 'rad mem'); + LOG && opt.log(S, +new Date - S, 'rad mem'); if(u !== val){ cb(u, r.range(val, o), o); if(atomic(val)){ return } @@ -77,6 +77,7 @@ r.thrash = function(){ var thrash = r.thrash; if(thrash.ing){ return thrash.more = true } + LOG = console.LOG; // dirty place to cheaply update LOG settings over time. thrash.more = false; thrash.ing = true; var batch = thrash.at = r.batch, i = 0; @@ -174,9 +175,9 @@ } f.write = function(){ var tmp = ename(file); - var start; LOG && (start = +new Date); // comment this out! + var S; LOG && (S = +new Date); opt.store.put(tmp, f.text, function(err){ - LOG && console.log("wrote to disk in", (+new Date) - start, tmp); // comment this out! + LOG && opt.log(S, +new Date - S, "wrote disk", tmp); if(err){ return cb(err) } r.list.add(tmp, cb); }); @@ -205,10 +206,10 @@ r.write.jsonify = function(f, file, rad, cb, o){ var raw; - var start; LOG && (start = +new Date); // comment this out! + var S; LOG && (S = +new Date); try{raw = JSON.stringify(rad.$); }catch(e){ return cb("Record too big!") } - LOG && console.log("stringified JSON in", +new Date - start); // comment this out! + LOG && opt.log(S, +new Date - S, "rad stringified JSON"); if(opt.chunk < raw.length && !o.force){ if(Radix.map(rad, f.each, true)){ return } } @@ -234,7 +235,7 @@ if(RAD && !o.next){ // cache var S; LOG && (S = +new Date); var val = RAD(key); - LOG && console.log(+new Date - S, 'rad cached'); + LOG && opt.log(S, +new Date - S, 'rad cached'); //if(u !== val){ //cb(u, val, o); if(atomic(val)){ cb(u, val, o); return } @@ -247,7 +248,7 @@ file = (u === file)? u : decodeURIComponent(file); tmp = o.next || key || (o.reverse? o.end || '\uffff' : o.start || ''); if(!file || (o.reverse? file < tmp : file > tmp)){ - LOG && console.log(+new Date - S, 'rad read lex'); S = +new Date; + LOG && opt.log(S, +new Date - S, 'rad read lex'); S = +new Date; if(o.next || o.reverse){ g.file = file } if(tmp = Q[g.file]){ tmp.push({key: key, ack: cb, file: g.file, opt: o}); @@ -268,9 +269,9 @@ g.info = info; if(disk){ RAD = g.disk = disk } disk = Q[g.file]; delete Q[g.file]; - LOG && console.log(+new Date - S, 'rad read it in, now ack to:', disk.length); S = +new Date; + LOG && opt.log(S, +new Date - S, 'rad read in, ack', disk.length); S = +new Date; map(disk, g.ack); - LOG && console.log(+new Date - S, 'rad read acked'); + LOG && opt.log(S, +new Date - S, 'rad read acked'); } g.ack = function(as){ if(!as.ack){ return } @@ -315,7 +316,7 @@ var p = function Parse(){}, info = {}; p.disk = Radix(); p.read = function(err, data){ var tmp; - LOG && console.log('read disk in', +new Date - S, ename(file)); // keep this commented out in + LOG && opt.log(S, +new Date - S, 'read disk', ename(file)); delete Q[file]; if((p.err = err) || (p.not = !data)){ return map(q, p.ack); @@ -332,12 +333,12 @@ } info.parsed = data.length; - LOG && (S = +new Date); // keep this commented out in production! + LOG && (S = +new Date); if(opt.jsonify || '{' === data[0]){ // temporary testing idea try{ var json = JSON.parse(data); p.disk.$ = json; - LOG && console.log('parsed JSON in', +new Date - S); // keep this commented out in production! + LOG && opt.log(S, +new Date - S, 'rad parsed JSON'); map(q, p.ack); return; }catch(e){ tmp = e } @@ -346,7 +347,7 @@ return map(q, p.ack); } } - LOG && (S = +new Date); // keep this commented out in production! + LOG && (S = +new Date); var tmp = p.split(data), pre = [], i, k, v; if(!tmp || 0 !== tmp[1]){ p.err = "File '"+file+"' does not have root radix! "; @@ -369,7 +370,7 @@ if(u !== k && u !== v){ p.disk(pre.join(''), v) } tmp = p.split(tmp[2]); } - LOG && console.log('parsed RAD in', +new Date - S); // keep this commented out in production! + LOG && opt.log(S, +new Date - S, 'parsed RAD'); //cb(err, p.disk); map(q, p.ack); }; @@ -389,7 +390,7 @@ if(p.err || p.not){ return cb(p.err, u, info) } cb(u, p.disk, info); } - var S; LOG && (S = +new Date); // keep this commented out in production! + var S; LOG && (S = +new Date); if(raw){ return p.read(null, raw) } opt.store.get(ename(file), p.read); } diff --git a/lib/server.js b/lib/server.js index 3ccc7b34..d2f763f4 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,6 +6,7 @@ if(u === root.opt.super){ root.opt.super = true; } + root.opt.log = root.opt.log || Gun.log; this.to.next(root); }) require('../nts'); diff --git a/lib/stats.js b/lib/stats.js index 5039f05d..5e92afba 100644 --- a/lib/stats.js +++ b/lib/stats.js @@ -39,6 +39,7 @@ Gun.on('opt', function(root){ stats.peers.count = Object.keys(root.opt.peers||{}).length; stats.node = {}; stats.node.count = Object.keys(root.graph||{}).length; + stats.all = all; var dam = root.opt.mesh; if(dam){ stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d, long: dam.hear.long}, 'out': {count: dam.say.c, done: dam.say.d}}; @@ -57,3 +58,14 @@ Gun.on('opt', function(root){ }, 1000 * 15); Object.keys = Object.keys || function(o){ return Gun.obj.map(o, function(v,k,t){t(k)}) } }); + + +var log = Gun.log, all = {}, max = 1000; +Gun.log = function(a,b,c,d){ + if('number' == typeof a && 'number' == typeof b && 'string' == typeof c){ + var tmp = (all[c] || (all[c] = [])); + if(max < tmp.push([a,b])){ all[c] = [] } // reset + return; + } + return log.apply(Gun, arguments); +} \ No newline at end of file diff --git a/lib/store.js b/lib/store.js index 3384e93c..ee98f033 100644 --- a/lib/store.js +++ b/lib/store.js @@ -30,12 +30,12 @@ Gun.on('create', function(root){ val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc); rad(soul+esc+key, val, (track? ack : u)); }); - //console.log(+new Date - S, 'put loop'); + Gun.log(S, +new Date - S, 'put loop'); function ack(err, ok){ acks--; if(ack.err){ return } if(ack.err = err){ - try{opt.store.stats.put.err = err}catch(e){} // STATS! + //Gun.log(); //try{opt.store.stats.put.err = err}catch(e){} // STATS! root.on('in', {'@': id, err: err}); return; } @@ -45,7 +45,7 @@ Gun.on('create', function(root){ }catch(e){} // STATS! //console.log(+new Date - S, 'put'); S = +new Date; root.on('in', {'@': id, ok: 1}); - //console.log(+new Date - S, 'put sent'); + //console.log(S, +new Date - S, 'put sent'); } }); @@ -89,7 +89,7 @@ Gun.on('create', function(root){ if(err){ opt.store.stats.get.err = err } }catch(e){} // STATS! //if(u === data && o.chunks > 1){ return } // if we already sent a chunk, ignore ending empty responses. // this causes tests to fail. - //console.log(+new Date - S, 'got'); S = +new Date; + Gun.log(S, +new Date - S, 'got'); S = +new Date; // MARK RETURN HERE!!!! Gun.log will always log unless off :/ switch to something like LOG && whatever? if(data){ if(typeof data !== 'string'){ if(o.atom){ @@ -100,11 +100,10 @@ Gun.on('create', function(root){ } if(!graph && data){ each(data, '') } } - //console.log(+new Date - S, 'got prep'); S = +new Date; + Gun.log(S, +new Date - S, 'got prep'); root.on('in', {'@': id, put: graph, '%': o.more? 1 : u, err: err? err : u, _: each}); - //console.log(+new Date - S, 'got sent'); }, o); - //console.log(+new Date - S, 'get call'); + Gun.log(S, +new Date - S, 'get call'); function each(val, has, a,b){ if(!val){ return } has = (key+has).split(esc); diff --git a/src/adapters/localStorage.js b/src/adapters/localStorage.js index 1d0e60cf..99da2e69 100644 --- a/src/adapters/localStorage.js +++ b/src/adapters/localStorage.js @@ -4,7 +4,7 @@ if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. 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!"); + Gun.log("Warning: No localStorage exists to persist data to!"); store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; } /* @@ -110,7 +110,6 @@ Gun.on('create', function(root){ data = Gun.state.to(data, has); } //if(!data && !Gun.obj.empty(opt.peers)){ return } // if data not found, don't ack if there are peers. // Hmm, what if we have peers but we are disconnected? - //console.log("lS get", lex, data); root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$});// || root.$}); }; Gun.debug? setTimeout(to,1) : to(); diff --git a/src/adapters/mesh.js b/src/adapters/mesh.js index f2596529..0c4ca13e 100644 --- a/src/adapters/mesh.js +++ b/src/adapters/mesh.js @@ -19,15 +19,14 @@ function Mesh(root){ if('[' === tmp){ try{msg = JSON.parse(raw);}catch(e){opt.log('DAM JSON parse error', e)} if(!msg){ return } - //console.log('hear batch length of', msg.length); + LOG && opt.log(msg.length, 'in hear batch'); (function go(){ - var S = +new Date; // STATS! + var S; LOG && (S = +new Date); // STATS! var m, c = 100; // hardcoded for now? while(c-- && (m = msg.shift())){ mesh.hear(m, peer); } - //console.log(+new Date - S, 'hear batch'); - (mesh.hear.long || (mesh.hear.long = [])).push(+new Date - S); + LOG && opt.log(+new Date - S, 'batch heard'); if(!msg.length){ return } puff(go, 0); }()); @@ -38,7 +37,7 @@ function Mesh(root){ }catch(e){return opt.log('DAM JSON parse error', e)} if(!msg){ return } if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } - if(msg.DBG_s){ console.log(+new Date - msg.DBG_s, 'to hear', id) } + if(msg.DBG_s){ opt.log(+new Date - msg.DBG_s, 'to hear', id) } if(dup.check(id)){ return } dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore? if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } @@ -54,9 +53,9 @@ function Mesh(root){ } return; } - //var S = +new Date; + var S; LOG && (S = +new Date); root.on('in', msg); - //!msg.nts && console.log(+new Date - S, 'msg', msg['#']); + LOG && !msg.nts && opt.log(+new Date - S, 'msg', msg['#']); return; } } @@ -70,7 +69,7 @@ function Mesh(root){ if(this.to){ this.to.next(msg) } // compatible with middleware adapters. if(!msg){ return false } var id, hash, tmp, raw; - //var S = +new Date; //msg.DBG_s = msg.DBG_s || +new Date; + var S; LOG && (S = +new Date); //msg.DBG_s = msg.DBG_s || +new Date; var meta = msg._||(msg._=function(){}); if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) } if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) } @@ -84,15 +83,15 @@ function Mesh(root){ } } } - //console.log(+new Date - S, 'mesh say prep'); + LOG && opt.log(+new Date - S, 'say prep'); dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more! if(!peer){ peer = (tmp = dup.s[msg['@']]) && (tmp = tmp.it) && (tmp = tmp._) && (tmp = tmp.via) } if(!peer && mesh.way){ return mesh.way(msg) } if(!peer || !peer.id){ message = msg; if(!Type.obj.is(peer || opt.peers)){ return false } - //var S = +new Date; + var S; LOG && (S = +new Date); Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. - //console.log(+new Date - S, 'mesh say loop'); + LOG && opt.log(+new Date - S, 'say loop'); return; } if(!peer.wire && mesh.wire){ mesh.wire(peer) } @@ -116,10 +115,10 @@ function Mesh(root){ peer.batch = peer.tail = null; if(!tmp){ return } if(!tmp.length){ return } // if(3 > tmp.length){ return } // TODO: ^ - //var S = +new Date; + var S; LOG && (S = +new Date); try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp)); }catch(e){return opt.log('DAM JSON stringify error', e)} - //console.log(+new Date - S, 'mesh flush', tmp.length); + LOG && opt.log(+new Date - S, 'say stringify', tmp.length); if(!tmp){ return } send(tmp, peer); } @@ -129,14 +128,14 @@ function Mesh(root){ // for now - find better place later. function send(raw, peer){ try{ var wire = peer.wire; - //var S = +new Date; + var S; LOG && (S = +new Date); if(peer.say){ peer.say(raw); } else if(wire.send){ wire.send(raw); } - //console.log(+new Date - S, 'wire send', raw.length); + LOG && opt.log(+new Date - S, 'wire send', raw.length); mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! }catch(e){ (peer.queue = peer.queue || []).push(raw); @@ -186,6 +185,7 @@ function Mesh(root){ root.on('bye', peer); var tmp = +(new Date); tmp = (tmp - (peer.met||tmp)); mesh.bye.time = ((mesh.bye.time || tmp) + tmp) / 2; + LOG = console.LOG; // dirty place to cheaply update LOG settings over time. } mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } mesh.hear['?'] = function(msg, peer){ @@ -266,6 +266,7 @@ function Mesh(root){ }()); var empty = {}, ok = true, u; +var LOG = console.LOG; try{ module.exports = Mesh }catch(e){} diff --git a/src/get.js b/src/get.js index 68a14167..6aa9591e 100644 --- a/src/get.js +++ b/src/get.js @@ -89,7 +89,6 @@ function soul(gun, cb, opt, as){ function use(msg){ var eve = this, as = eve.as, cat = as.at, root = cat.root, gun = msg.$, at = (gun||{})._ || {}, data = msg.put || at.put, tmp; if((tmp = root.now) && eve !== tmp[as.now]){ return eve.to.next(msg) } - //console.log("USE:", cat.id, cat.soul, cat.has, cat.get, msg, root.mum); //if(at.async && msg.root){ return } //if(at.async === 1 && cat.async !== true){ return } //if(root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true); diff --git a/src/put.js b/src/put.js index b74ee08c..ab3d48af 100644 --- a/src/put.js +++ b/src/put.js @@ -177,7 +177,7 @@ function any(soul, as, msg, eve){ as = as.as; if(!msg.$ || !msg.$._){ return } // TODO: Handle if(msg.err){ // TODO: Handle - console.log("Please report this as an issue! Put.any.err"); + Gun.log("Please report this as an issue! Put.any.err"); return; } var at = (msg.$._), data = at.put, opt = as.opt||{}, root, tmp; @@ -186,7 +186,7 @@ function any(soul, as, msg, eve){ if(as.ref !== as.$){ tmp = (as.$._).get || at.get; if(!tmp){ // TODO: Handle - console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? + Gun.log("Please report this as an issue! Put.no.get"); // TODO: BUG!?? return; } as.data = obj_put({}, tmp, as.data); diff --git a/src/root.js b/src/root.js index af58dc83..187a3bdc 100644 --- a/src/root.js +++ b/src/root.js @@ -162,20 +162,16 @@ Gun.dup = require('./dup'); // Maybe... in case the in-memory key we have is a local write // we still need to trigger a pull/merge from peers. } else { - //var S = +new Date; node = Gun.obj.copy(node); - //console.log(+new Date - S, 'copy node'); } node = Gun.graph.node(node); tmp = (at||empty).ack; - //var S = +new Date; root.on('in', { '@': msg['#'], how: 'mem', put: node, $: gun }); - //console.log(+new Date - S, 'root got send'); //if(0 < tmp){ return } root.on('get', msg); } diff --git a/test/panic/livestream.js b/test/panic/livestream.js new file mode 100644 index 00000000..41a87161 --- /dev/null +++ b/test/panic/livestream.js @@ -0,0 +1,171 @@ +/* +This is the first in a series of basic networking correctness tests. +Each test itself might be dumb and simple, but built up together, +they prove desired end goals for behavior at scale. +1. (this file) Is a browser write is confirmed as save by multiple peers even if by daisy chain. +2. +*/ + +var config = { + IP: require('ip').address(), + port: 8765, + servers: 1, + browsers: 2, + route: { + '/': __dirname + '/index.html', + '/gun.js': __dirname + '/../../gun.js', + '/jquery.js': __dirname + '/../../examples/jquery.js', + '/livestream.html': __dirname + '/livestream.html', + } +} + +var panic = require('panic-server'); +panic.server().on('request', function(req, res){ + config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res); +}).listen(config.port); + +var clients = panic.clients; +var manager = require('panic-manager')(); + +manager.start({ + clients: Array(config.servers).fill().map(function(u, i){ + return { + type: 'node', + port: config.port + (i + 1) + } + }), + panic: 'http://' + config.IP + ':' + config.port +}); + +var servers = clients.filter('Node.js'); +var bob = servers.pluck(1); +var browsers = clients.excluding(servers); +var alice = browsers.pluck(1); +var others = clients.excluding(alice); + +describe("Broadcast Video", function(){ + //this.timeout(5 * 60 * 1000); + this.timeout(10 * 60 * 1000); + + it("Servers have joined!", function(){ + return servers.atLeast(config.servers); + }); + + it("GUN started!", function(){ + var tests = [], i = 0; + servers.each(function(client){ + tests.push(client.run(function(test){ + var env = test.props; + test.async(); + try{ require('fs').unlinkSync(env.i+'data') }catch(e){} + try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){} + var server = require('http').createServer(function(req, res){ + res.end("I am "+ env.i +"!"); + }); + var port = env.config.port + env.i; + var Gun = require('gun'); + var peers = [], i = env.config.servers; + while(i--){ + var tmp = (env.config.port + (i + 1)); + if(port != tmp){ // ignore ourselves + peers.push('http://'+ env.config.IP + ':' + tmp + '/gun'); + } + } + console.log(port, " connect to ", peers); + var gun = Gun({file: env.i+'data', peers: peers, web: server}); + server.listen(port, function(){ + test.done(); + }); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it(config.browsers +" browser(s) have joined!", function(){ + console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!"); + return browsers.atLeast(config.browsers); + }); + + it("Browsers load QVDev's streaming!", function(){ + var tests = [], i = 0; + browsers.each(function(client, id){ + tests.push(client.run(function(test){ + test.async(); + console.log("load?"); + function load(src, cb){ + var script = document.createElement('script'); + script.onload = cb; script.src = src; + document.head.appendChild(script); + } + load('https://cdn.jsdelivr.net/gh/QVDev/GunStreamer/js/GunRecorder.js', function(){ + load('https://cdn.jsdelivr.net/gh/QVDev/GunStreamer/js/GunStreamer.js', function(){ + test.done(); + }); + }); + $('body').append(''); + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it("Browsers initialized gun!", function(){ + var tests = [], i = 0; + browsers.each(function(client, id){ + tests.push(client.run(function(test){ + try{ localStorage.clear() }catch(e){} + try{ indexedDB.deleteDatabase('radata') }catch(e){} + var env = test.props; + var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'); + window.gun = gun; + }, {i: i += 1, config: config})); + }); + return Promise.all(tests); + }); + + it("Stream", function(){ + return alice.run(function(test){ + console.log("I AM ALICE"); + test.async(); + var stream = window.stream = new GunStreamer({ + url: "https://cdn.jsdelivr.net/gh/QVDev/GunStreamer/js/parser_worker.js", + gun: gun, + streamId: 'livestream', + dbRecord: 'streams' + }); + var record = window.record = new GunRecorder({ + mimeType: 'video/webm; codecs="opus,vp8"', + //audioBitsPerSecond: 6000,//Audio bits per second this is the lowest quality + //videoBitsPerSecond: 100000,//Video bits per second this is the lowest quality + cameraOptions: {video:{width: 1280, height: 720, facingMode: "environment", frameRate: 60}, audio: true}, + video_id: "video", + recordInterval: 1000, // how long each chunk? + onRecordStateChange: function(state){ /* change play/pause buttons */ }, + onDataAvailable: function(data){ console.log('r -> s', data); stream.onDataAvailable(data) } // pass recorded data to streamer + }); + record.startCamera(); + $('#video').on('playing', function(eve){ + console.log("YES!!!"); + record.record(); + }); + console.log("start recording!"); + }, {acks: config.servers}); + }); + + it("All finished!", function(done){ + console.log("Done! Cleaning things up..."); + setTimeout(function(){ + done(); + },1000); + }); + + after("Everything shut down.", function(){ + browsers.run(function(){ + //location.reload(); + //setTimeout(function(){ + //}, 15 * 1000); + }); + return servers.run(function(){ + process.exit(); + }); + }); +}); \ No newline at end of file From 528db2016144041fa6bb34f6d7862bb8433ac75d Mon Sep 17 00:00:00 2001 From: Dletta Date: Fri, 25 Oct 2019 23:10:14 -0700 Subject: [PATCH 02/28] Overview Stats First Draft, non-auto loop version --- examples/NeonTubes2.woff | Bin 0 -> 15448 bytes examples/neonstats.html | 204 +++++++ examples/smoothie.js | 1104 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1308 insertions(+) create mode 100644 examples/NeonTubes2.woff create mode 100644 examples/neonstats.html create mode 100644 examples/smoothie.js diff --git a/examples/NeonTubes2.woff b/examples/NeonTubes2.woff new file mode 100644 index 0000000000000000000000000000000000000000..990bd8bf272c04e1e26d37a5f1f11086072754ea GIT binary patch literal 15448 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 literal 0 HcmV?d00001 diff --git a/examples/neonstats.html b/examples/neonstats.html new file mode 100644 index 00000000..80a6186b --- /dev/null +++ b/examples/neonstats.html @@ -0,0 +1,204 @@ + + + + + + + + + + +
    +Overview
    + +
    +Overview
    + + + + + + + + diff --git a/examples/smoothie.js b/examples/smoothie.js new file mode 100644 index 00000000..62ea7c19 --- /dev/null +++ b/examples/smoothie.js @@ -0,0 +1,1104 @@ +// MIT License: +// +// Copyright (c) 2010-2013, Joe Walnes +// 2013-2018, Drew Noakes +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +/** + * Smoothie Charts - http://smoothiecharts.org/ + * (c) 2010-2013, Joe Walnes + * 2013-2018, Drew Noakes + * + * v1.0: Main charting library, by Joe Walnes + * v1.1: Auto scaling of axis, by Neil Dunn + * v1.2: fps (frames per second) option, by Mathias Petterson + * v1.3: Fix for divide by zero, by Paul Nikitochkin + * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds + * v1.5: Set default frames per second to 50... smoother. + * .start(), .stop() methods for conserving CPU, by Dmitry Vyal + * options.interpolation = 'bezier' or 'line', by Dmitry Vyal + * options.maxValue to fix scale, by Dmitry Vyal + * v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla + * v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin + * Smooth rescaling, by Kostas Michalopoulos + * v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni + * v1.9: Display timestamps along the bottom, by Nick and Stev-io + * (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D) + * Refactored by Krishna Narni, to support timestamp formatting function + * v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh + * v1.11: options.grid.sharpLines option added, by @drewnoakes + * Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes + * v1.12: Support for horizontalLines added, by @drewnoakes + * Support for yRangeFunction callback added, by @drewnoakes + * v1.13: Fixed typo (#32), by @alnikitich + * v1.14: Timer cleared when last TimeSeries removed (#23), by @davidgaleano + * Fixed diagonal line on chart at start/end of data stream, by @drewnoakes + * v1.15: Support for npm package (#18), by @dominictarr + * Fixed broken removeTimeSeries function (#24) by @davidgaleano + * Minor performance and tidying, by @drewnoakes + * v1.16: Bug fix introduced in v1.14 relating to timer creation/clearance (#23), by @drewnoakes + * TimeSeries.append now deals with out-of-order timestamps, and can merge duplicates, by @zacwitte (#12) + * Documentation and some local variable renaming for clarity, by @drewnoakes + * v1.17: Allow control over font size (#10), by @drewnoakes + * Timestamp text won't overlap, by @drewnoakes + * v1.18: Allow control of max/min label precision, by @drewnoakes + * Added 'borderVisible' chart option, by @drewnoakes + * Allow drawing series with fill but no stroke (line), by @drewnoakes + * v1.19: Avoid unnecessary repaints, and fixed flicker in old browsers having multiple charts in document (#40), by @asbai + * v1.20: Add SmoothieChart.getTimeSeriesOptions and SmoothieChart.bringToFront functions, by @drewnoakes + * v1.21: Add 'step' interpolation mode, by @drewnoakes + * v1.22: Add support for different pixel ratios. Also add optional y limit formatters, by @copacetic + * v1.23: Fix bug introduced in v1.22 (#44), by @drewnoakes + * v1.24: Fix bug introduced in v1.23, re-adding parseFloat to y-axis formatter defaults, by @siggy_sf + * v1.25: Fix bug seen when adding a data point to TimeSeries which is older than the current data, by @Nking92 + * Draw time labels on top of series, by @comolosabia + * Add TimeSeries.clear function, by @drewnoakes + * v1.26: Add support for resizing on high device pixel ratio screens, by @copacetic + * v1.27: Fix bug introduced in v1.26 for non whole number devicePixelRatio values, by @zmbush + * v1.28: Add 'minValueScale' option, by @megawac + * Fix 'labelPos' for different size of 'minValueString' 'maxValueString', by @henryn + * v1.29: Support responsive sizing, by @drewnoakes + * v1.29.1: Include types in package, and make property optional, by @TrentHouliston + * v1.30: Fix inverted logic in devicePixelRatio support, by @scanlime + * v1.31: Support tooltips, by @Sly1024 and @drewnoakes + * v1.32: Support frame rate limit, by @dpuyosa + * v1.33: Use Date static method instead of instance, by @nnnoel + * Fix bug with tooltips when multiple charts on a page, by @jpmbiz70 + * v1.34: Add disabled option to TimeSeries, by @TechGuard (#91) + * Add nonRealtimeData option, by @annazhelt (#92, #93) + * Add showIntermediateLabels option, by @annazhelt (#94) + * Add displayDataFromPercentile option, by @annazhelt (#95) + * Fix bug when hiding tooltip element, by @ralphwetzel (#96) + * Support intermediate y-axis labels, by @beikeland (#99) + * v1.35: Fix issue with responsive mode at high DPI, by @drewnoakes (#101) + * v1.36: Add tooltipLabel to ITimeSeriesPresentationOptions. + * If tooltipLabel is present, tooltipLabel displays inside tooltip + * next to value, by @jackdesert (#102) + * Fix bug rendering issue in series fill when using scroll backwards, by @olssonfredrik + * Add title option, by @mesca + */ + +;(function(exports) { + + // Date.now polyfill + Date.now = Date.now || function() { return new Date().getTime(); }; + + var Util = { + extend: function() { + arguments[0] = arguments[0] || {}; + for (var i = 1; i < arguments.length; i++) + { + for (var key in arguments[i]) + { + if (arguments[i].hasOwnProperty(key)) + { + if (typeof(arguments[i][key]) === 'object') { + if (arguments[i][key] instanceof Array) { + arguments[0][key] = arguments[i][key]; + } else { + arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]); + } + } else { + arguments[0][key] = arguments[i][key]; + } + } + } + } + return arguments[0]; + }, + binarySearch: function(data, value) { + var low = 0, + high = data.length; + while (low < high) { + var mid = (low + high) >> 1; + if (value < data[mid][0]) + high = mid; + else + low = mid + 1; + } + return low; + } + }; + + /** + * Initialises a new TimeSeries with optional data options. + * + * Options are of the form (defaults shown): + * + *
    +   * {
    +   *   resetBounds: true,        // enables/disables automatic scaling of the y-axis
    +   *   resetBoundsInterval: 3000 // the period between scaling calculations, in millis
    +   * }
    +   * 
    + * + * Presentation options for TimeSeries are specified as an argument to SmoothieChart.addTimeSeries. + * + * @constructor + */ + function TimeSeries(options) { + this.options = Util.extend({}, TimeSeries.defaultOptions, options); + this.disabled = false; + this.clear(); + } + + TimeSeries.defaultOptions = { + resetBoundsInterval: 3000, + resetBounds: true + }; + + /** + * Clears all data and state from this TimeSeries object. + */ + TimeSeries.prototype.clear = function() { + this.data = []; + this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries. + this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries. + }; + + /** + * Recalculate the min/max values for this TimeSeries object. + * + * This causes the graph to scale itself in the y-axis. + */ + TimeSeries.prototype.resetBounds = function() { + if (this.data.length) { + // Walk through all data points, finding the min/max value + this.maxValue = this.data[0][1]; + this.minValue = this.data[0][1]; + for (var i = 1; i < this.data.length; i++) { + var value = this.data[i][1]; + if (value > this.maxValue) { + this.maxValue = value; + } + if (value < this.minValue) { + this.minValue = value; + } + } + } else { + // No data exists, so set min/max to NaN + this.maxValue = Number.NaN; + this.minValue = Number.NaN; + } + }; + + /** + * Adds a new data point to the TimeSeries, preserving chronological order. + * + * @param timestamp the position, in time, of this data point + * @param value the value of this data point + * @param sumRepeatedTimeStampValues if timestamp has an exact match in the series, this flag controls + * whether it is replaced, or the values summed (defaults to false.) + */ + TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) { + // Rewind until we hit an older timestamp + var i = this.data.length - 1; + while (i >= 0 && this.data[i][0] > timestamp) { + i--; + } + + if (i === -1) { + // This new item is the oldest data + this.data.splice(0, 0, [timestamp, value]); + } else if (this.data.length > 0 && this.data[i][0] === timestamp) { + // Update existing values in the array + if (sumRepeatedTimeStampValues) { + // Sum this value into the existing 'bucket' + this.data[i][1] += value; + value = this.data[i][1]; + } else { + // Replace the previous value + this.data[i][1] = value; + } + } else if (i < this.data.length - 1) { + // Splice into the correct position to keep timestamps in order + this.data.splice(i + 1, 0, [timestamp, value]); + } else { + // Add to the end of the array + this.data.push([timestamp, value]); + } + + this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value); + this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value); + }; + + TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) { + // We must always keep one expired data point as we need this to draw the + // line that comes into the chart from the left, but any points prior to that can be removed. + var removeCount = 0; + while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) { + removeCount++; + } + if (removeCount !== 0) { + this.data.splice(0, removeCount); + } + }; + + /** + * Initialises a new SmoothieChart. + * + * Options are optional, and should be of the form below. Just specify the values you + * need and the rest will be given sensible defaults as shown: + * + *
    +   * {
    +   *   minValue: undefined,                      // specify to clamp the lower y-axis to a given value
    +   *   maxValue: undefined,                      // specify to clamp the upper y-axis to a given value
    +   *   maxValueScale: 1,                         // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
    +   *   minValueScale: 1,                         // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
    +   *   yRangeFunction: undefined,                // function({min: , max: }) { return {min: , max: }; }
    +   *   scaleSmoothing: 0.125,                    // controls the rate at which y-value zoom animation occurs
    +   *   millisPerPixel: 20,                       // sets the speed at which the chart pans by
    +   *   enableDpiScaling: true,                   // support rendering at different DPI depending on the device
    +   *   yMinFormatter: function(min, precision) { // callback function that formats the min y value label
    +   *     return parseFloat(min).toFixed(precision);
    +   *   },
    +   *   yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
    +   *     return parseFloat(max).toFixed(precision);
    +   *   },
    +   *   yIntermediateFormatter: function(intermediate, precision) { // callback function that formats the intermediate y value labels
    +   *     return parseFloat(intermediate).toFixed(precision);
    +   *   },
    +   *   maxDataSetLength: 2,
    +   *   interpolation: 'bezier'                   // one of 'bezier', 'linear', or 'step'
    +   *   timestampFormatter: null,                 // optional function to format time stamps for bottom of chart
    +   *                                             // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
    +   *   scrollBackwards: false,                   // reverse the scroll direction of the chart
    +   *   horizontalLines: [],                      // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
    +   *   grid:
    +   *   {
    +   *     fillStyle: '#000000',                   // the background colour of the chart
    +   *     lineWidth: 1,                           // the pixel width of grid lines
    +   *     strokeStyle: '#777777',                 // colour of grid lines
    +   *     millisPerLine: 1000,                    // distance between vertical grid lines
    +   *     sharpLines: false,                      // controls whether grid lines are 1px sharp, or softened
    +   *     verticalSections: 2,                    // number of vertical sections marked out by horizontal grid lines
    +   *     borderVisible: true                     // whether the grid lines trace the border of the chart or not
    +   *   },
    +   *   labels
    +   *   {
    +   *     disabled: false,                        // enables/disables labels showing the min/max values
    +   *     fillStyle: '#ffffff',                   // colour for text of labels,
    +   *     fontSize: 15,
    +   *     fontFamily: 'sans-serif',
    +   *     precision: 2,
    +   *     showIntermediateLabels: false,          // shows intermediate labels between min and max values along y axis
    +   *     intermediateLabelSameAxis: true,
    +   *   },
    +   *   title
    +   *   {
    +   *     text: '',                               // the text to display on the left side of the chart
    +   *     fillStyle: '#ffffff',                   // colour for text
    +   *     fontSize: 15,
    +   *     fontFamily: 'sans-serif',
    +   *     verticalAlign: 'middle'                 // one of 'top', 'middle', or 'bottom'
    +   *   },
    +   *   tooltip: false                            // show tooltip when mouse is over the chart
    +   *   tooltipLine: {                            // properties for a vertical line at the cursor position
    +   *     lineWidth: 1,
    +   *     strokeStyle: '#BBBBBB'
    +   *   },
    +   *   tooltipFormatter: SmoothieChart.tooltipFormatter, // formatter function for tooltip text
    +   *   nonRealtimeData: false,                   // use time of latest data as current time
    +   *   displayDataFromPercentile: 1,             // display not latest data, but data from the given percentile
    +   *                                             // useful when trying to see old data saved by setting a high value for maxDataSetLength
    +   *                                             // should be a value between 0 and 1
    +   *   responsive: false,                        // whether the chart should adapt to the size of the canvas
    +   *   limitFPS: 0                               // maximum frame rate the chart will render at, in FPS (zero means no limit)
    +   * }
    +   * 
    + * + * @constructor + */ + function SmoothieChart(options) { + this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options); + this.seriesSet = []; + this.currentValueRange = 1; + this.currentVisMinValue = 0; + this.lastRenderTimeMillis = 0; + this.lastChartTimestamp = 0; + + this.mousemove = this.mousemove.bind(this); + this.mouseout = this.mouseout.bind(this); + } + + /** Formats the HTML string content of the tooltip. */ + SmoothieChart.tooltipFormatter = function (timestamp, data) { + var timestampFormatter = this.options.timestampFormatter || SmoothieChart.timeFormatter, + lines = [timestampFormatter(new Date(timestamp))], + label; + + for (var i = 0; i < data.length; ++i) { + label = data[i].series.options.tooltipLabel || '' + if (label !== ''){ + label = label + ' '; + } + lines.push('' + + label + + this.options.yMaxFormatter(data[i].value, this.options.labels.precision) + ''); + } + + return lines.join('
    '); + }; + + SmoothieChart.defaultChartOptions = { + millisPerPixel: 20, + enableDpiScaling: true, + yMinFormatter: function(min, precision) { + return parseFloat(min).toFixed(precision); + }, + yMaxFormatter: function(max, precision) { + return parseFloat(max).toFixed(precision); + }, + yIntermediateFormatter: function(intermediate, precision) { + return parseFloat(intermediate).toFixed(precision); + }, + maxValueScale: 1, + minValueScale: 1, + interpolation: 'bezier', + scaleSmoothing: 0.125, + maxDataSetLength: 2, + scrollBackwards: false, + displayDataFromPercentile: 1, + grid: { + fillStyle: '#000000', + strokeStyle: '#777777', + lineWidth: 1, + sharpLines: false, + millisPerLine: 1000, + verticalSections: 2, + borderVisible: true + }, + labels: { + fillStyle: '#ffffff', + disabled: false, + fontSize: 10, + fontFamily: 'monospace', + precision: 2, + showIntermediateLabels: false, + intermediateLabelSameAxis: true, + }, + title: { + text: '', + fillStyle: '#ffffff', + fontSize: 15, + fontFamily: 'monospace', + verticalAlign: 'middle' + }, + horizontalLines: [], + tooltip: false, + tooltipLine: { + lineWidth: 1, + strokeStyle: '#BBBBBB' + }, + tooltipFormatter: SmoothieChart.tooltipFormatter, + nonRealtimeData: false, + responsive: false, + limitFPS: 0 + }; + + // Based on http://inspirit.github.com/jsfeat/js/compatibility.js + SmoothieChart.AnimateCompatibility = (function() { + var requestAnimationFrame = function(callback, element) { + var requestAnimationFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(function() { + callback(Date.now()); + }, 16); + }; + return requestAnimationFrame.call(window, callback, element); + }, + cancelAnimationFrame = function(id) { + var cancelAnimationFrame = + window.cancelAnimationFrame || + function(id) { + clearTimeout(id); + }; + return cancelAnimationFrame.call(window, id); + }; + + return { + requestAnimationFrame: requestAnimationFrame, + cancelAnimationFrame: cancelAnimationFrame + }; + })(); + + SmoothieChart.defaultSeriesPresentationOptions = { + lineWidth: 1, + strokeStyle: '#ffffff' + }; + + /** + * Adds a TimeSeries to this chart, with optional presentation options. + * + * Presentation options should be of the form (defaults shown): + * + *
    +   * {
    +   *   lineWidth: 1,
    +   *   strokeStyle: '#ffffff',
    +   *   fillStyle: undefined,
    +   *   tooltipLabel: undefined
    +   * }
    +   * 
    + */ + SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) { + this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)}); + if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) { + timeSeries.resetBoundsTimerId = setInterval( + function() { + timeSeries.resetBounds(); + }, + timeSeries.options.resetBoundsInterval + ); + } + }; + + /** + * Removes the specified TimeSeries from the chart. + */ + SmoothieChart.prototype.removeTimeSeries = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + this.seriesSet.splice(i, 1); + break; + } + } + // If a timer was operating for that timeseries, remove it + if (timeSeries.resetBoundsTimerId) { + // Stop resetting the bounds, if we were + clearInterval(timeSeries.resetBoundsTimerId); + } + }; + + /** + * Gets render options for the specified TimeSeries. + * + * As you may use a single TimeSeries in multiple charts with different formatting in each usage, + * these settings are stored in the chart. + */ + SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + return this.seriesSet[i].options; + } + } + }; + + /** + * Brings the specified TimeSeries to the top of the chart. It will be rendered last. + */ + SmoothieChart.prototype.bringToFront = function(timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + var set = this.seriesSet.splice(i, 1); + this.seriesSet.push(set[0]); + break; + } + } + }; + + /** + * Instructs the SmoothieChart to start rendering to the provided canvas, with specified delay. + * + * @param canvas the target canvas element + * @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series + * from appearing on screen, with new values flashing into view, at the expense of some latency. + */ + SmoothieChart.prototype.streamTo = function(canvas, delayMillis) { + this.canvas = canvas; + this.delay = delayMillis; + this.start(); + }; + + SmoothieChart.prototype.getTooltipEl = function () { + // Create the tool tip element lazily + if (!this.tooltipEl) { + this.tooltipEl = document.createElement('div'); + this.tooltipEl.className = 'smoothie-chart-tooltip'; + this.tooltipEl.style.position = 'absolute'; + this.tooltipEl.style.display = 'none'; + document.body.appendChild(this.tooltipEl); + } + return this.tooltipEl; + }; + + SmoothieChart.prototype.updateTooltip = function () { + if(!this.options.tooltip){ + return; + } + var el = this.getTooltipEl(); + + if (!this.mouseover || !this.options.tooltip) { + el.style.display = 'none'; + return; + } + + var time = this.lastChartTimestamp; + + // x pixel to time + var t = this.options.scrollBackwards + ? time - this.mouseX * this.options.millisPerPixel + : time - (this.canvas.offsetWidth - this.mouseX) * this.options.millisPerPixel; + + var data = []; + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; + } + + // find datapoint closest to time 't' + var closeIdx = Util.binarySearch(timeSeries.data, t); + if (closeIdx > 0 && closeIdx < timeSeries.data.length) { + data.push({ series: this.seriesSet[d], index: closeIdx, value: timeSeries.data[closeIdx][1] }); + } + } + + if (data.length) { + el.innerHTML = this.options.tooltipFormatter.call(this, t, data); + el.style.display = 'block'; + } else { + el.style.display = 'none'; + } + }; + + SmoothieChart.prototype.mousemove = function (evt) { + this.mouseover = true; + this.mouseX = evt.offsetX; + this.mouseY = evt.offsetY; + this.mousePageX = evt.pageX; + this.mousePageY = evt.pageY; + if(!this.options.tooltip){ + return; + } + var el = this.getTooltipEl(); + el.style.top = Math.round(this.mousePageY) + 'px'; + el.style.left = Math.round(this.mousePageX) + 'px'; + this.updateTooltip(); + }; + + SmoothieChart.prototype.mouseout = function () { + this.mouseover = false; + this.mouseX = this.mouseY = -1; + if (this.tooltipEl) + this.tooltipEl.style.display = 'none'; + }; + + /** + * Make sure the canvas has the optimal resolution for the device's pixel ratio. + */ + SmoothieChart.prototype.resize = function () { + var dpr = !this.options.enableDpiScaling || !window ? 1 : window.devicePixelRatio, + width, height; + if (this.options.responsive) { + // Newer behaviour: Use the canvas's size in the layout, and set the internal + // resolution according to that size and the device pixel ratio (eg: high DPI) + width = this.canvas.offsetWidth; + height = this.canvas.offsetHeight; + + if (width !== this.lastWidth) { + this.lastWidth = width; + this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); + this.canvas.getContext('2d').scale(dpr, dpr); + } + if (height !== this.lastHeight) { + this.lastHeight = height; + this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); + this.canvas.getContext('2d').scale(dpr, dpr); + } + } else if (dpr !== 1) { + // Older behaviour: use the canvas's inner dimensions and scale the element's size + // according to that size and the device pixel ratio (eg: high DPI) + width = parseInt(this.canvas.getAttribute('width')); + height = parseInt(this.canvas.getAttribute('height')); + + if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) { + this.originalWidth = width; + this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); + this.canvas.style.width = width + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + + if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) { + this.originalHeight = height; + this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); + this.canvas.style.height = height + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); + } + } + }; + + /** + * Starts the animation of this chart. + */ + SmoothieChart.prototype.start = function() { + if (this.frame) { + // We're already running, so just return + return; + } + + this.canvas.addEventListener('mousemove', this.mousemove); + this.canvas.addEventListener('mouseout', this.mouseout); + + // Renders a frame, and queues the next frame for later rendering + var animate = function() { + this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() { + if(this.options.nonRealtimeData){ + var dateZero = new Date(0); + // find the data point with the latest timestamp + var maxTimeStamp = this.seriesSet.reduce(function(max, series){ + var dataSet = series.timeSeries.data; + var indexToCheck = Math.round(this.options.displayDataFromPercentile * dataSet.length) - 1; + indexToCheck = indexToCheck >= 0 ? indexToCheck : 0; + indexToCheck = indexToCheck <= dataSet.length -1 ? indexToCheck : dataSet.length -1; + if(dataSet && dataSet.length > 0) + { + // timestamp corresponds to element 0 of the data point + var lastDataTimeStamp = dataSet[indexToCheck][0]; + max = max > lastDataTimeStamp ? max : lastDataTimeStamp; + } + return max; + }.bind(this), dateZero); + // use the max timestamp as current time + this.render(this.canvas, maxTimeStamp > dateZero ? maxTimeStamp : null); + } else { + this.render(); + } + animate(); + }.bind(this)); + }.bind(this); + + animate(); + }; + + /** + * Stops the animation of this chart. + */ + SmoothieChart.prototype.stop = function() { + if (this.frame) { + SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame); + delete this.frame; + this.canvas.removeEventListener('mousemove', this.mousemove); + this.canvas.removeEventListener('mouseout', this.mouseout); + } + }; + + SmoothieChart.prototype.updateValueRange = function() { + // Calculate the current scale of the chart, from all time series. + var chartOptions = this.options, + chartMaxValue = Number.NaN, + chartMinValue = Number.NaN; + + for (var d = 0; d < this.seriesSet.length; d++) { + // TODO(ndunn): We could calculate / track these values as they stream in. + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; + } + + if (!isNaN(timeSeries.maxValue)) { + chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue; + } + + if (!isNaN(timeSeries.minValue)) { + chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue; + } + } + + // Scale the chartMaxValue to add padding at the top if required + if (chartOptions.maxValue != null) { + chartMaxValue = chartOptions.maxValue; + } else { + chartMaxValue *= chartOptions.maxValueScale; + } + + // Set the minimum if we've specified one + if (chartOptions.minValue != null) { + chartMinValue = chartOptions.minValue; + } else { + chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue); + } + + // If a custom range function is set, call it + if (this.options.yRangeFunction) { + var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue}); + chartMinValue = range.min; + chartMaxValue = range.max; + } + + if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) { + var targetValueRange = chartMaxValue - chartMinValue; + var valueRangeDiff = (targetValueRange - this.currentValueRange); + var minValueDiff = (chartMinValue - this.currentVisMinValue); + this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1; + this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff; + this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff; + } + + this.valueRange = { min: chartMinValue, max: chartMaxValue }; + }; + + SmoothieChart.prototype.render = function(canvas, time) { + var nowMillis = Date.now(); + + // Respect any frame rate limit. + if (this.options.limitFPS > 0 && nowMillis - this.lastRenderTimeMillis < (1000/this.options.limitFPS)) + return; + + if (!this.isAnimatingScale) { + // We're not animating. We can use the last render time and the scroll speed to work out whether + // we actually need to paint anything yet. If not, we can return immediately. + + // Render at least every 1/6th of a second. The canvas may be resized, which there is + // no reliable way to detect. + var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel); + + if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) { + return; + } + } + + this.resize(); + this.updateTooltip(); + + this.lastRenderTimeMillis = nowMillis; + + canvas = canvas || this.canvas; + time = time || nowMillis - (this.delay || 0); + + // Round time down to pixel granularity, so motion appears smoother. + time -= time % this.options.millisPerPixel; + + this.lastChartTimestamp = time; + + var context = canvas.getContext('2d'), + chartOptions = this.options, + dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight }, + // Calculate the threshold time for the oldest data points. + oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel), + valueToYPixel = function(value) { + var offset = value - this.currentVisMinValue; + return this.currentValueRange === 0 + ? dimensions.height + : dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height)); + }.bind(this), + timeToXPixel = function(t) { + if(chartOptions.scrollBackwards) { + return Math.round((time - t) / chartOptions.millisPerPixel); + } + return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel)); + }; + + this.updateValueRange(); + + context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily; + + // Save the state of the canvas context, any transformations applied in this method + // will get removed from the stack at the end of this method when .restore() is called. + context.save(); + + // Move the origin. + context.translate(dimensions.left, dimensions.top); + + // Create a clipped rectangle - anything we draw will be constrained to this rectangle. + // This prevents the occasional pixels from curves near the edges overrunning and creating + // screen cheese (that phrase should need no explanation). + context.beginPath(); + context.rect(0, 0, dimensions.width, dimensions.height); + context.clip(); + + // Clear the working area. + context.save(); + context.fillStyle = chartOptions.grid.fillStyle; + context.clearRect(0, 0, dimensions.width, dimensions.height); + context.fillRect(0, 0, dimensions.width, dimensions.height); + context.restore(); + + // Grid lines... + context.save(); + context.lineWidth = chartOptions.grid.lineWidth; + context.strokeStyle = chartOptions.grid.strokeStyle; + // Vertical (time) dividers. + if (chartOptions.grid.millisPerLine > 0) { + context.beginPath(); + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + if (chartOptions.grid.sharpLines) { + gx -= 0.5; + } + context.moveTo(gx, 0); + context.lineTo(gx, dimensions.height); + } + context.stroke(); + context.closePath(); + } + + // Horizontal (value) dividers. + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + context.beginPath(); + context.moveTo(0, gy); + context.lineTo(dimensions.width, gy); + context.stroke(); + context.closePath(); + } + // Bounding rectangle. + if (chartOptions.grid.borderVisible) { + context.beginPath(); + context.strokeRect(0, 0, dimensions.width, dimensions.height); + context.closePath(); + } + context.restore(); + + // Draw any horizontal lines... + if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) { + for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) { + var line = chartOptions.horizontalLines[hl], + hly = Math.round(valueToYPixel(line.value)) - 0.5; + context.strokeStyle = line.color || '#ffffff'; + context.lineWidth = line.lineWidth || 1; + context.beginPath(); + context.moveTo(0, hly); + context.lineTo(dimensions.width, hly); + context.stroke(); + context.closePath(); + } + } + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + context.save(); + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; + } + + var dataSet = timeSeries.data, + seriesOptions = this.seriesSet[d].options; + + // Delete old data that's moved off the left of the chart. + timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength); + + // Set style for this dataSet. + context.lineWidth = seriesOptions.lineWidth; + context.strokeStyle = seriesOptions.strokeStyle; + // Draw the line... + context.beginPath(); + // Retain lastX, lastY for calculating the control points of bezier curves. + var firstX = 0, firstY = 0, lastX = 0, lastY = 0; + for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) { + var x = timeToXPixel(dataSet[i][0]), + y = valueToYPixel(dataSet[i][1]); + + if (i === 0) { + firstX = x; + firstY = y; + context.moveTo(x, y); + } else { + switch (chartOptions.interpolation) { + case "linear": + case "line": { + context.lineTo(x,y); + break; + } + case "bezier": + default: { + // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves + // + // Assuming A was the last point in the line plotted and B is the new point, + // we draw a curve with control points P and Q as below. + // + // A---P + // | + // | + // | + // Q---B + // + // Importantly, A and P are at the same y coordinate, as are B and Q. This is + // so adjacent curves appear to flow as one. + // + context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop + Math.round((lastX + x) / 2), lastY, // controlPoint1 (P) + Math.round((lastX + x)) / 2, y, // controlPoint2 (Q) + x, y); // endPoint (B) + break; + } + case "step": { + context.lineTo(x,lastY); + context.lineTo(x,y); + break; + } + } + } + + lastX = x; lastY = y; + } + + if (dataSet.length > 1) { + if (seriesOptions.fillStyle) { + // Close up the fill region. + if (chartOptions.scrollBackwards) { + context.lineTo(lastX, dimensions.height + seriesOptions.lineWidth); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); + context.lineTo(firstX, firstY); + } else { + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY); + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); + } + context.fillStyle = seriesOptions.fillStyle; + context.fill(); + } + + if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') { + context.stroke(); + } + context.closePath(); + } + context.restore(); + } + + if (chartOptions.tooltip && this.mouseX >= 0) { + // Draw vertical bar to show tooltip position + context.lineWidth = chartOptions.tooltipLine.lineWidth; + context.strokeStyle = chartOptions.tooltipLine.strokeStyle; + context.beginPath(); + context.moveTo(this.mouseX, 0); + context.lineTo(this.mouseX, dimensions.height); + context.closePath(); + context.stroke(); + this.updateTooltip(); + } + + // Draw the axis values on the chart. + if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) { + var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision), + minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision), + maxLabelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(maxValueString).width - 2, + minLabelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(minValueString).width - 2; + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(maxValueString, maxLabelPos, chartOptions.labels.fontSize); + context.fillText(minValueString, minLabelPos, dimensions.height - 2); + } + + // Display intermediate y axis labels along y-axis to the left of the chart + if ( chartOptions.labels.showIntermediateLabels + && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max) + && chartOptions.grid.verticalSections > 0) { + // show a label above every vertical section divider + var step = (this.valueRange.max - this.valueRange.min) / chartOptions.grid.verticalSections; + var stepPixels = dimensions.height / chartOptions.grid.verticalSections; + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = dimensions.height - Math.round(v * stepPixels); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + var yValue = chartOptions.yIntermediateFormatter(this.valueRange.min + (v * step), chartOptions.labels.precision); + //left of right axis? + intermediateLabelPos = + chartOptions.labels.intermediateLabelSameAxis + ? (chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(yValue).width - 2) + : (chartOptions.scrollBackwards ? dimensions.width - context.measureText(yValue).width - 2 : 0); + + context.fillText(yValue, intermediateLabelPos, gy - chartOptions.grid.lineWidth); + } + } + + // Display timestamps along x-axis at the bottom of the chart. + if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) { + var textUntilX = chartOptions.scrollBackwards + ? context.measureText(minValueString).width + : dimensions.width - context.measureText(minValueString).width + 4; + for (var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine) { + var gx = timeToXPixel(t); + // Only draw the timestamp if it won't overlap with the previously drawn one. + if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) { + // Formats the timestamp based on user specified formatting function + // SmoothieChart.timeFormatter function above is one such formatting option + var tx = new Date(t), + ts = chartOptions.timestampFormatter(tx), + tsWidth = context.measureText(ts).width; + + textUntilX = chartOptions.scrollBackwards + ? gx + tsWidth + 2 + : gx - tsWidth - 2; + + context.fillStyle = chartOptions.labels.fillStyle; + if(chartOptions.scrollBackwards) { + context.fillText(ts, gx, dimensions.height - 2); + } else { + context.fillText(ts, gx - tsWidth, dimensions.height - 2); + } + } + } + } + + // Display title. + if (chartOptions.title.text !== '') { + context.font = chartOptions.title.fontSize + 'px ' + chartOptions.title.fontFamily; + var titleXPos = chartOptions.scrollBackwards ? dimensions.width - context.measureText(chartOptions.title.text).width - 2 : 2; + if (chartOptions.title.verticalAlign == 'bottom') { + context.textBaseline = 'bottom'; + var titleYPos = dimensions.height; + } else if (chartOptions.title.verticalAlign == 'middle') { + context.textBaseline = 'middle'; + var titleYPos = dimensions.height / 2; + } else { + context.textBaseline = 'top'; + var titleYPos = 0; + } + context.fillStyle = chartOptions.title.fillStyle; + context.fillText(chartOptions.title.text, titleXPos, titleYPos); + } + + context.restore(); // See .save() above. + }; + + // Sample timestamp formatting function + SmoothieChart.timeFormatter = function(date) { + function pad2(number) { return (number < 10 ? '0' : '') + number } + return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); + }; + + exports.TimeSeries = TimeSeries; + exports.SmoothieChart = SmoothieChart; + +})(typeof exports === 'undefined' ? this : exports); From d193a332bf8c86f6a9a1488c0c53a394b3c1a4ca Mon Sep 17 00:00:00 2001 From: Dletta Date: Sat, 26 Oct 2019 16:30:50 -0700 Subject: [PATCH 03/28] Second Iteration dynamic keys and lines --- examples/neonstats.html | 204 +++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 120 deletions(-) diff --git a/examples/neonstats.html b/examples/neonstats.html index 80a6186b..09524fb3 100644 --- a/examples/neonstats.html +++ b/examples/neonstats.html @@ -37,10 +37,16 @@
    Overview
    - +
    -Overview
    - +Time Items
    + +
    +Memory Items
    + +
    +QTY Items
    + @@ -48,86 +54,64 @@ + + - - - - diff --git a/examples/stats.html b/examples/stats.html index b6dfe27d..8d1d72df 100644 --- a/examples/stats.html +++ b/examples/stats.html @@ -2,161 +2,93 @@ - + - -
    0 peers 0 min 0 nodes 0 hours
    -
    -
    + + +
    -
    -
    -
    +
    +
    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    - - + \ No newline at end of file diff --git a/lib/meta.js b/lib/meta.js index b067f868..6cb610ac 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -16,133 +16,131 @@ if(typeof module !== "undefined"){ var common = module } ;USE(function(module){ - var noop = function(){}, u; - $.fn.or = function(s){ return this.length ? this : $(s||'body') }; - var m = window.meta = {edit:[]}; - var k = m.key = {}; - k.meta = {17:17, 91:17, 93:17, 224:17}; - k.down = function(eve){ - if(eve.repeat){ return } - var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; - if(!eve.fake && key === k.last){ return } k.last = key; - if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length){ - if(k.meta[key]){ k.down.meta = key = -1 } - if(!k.down.meta){ return } - } - (k.combo || (k.combo = [])).push(key); - m.check('on', key, k.at || (k.at = m.edit)); - if(k.meta[key]){ - m.list(k.at.back || m.edit); - if(k.at && !k.at.back){ m.flip() } - } + var noop = function(){}, u; + $.fn.or = function(s){ return this.length ? this : $(s||'body') }; + var m = window.meta = {edit:[]}; + var k = m.key = {}; + k.meta = {17:17, 91:17, 93:17, 224:17}; + k.down = function(eve){ + if(eve.repeat){ return } + var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; + if(!eve.fake && key === k.last){ return } k.last = key; + if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length){ + if(k.meta[key]){ k.down.meta = key = -1 } + if(!k.down.meta){ return } } - k.up = function(eve){ var tmp; - var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; - if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length){ + (k.combo || (k.combo = [])).push(key); + m.check('on', key, k.at || (k.at = m.edit)); if(k.meta[key]){ - k.down.meta = null; - key = -1; - } else - if(!k.down.meta){ return } - } - k.last = null; - if($(':focus').closest('#meta').length){ return } - m.check('up', key); - if(-1 === key || 27 === eve.which){ k.wipe() } - } - m.flip = function(tmp){ - var board = $('#meta .meta-menu'); - ((tmp === false) || (!tmp && board.is(':visible')))? - board.addClass('meta-none') - : board.removeClass('meta-none'); - } - m.flip.is = function(){ - return $('#meta .meta-menu').is(':visible'); - } - m.flip.wait = 500; - m.check = function(how, key, at){ - at = k.at || m.edit; - var edit = at[key]; - if(!edit){ return } - var tmp = k.eve || noop; - if(tmp.preventDefault){ tmp.preventDefault() } - if(edit[how]){ - if(tmp.fake && !edit.fake){ - m.tap.edit = edit; - } else { - edit[how](m.eve); - /*if(k.at !== m.edit && 'up' === how){ - if(k.down.meta){ m.list(k.at = m.edit) } - else { k.wipe() } - }*/ + m.list(k.at.back || m.edit); + if(k.at && !k.at.back){ m.flip() } } } - if('up' != how){ return } - if(at != edit){ edit.back = at } - m.list(edit, true); + k.up = function(eve){ var tmp; + var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode; + if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length){ + if(k.meta[key]){ + k.down.meta = null; + key = -1; + } else + if(!k.down.meta){ return } } - m.list = function(at, opt){ - if(!at){ return m.flip(false) } - var l = []; - $.each(at, function(i,k){ 'back' != i && k.combo && k.name && l.push(k) }); - if(!l.length){ return } - k.at = at; - l = l.sort(function(a,b){ - a = a.combo.slice(-1)[0] || 0; - if(a.length){ a = a.toUpperCase().charCodeAt(0) } - b = b.combo.slice(-1)[0] || 0; - if(b.length){ b = b.toUpperCase().charCodeAt(0) } - return (a < b)? -1 : 1; - }); - var $ul = $('#meta .meta-menu ul') - $ul.children('li').addClass('meta-none').hide(); setTimeout(function(){ $ul.children('.meta-none').remove() },250); // necessary fix for weird bug glitch - $.each(l, function(i, k){ - $ul.append($('
  • ').text(k.name)); - }); - if(opt){ m.flip(true) } - $ul.append($('
  • ').html('←').one('click', function(){ - m.list(k.at = at.back); - })); + k.last = null; + if($(':focus').closest('#meta').length){ return } + m.check('up', key); + if(-1 === key || 27 === eve.which){ k.wipe() } + } + m.flip = function(tmp){ + var board = $('#meta .meta-menu'); + ((tmp === false) || (!tmp && board.is(':visible')))? + board.addClass('meta-none') + : board.removeClass('meta-none'); + } + m.flip.is = function(){ + return $('#meta .meta-menu').is(':visible'); + } + m.flip.wait = 500; + m.check = function(how, key, at){ + at = k.at || m.edit; + var edit = at[key]; + if(!edit){ return } + var tmp = k.eve || noop; + if(tmp.preventDefault){ tmp.preventDefault() } + if(edit[how]){ + //if(tmp.fake && !edit.fake){ + //m.tap.edit = edit; + //} else { + edit[how](m.eve); + /*if(k.at !== m.edit && 'up' === how){ + if(k.down.meta){ m.list(k.at = m.edit) } + else { k.wipe() } + }*/ + //} } - m.ask = function(help, cb){ - var $ul = $('#meta .meta-menu ul').empty(); - var $put = $('').attr('id', 'meta-ask').attr('placeholder', help); - var $form = $('
    ').append($put).on('submit', function(eve){ - eve.preventDefault(); - cb($put.val()); - $li.remove(); - k.wipe(); - }); - var $li = $('
  • ').append($form); - $ul.append($li); - m.flip(true); - $put.focus(); - } - k.wipe = function(opt){ - k.down.meta = false; - k.combo = []; - if(!opt){ m.flip(false) } - m.list(k.at = m.edit); - }; - m.tap = function(){ - var on = $('.meta-on') - .or($($(document.querySelectorAll(':hover')).get().reverse()).first()) - .or($(document.elementFromPoint(meta.tap.x, meta.tap.y))); - return on; - } - meta.edit = function(edit){ - var tmp = edit.combow = []; - $.each(edit.combo || (edit.combo = []), function(i,k){ - if(!k || !k.length){ if('number' == typeof k){ tmp.push(k) } return } - tmp.push(k.toUpperCase().charCodeAt(0)); - }); - var at = meta.edit, l = edit.combo.length; - $.each(tmp, function(i,k){ at = at[k] = (++i >= l)? edit : at[k] || {} }); - edit.combow = edit.combow.join(','); - m.list(meta.edit); - } - - + if('up' != how){ return } + if(at != edit){ edit.back = at } + m.list(edit, true); + } + m.list = function(at, opt){ + if(!at){ return m.flip(false) } + var l = []; + $.each(at, function(i,k){ 'back' != i && k.combo && k.name && l.push(k) }); + if(!l.length){ return } + k.at = at; + l = l.sort(function(a,b){ + a = a.combo.slice(-1)[0] || 0; + if(a.length){ a = a.toUpperCase().charCodeAt(0) } + b = b.combo.slice(-1)[0] || 0; + if(b.length){ b = b.toUpperCase().charCodeAt(0) } + return (a < b)? -1 : 1; + }); + var $ul = $('#meta .meta-menu ul') + $ul.children('li').addClass('meta-none').hide(); setTimeout(function(){ $ul.children('.meta-none').remove() },250); // necessary fix for weird bug glitch + $.each(l, function(i, k){ + $ul.append($('
  • ').text(k.name).data(k)); + }); + if(opt){ m.flip(true) } + $ul.append($('
  • ').html('←').one('click', function(){ + m.list(k.at = at.back); + })); + } + m.ask = function(help, cb){ + var $ul = $('#meta .meta-menu ul').empty(); + var $put = $('').attr('id', 'meta-ask').attr('placeholder', help); + var $form = $('').append($put).on('submit', function(eve){ + eve.preventDefault(); + cb($put.val()); + $li.remove(); + k.wipe(); + }); + var $li = $('
  • ').append($form); + $ul.append($li); + m.flip(true); + $put.focus(); + } + k.wipe = function(opt){ + k.down.meta = false; + k.combo = []; + if(!opt){ m.flip(false) } + m.list(k.at = m.edit); + }; + m.tap = function(){ + var on = $('.meta-on') + .or($($(document.querySelectorAll(':hover')).get().reverse()).first()) + .or($(document.elementFromPoint(meta.tap.x, meta.tap.y))); + return on; + } + meta.edit = function(edit){ + var tmp = edit.combow = []; + $.each(edit.combo || (edit.combo = []), function(i,k){ + if(!k || !k.length){ if('number' == typeof k){ tmp.push(k) } return } + tmp.push(k.toUpperCase().charCodeAt(0)); + }); + var at = meta.edit, l = edit.combo.length; + $.each(tmp, function(i,k){ at = at[k] = (++i >= l)? edit : at[k] || {} }); + edit.combow = edit.combow.join(','); + m.list(meta.edit); + } })(USE, './metaCore'); ;USE(function(module){ try{ @@ -235,8 +233,7 @@ tag.innerHTML = tmp; document.body.appendChild(tag); } - }catch(e){} - + }catch(e){} })(USE, './metaUI'); ;USE(function(module){ // include basic text editing by default. @@ -380,8 +377,6 @@ on: function(eve){ meta.text.editor('fontSize', 7) }, up: function(){} }); - - })(USE, './metaText'); ;USE(function(module){ var m = meta, k = m.key; @@ -399,6 +394,11 @@ }); $(document).on('touchstart', '#meta .meta-start', function(eve){ m.tap.stun = true }); $(document).on('click', '#meta .meta-menu li', function(eve){ + eve.fake = eve.which = $(this).data().combo.slice(-1)[0].charCodeAt(0); + eve.tap = true; + k.down(eve); + //k.up(eve); + return; if(m.tap.stun){ return m.tap.stun = false } if(!(eve.fake = eve.which = (($(this).text().match(/[A-Z]/)||{})[0]||'').toUpperCase().charCodeAt(0))){ return } eve.tap = true; @@ -407,7 +407,5 @@ }); $(document).on('keydown', k.down).on('keyup', k.up); $(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.text.on); - - })(USE, './metaEvents'); }()); diff --git a/sea.js b/sea.js index ee2fbf8f..f42db5d0 100644 --- a/sea.js +++ b/sea.js @@ -964,6 +964,7 @@ } // If authenticated user wants to delete his/her account, let's support it! User.prototype.delete = async function(alias, pass, cb){ + console.log("user.delete() IS DEPRECATED AND WILL BE MOVED TO A MODULE!!!"); var gun = this, root = gun.back(-1), user = gun.back('user'); try { user.auth(alias, pass, function(ack){ @@ -1005,6 +1006,7 @@ return gun; } User.prototype.alive = async function(){ + console.log("user.alive() IS DEPRECATED!!!"); const gunRoot = this.back(-1) try { // All is good. Should we do something more with actual recalled data? @@ -1024,25 +1026,32 @@ console.log(ctx, ev) }) } + user.get('trust').get(path).put(theirPubkey); + + // do a lookup on this gun chain directly (that gets bob's copy of the data) + // do a lookup on the metadata trust table for this path (that gets all the pubkeys allowed to write on this path) + // do a lookup on each of those pubKeys ON the path (to get the collab data "layers") + // THEN you perform Jachen's mix operation + // and return the result of that to... } 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 = ''; + var gun = this, user = gun.back(-1).user(), pair = user._.sea, path = ''; 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(); + var enc, sec = await user.get('grant').get(pair.pub).get(path).then(); sec = await SEA.decrypt(sec, pair); if(!sec){ sec = SEA.random(16).toString(); enc = await SEA.encrypt(sec, pair); - user.get('trust').get(pair.pub).get(path).put(enc); + user.get('grant').get(pair.pub).get(path).put(enc); } var pub = to.get('pub').then(); var epub = to.get('epub').then(); pub = await pub; epub = await epub; var dh = await SEA.secret(epub, pair); enc = await SEA.encrypt(sec, dh); - user.get('trust').get(pub).get(path).put(enc, cb); + user.get('grant').get(pub).get(path).put(enc, cb); }()); return gun; } From b5b4357a5d5b4af2513b3438243b0f4e262f3e54 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Fri, 8 Nov 2019 14:58:59 -0800 Subject: [PATCH 10/28] screen recorder thanks to @JamieRez --- examples/basic/screen.html | 57 ++++++++++++++++++++++++++++++++++++++ examples/basic/space.html | 50 --------------------------------- 2 files changed, 57 insertions(+), 50 deletions(-) create mode 100644 examples/basic/screen.html delete mode 100644 examples/basic/space.html diff --git a/examples/basic/screen.html b/examples/basic/screen.html new file mode 100644 index 00000000..7a9fdcb9 --- /dev/null +++ b/examples/basic/screen.html @@ -0,0 +1,57 @@ + +
    + + +
    + + + + + \ No newline at end of file diff --git a/examples/basic/space.html b/examples/basic/space.html deleted file mode 100644 index 72a7f15e..00000000 --- a/examples/basic/space.html +++ /dev/null @@ -1,50 +0,0 @@ -

    Search

    - - - - - -
    -
      - -Note: No data is indexed by default, you need to add some! - - - - - - - \ No newline at end of file From f8bb17e27f54a8055d3cbe8c2ed747fdbc1292cc Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 14:38:02 -0800 Subject: [PATCH 11/28] paste example --- examples/basic/paste.html | 20 ++++++++++++++++++++ examples/basic/schedule.html | 1 + examples/basic/screen.html | 6 +++--- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 examples/basic/paste.html diff --git a/examples/basic/paste.html b/examples/basic/paste.html new file mode 100644 index 00000000..9aa6edb4 --- /dev/null +++ b/examples/basic/paste.html @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/examples/basic/schedule.html b/examples/basic/schedule.html index 76bed9a2..132afe00 100644 --- a/examples/basic/schedule.html +++ b/examples/basic/schedule.html @@ -5,6 +5,7 @@ +

      Schedule

      diff --git a/examples/basic/screen.html b/examples/basic/screen.html index 7a9fdcb9..e256a5f8 100644 --- a/examples/basic/screen.html +++ b/examples/basic/screen.html @@ -34,8 +34,8 @@ record.save = function(data){ record.file.readAsDataURL(data); record.file.onloadend = function(){ var b64 = record.file.result; - b64 = "data:video/webm" + b64.substr(b64.indexOf(';')); - gun.get('screen').get('test').put(b64); + b64 = "data:video/webm" + b64.slice(b64.indexOf(';')); + gun.get('test').get('screen').put(b64); } } @@ -48,7 +48,7 @@ $('#play').on('click', ()=>{ } $('#play').text("Stop"); record.playing = true; - gun.get('screen').get('test').once((data)=>{ + gun.get('test').get('screen').once((data)=>{ if(!data){ return } $('#video').get(0).src = data; $('#video').get(0).play() From 1ba73dd47b8048329955ed0d76c04f6ed8b07dfb Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 14:56:16 -0800 Subject: [PATCH 12/28] update basics --- examples/basic/paste.html | 7 ++++--- examples/basic/private.html | 5 ++--- examples/basic/screen.html | 4 +++- examples/basic/tables.html | 2 ++ examples/basic/user.html | 14 ++++++++------ 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/examples/basic/paste.html b/examples/basic/paste.html index 9aa6edb4..39a1f876 100644 --- a/examples/basic/paste.html +++ b/examples/basic/paste.html @@ -1,13 +1,14 @@ + + - \ No newline at end of file + \ No newline at end of file diff --git a/examples/basic/screen.html b/examples/basic/screen.html index e256a5f8..3587027d 100644 --- a/examples/basic/screen.html +++ b/examples/basic/screen.html @@ -1,3 +1,5 @@ + +
      @@ -8,7 +10,7 @@ @@ -27,19 +29,19 @@ $('#up').on('click', function(e){ user.create($('#alias').val(), $('#pass').val(), login); }); function login(e){ - user.auth($('#alias').val(), $('#pass').val()); + user.auth($('#alias').val(), $('#pass').val()); return false; // e.preventDefault(); }; $('#sign').on('submit', login); $('#mask').on('click', login); gun.on('auth', function(){ - $('#sign').hide(); - user.get('said').map().on(UI); + $('#sign').hide(); + user.get('said').map().on(UI); }); $('#said').on('submit', function(e){ - e.preventDefault(); + e.preventDefault(); //if(!user.is){ return } user.get('said').set($('#say').val()); $('#say').val(""); From 8da4689fea547e810fdec1078b3cd60d69a27a0d Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 15:02:47 -0800 Subject: [PATCH 13/28] paste.html ! --- examples/basic/paste.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/basic/paste.html b/examples/basic/paste.html index 39a1f876..3b74295a 100644 --- a/examples/basic/paste.html +++ b/examples/basic/paste.html @@ -1,6 +1,7 @@ - + + From 8dc8b27cd0b266a92e989bb7e5ec29201560b453 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 15:22:06 -0800 Subject: [PATCH 14/28] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 984120fc..fbae89e9 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,8 @@ Thanks to:
      Dário Freire, John Williamson, Robin Bron, -Elie Makhoul +Elie Makhoul, +Mike Staub

      - Join others in sponsoring code: https://www.patreon.com/gunDB ! From 1c3ea910115e9d6dec2c47d2a248506495312b6a Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 15:25:23 -0800 Subject: [PATCH 15/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbae89e9..5bb40adc 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ var Gun = require('gun/gun'); If you also need to install SEA for user auth and crypto, also install some of its dependencies like this: -`npm install text-encoding node-webcrypto-ossl --save` +`npm install text-encoding @peculiar/webcrypto --save` You will need to require it too (it will be automatically added to the Gun object): From 0a02e7bfaaef0c49e506fb0fc7ac467f5b71d90b Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 15:22:06 -0800 Subject: [PATCH 16/28] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 984120fc..fbae89e9 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,8 @@ Thanks to:
      Dário Freire, John Williamson, Robin Bron, -Elie Makhoul +Elie Makhoul, +Mike Staub

      - Join others in sponsoring code: https://www.patreon.com/gunDB ! From c99ad39c565f93de6cd9fbbed238a4b3e0d6cc5d Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 9 Nov 2019 15:25:23 -0800 Subject: [PATCH 17/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbae89e9..5bb40adc 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ var Gun = require('gun/gun'); If you also need to install SEA for user auth and crypto, also install some of its dependencies like this: -`npm install text-encoding node-webcrypto-ossl --save` +`npm install text-encoding @peculiar/webcrypto --save` You will need to require it too (it will be automatically added to the Gun object): From 301f8910fb11e119b4d16b80162f0472ae8b1af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jussi=20Rytk=C3=B6nen?= Date: Mon, 11 Nov 2019 17:33:22 +0200 Subject: [PATCH 18/28] meta.js update from edide --- lib/meta.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/meta.js b/lib/meta.js index 6cb610ac..c3ab5ff2 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -14,7 +14,8 @@ } } if(typeof module !== "undefined"){ var common = module } - + + /* UNBUILD */ ;USE(function(module){ var noop = function(){}, u; $.fn.or = function(s){ return this.length ? this : $(s||'body') }; @@ -145,9 +146,8 @@ ;USE(function(module){ try{ /* UI */ - if(meta.css){ return } var $m = $('
      ').attr('id', 'meta'); - $m.append($('').html('☰').addClass('meta-start')); + $m.append($('').text('+').addClass('meta-start')); $m.append($('
      ').addClass('meta-menu meta-none').append('
        ')); $(document.body).append($m); css({ @@ -159,7 +159,7 @@ background: 'white', 'font-size': '18pt', 'font-family': 'Tahoma, arial', - //'box-shadow': '0px 0px 1px #000044', + 'box-shadow': '0px 0px 1px #000044', 'border-radius': '1em', 'text-align': 'center', 'z-index': 999999, @@ -231,9 +231,10 @@ }); var tag = document.createElement('style'); tag.innerHTML = tmp; - document.body.appendChild(tag); + $m.append(tag) } - }catch(e){} + }catch(e){} + })(USE, './metaUI'); ;USE(function(module){ // include basic text editing by default. @@ -377,6 +378,8 @@ on: function(eve){ meta.text.editor('fontSize', 7) }, up: function(){} }); + + })(USE, './metaText'); ;USE(function(module){ var m = meta, k = m.key; @@ -394,10 +397,10 @@ }); $(document).on('touchstart', '#meta .meta-start', function(eve){ m.tap.stun = true }); $(document).on('click', '#meta .meta-menu li', function(eve){ - eve.fake = eve.which = $(this).data().combo.slice(-1)[0].charCodeAt(0); + var combo = $(this).data().combo; + eve.fake = eve.which = combo && combo.slice(-1)[0].charCodeAt(0); eve.tap = true; - k.down(eve); - //k.up(eve); + k.down(eve); k.up(eve); return; if(m.tap.stun){ return m.tap.stun = false } if(!(eve.fake = eve.which = (($(this).text().match(/[A-Z]/)||{})[0]||'').toUpperCase().charCodeAt(0))){ return } @@ -408,4 +411,4 @@ $(document).on('keydown', k.down).on('keyup', k.up); $(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.text.on); })(USE, './metaEvents'); -}()); +}()); \ No newline at end of file From 5a58f77a321c070d082098e39dca94bec0733e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jussi=20Rytk=C3=B6nen?= Date: Mon, 11 Nov 2019 17:38:56 +0200 Subject: [PATCH 19/28] meta.js update from edide --- lib/meta.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/meta.js b/lib/meta.js index c3ab5ff2..f484a87b 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -14,9 +14,10 @@ } } if(typeof module !== "undefined"){ var common = module } - + /* UNBUILD */ ;USE(function(module){ + var noop = function(){}, u; $.fn.or = function(s){ return this.length ? this : $(s||'body') }; var m = window.meta = {edit:[]}; @@ -142,12 +143,14 @@ edit.combow = edit.combow.join(','); m.list(meta.edit); } + + })(USE, './metaCore'); ;USE(function(module){ try{ /* UI */ var $m = $('
        ').attr('id', 'meta'); - $m.append($('').text('+').addClass('meta-start')); + $m.append($('').html('☰').addClass('meta-start')); $m.append($('
        ').addClass('meta-menu meta-none').append('
          ')); $(document.body).append($m); css({ @@ -159,7 +162,7 @@ background: 'white', 'font-size': '18pt', 'font-family': 'Tahoma, arial', - 'box-shadow': '0px 0px 1px #000044', + //'box-shadow': '0px 0px 1px #000044', 'border-radius': '1em', 'text-align': 'center', 'z-index': 999999, @@ -231,10 +234,11 @@ }); var tag = document.createElement('style'); tag.innerHTML = tmp; - $m.append(tag) + $m.append(tag) } - }catch(e){} - + }catch(e){} + + })(USE, './metaUI'); ;USE(function(module){ // include basic text editing by default. @@ -378,8 +382,8 @@ on: function(eve){ meta.text.editor('fontSize', 7) }, up: function(){} }); - - + + })(USE, './metaText'); ;USE(function(module){ var m = meta, k = m.key; @@ -410,5 +414,7 @@ }); $(document).on('keydown', k.down).on('keyup', k.up); $(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.text.on); + + })(USE, './metaEvents'); }()); \ No newline at end of file From e048b897da91a215add55e2b0826154414e5b196 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sun, 17 Nov 2019 03:35:12 -0800 Subject: [PATCH 20/28] stuff --- examples/docs.html | 55 ++++++++++++---- lib/normalize.js | 3 +- lib/stats.js | 1 + test/panic/4dht.js | 136 +++++++++++++++++++++++++++++++++++++++ test/panic/livestream.js | 31 +++++++-- 5 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 test/panic/4dht.js diff --git a/examples/docs.html b/examples/docs.html index 87412a8a..abc11b64 100644 --- a/examples/docs.html +++ b/examples/docs.html @@ -46,6 +46,8 @@ var gun = Gun('https://guntest.herokuapp.com/gun'); }); })(); +$("