Browse Source

update gundb

Nikolay Suslov 5 years ago
parent
commit
1eaab1e569

+ 21 - 0
public/helpers.js

@@ -429,6 +429,27 @@ class Helpers {
         }
     }
 
+    authUser(alias, pass){
+
+        _LCSDB.user().auth(alias, pass
+            //     , function(ack) {
+    
+            //     if (ack.err) {
+            //         new Noty({
+            //             text: ack.err,
+            //             timeout: 2000,
+            //             theme: 'mint',
+            //             layout: 'bottomRight',
+            //             type: 'error'
+            //         }).show();
+    
+            //     }
+             //}
+             );
+
+    }
+    
+
 }
 
 export { Helpers } 

+ 55 - 44
public/lib/gundb/axe.js

@@ -1,22 +1,17 @@
 ;(function(){
 
-	/* UNBUILD */
-	var root;
-	if(typeof window !== "undefined"){ root = window }
-	if(typeof global !== "undefined"){ root = global }
-	root = root || {};
-	var console = root.console || {log: function(){}};
-	function USE(arg, req){
-		return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
-			arg(mod = {exports: {}});
-			USE[R(path)] = mod.exports;
-		}
-		function R(p){
-			return p.split('/').slice(-1).toString().replace('.js','');
-		}
-	}
-	if(typeof module !== "undefined"){ var common = module }
-	/* UNBUILD */
+  /* UNBUILD */
+  function USE(arg, req){
+    return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
+      arg(mod = {exports: {}});
+      USE[R(path)] = mod.exports;
+    }
+    function R(p){
+      return p.split('/').slice(-1).toString().replace('.js','');
+    }
+  }
+  if(typeof module !== "undefined"){ var MODULE = module }
+  /* UNBUILD */
 
 	;USE(function(module){
     if(typeof window !== "undefined"){ module.window = window }
@@ -24,7 +19,7 @@
 		var AXE = tmp.AXE || function(){};
 
     if(AXE.window = module.window){ AXE.window.AXE = AXE }
-    try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
+    try{ if(typeof MODULE !== "undefined"){ MODULE.exports = AXE } }catch(e){}
     module.exports = AXE;
 	})(USE, './root');
   
@@ -32,6 +27,7 @@
 
 		var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
 		(Gun.AXE = AXE).GUN = AXE.Gun = Gun;
+    var ST = 0;
 
 		Gun.on('opt', function(at){
 			start(at);
@@ -73,11 +69,12 @@
 				The mob threshold might be determined by other factors,
 				like how much RAM or CPU stress we have.
 			*/
-			opt.mob = opt.mob || Infinity;
+			opt.mob = opt.mob || 9876 || Infinity;
 			var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
 			console.log("AXE enabled.");
 
 			function verify(dht, msg) {
+				var S = (+new Date);
 				var puts = Object.keys(msg.put);
 				var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
 				var subs = dht(soul);
@@ -93,17 +90,20 @@
 				if (opt.super) {
 					dht(soul, tmp.join(','));
 				}
+				console.STAT && console.STAT(S, +new Date - S, 'axe verify');
 			}
 			function route(get){ var tmp;
 				if(!get){ return }
 				if('string' != typeof (tmp = get['#'])){ return }
 				return tmp;
 			}
+			// TODO: AXE NEEDS TO BE CHECKED FOR NEW CODE SYSTEM!!!!!!!!!!
 
 			var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
 			at.opt.dht = Rad();
-			at.on('in', function input(msg){
-				var to = this.to, peer = (msg._||{}).via;
+			at.on('in', input);
+			function input(msg){
+				var to = this.to, peer = (msg._||'').via; // warning! mesh.leap could be buggy!
 				var dht = opt.dht;
 				var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
 				var get = msg.get, hash, tmp;
@@ -128,8 +128,8 @@
 						}
 					}*/
 				}
-				if((tmp = msg['@']) && (tmp = at.dup.s[tmp]) && (tmp = tmp.it)){
-					(tmp = (tmp._||ok)).ack = (tmp.ack || 0) + 1; // count remote ACKs to GET.
+				if((tmp = msg['@']) && (tmp = at.dup.s[tmp])){
+					tmp.ack = (tmp.ack || 0) + 1; // count remote ACKs to GET.
 				}
 				to.next(msg);
 
@@ -144,7 +144,7 @@
 						});
 					});
 				}
-			});
+			};
 
 			//try{console.log(req.connection.remoteAddress)}catch(e){};
 			mesh.hear['opt'] = function(msg, peer){
@@ -189,6 +189,7 @@
 						var peers = routes[hash];
 						function chat(peers, old){ // what about optimizing for directed peers?
 							if(!peers){ return chat(opt.peers) }
+							var S = (+new Date); // STATS!
 							var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
 							var meta = (msg._||yes);
 							clearTimeout(meta.lack);
@@ -198,12 +199,13 @@
 								meta.turn = (meta.turn || 0) + 1;
 								if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
 							}
+	            console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe chat');
 							//console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
 							if(0 < c){
 								if(peers === opt.peers){ return } // prevent infinite lack loop.
 								return meta.turn = 0, chat(opt.peers, peers) 
 							}
-							var hash = msg['##'], ack = meta.ack;
+							var hash = msg['##'], ack = meta.ack || at.dup.s[msg['#']];
 							meta.lack = setTimeout(function(){
 								if(ack && hash && hash === msg['##']){ return }
 								if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
@@ -215,6 +217,7 @@
 					}
 					// TODO: PUTs need to only go to subs!
 					if(msg.put){
+						var S = (+new Date); // STATS!
 						var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
 						var peers = {};
 						Gun.obj.map(msg.put, function(node, soul){
@@ -223,6 +226,7 @@
 							if(!to){ return }
 							Gun.obj.to(to, peers);
 						});
+						console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe put');
 						mesh.say(msg, peers);
 						return;
 					}
@@ -246,24 +250,6 @@
 						}
 					});
 				};
-				/*var connections = 0; // THIS HAS BEEN MOVED TO CORE NOW!
-				at.on('hi', function(opt) {
-					this.to.next(opt);
-					//console.log('AXE PEER [HI]', new Date(), opt);
-					connections++;
-					/// The first connection don't need to resubscribe the nodes.
-					if (connections === 1) { return; }
-					/// Resubscribe all nodes.
-					setTimeout(function() {
-						var souls = Object.keys(at.graph);
-						for (var i=0; i < souls.length; ++i) {
-							//at.gun.get(souls[i]).off();
-							at.next[souls[i]].ack = 0;
-							at.gun.get(souls[i]).once(function(){});
-						}
-					//location.reload();
-					}, 500);
-				}, at);*/
 			}
 			axe.up = {};
 			at.on('hi', function(peer){
@@ -273,12 +259,14 @@
 			});
 			at.on('bye', function(peer){ this.to.next(peer);
 				if(peer.url){ delete axe.up[peer.id] }
+				var S = +new Date;
 				Gun.obj.map(peer.routes, function(route, hash){
 					delete route[peer.id];
 					if(Gun.obj.empty(route)){
 						delete axe.routes[hash];
 					}
 				});
+				console.STAT && console.STAT(S, +new Date - S, 'axe bye');
 			});
 
 			// handle rebalancing a mob of peers:
@@ -287,12 +275,35 @@
 				if(peer.url){ return } // I am assuming that if we are wanting to make an outbound connection to them, that we don't ever want to drop them unless our actual config settings change.
 				var count = Object.keys(opt.peers).length;
 				if(opt.mob >= count){ return }  // TODO: Make dynamic based on RAM/CPU also. Or possibly even weird stuff like opt.mob / axe.up length?
-				mesh.say({dam: 'mob', mob: count, peers: Object.keys(axe.up)}, peer);
+				var peers = Object.keys(axe.up);
+				if(!peers.length){ return }
+				mesh.say({dam: 'mob', mob: count, peers: peers}, peer);
 				//setTimeout(function(){ mesh.bye(peer) }, 9); // something with better perf? // UNCOMMENT WHEN WE ACTIVATE THIS FEATURE
 			});
 			at.on('bye', function(peer){
 				this.to.next(peer);
 			});
+
+			at.on('hi', function(peer){
+				this.to.next(peer);
+				// this code handles disconnecting from self & duplicates
+				setTimeout(function(){ // must wait
+					if(peer.pid !== opt.pid){
+						// this extra logic checks for duplicate connections between 2 peers.
+						if(!Gun.obj.map(axe.up, function(p){
+							if(peer.pid === p.pid && peer !== p){
+								return yes = true;
+							}
+						})){ return }
+					}
+					mesh.say({dam: '-'}, peer);
+					delete at.dup.s[peer.last];
+				}, Math.random() * 100);
+			});
+			mesh.hear['-'] = function(msg, peer){
+				mesh.bye(peer);
+				peer.url = '';
+			}
 		}
 
 		function joindht(dht, soul, pids) {

File diff suppressed because it is too large
+ 362 - 204
public/lib/gundb/gun.js


File diff suppressed because it is too large
+ 0 - 0
public/lib/gundb/gun.min.js


+ 32 - 0
public/lib/gundb/lib/crashed.js

@@ -0,0 +1,32 @@
+;(function(){ try {
+	var fs = require('fs'), logs = [], up = __dirname+'/../';
+	fs.readdir(up, function(err, list){ try{
+		var i = 0, f; while(f = list[i++]){
+			if(0 === f.indexOf('isolate-') && '.log' === f.slice(-4)){ logs.push(f) }
+		}
+		logs = logs.sort();
+		var i = 0, f, lf; while(f = list[i++]){
+			if(0 <= f.indexOf('-v8-') && '.log' === f.slice(-4)){ lf = f }
+		} f = lf;
+		if(!f){ return }
+		fs.rename(up+f, up+'v8.log', function(err,ok){
+			var i = 0, f; while(f = logs[i++]){ fs.unlink(up+f, noop) }
+			if(!process.env.EMAIL){ return } // ONLY EMAIL IF DEVELOPER OPTS IN!!!
+			email(); // ONLY EMAIL IF DEVELOPER OPTS IN!!!
+		});
+	}catch(e){} });
+	function noop(){};
+	function email(){ try{
+		if(!process.env.EMAIL){ return } // ONLY EMAIL IF DEVELOPER OPTS IN!!!
+		var address = process.env.EMAIL || "mark@gun.eco";
+		// you also have to specify your EMAIL_KEY gmail 2F' app's password (not reg) to send out.
+		require('./email').send({
+			text: "log attached",
+			from: address,
+			to: address,
+			subject: "GUN V8 LOG",
+			attachment:[{path: up+'v8.log', type:"text/plain", name:"v8.log"}]
+		}, noop);
+	}catch(e){} };
+}catch(e){}
+}());

+ 11 - 0
public/lib/gundb/lib/doll.js

@@ -41,4 +41,15 @@
 		})
 	})}
 	$.fn.appendTo = function(html){ return this.append(html, 0, 1) }
+	$.fn.parents = function(q, c){
+		var I = $(), l = I.tags, p = 'parentElement';
+		this.each(function(i, tag){
+			if(c){ (c = {})[p] = tag ; tag = c }
+			while(tag){ if((tag = tag[p]) && $(tag).is(q)){
+				l.push(tag); if(c){ return }
+			}}
+		});
+		return I;
+	}
+	$.fn.closest = function(q, c){ return this.parents(q, 1) }
 }());

+ 15 - 8
public/lib/gundb/lib/evict.js

@@ -5,26 +5,33 @@
 		this.to.next(root);
 		if(root.once){ return }
 		if(typeof process == 'undefined'){ return }
-		var util = process.memoryUsage;
+		var util = process.memoryUsage, heap;
 		if(!util){ return }
-		
-		ev.max = parseFloat(root.opt.memory || process.env.WEB_MEMORY || 1399) * 0.8; // max_old_space_size defaults to 1400 MB. Note: old space !== memory space though.
+		try{ heap = require('v8').getHeapStatistics }catch(e){}
+		if(!heap){ return }
+
+		ev.max = parseFloat(root.opt.memory || (heap().heap_size_limit / 1024 / 1024) || process.env.WEB_MEMORY || 1399) * 0.8; // max_old_space_size defaults to 1400 MB. Note: old space !== memory space though. // KEEPING USED_HEA_SIZE < HEAP_SIZE_LIMIT ONLY THING TO BE BELOW TO PREVENT CRASH!
 		
 		setInterval(check, 1000);
 		function check(){
-			var used = ev.used = util().rss / 1024 / 1024;
-			if(used < ev.max){ return }
-			setTimeout(GC, 1);
+			var used = util().rss / 1024 / 1024;
+			var hused = heap().used_heap_size / 1024 / 1024;
+			if(hused < ev.max && used < ev.max){ return }
+			//if(used < ev.max){ return }
+			console.STAT && console.STAT('evict memory:', hused.toFixed(), used.toFixed(), ev.max.toFixed());
+			GC();//setTimeout(GC, 1);
 		}
 		function GC(){
+			var S = +new Date;
 			var souls = Object.keys(root.graph||empty);
 			var toss = Math.ceil(souls.length * 0.01);
 			//var S = +new Date;
 			Gun.list.map(souls, function(soul){
 				if(--toss < 0){ return }
-				root.gun.get(soul).off();
+				root.$.get(soul).off();
 			});
-			//console.log(+new Date - S, 'gc');
+			root.dup.drop(1000 * 9); // clean up message tracker
+			console.STAT && console.STAT(S, +new Date - S, 'evict');
 		}
 		/*
 		root.on('in', function(msg){

+ 76 - 256
public/lib/gundb/lib/meta.js

@@ -14,113 +14,105 @@
     }
   }
   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:[]};
 		var k = m.key = {};
-		k.meta = {17:17, 91:17, 93:17, 224:17}; // ctrl met
-
-		function withMeta(eve){ return eve.metaKey || eve.ctrlKey }
-
+		k.meta = {17:17, 91:17, 93:17, 224:17, 18: 17}; // ALT added
+		function withMeta(eve){ return eve.metaKey || eve.ctrlKey || eve.altKey } // ALT added
+		var defaults = {
+			8: { // backspace: close root or go back on submenu
+				on: () => k.at == m.edit ? m.flip(false) : m.check('down', 'back')
+			},
+			27: { // esc: close and reset menu
+			  up: () => k.wipe()
+			}
+		}
 		k.down = function(eve){
-		  if(eve.repeat){ return }
 			var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode;
-
-			// ADDED
-			//if(!m.flip.is() && !k.meta[key]){ return } // cancel non-open events when closed TODO make optional
-			if(!k.meta[key] && withMeta(eve) && !k.at[key]) { return m.flip(false) } // cancel and close when no action and "meta key" held down (e.g. ctrl+c)
-
+		  if(eve.repeat){ return }
+			if(!k.meta[key] && withMeta(eve) && !k.at[key]) {
+			  return m.flip(false)
+		  } // cancel and close when no action and "meta key" held down (e.g. ctrl+c)
 			if(!eve.fake && key === k.last){ return }; k.last = key; // jussi: polyfilling eve.repeat?
 			if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length && !$(eve.target).closest('#meta').length){
-				if(!meta.flip.is() && !withMeta(eve)){ // cancel meta/hud during text input UNLESS hud is open OR cmd key is held down.
-					return;
-				}
-				//if(k.meta[key]){ k.down.meta = key = -1 }
-				//if(!k.down.meta){ return }
-				// hmmm?
-				//if(!k.meta[key] && !meta.flip.is()) return // aserwed
+		    if(meta.flip.is() && !withMeta(eve)) eve.preventDefault()
 			}
 			m.check('on', key, k.at || (k.at = m.edit));
-			if(k.meta[key]){
-		//		m.list(k.at.back || m.edit);
-		//		if(k.at){ m.flip() } //  && !k.at.back
-				m.flip()
-			}
+			if(k.meta[key]){ m.flip() }
 		}
+		k.down.keys = {} // currently pressed keys
 		k.up = function(eve){ var tmp;
 			var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode;
-			//if(!m.flip.is() && !k.meta[key]){ return } // ADDED cancel non-open events when closed TODO make optional
-		//	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 }
-		//	}
 			k.last = null;
-		//	if($(':focus').closest('#meta').length){ return }
 			m.check('up', key);
-			if(27 === eve.which){ k.wipe() } // -1 === key ||
+			if(k.meta[key] && m.check.fired){
+				m.close()
+			}
 		}
 		m.flip = function(tmp){
-			var board = $('#meta .meta-menu');
-			((tmp === false) || (!tmp && board.is(':visible')))?
-				board.addClass('meta-none')
-			: board.removeClass('meta-none');
+			((tmp === false) || (!tmp && m.ui.board.is(':visible')))?
+				m.close() : m.open();
+		}
+		m.open = function(){
+		  m.check.fired = null;
+		  m.ui.board.removeClass('meta-none');
+		}
+		m.close = function(){
+			Object.keys(k.down.keys).forEach((keyDown) => {
+			  m.check('up', keyDown);
+			})
+			m.ui.board.addClass('meta-none')
 		}
 		m.flip.is = function(){
-			return $('#meta .meta-menu').is(':visible');
+			return m.ui.board.is(':visible');
 		}
 		m.flip.wait = 500;
 		m.check = function(how, key, at){
-			at = k.at || m.edit;
+		  if(!m.flip.is() && !k.meta[key]){ return } // TEMP: cancel non-open events when closed TODO make optional
+		  at = k.at || m.edit;
 			var next = at[key];
 			if(!next){ return }
 			var tmp = k.eve || noop;
 			if(tmp.preventDefault){ tmp.preventDefault()} // prevent typing (etc) when action found
 			if(next[how]){
-				//if(tmp.fake && !next.fake){
-					//m.tap.next = next;
-				//} else {
 					next[how](m.eve);
-					meta.ui.blink()
-					/*if(k.at !== m.next && 'up' === how){
-						if(k.down.meta){ m.list(k.at = m.next) }
-						else { k.wipe() }
-					}*/
-				//}
+					meta.ui.blink();
+					m.check.fired = true;
+					if(how == 'up') delete k.down.keys[key]
+					else            k.down.keys[key] = 1;
 			}
 			if('up' == how){ return }
 			if(at != next){ next.back = at }
 			(k.combo || (k.combo = [])).push(key);
 			m.list(next, true);
 		}
+		function defaultSort(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;
+		}
 		m.list = function(at, opt){
 			if(!at){ return m.flip(false) }
-		//	m.ui.depth(m.key.combo ? m.key.combo.length : 0)
 			var l = [];
-			$.each(at, function(i,k){ 'back' != i && k.combo && k.name && l.push(k) });
+			$.each(at, function(i,k){ 'back' != i && k && 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;
-			});
+			if(at.sort !== null){ l = l.sort(at.sort || defaultSort) }
 			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($('<li>').text(k.name).data(k));
+			  var $li = $('<li>').text(k.name).data(k)
+				$ul.append($li);
+				if(k.styles) meta.ui.iniline($li[0], k.styles);
 			});
 			if(opt){ m.flip(true) }
 			$ul.append($('<li>').html('&larr;').on('click', function(){
-		//	  m.key.combo.pop()
 				m.list(at.back);
 			}));
 		}
@@ -140,7 +132,6 @@
 			$put.focus();
 		}
 		k.wipe = function(opt){
-		//	k.down.meta = false;
 			k.combo = [];
 			if(!opt){ m.flip(false) }
 			m.list(k.at = m.edit);
@@ -151,19 +142,19 @@
 				.or($(document.elementFromPoint(meta.tap.x||0, meta.tap.y||0)));
 			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));
+		meta.edit = function(e){
+			var path = [];
+			$.each(e.combo || (e.combo = []), function(i,k){
+				if(!k || !k.length){ if('number' == typeof k){ path.push(k) } return }
+				path.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(',');
+			var at = meta.edit, l = e.combo.length;
+			$.each(path, function(i,k){ at = at[k] = at[k] || Object.create(defaults) });
+		  $.extend(at, e) // fixes overwriting when sub action is defined before parent
+			e.combow = path.join(','); // deprecate?
 			m.list(k.at || meta.edit);
 		}
-
-
+		$.extend(meta.edit, defaults)
 	})(USE, './metaCore');
 	;USE(function(module){
 		/* UI */
@@ -187,6 +178,7 @@
 		$m.append($('<span>').html('&#9776;').addClass('meta-start'));
 		$m.append($('<div>').addClass('meta-menu meta-none').append('<ul>'));
 		$(document.body).append($m);
+		meta.ui.board = $('.meta-menu', $m);
 		css({
 			'#meta': {
 				display: 'block',
@@ -196,7 +188,6 @@
 				background: 'white',
 				'font-size': '18pt',
 				'font-family': 'Tahoma, arial',
-				//'box-shadow': '0px 0px 1px #000044',
 				'border-radius': '1em',
 				'text-align': 'center',
 				'z-index': 999999,
@@ -204,7 +195,6 @@
 				padding: 0,
 				width: '2em',
 				height: '2em',
-				opacity: 0.7,
 				outline: 'none',
 				color: '#000044',
 				overflow: 'visible',
@@ -232,31 +222,28 @@
 			},
 			'#meta .meta-menu ul li': {
 				display: 'block',
+				'float': 'right',
 				background: 'white',
+				opacity: 0.7,
 				padding: '0.5em 1em',
 				'border-radius': '1em',
 				'margin-left': '0.25em',
 				'margin-top': '0.25em',
-				'float': 'right',
 				'cursor':  'pointer'
 			},
+			'#meta .meta-menu ul li:hover': {
+				opacity: 1
+			},
 			'#meta a': {color: 'black'},
-		//	'#meta:hover': {opacity: 1},
 			'#meta .meta-menu ul:before': {
 				content: "' '",
 				display: 'block',
 				'min-height': '15em',
 				height: '50vh'
 			},
-		//	'#meta li': {
-		//		background: 'white',
-		//		padding: '0.5em 1em',
-		//		'border-radius': '1em',
-		//		'margin-left': '0.25em',
-		//		'margin-top': '0.25em',
-		//		'float': 'right'
-		//	},
-		//	'#meta:hover .meta-menu': {display: 'block'}
+			'#meta .meta-start': {
+				cursor: 'pointer'
+			}
 		});
 		function css(css){
 			var tmp = '';
@@ -271,156 +258,10 @@
 			tag.innerHTML = tmp;
 			$m.append(tag)
 		}
-		//}catch(e){}
-
-
-	})(USE, './metaUI');
-	;USE(function(module){
-
-		// include basic text editing by default.
-		var monotype = window.monotype || function(){console.log("monotype needed")};
-		var m = meta;
-		m.text = {zws: '&#8203;'};
-		m.text.on = function(eve){ var tmp;
-			if($((eve||{}).target).closest('#meta').length){ return }
-			m.text.range = null;
-			if(!(m.text.copy()||'').trim()){
-				m.flip(false);
-				m.list(m.text.it);
-				return;
-			}
-			m.text.range = monotype((eve||{}).target);
-			m.text.it.on(eve);
+		meta.ui.iniline = function(el, cssObj){
+			for(var k in cssObj) { el.style[k] = cssObj[k]; }
 		}
-		m.text.copy = function(tmp){
-			return ((tmp = window.getSelection) && tmp().toString()) ||
-				((tmp = document.selection) && tmp.createRange().text) || '';
-		}
-		m.text.editor = function(opt, as){ var tmp;
-			if(!opt){ return }
-			opt = (typeof opt == 'string')? {edit: opt} : opt.tag? opt : {tag: opt};
-			var r = opt.range = opt.range || m.text.range || monotype(), cmd = opt.edit;
-			as = opt.as = opt.as || as;
-			if(cmd && document.execCommand){
-				r.restore();
-				if(document.execCommand(cmd, null, as||null)){
-					if(m.text.range){ m.text.range = monotype() }
-					return meta.flip(false); // ADDED meta.flip
-				}
-			}
-			if(!opt.tag){ return }
-			opt.tag = $(opt.tag);
-			opt.name = opt.name || opt.tag.prop('tagName');
-			if((tmp = $(r.get()).closest(opt.name)).length){
-				if(r.s === r.e){
-					tmp.after(m.text.zws);
-					r = r.select(monotype.next(tmp[0]),1);
-				} else {
-					tmp.contents().unwrap(opt.name);
-				}
-			} else
-			if(r.s === r.e){
-				r.insert(opt.tag);
-				r = r.select(opt.tag);
-			} else {
-				r.wrap(opt.tag);
-			}
-			r.restore();
-			opt.range = null;
-			if(m.text.range){ m.text.range = monotype() }
-		}
-		meta.edit(meta.text.it = {combo: [-1], on: function(){ m.list(this, true) }, back: meta.edit}); // -1 is key for typing.
-		meta.text.it[-1] = meta.text.it;
-		meta.edit({
-			name: "Bold",
-			combo: [-1,'B'], fake: -1,
-			on: function(eve){
-				meta.text.editor('bold');
-			},
-			up: function(){}
-		});
-		meta.edit({
-			name: "Italic",
-			combo: [-1,'I'], fake: -1,
-			on: function(eve){
-				meta.text.editor('italic');
-			},
-			up: function(){}
-		});
-		/*meta.edit({
-			name: "Underline",
-			combo: [-1,'U'], fake: -1,
-			on: function(eve){
-				meta.text.editor('underline');
-			},
-			up: function(){}
-		});*/
-		meta.edit({
-			name: "linK",
-			combo: [-1,'K'], fake: -1,
-			on: function(eve){
-				var range = meta.text.range || monotype();
-				meta.ask('Paste or type link...', function(url){
-					meta.text.editor({tag: $('<a href="'+url+'">link</a>'), edit: url? 'createLink' : 'unlink', as: url, range: range});
-				})
-			}
-		});
-		//meta.edit({name: "aliGn", combo: [-1,'G']}); // MOVE TO ADVANCED MENu!
-		meta.edit({
-			name: "Left",
-			combo: [-1,'G','L'], fake: -1,
-			on: function(eve){ meta.text.editor('justifyLeft') },
-			up: function(){}
-		});
-		meta.edit({
-			name: "Right",
-			combo: [-1,'G','R'], fake: -1,
-			on: function(eve){ meta.text.editor('justifyRight') },
-			up: function(){ }
-		});
-		meta.edit({
-			name: "Middle",
-			combo: [-1,'G','M'], fake: -1,
-			on: function(eve){ meta.text.editor('justifyCenter') },
-			up: function(){ }
-		});
-		meta.edit({
-			name: "Justify",
-			combo: [-1,'G','J'], fake: -1,
-			on: function(eve){ meta.text.editor('justifyFull') },
-			up: function(){}
-		});
-		// Align Number
-		// Align Points
-		// Align Strike
-		meta.edit({name: "Size", combo: [-1,'S'], on: function(){ m.list(this, true) }});
-		meta.edit({
-			name: "Small",
-			combo: [-1,'S','S'], fake: -1,
-			on: function(eve){ meta.text.editor('fontSize', 2) },
-			up: function(){ }
-		});
-		meta.edit({
-			name: "Normal",
-			combo: [-1,'S','N'], fake: -1,
-			on: function(eve){ meta.text.editor('fontSize', 5) },
-			up: function(){}
-		});
-		meta.edit({
-			name: "Header",
-			combo: [-1,'S','H'], fake: -1,
-			on: function(eve){ meta.text.editor('fontSize', 6) },
-			up: function(){}
-		});
-		meta.edit({
-			name: "Title",
-			combo: [-1,'S','T'], fake: -1,
-			on: function(eve){ meta.text.editor('fontSize', 7) },
-			up: function(){}
-		});
-
-
-	})(USE, './metaText');
+	})(USE, './metaUI');
 	;USE(function(module){
 		var m = meta, k = m.key;
 		$(window).on('focus', k.wipe.bind(null, false)); // .on('blur', k.wipe.bind(null, false))
@@ -430,42 +271,21 @@
 			m.tap.y = eve.pageY||0;
 			m.tap.on = $(eve.target);
 		})
-		// Setting m.tap.edit has been commented, so should never end up here?
-		//.on('mousedown touchstart', function(eve){
-		//	var tmp = m.tap.edit;
-		//	if(!tmp || !tmp.on){ return }
-		//	tmp.on(eve);
-		//	m.tap.edit = null;
-		//});
-
-		//$(document).on('touchstart', '#meta .meta-start', function(eve){ m.tap.stun = true });
-
 		var [start, end] = 'ontouchstart' in window
 												? ['touchstart', 'touchend']
 												: ['mousedown', 'mouseup']
-
 		$(document).on(start, '#meta .meta-menu li', function(eve){
 			var combo = $(this).data().combo;
-			eve.fake = eve.which = combo && combo.slice(-1)[0].charCodeAt(0);
+			eve.fake = eve.which = combo && combo.slice(-1)[0].toUpperCase().charCodeAt(0);
 			eve.tap = true;
 			k.down(eve);
 			$(document).one(end, () => 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;
-		//	k.down(eve);
-		//	k.up(eve);
 		});
 		$(document).on('keydown', k.down).on('keyup', k.up);
-
-		$('#meta').on('click', function(ev) {
+		$('#meta').on(start, function(ev) {
 		  if (ev.target.tagName == 'LI' || ev.target.tagName == 'UL') return
 			meta.flip()
 		})
-
-		//$(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.text.on);
-
-
 	})(USE, './metaEvents');
 }());

+ 3 - 4
public/lib/gundb/lib/multicast.js

@@ -22,11 +22,10 @@ Gun.on('create', function(root){
   socket.bind({port: udp.port, exclusive: true}, function(){
     socket.setBroadcast(true);
     socket.setMulticastTTL(128);
-    try{ socket.addMembership(udp.address); }catch(e){}
   });
 
   socket.on("listening", function(){
-    try { socket.addMembership(udp.address) }catch(e){ return }
+    try { socket.addMembership(udp.address) }catch(e){ console.error(e); return; }
     udp.peer = {id: udp.address + ':' + udp.port, wire: socket};
 
     udp.peer.say = function(raw){
@@ -60,7 +59,7 @@ Gun.on('create', function(root){
 
     var url = 'http://' + info.address + ':' + (port || (opt.web && opt.web.address()||{}).port) + '/gun';
     if(root.opt.peers[url]){ return }
-  
+
     //console.log('discovered', url, message, info);
     root.$.opt(url);
 
@@ -84,7 +83,7 @@ Gun.on('create', function(root){
     }
     if((tmp = root.stats) && (tmp = tmp.gap) && info){ (tmp.near || (tmp.near = {}))[info.address] = info.port || 1 } // STATS!
     if(check.on || id === pid){ return }
-    root.on('out', check.on = say);
+    root.on('out', check.on = say); // TODO: MULTICAST NEEDS TO BE CHECKED FOR NEW CODE SYSTEM!!!!!!!!!!
   }
 
   setInterval(check, 1000 * 1);

+ 4 - 1
public/lib/gundb/lib/open.js

@@ -5,6 +5,7 @@ Gun.chain.open = function(cb, opt, at){
 	opt.doc = opt.doc || {};
 	opt.ids = opt.ids || {};
 	opt.any = opt.any || cb;
+	opt.meta = opt.meta || false;
 	opt.ev = opt.ev || {off: function(){
 		Gun.obj.map(opt.ev.s, function(e){
 			if(e){ e.off() }
@@ -12,7 +13,9 @@ Gun.chain.open = function(cb, opt, at){
 		opt.ev.s = {};
 	}, s:{}}
 	return this.on(function(data, key, ctx, ev){
-		delete ((data = Gun.obj.copy(data))||{})._;
+		if(opt.meta !== true){
+			delete ((data = Gun.obj.copy(data))||{})._;
+		}
 		clearTimeout(opt.to);
 		opt.to = setTimeout(function(){
 			if(!opt.any){ return }

+ 322 - 276
public/lib/gundb/lib/radisk.js

@@ -16,10 +16,14 @@
 		opt.code.from = opt.code.from || '!';
 		opt.jsonify = true;
 
+
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
 		function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
+		var timediate = (typeof setImmediate === "undefined")? setTimeout : setImmediate;
+		var puff = setTimeout.puff || timediate;
 		var map = Gun.obj.map;
-		var LOG = console.LOG;
+		var obj_empty = Gun.obj.empty;
+		var ST = 0;
 
 		if(!opt.store){
 			return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
@@ -39,112 +43,96 @@
 			1. Because writing to disk takes time, we should batch data to disk. This improves performance, and reduces potential disk corruption.
 			2. If a batch exceeds a certain number of writes, we should immediately write to disk when physically possible. This caps total performance, but reduces potential loss.
 		*/
-		var r = function(key, val, cb){
-			key = ''+key;
-			if(val instanceof Function){
+		var r = function(key, data, cb, tag, DBG){
+			if('function' === typeof data){
 				var o = cb || {};
-				cb = val;
-				var S; LOG && (S = +new Date);
-				val = r.batch(key);
-				LOG && opt.log(S, +new Date - S, 'rad mem');
-				if(u !== val){
-					cb(u, r.range(val, o), o);
-					if(atomic(val)){ return }
-					// if a node is requested and some of it is cached... the other parts might not be.
-				}
-				if(r.thrash.at){
-					val = r.thrash.at(key);
-					if(u !== val){
-						cb(u, r.range(val, o), o);
-						if(atomic(val)){ cb(u, val, o); return }
-						// if a node is requested and some of it is cached... the other parts might not be.
-					}
-				}
-				return r.read(key, cb, o);
+				cb = data;
+				r.read(key, cb, o, DBG || tag);
+				return;
 			}
-			r.batch(key, val);
-			if(cb){ r.batch.acks.push(cb) }
-			if(++r.batch.ed >= opt.batch){ return r.thrash() } // (2)
-			if(r.batch.to){ return }
-			//clearTimeout(r.batch.to); // (1) // THIS LINE IS EVIL! NEVER USE IT! ALSO NEVER DELETE THIS SO WE NEVER MAKE THE SAME MISTAKE AGAIN!
-			r.batch.to = setTimeout(r.thrash, opt.until || 1);
-		}
-
-		r.batch = Radix();
-		r.batch.acks = [];
-		r.batch.ed = 0;
-
-		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;
-			clearTimeout(r.batch.to);
-			r.batch = null;
-			r.batch = Radix();
-			r.batch.acks = [];
-			r.batch.ed = 0;
-			//console.debug(99); var ID = Gun.text.random(2), S = (+new Date); console.log("[[[[[[[[", ID, batch.acks.length);
-			r.save(batch, function(err, ok){
-				if(++i > 1){ opt.log('RAD ERR: Radisk has callbacked multiple times, please report this as a BUG at github.com/amark/gun/issues ! ' + i); return }
-				if(err){ opt.log('err', err) }
-				//console.debug(99); var TMP; console.log("]]]]]]]]", ID, batch.acks.length, (TMP = +new Date) - S, 'more?', thrash.more);
-				map(batch.acks, function(cb){ cb(err, ok) });
-				//console.log("][", +new Date - TMP, thrash.more);
-				thrash.at = null;
-				thrash.ing = false;
-				if(thrash.more){ thrash() }
-			});
+			//var tmp = (tmp = r.batch = r.batch || {})[key] = tmp[key] || {};
+			//var tmp = (tmp = r.batch = r.batch || {})[key] = data;
+			r.save(key, data, cb, tag, DBG);
 		}
-
-		/*
-			1. Find the first radix item in memory.
-			2. Use that as the starting index in the directory of files.
-			3. Find the first file that is lexically larger than it,
-			4. Read the previous file to that into memory
-			5. Scan through the in memory radix for all values lexically less than the limit.
-			6. Merge and write all of those to the in-memory file and back to disk.
-			7. If file too large, split. More details needed here.
-		*/
-		r.save = function(rad, cb){
-			var s = function Span(){};
-			s.find = function(tree, key){
-				if(key < s.start){ return }
-				s.start = key;
-				r.list(s.lex);
-				return true;
+		r.save = function(key, data, cb, tag, DBG){
+			var s = {key: key}, tags, f, d, q;
+			s.find = function(file){ var tmp;
+				s.file = file || (file = opt.code.from);
+				DBG && (DBG = DBG[file] = DBG[file] || {});
+				DBG && (DBG.sf = DBG.sf || +new Date);
+				if(tmp = r.disk[file]){ s.mix(u, tmp); return }
+				r.parse(file, s.mix, u, DBG);
 			}
-			s.lex = function(file){
-				file = (u === file)? u : decodeURIComponent(file);
-				if(!file || file > s.start){
-					s.mix(s.file || opt.code.from, s.start, s.end = file);
-					return true;
+			s.mix = function(err, disk){
+				DBG && (DBG.sml = +new Date);
+				DBG && (DBG.sm = DBG.sm || +new Date);
+				if(s.err = err || s.err){ cb(err); return } // TODO: HANDLE BATCH EMIT
+				var file = s.file = (disk||'').file || s.file, tmp;
+				if(!disk && file !== opt.code.from){ // corrupt file?
+					r.find.bad(file); // remove from dir list
+					r.save(key, data, cb, tag); // try again
+					return;
+				}
+				(disk = r.disk[file] || (r.disk[file] = disk || Radix())).file || (disk.file = file);
+				if(opt.compare){
+					data = opt.compare(disk(key), data, key, file);
+					if(u === data){ cb(err, -1); return } // TODO: HANDLE BATCH EMIT
 				}
-				s.file = file;
+				(s.disk = disk)(key, data);
+				if(tag){
+					(tmp = (tmp = disk.tags || (disk.tags = {}))[tag] || (tmp[tag] = r.tags[tag] || (r.tags[tag] = {})))[file] || (tmp[file] = r.one[tag] || (r.one[tag] = cb));
+					cb = null;
+				}
+				DBG && (DBG.st = DBG.st || +new Date);
+				if(disk.Q){ cb && disk.Q.push(cb); return } disk.Q = (cb? [cb] : []);
+				disk.to = setTimeout(s.write, opt.until);
 			}
-			s.mix = function(file, start, end){
-				s.start = s.end = s.file = u;
-				r.parse(file, function(err, disk){
-					if(err){ return cb(err) }
-					disk = disk || Radix();
-					Radix.map(rad, function(val, key){
-						if(key < start){ return }
-						if(end && end < key){ return s.start = key }
-						// PLUGIN: consider adding HAM as an extra layer of protection
-						disk(key, val); // merge batch[key] -> disk[key]
-					});
-					r.write(file, disk, s.next);
-				});
+			s.write = function(){
+				DBG && (DBG.sto = DBG.sto || +new Date);
+				var file = f = s.file, disk = d = s.disk;
+				q = s.q = disk.Q;
+				tags = s.tags = disk.tags;
+				delete disk.Q;
+				delete r.disk[file];
+				delete disk.tags;
+				r.write(file, disk, s.ack, u, DBG);
 			}
-			s.next = function(err, ok){
-				if(s.err = err){ return cb(err) }
-				if(s.start){ return Radix.map(rad, s.find) }
-				cb(err, ok);
+			s.ack = function(err, ok){
+				DBG && (DBG.sa = DBG.sa || +new Date);
+				DBG && (DBG.sal = q.length);
+				var ack, tmp;
+				// TODO!!!! CHANGE THIS INTO PUFF!!!!!!!!!!!!!!!!
+				for(var id in r.tags){
+					if(!r.tags.hasOwnProperty(id)){ continue } var tag = r.tags[id];
+					if((tmp = r.disk[f]) && (tmp = tmp.tags) && tmp[tag]){ continue }
+					ack = tag[f];
+					delete tag[f];
+					if(!obj_empty(tag)){ continue }
+					delete r.tags[tag];
+					ack && ack(err, ok);
+				}
+				!q && (q = '');
+				var l = q.length, i = 0;
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				// TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!!
+				var S = +new Date;
+				for(;i < l; i++){ (ack = q[i]) && ack(err, ok) }
+				console.STAT && console.STAT(S, +new Date - S, 'rad acks', ename(s.file));
+				console.STAT && console.STAT(S, q.length, 'rad acks #', ename(s.file));
 			}
-			Radix.map(rad, s.find);
-		}
+			cb || (cb = function(err, ok){ // test delete!
+				if(!err){ return }
+			});
+			r.find(key, s.find);
+    }
+    r.disk = {};
+    r.one = {};
+    r.tags = {};
 
 		/*
 			Any storage engine at some point will have to do a read in order to write.
@@ -152,66 +140,99 @@
 			Therefore it is unavoidable that a read will have to happen,
 			the question is just how long you delay it.
 		*/
-		r.write = function(file, rad, cb, o){
+		var RWC = 0;
+		r.write = function(file, rad, cb, o, DBG){
+			if(!rad){ cb('No radix!'); return }
 			o = ('object' == typeof o)? o : {force: o};
-			var f = function Fractal(){};
+			var f = function Fractal(){}, a, b;
 			f.text = '';
-			f.count = 0;
-			f.file = file;
-			f.each = function(val, key, k, pre){
-				//console.log("RAD:::", JSON.stringify([val, key, k, pre]));
-				if(u !== val){ f.count++ }
-				if(opt.pack <= (val||'').length){ return cb("Record too big!"), true }
-				var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n';
-				if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){
-					f.text = '';
-					f.limit = Math.ceil(f.count/2);
-					f.count = 0;
-					f.sub = Radix();
-					Radix.map(rad, f.slice);
-					return true;
-				}
-				f.text += enc;
-			}
+			f.file = file = rad.file || (rad.file = file);
+			if(!file){ cb('What file?'); return }
 			f.write = function(){
-				var tmp = ename(file);
-				var S; LOG && (S = +new Date);
-				opt.store.put(tmp, f.text, function(err){
-					LOG && opt.log(S, +new Date - S, "wrote disk", tmp);
-					if(err){ return cb(err) }
-					r.list.add(tmp, cb);
+				var text = rad.raw = f.text;
+				r.disk[file = rad.file || f.file || file] = rad;
+				var S = +new Date;
+				DBG && (DBG.wd = S);
+				r.find.add(file, function add(err){
+					DBG && (DBG.wa = +new Date);
+					if(err){ cb(err); return }
+					opt.store.put(ename(file), text, function safe(err, ok){
+						DBG && (DBG.wp = +new Date);
+						console.STAT && console.STAT(S, ST = +new Date - S, "wrote disk", JSON.stringify(file), ++RWC, 'total all writes.');
+						cb(err, ok || 1);
+						if(!rad.Q){ delete r.disk[file] } // VERY IMPORTANT! Clean up memory, but not if there is already queued writes on it!
+					});
 				});
 			}
-			f.slice = function(val, key){
-				if(key < f.file){ return }
-				if(f.limit < (++f.count)){
-					var name = f.file;
-					f.file = key;
-					f.count = 0;
-					r.write(name, f.sub, f.next, o);
-					return true;
+			f.split = function(){
+				var S = +new Date;
+				DBG && (DBG.wf = S);
+				f.text = '';
+				if(!f.count){ f.count = 0;
+					Radix.map(rad, function count(){ f.count++ }); // TODO: Perf? Any faster way to get total length?
 				}
-				f.sub(key, val);
-			}
-			f.next = function(err){
-				if(err){ return cb(err) }
+				DBG && (DBG.wfc = f.count);
+				f.limit = Math.ceil(f.count/2);
+				var SC = f.count;
+				f.count = 0;
+				DBG && (DBG.wf1 = +new Date);
 				f.sub = Radix();
-				if(!Radix.map(rad, f.slice)){
-					r.write(f.file, f.sub, cb, o);
+				Radix.map(rad, f.slice, {reverse: 1}); // IMPORTANT: DO THIS IN REVERSE, SO LAST HALF OF DATA MOVED TO NEW FILE BEFORE DROPPING FROM CURRENT FILE.
+				DBG && (DBG.wf2 = +new Date);
+				r.write(f.end, f.sub, f.both, o);
+				DBG && (DBG.wf3 = +new Date);
+				f.hub = Radix();
+				Radix.map(rad, f.stop);
+				DBG && (DBG.wf4 = +new Date);
+				r.write(rad.file, f.hub, f.both, o);
+				DBG && (DBG.wf5 = +new Date);
+				console.STAT && console.STAT(S, +new Date - S, "rad split", ename(rad.file), SC);
+				return true;
+			}
+			f.slice = function(val, key){
+				f.sub(f.end = key, val);
+				if(f.limit <= (++f.count)){ return true }
+			}
+			f.stop = function(val, key){
+				if(key >= f.end){ return true }
+				f.hub(key, val);
+			}
+			f.both = function(err, ok){
+				DBG && (DBG.wfd = +new Date);
+				if(b){ cb(err || b); return }
+				if(a){ cb(err, ok); return }
+				a = true;
+				b = err;
+			}
+			f.each = function(val, key, k, pre){
+				if(u !== val){ f.count++ }
+				if(opt.pack <= (val||'').length){ return cb("Data too big!"), true }
+				var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n';
+				if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){
+					return f.split();
 				}
+				f.text += enc;
 			}
-			if(opt.jsonify){ return r.write.jsonify(f, file, rad, cb, o) } // temporary testing idea
+			if(opt.jsonify){ r.write.jsonify(f, rad, cb, o, DBG); return } // temporary testing idea
 			if(!Radix.map(rad, f.each, true)){ f.write() }
 		}
 
-		r.write.jsonify = function(f, file, rad, cb, o){
+		r.write.jsonify = function(f, rad, cb, o, DBG){
 			var raw;
-			var S; LOG && (S = +new Date);
+			var S = +new Date;
+			DBG && (DBG.w = S);
 			try{raw = JSON.stringify(rad.$);
-			}catch(e){ return cb("Record too big!") }
-			LOG && opt.log(S, +new Date - S, "rad stringified JSON");
+			}catch(e){ cb("Cannot radisk!"); return }
+			DBG && (DBG.ws = +new Date);
+			console.STAT && console.STAT(S, +new Date - S, "rad stringified JSON");
 			if(opt.chunk < raw.length && !o.force){
-				if(Radix.map(rad, f.each, true)){ return }
+				var c = 0;
+				Radix.map(rad, function(){
+					if(c++){ return true } // more than 1 item
+				});
+				if(c > 1){
+					return f.split();
+				}
 			}
 			f.text = raw;
 			f.write();
@@ -222,81 +243,85 @@
 			if(u === o.start && u === o.end){ return tree }
 			if(atomic(tree)){ return tree }
 			var sub = Radix();
-			Radix.map(tree, function(v,k){
-				sub(k,v);
-			}, o);
+			Radix.map(tree, function(v,k){ sub(k,v) }, o); // ONLY PLACE THAT TAKES TREE, maybe reduce API for better perf?
 			return sub('');
 		}
 
 		;(function(){
-			var Q = {};
-			r.read = function(key, cb, o){
+			r.read = function(key, cb, o, DBG){
 				o = o || {};
-				if(RAD && !o.next){ // cache
-					var S; LOG && (S = +new Date);
-					var val = RAD(key);
-					LOG && opt.log(S, +new Date - S, 'rad cached');
-					//if(u !== val){
-						//cb(u, val, o);
-						if(atomic(val)){ cb(u, val, o); return }
-						// if a node is requested and some of it is cached... the other parts might not be.
-					//}
+				var g = {key: key};
+				g.find = function(file){ var tmp;
+					g.file = file || (file = opt.code.from);
+					DBG && (DBG = DBG[file] = DBG[file] || {});
+					DBG && (DBG.rf = DBG.rf || +new Date);
+					if(tmp = r.disk[g.file = file]){ g.check(u, tmp); return }
+					r.parse(file, g.check, u, DBG);
 				}
-				o.span = (u !== o.start) || (u !== o.end); // is there a start or end?
-				var g = function Get(){};
-				g.lex = function(file){ var tmp;
-					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 && 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});
-							return true;
-						}
-						Q[g.file] = [{key: key, ack: cb, file: g.file, opt: o}];
-						if(!g.file){
-							g.it(null, u, {});
-							return true; 
-						}
-						r.parse(g.file, g.it);
-						return true;
+				g.get = function(err, disk, info){
+					DBG && (DBG.rgl = +new Date);
+					DBG && (DBG.rg = DBG.rg || +new Date);
+					if(g.err = err || g.err){ cb(err); return }
+					var file = g.file = (disk||'').file || g.file;
+					if(!disk && file !== opt.code.from){ // corrupt file?
+						r.find.bad(file); // remove from dir list
+						r.read(key, cb, o); // try again
+						return;
 					}
-					g.file = file;
-				}
-				g.it = function(err, disk, info){
-					if(g.err = err){ opt.log('err', err) }
-					g.info = info;
-					if(disk){ RAD = g.disk = disk }
-					disk = Q[g.file]; delete Q[g.file];
-					LOG && opt.log(S, +new Date - S, 'rad read in, ack', disk.length); S = +new Date;
-					map(disk, g.ack);
-					LOG && opt.log(S, +new Date - S, 'rad read acked');
-				}
-				g.ack = function(as){
-					if(!as.ack){ return }
-					var key = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(key), o), last = rad.last || Radix.map(rad, rev, revo);
-					o.parsed = (o.parsed || 0) + (info.parsed||0);
+					disk = r.disk[file] || (r.disk[file] = disk);
+					if(!disk){ cb(file === opt.code.from? u : "No file!"); return }
+					disk.file || (disk.file = file);
+					var data = r.range(disk(key), o);
+					DBG && (DBG.rr = +new Date);
+					o.unit = disk.unit;
 					o.chunks = (o.chunks || 0) + 1;
-					o.more = true;
-					if((!as.file) // if no more places to look
-					|| (!o.span && last === key) // if our key exactly matches the very last atomic record
-					|| (!o.span && last && last > key && 0 != last.indexOf(key)) // 'zach' may be lexically larger than 'za', but there still might be more, like 'zane' in the 'za' prefix bucket so do not end here.
-					){
-						o.more = u;
-						as.ack(g.err, data, o);
-						return 
+					o.parsed = (o.parsed || 0) + ((info||'').parsed||(o.chunks*opt.chunk));
+					o.more = 1;
+					o.next = u;
+					Radix.map(r.list, function next(v,f){
+						if(!v || file === f){ return }
+						o.next = f;
+						return 1;
+					}, o.reverse? {reverse: 1, end: file} : {start: file});
+					DBG && (DBG.rl = +new Date);
+					if(!o.next){ o.more = 0 }
+					if(o.next){
+						if(!o.reverse && (key < o.next && 0 != o.next.indexOf(key)) || (u !== o.end && (o.end || '\uffff') < o.next)){ o.more = 0 }
+						if(o.reverse && (key > o.next && 0 != key.indexOf(o.next)) || (u !== o.start && (o.start || '') > o.next)){ o.more = 0 }
 					}
-					if(u !== data){
-						as.ack(g.err, data, o); // more might be coming!
-						if(o.parsed >= o.limit){ return } // even if more, we've hit our limit, asking peer will need to make a new ask with a new starting point.
-					} 
-					o.next = as.file;
-					r.read(key, as.ack, o);
+					if(!o.more){ cb(g.err, data, o); return }
+					if(data){ cb(g.err, data, o) }
+					if(o.parsed >= o.limit){ return }
+					var S = +new Date;
+					DBG && (DBG.rm = S);
+					var next = o.next;
+					timediate(function(){
+						console.STAT && console.STAT(S, +new Date - S, 'rad more');
+						r.parse(next, g.check);
+					},0);
 				}
-				if(o.reverse){ g.lex.reverse = true }
-				LOG && (S = +new Date);
-				r.list(g.lex);
+				g.check = function(err, disk, info){
+					g.get(err, disk, info);
+					if(!disk || disk.check){ return } disk.check = 1;
+					var S = +new Date;
+					(info || (info = {})).file || (info.file = g.file);
+					Radix.map(disk, function(val, key){
+						// assume in memory for now, since both write/read already call r.find which will init it.
+						r.find(key, function(file){
+							if((file || (file = opt.code.from)) === info.file){ return }
+							var id = Gun.text.random(3);
+							puff(function(){
+							r.save(key, val, function ack(err, ok){
+								if(err){ r.save(key, val, ack); return } // ad infinitum???
+								// TODO: NOTE!!! Mislocated data could be because of a synchronous `put` from the `g.get(` other than perf shouldn't we do the check first before acking?
+								console.STAT && console.STAT("MISLOCATED DATA CORRECTED", id, ename(key), ename(info.file), ename(file));
+							});
+							},0);
+						})
+					});
+					console.STAT && console.STAT(S, +new Date - S, "rad check");
+				}
+				r.find(key, g.find); 
 			}
 			function rev(a,b){ return b }
 			var revo = {reverse: true};
@@ -310,18 +335,19 @@
 				with how much performance and scale we can get out of only one.
 				Then we can work on the harder problem of being multi-process.
 			*/
+			var RPC = 0;
 			var Q = {}, s = String.fromCharCode(31);
-			r.parse = function(file, cb, raw){ var q;
-				if(q = Q[file]){ return q.push(cb) } q = Q[file] = [cb];
-				var p = function Parse(){}, info = {};
-				p.disk = Radix();
+			r.parse = function(file, cb, raw, DBG){ var q;
+				if(!file){ return cb(); }
+				if(q = Q[file]){ q.push(cb); return } q = Q[file] = [cb];
+				var p = function Parse(){}, info = {file: file};
+				(p.disk = Radix()).file = file;
 				p.read = function(err, data){ var tmp;
-					LOG && opt.log(S, +new Date - S, 'read disk', ename(file));
+					DBG && (DBG.rpg = +new Date);
+					console.STAT && console.STAT(S, +new Date - S, 'read disk', JSON.stringify(file), ++RPC, 'total all parses.');
 					delete Q[file];
-					if((p.err = err) || (p.not = !data)){
-						return map(q, p.ack);
-					}
-					if(typeof data !== 'string'){
+					if((p.err = err) || (p.not = !data)){ p.map(q, p.ack); return }
+					if('string' !== typeof data){
 						try{
 							if(opt.pack <= data.length){
 								p.err = "Chunk too big!";
@@ -329,29 +355,55 @@
 								data = data.toString(); // If it crashes, it crashes here. How!?? We check size first!
 							}
 						}catch(e){ p.err = e }
-						if(p.err){ return map(q, p.ack) }
+						if(p.err){ p.map(q, p.ack); return }
 					}
 					info.parsed = data.length;
-
-					LOG && (S = +new Date);
-					if(opt.jsonify || '{' === data[0]){ // temporary testing idea
+					DBG && (DBG.rpl = info.parsed);
+					DBG && (DBG.rpa = q.length);
+					S = +new Date;
+					if(opt.jsonify || '{' === data[0]){
 						try{
-							var json = JSON.parse(data);
+							var json = JSON.parse(data); // TODO: this caused a out-of-memory crash!
 							p.disk.$ = json;
-							LOG && opt.log(S, +new Date - S, 'rad parsed JSON');
-							map(q, p.ack);
+							console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'rad parsed JSON');
+							DBG && (DBG.rpd = +new Date);
+							p.map(q, p.ack); // hmmm, v8 profiler can't see into this cause of try/catch?
 							return;
 						}catch(e){ tmp = e }
 						if('{' === data[0]){
 							p.err = tmp || "JSON error!";
-							return map(q, p.ack);
+							p.map(q, p.ack);
+							return;
 						}
 					}
-					LOG && (S = +new Date);
+					p.radec(err, data);
+				}
+				p.map = function(){
+					if(!q || !q.length){ return }
+					//var i = 0, l = q.length, ack;
+					var S = +new Date;
+					var err = p.err, data = p.not? u : p.disk;
+					var i = 0, ack; while(i < 9 && (ack = q[i++])){ ack(err, data, info) } // too much?
+					console.STAT && console.STAT(S, +new Date - S, 'rad packs', ename(file));
+					console.STAT && console.STAT(S, i, 'rad packs #', ename(file)); 
+					if(!(q = q.slice(i)).length){ return }
+					puff(p.map, 0);
+				}
+				p.ack = function(cb){
+					if(!cb){ return }
+					if(p.err || p.not){
+						cb(p.err, u, info);
+						return;
+					}
+					cb(u, p.disk, info);
+				}
+				p.radec = function(err, data){
+					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! ";
-						return map(q, p.ack);
+						p.map(q, p.ack);
+						return; 
 					}
 					while(tmp){
 						k = v = u;
@@ -370,9 +422,8 @@
 						if(u !== k && u !== v){ p.disk(pre.join(''), v) }
 						tmp = p.split(tmp[2]);
 					}
-					LOG && opt.log(S, +new Date - S, 'parsed RAD');
-					//cb(err, p.disk);
-					map(q, p.ack);
+					console.STAT && console.STAT(S, +new Date - S, 'parsed RAD');
+					p.map(q, p.ack);
 				};
 				p.split = function(t){
 					if(!t){ return }
@@ -385,81 +436,76 @@
 					l[2] = t.slice(i + o.i);
 					return l;
 				}
-				p.ack = function(cb){ 
-					if(!cb){ return }
-					if(p.err || p.not){ return cb(p.err, u, info) }
-					cb(u, p.disk, info);
-				}
-				var S; LOG && (S = +new Date);
-				if(raw){ return p.read(null, raw) }
+				if(r.disk){ raw || (raw = (r.disk[file]||'').raw) }
+				var S = +new Date, SM, SL;
+				DBG && (DBG.rp = S);
+				if(raw){ return puff(function(){ p.read(u, raw) }, 0) }
 				opt.store.get(ename(file), p.read);
+				// TODO: What if memory disk gets filled with updates, and we get an old one back?
 			}
 		}());
 
 		;(function(){
-			var dir, q, f = String.fromCharCode(28), ef = ename(f);
-			r.list = function(cb){
-				if(dir){
-					var tmp = {reverse: (cb.reverse)? 1 : 0};
-					Radix.map(dir, function(val, key){
-						return cb(key);
-					}, tmp) || cb();
+			var dir, f = String.fromCharCode(28), Q;
+			r.find = function(key, cb){
+				if(!dir){
+					if(Q){ Q.push([key, cb]); return } Q = [[key, cb]];
+					r.parse(f, init);
 					return;
 				}
-				if(q){ return q.push(cb) } q = [cb];
-				r.parse(f, r.list.init);
+				Radix.map(r.list = dir, function(val, key){
+					if(!val){ return }
+					return cb(key) || true;
+				}, {reverse: 1, end: key}) || cb(opt.code.from);
 			}
-			r.list.add = function(file, cb){
+			r.find.add = function(file, cb){
 				var has = dir(file);
-				if(has || file === ef){
-					return cb(u, 1);
-				}
-				dir(file, true);
-				cb.listed = (cb.listed || 0) + 1;
+				if(has || file === f){ cb(u, 1); return }
+				dir(file, 1);
+				cb.found = (cb.found || 0) + 1;
 				r.write(f, dir, function(err, ok){
-					if(err){ return cb(err) }
-					cb.listed = (cb.listed || 0) - 1;
-					if(cb.listed !== 0){ return }
+					if(err){ cb(err); return }
+					cb.found = (cb.found || 0) - 1;
+					if(0 !== cb.found){ return }
 					cb(u, 1);
 				}, true);
 			}
-			r.list.init = function(err, disk){
+			r.find.bad = function(file, cb){
+				dir(file, 0);
+				r.write(f, dir, cb||noop);
+			}
+			function init(err, disk){
 				if(err){
 					opt.log('list', err);
-					setTimeout(function(){ r.parse(f, r.list.init) }, 1000);
-					return;
-				}
-				if(disk){
-					r.list.drain(disk);
-					return;
-				}
-				if(!opt.store.list){
-					r.list.drain(Radix());
+					setTimeout(function(){ r.parse(f, init) }, 1000);
 					return;
 				}
+				if(disk){ drain(disk); return }
+				dir = dir || disk || Radix();
+				if(!opt.store.list){ drain(dir); return }
 				// import directory.
 				opt.store.list(function(file){
-					dir = dir || Radix();
-					if(!file){ return r.list.drain(dir) }
-					r.list.add(file, noop);
+					if(!file){ drain(dir); return }
+					r.find.add(file, noop);
 				});
 			}
-			r.list.drain = function(rad, tmp){
-				r.list.dir = dir = rad;
-				tmp = q; q = null;
-				Gun.list.map(tmp, function(cb){
-					r.list(cb);
+			function drain(rad, tmp){
+				dir = dir || rad;
+				dir.file = f;
+				tmp = Q; Q = null;
+				Gun.list.map(tmp, function(arg){
+					r.find(arg[0], arg[1]);
 				});
 			}
 		}());
 
+		try{ !Gun.window && require('./radmigtmp')(r) }catch(e){}
+
 		var noop = function(){}, RAD, u;
 		Radisk.has[opt.file] = r;
 		return r;
 	}
 
-
-
 	;(function(){
 		var _ = String.fromCharCode(31), u;
 		Radisk.encode = function(d, o, s){ s = s || _;

+ 29 - 14
public/lib/gundb/lib/radix.js

@@ -2,13 +2,14 @@
 
 	function Radix(){
 		var radix = function(key, val, t){
-			key = ''+key;
+			radix.unit = 0;
 			if(!t && u !== val){ 
-				radix.last = (key < radix.last)? radix.last : key;
+				radix.last = (''+key < radix.last)? radix.last : ''+key;
 				delete (radix.$||{})[_];
 			}
 			t = t || radix.$ || (radix.$ = {});
 			if(!key && Object.keys(t).length){ return t }
+			key = ''+key;
 			var i = 0, l = key.length-1, k = key[i], at, tmp;
 			while(!(at = t[k]) && i < l){
 				k += key[++i];
@@ -22,57 +23,71 @@
 					if(kk){
 						if(u === val){
 							if(ii <= l){ return }
-							return (tmp || (tmp = {}))[s.slice(ii)] = r;
+							(tmp || (tmp = {}))[s.slice(ii)] = r;
+							//(tmp[_] = function $(){ $.sort = Object.keys(tmp).sort(); return $ }()); // get rid of this one, cause it is on read?
+							return r;
 						}
 						var __ = {};
 						__[s.slice(ii)] = r;
 						ii = key.slice(ii);
 						('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val);
+						//(__[_] = function $(){ $.sort = Object.keys(__).sort(); return $ }());
 						t[kk] = __;
 						delete t[s];
+						//(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }());
 						return true;
 					}
 				})){
 					if(u === val){ return; }
 					(t[k] || (t[k] = {}))[''] = val;
+					//(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }());
 				}
 				if(u === val){
 					return tmp;
 				}
 			} else 
 			if(i == l){
-				if(u === val){ return (u === (tmp = at['']))? at : tmp }
+				//if(u === val){ return (u === (tmp = at['']))? at : tmp } // THIS CODE IS CORRECT, below is
+				if(u === val){ return (u === (tmp = at['']))? at : ((radix.unit = 1) && tmp) } // temporary help??
 				at[''] = val;
+				//(at[_] = function $(){ $.sort = Object.keys(at).sort(); return $ }());
 			} else {
 				if(u !== val){ delete at[_] }
+				//at && (at[_] = function $(){ $.sort = Object.keys(at).sort(); return $ }());
 				return radix(key.slice(++i), val, at || (at = {}));
 			}
 		}
 		return radix;
 	};
 
-	Radix.map = function map(radix, cb, opt, pre){ pre = pre || [];
+	Radix.map = function rap(radix, cb, opt, pre){ pre = pre || []; // TODO: BUG: most out-of-memory crashes come from here.
 		var t = ('function' == typeof radix)? radix.$ || {} : radix;
+		//!opt && console.log("WHAT IS T?", JSON.stringify(t).length);
 		if(!t){ return }
-		var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort, rev;
+		var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort, rev; // ONLY 17% of ops are pre-sorted!
 		//var keys = Object.keys(t).sort();
 		opt = (true === opt)? {branch: true} : (opt || {});
-		if(rev = opt.reverse){ keys = keys.slice().reverse() }
-		var start = opt.start, end = opt.end;
+		if(rev = opt.reverse){ keys = keys.slice(0).reverse() }
+		var start = opt.start, end = opt.end, END = '\uffff';
 		var i = 0, l = keys.length;
 		for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt;
 			if(!tree || '' === key || _ === key){ continue }
-			p = pre.slice(); p.push(key);
+			p = pre.slice(0); p.push(key);
 			pt = p.join('');
 			if(u !== start && pt < (start||'').slice(0,pt.length)){ continue }
-			if(u !== end && (end || '\uffff') < pt){ continue }
+			if(u !== end && (end || END) < pt){ continue }
 			if(rev){ // children must be checked first when going in reverse.
-				tmp = map(tree, cb, opt, p);
+				tmp = rap(tree, cb, opt, p);
 				if(u !== tmp){ return tmp }
 			}
 			if(u !== (tmp = tree[''])){
-				tmp = cb(tmp, pt, key, pre);
-				if(u !== tmp){ return tmp }
+				var yes = 1;
+				if(u !== start && pt < (start||'')){ yes = 0 }
+				if(u !== end && pt > (end || END)){ yes = 0 }
+				if(yes){
+					tmp = cb(tmp, pt, key, pre);
+					if(u !== tmp){ return tmp }
+				}
 			} else
 			if(opt.branch){
 				tmp = cb(u, pt, key, pre);
@@ -80,7 +95,7 @@
 			}
 			pre = p;
 			if(!rev){
-				tmp = map(tree, cb, opt, pre);
+				tmp = rap(tree, cb, opt, pre);
 				if(u !== tmp){ return tmp }
 			}
 			pre.pop();

+ 22 - 0
public/lib/gundb/lib/radmigtmp.js

@@ -0,0 +1,22 @@
+module.exports = function(r){
+	var Radix = require('./radix');
+	r.find('a', function(){
+		var l = [];
+		Radix.map(r.list, function(v,f){
+			if(!(f.indexOf('%1B') + 1)){ return }
+			if(!v){ return }
+			l.push([f,v]);
+		});
+		if(l.length){
+			console.log("\n! ! ! WARNING ! ! !\nRAD v0.2020.x has detected OLD v0.2019.x data & automatically migrating. Automatic migration will be turned OFF in future versions! If you are just developing/testing, we recommend you reset your data. Please contact us if you have any concerns.\nThis message should only log once.")
+		}
+		var f, v;
+		l.forEach(function(a){
+			f = a[0]; v = a[1];
+			r.list(decodeURIComponent(f), v);
+			r.list(f, 0);
+		});
+		if(!f){ return }
+		r.find.bad(f);
+	})
+};

+ 16 - 3
public/lib/gundb/lib/rfs.js

@@ -10,22 +10,27 @@ function Store(opt){
 		return Store[opt.file];
 	}
 	Store[opt.file] = store;
+	var puts = {};
 
+	// TODO!!! ADD ZLIB INFLATE / DEFLATE COMPRESSION!
 	store.put = function(file, data, cb){
+		puts[file] = data;
 		var random = Math.random().toString(36).slice(-3);
 		var tmp = opt.file+'-'+file+'-'+random+'.tmp';
 		fs.writeFile(tmp, data, function(err, ok){
+			delete puts[file];
 			if(err){ return cb(err) }
 			move(tmp, opt.file+'/'+file, cb);
 		});
 	};
-	store.get = function(file, cb){
+	store.get = function(file, cb){ var tmp; // this took 3s+?
+		if(tmp = puts[file]){ cb(u, tmp); return }
 		fs.readFile(opt.file+'/'+file, function(err, data){
 			if(err){
 				if('ENOENT' === (err.code||'').toUpperCase()){
-					return cb(null);
+					return cb();
 				}
-				opt.log("ERROR:", err)
+				opt.log("ERROR:", err);
 			}
 			cb(err, data);
 		});
@@ -56,6 +61,14 @@ function Store(opt){
 			}
 		});
 	};
+
+	store.list = function(cb, match, params, cbs){
+		var dir = fs.readdirSync(opt.file);
+		dir.forEach(function(file){
+			cb(file);
+		})
+		cb();
+	};
 	
 	return store;
 }

+ 23 - 0
public/lib/gundb/lib/rfsmix.js

@@ -0,0 +1,23 @@
+module.exports = function(opt, store){
+	var rfs = require('./rfs')(opt);
+	var p = store.put;
+	var g = store.get;
+	store.put = function(file, data, cb){
+		var a, b, c = function(err, ok){
+			if(b){ return cb(err || b) }
+			if(a){ return cb(err, ok) }
+			a = true;
+			b = err;
+		}
+		p(file, data, c); // parallel
+		rfs.put(file, data, c); // parallel
+	}
+	store.get = function(file, cb){
+		rfs.get(file, function(err, data){
+			//console.log("rfs3 hijacked", file);
+			if(data){ return cb(err, data) }
+			g(file, cb);
+		});
+	}
+	return store;
+}

+ 16 - 13
public/lib/gundb/lib/rs3.js

@@ -52,24 +52,26 @@ function Store(opt){
 	store.put = function(file, data, cb){
 		var params = {Bucket: opts.bucket, Key: file, Body: data};
 		//console.log("RS3 PUT ---->", (data||"").slice(0,20));
-		Gun.obj.del(c.g, file);
-		Gun.obj.del(c.l, 1);
-    s3.putObject(params, cb);
+		c.p[file] = data;
+		delete c.g[file];//Gun.obj.del(c.g, file);
+		delete c.l[1];//Gun.obj.del(c.l, 1);
+    s3.putObject(params, function(err, ok){
+    	delete c.p[file];
+    	cb(err, 's3');
+    });
 	};
-	store.get = function(file, cb){
-		if(c.g[file]){ return c.g[file].push(cb) }
+	store.get = function(file, cb){ var tmp;
+		if(tmp = c.p[file]){ cb(u, tmp); return }
+		if(tmp = c.g[file]){ tmp.push(cb); return }
 		var cbs = c.g[file] = [cb];
 		var params = {Bucket: opts.bucket, Key: file||''};
 		//console.log("RS3 GET ---->", file);
-		s3.getObject(params, function(err, ack){
+		s3.getObject(params, function got(err, ack){
+			if(err && 'NoSuchKey' === err.code){ err = u }
 			//console.log("RS3 GOT <----", err, file, cbs.length, ((ack||{}).Body||'').toString().slice(0,20));
-			Gun.obj.del(c.g, file);
-			var data, cbe = function(cb){
-				if(!ack){ cb(null); return; }
-				cb(err, data);
-			};
-			data = (ack||{}).Body; //if(data = (ack||{}).Body){ data = data.toString() }
-			Gun.obj.map(cbs, cbe);
+			delete c.g[file];//Gun.obj.del(c.g, file);
+			var data, data = (ack||'').Body;
+			var i = 0, cba; while(cba = cbs[i++]){ cba && cba(err, data) }//Gun.obj.map(cbs, cbe);
 		});
 	};
 	store.list = function(cb, match, params, cbs){
@@ -98,6 +100,7 @@ function Store(opt){
     });
 	};
 	//store.list(function(){ return true });
+	require('./rfsmix')(opt, store); // ugly, but gotta move fast for now.
 	return store;
 }
 

+ 13 - 9
public/lib/gundb/lib/serve.js

@@ -8,10 +8,10 @@ function CDN(dir){
 		req.url = (req.url||'').replace(dot,'').replace(slash,'/');
 		if(serve(req, res)){ return } // filters GUN requests!
 		fs.createReadStream(path.join(dir, req.url)).on('error',function(tmp){ // static files!
-			try{ tmp = fs.readFileSync(path.join(dir, 'index.html')) }catch(e){}
-			try{ res.writeHead(200, {'Content-Type': 'text/html'});
-			res.end(tmp+''); }catch(e){} // or default to index
-		}).pipe(res); // stream
+			fs.readFile(path.join(dir, 'index.html'), function(err, tmp){
+				try{ res.writeHead(200, {'Content-Type': 'text/html'});
+				res.end(tmp+''); }catch(e){} // or default to index
+		})}).pipe(res); // stream
 	}
 }
 
@@ -27,13 +27,17 @@ function serve(req, res, next){ var tmp;
 		return true;
 	}
 	if(0 <= req.url.indexOf('gun/')){
-		res.writeHead(200, {'Content-Type': 'text/javascript'});
-		var path = __dirname + '/../' + req.url.split('/').slice(2).join('/'), file;
-		try{file = require('fs').readFileSync(path)}catch(e){}
-		if(file){
-			res.end(file);
+		var path = __dirname + '/../' + req.url.split('/').slice(2).join('/');
+		if('/' === path.slice(-1)){
+			fs.readdir(path, function(err, dir){ res.end((dir || (err && 404))+'') });
 			return true;
 		}
+		var S = +new Date;
+		var rs = fs.createReadStream(path);
+		rs.on('open', function(){ console.STAT && console.STAT(S, +new Date - S, 'serve file open'); rs.pipe(res) });
+		rs.on('error', function(err){ res.end(404+'') });
+		rs.on('end', function(){ console.STAT && console.STAT(S, +new Date - S, 'serve file end') });
+		return true;
 	}
 	if((tmp = req.socket) && (tmp = tmp.server) && (tmp = tmp.route)){ var url;
 		if(tmp = tmp[(((req.url||'').slice(1)).split('/')[0]||'').split('.')[0]]){

+ 4 - 6
public/lib/gundb/lib/server.js

@@ -2,11 +2,10 @@
 	var Gun = require('../gun'), u;
 	Gun.serve = require('./serve');
 	//process.env.GUN_ENV = process.env.GUN_ENV || 'debug';
-	console.LOG = true; // only temporarily, REVERT THIS IN FUTURE!
+	//console.LOG = {}; // only do this for dev.
 	Gun.on('opt', function(root){
-		if(u === root.opt.super){
-			root.opt.super = true;
-		}
+		if(u === root.opt.super){ root.opt.super = true }
+		if(u === root.opt.faith){ root.opt.faith = true } // HNPERF: This should probably be off, but we're testing performance improvements, please audit.
 		root.opt.log = root.opt.log || Gun.log;
 		this.to.next(root);
 	})
@@ -16,10 +15,9 @@
 	require('./wire');
 	try{require('../sea');}catch(e){}
 	try{require('../axe');}catch(e){}
-	require('./file');
+	//require('./file');
 	require('./evict');
 	require('./multicast');
 	require('./stats');
-	if('debug' === process.env.GUN_ENV){ require('./debug') }
 	module.exports = Gun;
 }());

+ 10 - 5
public/lib/gundb/lib/stats.js

@@ -26,10 +26,13 @@ Gun.on('opt', function(root){
 		root.stats.up.count = (root.stats.up.count || 0) + 1;
 		root.stats.stay = root.stats.stay || {};
 		root.stats.gap = {};
+		root.stats.over = +new Date;
 	},1);
 	setInterval(function(){
 		if(!root.stats){ root.stats = {} }
+		var S = +new Date;
 		var stats = root.stats, tmp;
+		stats.over = S - (stats.over||S);
 		(stats.up||{}).time = process.uptime();
 		stats.memory = process.memoryUsage() || {};
 		stats.memory.totalmem = os.totalmem();
@@ -43,18 +46,19 @@ Gun.on('opt', function(root){
 		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}};
+			stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d}, 'out': {count: dam.say.c, done: dam.say.d}};
 			dam.hear.c = dam.hear.d = dam.say.c = dam.say.d = 0; // reset
 			stats.peers.time = dam.bye.time || 0;
-			dam.hear.long = [];
 		}
 		var rad = root.opt.store; rad = rad && rad.stats;
 		if(rad){
 			stats.rad = rad;
 			root.opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // reset
 		}
-
-		fs.writeFile(__dirname+'/../stats.'+root.opt.file, JSON.stringify(stats, null, 2), function(err){});
+		console.STAT && console.STAT(S, +new Date - S, 'stats');
+		S = +new Date;
+		fs.writeFile(__dirname+'/../stats.'+root.opt.file, JSON.stringify(stats, null, 2), function(err){ console.STAT && console.STAT(S, +new Date - S, 'stats stash') });
+		stats.over = S;
 		stats.gap = {};
 	}, 1000 * 15);
 	Object.keys = Object.keys || function(o){ return Gun.obj.map(o, function(v,k,t){t(k)}) }
@@ -62,11 +66,12 @@ Gun.on('opt', function(root){
 
 
 var log = Gun.log, all = {}, max = 1000;
-Gun.log = function(a,b,c,d){
+Gun.log = console.STAT = 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;
 	}
+	if(!console.LOG || log.off){ return }
 	return log.apply(Gun, arguments);
 }

+ 70 - 55
public/lib/gundb/lib/store.js

@@ -7,51 +7,32 @@ Gun.on('create', function(root){
     if(false === opt.radisk){ return }
     var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
     var Radix = Radisk.Radix;
-    var LOG = console.LOG;
+    var ST = 0;
  
     opt.store = opt.store || (!Gun.window && require('./rfs')(opt));
-    var rad = Radisk(opt), esc = String.fromCharCode(27);
+    var dare = Radisk(opt), esc = String.fromCharCode(27);
  
     root.on('put', function(msg){
         this.to.next(msg);
-        var id = msg['#'] || Gun.text.random(3), track = !msg['@'], acks = track? 0 : u; // only ack non-acks.
-        var got = (msg._||empty).rad, now = Gun.state();
-        var S = (+new Date); // STATS!
-        Gun.graph.is(msg.put, null, function(val, key, node, soul){
-            if(!track && got){
-                var at = (root.next||empty)[soul];
-                if(!at){ return }
-                if(u !== got['.']){ at = (at.next||empty)[key] }
-                if(!at){ return }
-                at.rad = now;
-                return;
-            }
-            if(track){ ++acks }
-            //console.log('put:', soul, key, val);
-            val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc);
-            rad(soul+esc+key, val, (track? ack : u));
-        });
-        LOG && Gun.log(S, +new Date - S, 'put loop');
-        function ack(err, ok){
-            acks--;
-            if(ack.err){ return }
-            if(ack.err = err){
-                //Gun.log(); //try{opt.store.stats.put.err = err}catch(e){} // STATS!
-                root.on('in', {'@': id, err: err});
-                return;
-            }
-            if(acks){ return }
-            try{opt.store.stats.put.time[statp % 50] = (+new Date) - S; ++statp;
-                opt.store.stats.put.count++;
-            }catch(e){} // STATS!
-            //console.log(+new Date - S, 'put'); S = +new Date;
-            root.on('in', {'@': id, ok: 1});
-            //console.log(S, +new Date - S, 'put sent');
-        }
+        if((msg._||'').rad){ return } // don't save what just came from a read.
+        var id = msg['#'], put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], tmp;
+        var DBG = (msg._||'').DBG; DBG && (DBG.sp = DBG.sp || +new Date);
+        var lot = (msg._||'').lot||''; count[id] = (count[id] || 0) + 1; 
+        var S = (msg._||'').RPS || ((msg._||'').RPS = +new Date);
+        dare(soul+esc+key, {':': val, '>': state}, dare.one[id] || function(err, ok){
+            DBG && (DBG.spd = DBG.spd || +new Date);
+            console.STAT && console.STAT(S, +new Date - S, 'put');
+            if(!err && count[id] !== lot.s){ console.log(err = "Disk count not same as ram count."); console.STAT && console.STAT(+new Date, lot.s - count[id], 'put ack != count') } delete count[id];
+            if(err){ root.on('in', {'@': id, err: err, DBG: DBG}); return }
+            root.on('in', {'@': id, ok: ok, DBG: DBG});
+        }, id, DBG && (DBG.r = DBG.r || {}));
+        DBG && (DBG.sps = DBG.sps || +new Date);
     });
+    var count = {}, obj_empty = Gun.obj.empty;
  
     root.on('get', function(msg){
         this.to.next(msg);
+        var ctx = msg._||'', DBG = ctx.DBG = msg.DBG; DBG && (DBG.sg = +new Date);
         var id = msg['#'], get = msg.get, soul = msg.get['#'], has = msg.get['.']||'', o = {}, graph, lex, key, tmp, force;
         if('string' == typeof soul){
             key = soul;
@@ -78,52 +59,86 @@ Gun.on('create', function(root){
             o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1;
         }
         if(has['-'] || (soul||{})['-']){ o.reverse = true }
-        if((tmp = (root.next||empty)[soul]) && tmp.put){
+        if((tmp = (root.next||'')[soul]) && tmp.put){
             if(o.atom){
-                tmp = (tmp.next||empty)[o.atom] ;
+                tmp = (tmp.next||'')[o.atom] ;
                 if(tmp && tmp.rad){ return }
             } else
-            if(tmp && tmp.rad){
-                return;
-            }
+            if(tmp && tmp.rad){ return }
         }
-        var S = (+new Date); // STATS!
-        rad(key||'', function(err, data, o){
+        var now = Gun.state();
+        var S = (+new Date), C = 0, SPT = 0; // STATS!
+        DBG && (DBG.sgm = S);
+        dare(key||'', function(err, data, info){
+            DBG && (DBG.sgr = +new Date);
+            DBG && (DBG.sgi = info);
             try{opt.store.stats.get.time[statg % 50] = (+new Date) - S; ++statg;
                 opt.store.stats.get.count++;
                 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.
-            LOG && 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(u === data && info.chunks > 1){ return } // if we already sent a chunk, ignore ending empty responses. // this causes tests to fail.
+            console.STAT && console.STAT(S, +new Date - S, 'got', JSON.stringify(key)); S = +new Date;
+            info = info || '';
+            var va, ve;
+            if(info.unit && data && u !== (va = data[':']) && u !== (ve = data['>'])){ // new format
+                var tmp = key.split(esc), so = tmp[0], ha = tmp[1];
+                (graph = graph || {})[so] = Gun.state.ify(graph[so], ha, ve, va, so);
+                root.$.get(so).get(ha)._.rad = now;
+                // REMEMBER TO ADD _rad TO NODE/SOUL QUERY!
+            } else
+            if(data){ // old code path
                 if(typeof data !== 'string'){
                     if(o.atom){
                         data = u;
                     } else {
-                        Radix.map(data, each) 
+                        Radix.map(data, each); // IS A RADIX TREE, NOT FUNCTION!
                     }
                 }
                 if(!graph && data){ each(data, '') }
+                // TODO: !has what about soul lookups?
+                if(!o.atom && !has & 'string' == typeof soul && !o.limit && !o.more){
+                    root.$.get(soul)._.rad = now;
+                }
             }
-            LOG && Gun.log(S, +new Date - S, 'got prep');
-            root.on('in', {'@': id, put: graph, '%': o.more? 1 : u, err: err? err : u, _: each});
-        }, o);
-        LOG && Gun.log(S, +new Date - S, 'get call');
-        function each(val, has, a,b){
+            DBG && (DBG.sgp = +new Date);
+            // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps?
+            // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps?
+            // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps?
+            // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps?
+            // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps?
+            // Or benchmark by reusing first start date.
+            if(console.STAT && (ST = +new Date - S) > 9){ console.STAT(S, ST, 'got prep time'); console.STAT(S, C, 'got prep #') } SPT += ST; C = 0; S = +new Date;
+            var faith = function(){}; faith.faith = true; faith.rad = get; // HNPERF: We're testing performance improvement by skipping going through security again, but this should be audited.
+            root.on('in', {'@': id, put: graph, '%': info.more? 1 : u, err: err? err : u, _: faith, DBG: DBG});
+            console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'got emit', Object.keys(graph||{}).length);
+            graph = u; // each is outside our scope, we have to reset graph to nothing!
+        }, o, DBG && (DBG.r = DBG.r || {}));
+        DBG && (DBG.sgd = +new Date);
+        console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'get call'); // TODO: Perf: this was half a second??????
+        function each(val, has, a,b){ // TODO: THIS CODE NEEDS TO BE FASTER!!!!
+            C++;
             if(!val){ return }
             has = (key+has).split(esc);
             var soul = has.slice(0,1)[0];
             has = has.slice(-1)[0];
+            if(o.limit && o.limit <= o.count){ return true }
+            var va, ve, so = soul, ha = has;
+            //if(u !== (va = val[':']) && u !== (ve = val['>'])){ // THIS HANDLES NEW CODE!
+            if('string' != typeof val){ // THIS HANDLES NEW CODE!
+                va = val[':']; ve = val['>'];
+                (graph = graph || {})[so] = Gun.state.ify(graph[so], ha, ve, va, so);
+                //root.$.get(so).get(ha)._.rad = now;
+                o.count = (o.count || 0) + ((va||'').length || 9);
+                return;
+            }
             o.count = (o.count || 0) + val.length;
             var tmp = val.lastIndexOf('>');
             var state = Radisk.decode(val.slice(tmp+1), null, esc);
             val = Radisk.decode(val.slice(0,tmp), null, esc);
             (graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul);
-            if(o.limit && o.limit <= o.count){ return true }
         }
-        each.rad = get;
-        LOG = console.LOG;
     });
+    var val_is = Gun.val.is
     opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS!
     var statg = 0, statp = 0; // STATS!
 });

+ 3 - 2
public/lib/gundb/lib/then.js

@@ -11,11 +11,12 @@ Gun.chain.promise = function(cb) {
 };
 
 // Returns a promise for the data, key of the gun call
-Gun.chain.then = function() {
+Gun.chain.then = function(cb) {
 	var gun = this;
-  return (new Promise((res, rej)=>{
+  var p = (new Promise((res, rej)=>{
     gun.once(function (data, key) {
       res(data, key); //call resolve when data is returned
     })
   }))
+  return cb ? p.then(cb) : p;
 };

+ 86 - 19
public/lib/gundb/lib/music.js → public/lib/gundb/lib/wave.js

@@ -20,7 +20,7 @@ return this;
 }).call({}, edide);
 "use strict";edide.var = (function _var (edide) {
 var currentReact, debugging, dependees, dependsOn, depsRequired, inInitCall, infinityCheck, initSetter, newVar, parent, setLinks, setters, updateVar, values;
-values = new Map; // TODO //edide.keep ?
+values = new Map; // //edide.keep ?
 setters = new Map; // varName => setter:func
 dependees = new Map; // varName => deps:
 setLinks = new Map; // for reactiveGraph, show setters inside reactive/setter:s
@@ -190,7 +190,13 @@ return (initialVars = {}) => {
     }
     return edide.var(func);
   };
-  return {revar, unvar, react}; // , dom
+  return {
+    react,
+    revar,
+    unvar,
+    un: unvar,
+    re: revar // , dom
+  };
 };
 return this;
 }).call({}, edide);
@@ -416,7 +422,7 @@ this.fromKeyCode = (key) => {
 };
 return this;
 }).call({}, edide);
-"use strict";edide.new = (function  (edide) {
+"use strict";edide.clone = (function _clone (edide) {
 return function(...objects) {
   if (Array.isArray(objects[0])) {
     return Object.assign([], ...objects);
@@ -443,7 +449,7 @@ this.hasVar = (varName) => {
 this.activateVar = (name, value) => {
   var configs;
   if (value != null) {
-    configs = edide.new(mutatingVars[name]);
+    configs = edide.clone(mutatingVars[name]);
     if (edide.strParsesToNumber(value)) {
       value = parseFloat(value);
     }
@@ -601,7 +607,7 @@ return this;
 }).call({}, edide);
 "use strict";edide.mmPipe = (function _mmPipe (edide) {Object.defineProperty(this, 'module_name', {value:'mmPipe'});
 var dist, lowpass, react, revar, reverber, unvar;
-({revar, unvar, react} = edide.mmState);
+({revar, unvar, react} = edide.mmState.init());
 dist = lowpass = reverber = null;
 this.initPipe = () => {
   lowpass = new Tone.Filter(unvar.lowpass, 'lowpass', -12);
@@ -932,12 +938,35 @@ return sleep;
 return this;
 }).call({}, edide);
 "use strict";edide.mmInstrument = (function _mmInstrument (edide) {Object.defineProperty(this, 'module_name', {value:'mmInstrument'});
-var cloneOrCreateInstrument, getInstruments, instruments, play, react, revar, unvar;
+var cloneOrCreateInstrument, getInstruments, instruments, play, react, revar, unvar, updateVolAndTune;
+instruments = {}; // name: [instrument instances...]
 ({unvar, revar, react} = edide.mmState);
-revar.beatDelay = () => {
-  return (1 / revar.bpm) * 60 * 1000;
+react('delay from bmp', () => {
+  return revar.beatDelay = (1 / revar.bpm) * 60 * 1000;
+});
+react(() => {});
+updateVolAndTune = (inst) => {
+  var detune, volume;
+  ({volume, detune} = unvar);
+  if ((detune != null) && (inst.detune != null)) {
+    inst.set("detune", detune);
+  }
+  if ((volume != null) && (inst.volume != null)) {
+    return inst.set("volume", volume);
+  }
 };
-instruments = {}; // name: [instrument instances...]
+react(() => {
+  var inst, insts, j, len, name;
+  revar.volume;
+  revar.detune;
+  for (name in instruments) {
+    insts = instruments[name];
+    for (j = 0, len = insts.length; j < len; j++) {
+      inst = insts[j];
+      updateVolAndTune(inst);
+    }
+  }
+});
 react('create first instrument', () => {
   var name, ref;
   if (!(name = (ref = revar.instrumentConf) != null ? ref.name : void 0)) { // e.g. illegal instrument name
@@ -950,13 +979,9 @@ react('create first instrument', () => {
 });
 cloneOrCreateInstrument = (name) => {
   var inst;
-  if (instruments[name][0].soundFontInstrument) {
-    inst = edide.cloneSibling(instruments[name][0]);
-    inst.isPlaying = false;
-    return inst;
-  } else {
-    return edide.instrumentConfigs.createNew();
-  }
+  inst = instruments[name][0].soundFontInstrument ? (inst = edide.cloneSibling(instruments[name][0]), inst.isPlaying = false, inst) : edide.instrumentConfigs.createNew();
+  updateVolAndTune(inst);
+  return inst;
 };
 getInstruments = (n) => {
   var all, free, name;
@@ -1164,12 +1189,54 @@ this.play = (str) => {
 this.stop = () => revar.playing = false
 edide.instrumentConfigs.initInstrument()
 var initialSettings = {
-  "instrument": "piano"
+  "instrument": "synth-simple", //"piano"
 }
 react(() => {
   if(!revar.pipeReady) return
-  this.play(JSON.stringify(initialSettings))
-  this.play(playQueue.join("\n\n"))
+  edide.mmConfigs.activate(initialSettings)
+  this.play(playQueue.join("\n\n")) // JSON.stringify(initialSettings)
 }) // TEMP init instrument
 return this;
+}).call({}, edide);
+"use strict";edide.wave = (function _wave (edide) {
+var parent = {
+  play: function(){
+    edide.music.play(this.sheet)
+    return this
+  },
+  stop: function(){
+    edide.music.stop(this.sheet)
+    return this
+  },
+  blur: function(val){
+    edide.mmConfigs.activate({'blur': val})
+    return this
+  },
+  itch: function(val){
+    edide.mmConfigs.activate({'itch': val})
+    return this
+  },
+  long: function(val){ // note delay in seconds
+    edide.mmConfigs.activate({'beatDelay': val*1000})
+    return this
+  },
+  pace: function(val) { // note delay in beats per minute
+    edide.mmConfigs.activate({'bpm': val})
+    return this
+  },
+  vary: function(val){
+    edide.mmConfigs.activate({'detune': val})
+    return this
+  },
+  loud: function(val){ // increase (>0) or decrease (<0) amplitude in decibels
+    edide.mmConfigs.activate({'volume': val})
+    return this
+  },
+}
+return edide.global.wave = (str) => {
+  var obj = Object.create(parent)
+  obj.sheet = str
+  return obj
+}
+return this;
 }).call({}, edide);

+ 9 - 6
public/lib/gundb/lib/webrtc.js

@@ -21,13 +21,16 @@
 		opt.RTCSessionDescription = rtcsd;
 		opt.RTCIceCandidate = rtcic;
 		opt.rtc = opt.rtc || {'iceServers': [
-      {url: 'stun:stun.l.google.com:19302'},
-      {url: "stun:stun.sipgate.net:3478"},
-      {url: "stun:stun.stunprotocol.org"},
-      {url: "stun:stun.sipgate.net:10000"},
-      {url: "stun:217.10.68.152:10000"},
-      {url: 'stun:stun.services.mozilla.com'} 
+      {urls: 'stun:stun.l.google.com:19302'},
+      {urls: "stun:stun.sipgate.net:3478"}/*,
+      {urls: "stun:stun.stunprotocol.org"},
+      {urls: "stun:stun.sipgate.net:10000"},
+      {urls: "stun:217.10.68.152:10000"},
+      {urls: 'stun:stun.services.mozilla.com'}*/ 
     ]};
+    // TODO: Select the most appropriate stuns. 
+    // FIXME: Find the wire throwing ICE Failed
+    // The above change corrects at least firefox RTC Peer handler where it **throws** on over 6 ice servers, and updates url: to urls: removing deprecation warning 
     opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2};
     opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
     opt.announce = function(to){

+ 1 - 1
public/lib/gundb/lib/wire.js

@@ -78,7 +78,7 @@ Gun.on('opt', function(root){
 				opt.mesh.bye(peer);
 			});
 			wire.on('error', function(e){});
-			setTimeout(function heart(){ if(!opt.peers[peer.id]){ return } try{ wire.send("[]"); setTimeout(heart, 1000 * 20) }catch(e){} }, 1000 * 20); // Some systems, like Heroku, require heartbeats to not time out. // TODO: Make this configurable?
+			setTimeout(function heart(){ if(!opt.peers[peer.id]){ return } try{ wire.send("[]") }catch(e){} ;setTimeout(heart, 1000 * 20) }, 1000 * 20); // Some systems, like Heroku, require heartbeats to not time out. // TODO: Make this configurable? // TODO: PERF: Find better approach than try/timeouts?
 		});
 	}
 

+ 153 - 0
public/lib/gundb/lib/yson.js

@@ -0,0 +1,153 @@
+;(function(){
+// JSON: JavaScript Object Notation
+// YSON: Yielding javaScript Object Notation
+var yson = {}, u, sI = setTimeout.turn || (typeof setImmediate != ''+u && setImmediate) || setTimeout;
+yson.parseAsync = function(text, done, revive, M){
+	var ctx = {i: 0, text: text, done: done, o: {}, l: text.length, up: []};
+	ctx.at = ctx.o;
+	//M = 1024 * 1024 * 100;
+	//M = M || 1024 * 64;
+	M = M || 1024 * 32;
+	parse();
+	function parse(){
+		//var S = +new Date;
+		var s = ctx.text, o = ctx.o;
+		var i = ctx.i, l = ctx.l, j = 0;
+		var w = ctx.w, b, tmp;
+		while(j++ < M){
+			var c = s[i++];
+			if(i > l){
+				ctx.end = true;
+				break;
+			}
+			if(w){
+				i = s.indexOf('"', i-1); c = s[i];
+				tmp = '\\' == s[i-1];
+				b = b || tmp;
+				if('"' == c && !tmp){
+					w = u;
+					tmp = ctx.s;
+					if(ctx.a){
+						tmp = s.slice(ctx.sl, i);
+						if(b){ tmp = JSON.parse('"'+tmp+'"') }
+						if(ctx.at instanceof Array){
+							ctx.at.push(ctx.s = tmp);
+						} else {
+							ctx.at[ctx.s] = ctx.s = tmp;
+						}
+					} else {
+						ctx.s = s.slice(ctx.sl, i);
+						if(b){ ctx.s = JSON.parse('"'+ctx.s+'"'); }
+					}
+					ctx.a = b = u;
+				}
+				++i;
+			} else {
+				switch(c){
+				case '"':
+					ctx.sl = i;
+					w = true;
+					break;
+				case ':':
+					ctx.ai = i;
+					ctx.a = true;
+					break;
+				case ',':
+					if(ctx.a || ctx.at instanceof Array){
+						if(tmp = s.slice(ctx.ai, i-1)){
+							if(u !== (tmp = value(tmp))){
+								if(ctx.at instanceof Array){
+									ctx.at.push(tmp);
+								} else {
+									ctx.at[ctx.s] = tmp;
+								}
+							}
+						}
+					}
+					ctx.a = u;
+					if(ctx.at instanceof Array){
+						ctx.a = true;
+						ctx.ai = i;
+					}
+					break;
+				case '{':
+					ctx.up.push(ctx.at);
+					if(ctx.at instanceof Array){
+						ctx.at.push(ctx.at = {});
+					} else
+					if(tmp = ctx.s){
+						ctx.at[tmp] = ctx.at = {};
+					}
+					ctx.a = u;
+					break;
+				case '}':
+					if(ctx.a){
+						if(tmp = s.slice(ctx.ai, i-1)){
+							if(u !== (tmp = value(tmp))){
+								if(ctx.at instanceof Array){
+									ctx.at.push(tmp);
+								} else {
+									ctx.at[ctx.s] = tmp;
+								}
+							}
+						}
+					}
+					ctx.a = u;
+					ctx.at = ctx.up.pop();
+					break;
+				case '[':
+					if(tmp = ctx.s){
+						ctx.up.push(ctx.at);
+						ctx.at[tmp] = ctx.at = [];
+					}
+					ctx.a = true;
+					break;
+				case ']':
+					if(ctx.a){
+						if(tmp = s.slice(ctx.ai, i-1)){
+							if(u !== (tmp = value(tmp))){
+								if(ctx.at instanceof Array){
+									ctx.at.push(tmp);
+								} else {
+									ctx.at[ctx.s] = tmp;
+								}
+							}
+						}
+					}
+					ctx.a = u;
+					ctx.at = ctx.up.pop();
+					break;
+				}
+			}
+		}
+		ctx.i = i;
+		ctx.w = w;
+		//console.log("!!!!!!!!", +new Date - S, ctx.i, ctx.l);
+		if(ctx.end){
+			ctx.done(u, ctx.o);
+		} else {
+			//setTimeout.turn(parse);
+			sI(parse);
+		}
+	}
+}
+function value(s){
+	var n = parseFloat(s);
+	if(!isNaN(n)){
+		return n;
+	}
+	s = s.trim();
+	if('true' == s){
+		return true;
+	}
+	if('false' == s){
+		return false;
+	}
+	if('null' == s){
+		return null;
+	}
+}
+
+module.exports = yson;
+
+}());

+ 156 - 56
public/lib/gundb/sea.js

@@ -1,11 +1,6 @@
 ;(function(){
 
   /* UNBUILD */
-  var root;
-  if(typeof window !== "undefined"){ root = window }
-  if(typeof global !== "undefined"){ root = global }
-  root = root || {};
-  var console = root.console || {log: function(){}};
   function USE(arg, req){
     return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
       arg(mod = {exports: {}});
@@ -15,7 +10,7 @@
       return p.split('/').slice(-1).toString().replace('.js','');
     }
   }
-  if(typeof module !== "undefined"){ var common = module }
+  if(typeof module !== "undefined"){ var MODULE = module }
   /* UNBUILD */
 
   ;USE(function(module){
@@ -31,7 +26,7 @@
 
     if(SEA.window = module.window){ SEA.window.SEA = SEA }
 
-    try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
+    try{ if(typeof MODULE !== "undefined"){ MODULE.exports = SEA } }catch(e){}
     module.exports = SEA;
   })(USE, './root');
 
@@ -47,10 +42,12 @@
   })(USE, './https');
 
   ;USE(function(module){
-    if(typeof global !== "undefined"){
-      var g = global;
-      g.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
-      g.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
+    if(typeof btoa === "undefined"){
+      if(typeof Buffer === "undefined") {
+        global.Buffer = require("buffer").Buffer
+      }
+      global.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
+      global.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
     }
   })(USE, './base64');
 
@@ -96,7 +93,7 @@
     Object.assign(SafeBuffer, {
       // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
       from() {
-        if (!Object.keys(arguments).length) {
+        if (!Object.keys(arguments).length || arguments[0]==null) {
           throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
         }
         const input = arguments[0]
@@ -167,28 +164,26 @@
     var o = {};
 
     if(SEA.window){
-      api.crypto = window.crypto || window.msCrypto;
+      api.crypto = navigator && navigator.product === 'ReactNative' ? require('isomorphic-webcrypto') : window.crypto || window.msCrypto || require('isomorphic-webcrypto');
       api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
       api.TextEncoder = window.TextEncoder;
       api.TextDecoder = window.TextDecoder;
-      api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
+      api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))));
+    }
+    if(!api.TextDecoder)
+    {
+      const { TextEncoder, TextDecoder } = require('text-encoding');
+      api.TextDecoder = TextDecoder;
+      api.TextEncoder = TextEncoder;
     }
     if(!api.crypto){try{
       var crypto = USE('crypto', 1);
-      const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
       Object.assign(api, {
         crypto,
-        //subtle,
-        TextEncoder,
-        TextDecoder,
         random: (len) => Buffer.from(crypto.randomBytes(len))
-      });
-      //try{
-        const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1);
-        api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
-      //}catch(e){
-        //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
-      //}
+      });      
+      const isocrypto = require('isomorphic-webcrypto');
+      api.ossl = api.subtle = isocrypto.subtle;
     }catch(e){
       console.log("text-encoding and @peculiar/webcrypto may not be included by default, please add it to your package.json!");
       TEXT_ENCODING_OR_PECULIAR_WEBCRYPTO_NOT_INSTALLED;
@@ -201,7 +196,7 @@
     var SEA = USE('./root');
     var Buffer = USE('./buffer');
     var s = {};
-    s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64};
+    s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64};
     s.ecdsa = {
       pair: {name: 'ECDSA', namedCurve: 'P-256'},
       sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
@@ -217,6 +212,13 @@
       if(d){ jwk.d = d }
       return jwk;
     };
+    
+    s.keyToJwk = function(keyBytes) {
+      const keyB64 = keyBytes.toString('base64');
+      const k = keyB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
+      return { kty: 'oct', k: k, ext: false, alg: 'A256GCM' };
+    }
+
     s.recall = {
       validity: 12 * 60 * 60, // internally in seconds : 12 hours
       hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props)
@@ -267,13 +269,13 @@
         cb = salt;
         salt = u;
       }
-      salt = salt || shim.random(9);
       data = (typeof data == 'string')? data : JSON.stringify(data);
       if('sha' === (opt.name||'').toLowerCase().slice(0,3)){
         var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64')
         if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
         return rsha;
       }
+      salt = salt || shim.random(9);
       var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']);
       var work = await (shim.ossl || shim.subtle).deriveBits({
         name: opt.name || 'PBKDF2',
@@ -317,7 +319,7 @@
 
       var ecdhSubtle = shim.ossl || shim.subtle;
       // First: ECDSA keys for signing/verifying...
-      var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ])
+      var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ])
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
@@ -337,7 +339,7 @@
       // Next: ECDH keys for encryption/decryption...
 
       try{
-      var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey'])
+      var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey'])
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         var key = {};
@@ -396,8 +398,8 @@
       var priv = pair.priv;
       var jwk = S.jwk(pub, priv);
       var hash = await sha(json);
-      var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
-      .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
+      var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign'])
+      .then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
       var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
       if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
 
@@ -431,12 +433,12 @@
       opt = opt || {};
       // SEA.I // verify is free! Requires no user permission.
       var pub = pair.pub || pair;
-      var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']);
+      var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', S.jwk(pub), {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']);
       var hash = await sha(json.m);
       var buf, sig, check, tmp; try{
         buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
         sig = new Uint8Array(buf);
-        check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash));
+        check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash));
         if(!check){ throw "Signature did not match." }
       }catch(e){
         if(SEA.opt.fallback){
@@ -462,27 +464,30 @@
     var keyForPair = SEA.opt.slow_leak = pair => {
       if (knownKeys[pair]) return knownKeys[pair];
       var jwk = S.jwk(pair);
-      knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]);
+      knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]);
       return knownKeys[pair];
     };
 
-
+    var O = SEA.opt;
     SEA.opt.fall_verify = async function(data, pair, cb, opt, f){
       if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
+      var tmp = data||'';
+      data = SEA.opt.unpack(data) || data;
       var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
       var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility.
       var buf; var sig; var check; try{
         buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
         sig = new Uint8Array(buf)
-        check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
+        check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash))
         if(!check){ throw "Signature did not match." }
       }catch(e){
         buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA!
         sig = new Uint8Array(buf)
-        check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
+        check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash))
         if(!check){ throw "Signature did not match." }
       }
       var r = check? S.parse(json.m) : u;
+      O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>'];
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     }
@@ -492,6 +497,7 @@
 
   ;USE(function(module){
     var shim = USE('./shim');
+    var S = USE('./settings');
     var sha256hash = USE('./sha256');
 
     const importGen = async (key, salt, opt) => {
@@ -499,7 +505,9 @@
       var opt = opt || {};
       const combo = key + (salt || shim.random(8)).toString('utf8'); // new
       const hash = shim.Buffer.from(await sha256hash(combo), 'binary')
-      return await shim.subtle.importKey('raw', new Uint8Array(hash), opt.name || 'AES-GCM', false, ['encrypt', 'decrypt'])
+      
+      const jwkKey = S.keyToJwk(hash)      
+      return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt'])
     }
     module.exports = importGen;
   })(USE, './aeskey');
@@ -563,7 +571,7 @@
         bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
         bufct = shim.Buffer.from(json.ct, opt.encode || 'base64');
         var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({  // Keeping aesKey scope as private as possible...
-          name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv)
+          name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv), tagLength: 128
         }, aes, new Uint8Array(bufct)));
       }catch(e){
         if('utf8' === opt.encode){ throw "Could not decrypt" }
@@ -601,11 +609,13 @@
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
-      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy !
+      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },{name: 'ECDH', namedCurve: 'P-256'}); // Thanks to @sirpy !
       var privKeyData = keysToEcdhJwk(epub, epriv);
-      var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => {
+      var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveBits']).then(async (privKey) => {
         // privateKey scope doesn't leak out from here!
-        var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]);
+        var derivedBits = await ecdhSubtle.deriveBits(props, privKey, 256);
+        var rawBits = new Uint8Array(derivedBits);
+        var derivedKey = await ecdhSubtle.importKey('raw', rawBits,{ name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]);
         return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k);
       })
       var r = derived;
@@ -630,7 +640,7 @@
           jwk,
           { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
         ), // ??? refactor
-        S.ecdh
+        {name: 'ECDH', namedCurve: 'P-256'}
       ]
     }
 
@@ -639,13 +649,14 @@
 
   ;USE(function(module){
     var shim = USE('./shim');
-    // Practical examples about usage found from ./test/common.js
+    // Practical examples about usage found in tests.
     var SEA = USE('./root');
     SEA.work = USE('./work');
     SEA.sign = USE('./sign');
     SEA.verify = USE('./verify');
     SEA.encrypt = USE('./encrypt');
     SEA.decrypt = USE('./decrypt');
+    SEA.opt.aeskey = USE('./aeskey'); // not official!
 
     SEA.random = SEA.random || shim.random;
 
@@ -687,7 +698,7 @@
     // But all other behavior needs to be equally easy, like opinionated ways of
     // Adding friends (trusted public keys), sending private messages, etc.
     // Cheers! Tell me what you think.
-    var Gun = (SEA.window||{}).Gun || USE((typeof common == "undefined"?'.':'')+'./gun', 1);
+    var Gun = (SEA.window||{}).Gun || USE((typeof MODULE == "undefined"?'.':'')+'./gun', 1);
     Gun.SEA = SEA;
     SEA.GUN = SEA.Gun = Gun;
 
@@ -723,13 +734,13 @@
       if(user = root.back('user')){ return user }
       var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
       (at = (user = at.user = gun.chain(new User))._).opt = {};
-      at.opt.uuid = function(cb){
+      /*at.opt.uuid = function(cb){
         var id = uuid(), pub = root.user;
         if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
         id = id + '~' + pub + '.';
         if(cb && cb.call){ cb(null, id) }
         return id;
-      }
+      }*/
       return user;
     }
     Gun.User = User;
@@ -1076,17 +1087,17 @@
   })(USE, './create');
 
   ;USE(function(module){
-    const SEA = USE('./sea')
-    const Gun = SEA.Gun;
+    var SEA = USE('./sea')
+    var Gun = SEA.Gun;
     // After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
 
     // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
     Gun.on('opt', function(at){
       if(!at.sea){ // only add SEA once per instance, on the "at" context.
         at.sea = {own: {}};
-        at.on('in', security, at); // now listen to all input data, acting as a firewall.
-        at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
-        at.on('node', each, at);
+        //at.on('in', security, at); // now listen to all input data, acting as a firewall.
+        //at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
+        at.on('put', check, at);
       }
       this.to.next(at); // make sure to call the "next" middleware adapter.
     });
@@ -1135,6 +1146,86 @@
       security.call(this, msg);
     }
 
+    var u;
+    function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB?
+      var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
+      if(!soul || !key){ return }
+      if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){
+        SEA.verify(SEA.opt.pack(put), false, function(data){ // this is synchronous if false
+          put['='] = SEA.opt.unpack(data);
+          eve.to.next(msg);
+        });
+        return 
+      }
+      var no = function(why){ at.on('in', {'@': id, err: why}) };
+      //var no = function(why){ msg.ack(why) };
+      (msg._||'').DBG && ((msg._||'').DBG.c = +new Date);
+      if(0 <= soul.indexOf('#')){ // special case for content addressing immutable hashed data.
+        check.hash(eve, msg, val, key, soul, at, no); return;
+      } 
+      if('~@' === soul){  // special case for shared system data, the list of aliases.
+        check.alias(eve, msg, val, key, soul, at, no); return;
+      }
+      if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
+        check.pubs(eve, msg, val, key, soul, at, no); return;
+      }
+      //if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
+      if(tmp = SEA.opt.pub(soul)){ // special case, account data for a public key.
+        check.pub(eve, msg, val, key, soul, at, no, at.user||'', tmp); return;
+      }
+      check.any(eve, msg, val, key, soul, at, no, at.user||''); return;
+      eve.to.next(msg); // not handled
+    }
+    check.hash = function(eve, msg, val, key, soul, at, no){
+      SEA.work(val, null, function(data){
+        if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) }
+        no("Data hash not same as hash!");
+      }, {name: 'SHA-256'});
+    }
+    check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}}
+      if(!val){ return no("Data must exist!") } // data MUST exist
+      if('~@'+key === link_is(val)){ return eve.to.next(msg) } // in fact, it must be EXACTLY equal to itself
+      no("Alias not same!"); // if it isn't, reject.
+    };
+    check.pubs = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
+      if(!val){ return no("Alias must exist!") } // data MUST exist
+      if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
+      no("Alias not same!"); // that way nobody can tamper with the list of public keys.
+    };
+    check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
+      if('pub' === key && '~'+pub === soul){
+        if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key.
+        return no("Account not same!");
+      }
+      if((tmp = user.is) && pub === tmp.pub){
+        SEA.sign(SEA.opt.pack(msg.put), (user._).sea, function(data){
+          if(u === data){ return no(SEA.err || 'Signature fail.') }
+          if(tmp = link_is(val)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
+          msg.put[':'] = JSON.stringify({':': tmp = SEA.opt.unpack(data.m), '~': data.s});
+          msg.put['='] = tmp;
+          eve.to.next(msg);
+        }, {raw: 1});
+        return;
+      }
+      SEA.verify(SEA.opt.pack(msg.put), pub, function(data){ var tmp;
+        data = SEA.opt.unpack(data);
+        if(u === data){ return no("Unverified data.") } // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
+        if((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
+        msg.put['='] = data;
+        eve.to.next(msg);
+      });
+    };
+    check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub;
+      if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") }
+      // TODO: Ask community if should auto-sign non user-graph data.
+      at.on('secure', function(msg){ this.off();
+        if(!at.opt.secure){ return eve.to.next(msg) }
+        no("Data cannot be changed.");
+      }).on.on('secure', msg);
+      return;
+    }
+    var link_is = Gun.val.link.is, state_ify = Gun.state.ify;
+
     // okay! The security function handles all the heavy lifting.
     // It needs to deal read and write of input and output of system data, account/public key data, and regular data.
     // This is broken down into some pretty clear edge cases, let's go over them:
@@ -1160,6 +1251,11 @@
         }
       }
       if(msg.put){
+        /*
+          NOTICE: THIS IS OLD AND GETTING DEPRECATED.
+          ANY SECURITY CHANGES SHOULD HAPPEN ABOVE FIRST
+          THEN PORTED TO HERE.
+        */
         // potentially parallel async operations!!!
         var check = {}, each = {}, u;
         each.node = function(node, soul){
@@ -1290,6 +1386,7 @@
       if(!s || !(s = s[1])){ return }
       s = s.split('.');
       if(!s || 2 > s.length){ return }
+      if('@' === (s[0]||'')[0]){ return } // TODO: Should check ~X.Y. are alphanumeric, not just not @.
       s = s.slice(0,2).join('.');
       return s;
     }
@@ -1298,16 +1395,18 @@
     }
     SEA.opt.pack = function(d,k, n,s){ // pack for verifying
       if(SEA.opt.check(d)){ return d }
-      var meta = (Gun.obj.ify(d)||noop), sig = meta['~'];
-      return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d;
+      var meta = (Gun.obj.ify((d && d[':'])||d)||''), sig = meta['~'];
+      return sig? {m: {'#':s||d['#'],'.':k||d['.'],':':meta[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig} : d;
     }
+    var O = SEA.opt;
     SEA.opt.unpack = function(d, k, n){ var tmp;
       if(u === d){ return }
       if(d && (u !== (tmp = d[':']))){ return tmp }
+      k = k || O.fall_key; if(!n && O.fall_val){ n = {}; n[k] = O.fall_val }
       if(!k || !n){ return }
       if(d === n[k]){ return d }
       if(!SEA.opt.check(n[k])){ return d }
-      var soul = Gun.node.soul(n), s = Gun.state.is(n, k);
+      var soul = Gun.node.soul(n) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
       if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
         return d[2];
       }
@@ -1319,7 +1418,8 @@
     var noop = function(){}, u;
     var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
     var rel_is = Gun.val.rel.is;
+    var obj_ify = Gun.obj.ify;
     // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
 
   })(USE, './index');
-}());
+}());

+ 4 - 1
public/lib/gundb/sea/aeskey.js

@@ -1,5 +1,6 @@
 
     var shim = require('./shim');
+    var S = require('./settings');
     var sha256hash = require('./sha256');
 
     const importGen = async (key, salt, opt) => {
@@ -7,7 +8,9 @@
       var opt = opt || {};
       const combo = key + (salt || shim.random(8)).toString('utf8'); // new
       const hash = shim.Buffer.from(await sha256hash(combo), 'binary')
-      return await shim.subtle.importKey('raw', new Uint8Array(hash), opt.name || 'AES-GCM', false, ['encrypt', 'decrypt'])
+      
+      const jwkKey = S.keyToJwk(hash)      
+      return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt'])
     }
     module.exports = importGen;
   

+ 6 - 4
public/lib/gundb/sea/base64.js

@@ -1,7 +1,9 @@
 
-    if(typeof global !== "undefined"){
-      var g = global;
-      g.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
-      g.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
+    if(typeof btoa === "undefined"){
+      if(typeof Buffer === "undefined") {
+        root.Buffer = require("buffer").Buffer
+      }
+      root.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
+      root.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
     }
   

+ 1 - 1
public/lib/gundb/sea/buffer.js

@@ -14,7 +14,7 @@
     Object.assign(SafeBuffer, {
       // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
       from() {
-        if (!Object.keys(arguments).length) {
+        if (!Object.keys(arguments).length || arguments[0]==null) {
           throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
         }
         const input = arguments[0]

+ 1 - 1
public/lib/gundb/sea/decrypt.js

@@ -17,7 +17,7 @@
         bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
         bufct = shim.Buffer.from(json.ct, opt.encode || 'base64');
         var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({  // Keeping aesKey scope as private as possible...
-          name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv)
+          name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv), tagLength: 128
         }, aes, new Uint8Array(bufct)));
       }catch(e){
         if('utf8' === opt.encode){ throw "Could not decrypt" }

+ 2 - 2
public/lib/gundb/sea/pair.js

@@ -19,7 +19,7 @@
 
       var ecdhSubtle = shim.ossl || shim.subtle;
       // First: ECDSA keys for signing/verifying...
-      var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ])
+      var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ])
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
@@ -39,7 +39,7 @@
       // Next: ECDH keys for encryption/decryption...
 
       try{
-      var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey'])
+      var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey'])
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         var key = {};

+ 1 - 0
public/lib/gundb/sea/sea.js

@@ -7,6 +7,7 @@
     SEA.verify = require('./verify');
     SEA.encrypt = require('./encrypt');
     SEA.decrypt = require('./decrypt');
+    SEA.aeskey = require('./aeskey');
 
     SEA.random = SEA.random || shim.random;
 

+ 6 - 4
public/lib/gundb/sea/secret.js

@@ -13,11 +13,13 @@
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
-      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy !
+      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },{name: 'ECDH', namedCurve: 'P-256'}); // Thanks to @sirpy !
       var privKeyData = keysToEcdhJwk(epub, epriv);
-      var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => {
+      var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveBits']).then(async (privKey) => {
         // privateKey scope doesn't leak out from here!
-        var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]);
+        var derivedBits = await ecdhSubtle.deriveBits(props, privKey, 256);
+        var rawBits = new Uint8Array(derivedBits);
+        var derivedKey = await ecdhSubtle.importKey('raw', rawBits,{ name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]);
         return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k);
       })
       var r = derived;
@@ -42,7 +44,7 @@
           jwk,
           { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
         ), // ??? refactor
-        S.ecdh
+        {name: 'ECDH', namedCurve: 'P-256'}
       ]
     }
 

+ 8 - 1
public/lib/gundb/sea/settings.js

@@ -2,7 +2,7 @@
     var SEA = require('./root');
     var Buffer = require('./buffer');
     var s = {};
-    s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64};
+    s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64};
     s.ecdsa = {
       pair: {name: 'ECDSA', namedCurve: 'P-256'},
       sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
@@ -18,6 +18,13 @@
       if(d){ jwk.d = d }
       return jwk;
     };
+    
+    s.keyToJwk = function(keyBytes) {
+      const keyB64 = keyBytes.toString('base64');
+      const k = keyB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
+      return { kty: 'oct', k: k, ext: false, alg: 'A256GCM' };
+    }
+
     s.recall = {
       validity: 12 * 60 * 60, // internally in seconds : 12 hours
       hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props)

+ 11 - 13
public/lib/gundb/sea/shim.js

@@ -5,28 +5,26 @@
     var o = {};
 
     if(SEA.window){
-      api.crypto = window.crypto || window.msCrypto;
+      api.crypto = navigator && navigator.product === 'ReactNative' ? require('isomorphic-webcrypto') : window.crypto || window.msCrypto || require('isomorphic-webcrypto');
       api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
       api.TextEncoder = window.TextEncoder;
       api.TextDecoder = window.TextDecoder;
-      api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
+      api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))));
+    }
+    if(!api.TextDecoder)
+    {
+      const { TextEncoder, TextDecoder } = require('text-encoding');
+      api.TextDecoder = TextDecoder;
+      api.TextEncoder = TextEncoder;
     }
     if(!api.crypto){try{
       var crypto = require('crypto', 1);
-      const { TextEncoder, TextDecoder } = require('text-encoding', 1)
       Object.assign(api, {
         crypto,
-        //subtle,
-        TextEncoder,
-        TextDecoder,
         random: (len) => Buffer.from(crypto.randomBytes(len))
-      });
-      //try{
-        const { Crypto: WebCrypto } = require('@peculiar/webcrypto', 1);
-        api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
-      //}catch(e){
-        //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
-      //}
+      });      
+      const isocrypto = require('isomorphic-webcrypto');
+      api.ossl = api.subtle = isocrypto.subtle;
     }catch(e){
       console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!");
       OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;

+ 2 - 2
public/lib/gundb/sea/sign.js

@@ -24,8 +24,8 @@
       var priv = pair.priv;
       var jwk = S.jwk(pub, priv);
       var hash = await sha(json);
-      var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
-      .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
+      var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign'])
+      .then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
       var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
       if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
 

+ 5 - 5
public/lib/gundb/sea/verify.js

@@ -15,12 +15,12 @@
       opt = opt || {};
       // SEA.I // verify is free! Requires no user permission.
       var pub = pair.pub || pair;
-      var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']);
+      var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']);
       var hash = await sha(json.m);
       var buf, sig, check, tmp; try{
         buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
         sig = new Uint8Array(buf);
-        check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash));
+        check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash));
         if(!check){ throw "Signature did not match." }
       }catch(e){
         if(SEA.opt.fallback){
@@ -46,7 +46,7 @@
     var keyForPair = SEA.opt.slow_leak = pair => {
       if (knownKeys[pair]) return knownKeys[pair];
       var jwk = S.jwk(pair);
-      knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]);
+      knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]);
       return knownKeys[pair];
     };
 
@@ -58,12 +58,12 @@
       var buf; var sig; var check; try{
         buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
         sig = new Uint8Array(buf)
-        check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
+        check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash))
         if(!check){ throw "Signature did not match." }
       }catch(e){
         buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA!
         sig = new Uint8Array(buf)
-        check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
+        check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash))
         if(!check){ throw "Signature did not match." }
       }
       var r = check? S.parse(json.m) : u;

+ 18 - 13
public/web/index-app.js

@@ -655,19 +655,24 @@ class IndexApp {
                                             "label": 'Sign IN',
                                             "onclick": function (e) {
                                                 e.preventDefault();
-                                                _LCSDB.user().auth(this._aliasField.value, this._passField.value, function(ack) {
-
-                                                    if (ack.err) {
-                                                        new Noty({
-                                                            text: ack.err,
-                                                            timeout: 2000,
-                                                            theme: 'mint',
-                                                            layout: 'bottomRight',
-                                                            type: 'error'
-                                                        }).show();
-
-                                                    }
-                                                });
+                                                let alias = this._aliasField.value;
+                                                let pass = this._passField.value
+                                                _app.helpers.authUser(alias, pass);
+                                                // _LCSDB.user().auth.call(_LCSDB, alias, pass
+                                                // //     , function(ack) {
+
+                                                // //     if (ack.err) {
+                                                // //         new Noty({
+                                                // //             text: ack.err,
+                                                // //             timeout: 2000,
+                                                // //             theme: 'mint',
+                                                // //             layout: 'bottomRight',
+                                                // //             type: 'error'
+                                                // //         }).show();
+
+                                                // //     }
+                                                //  //}
+                                                //  );
                                             }
                                         })
 

Some files were not shown because too many files changed in this diff