Browse Source

update gundb

Nikolay Suslov 5 years ago
parent
commit
1eaab1e569
38 changed files with 1577 additions and 1029 deletions
  1. 21 0
      public/helpers.js
  2. 55 44
      public/lib/gundb/axe.js
  3. 362 204
      public/lib/gundb/gun.js
  4. 0 0
      public/lib/gundb/gun.min.js
  5. 32 0
      public/lib/gundb/lib/crashed.js
  6. 11 0
      public/lib/gundb/lib/doll.js
  7. 15 8
      public/lib/gundb/lib/evict.js
  8. 76 256
      public/lib/gundb/lib/meta.js
  9. 3 4
      public/lib/gundb/lib/multicast.js
  10. 4 1
      public/lib/gundb/lib/open.js
  11. 322 276
      public/lib/gundb/lib/radisk.js
  12. 29 14
      public/lib/gundb/lib/radix.js
  13. 22 0
      public/lib/gundb/lib/radmigtmp.js
  14. 16 3
      public/lib/gundb/lib/rfs.js
  15. 23 0
      public/lib/gundb/lib/rfsmix.js
  16. 16 13
      public/lib/gundb/lib/rs3.js
  17. 13 9
      public/lib/gundb/lib/serve.js
  18. 4 6
      public/lib/gundb/lib/server.js
  19. 10 5
      public/lib/gundb/lib/stats.js
  20. 70 55
      public/lib/gundb/lib/store.js
  21. 3 2
      public/lib/gundb/lib/then.js
  22. 86 19
      public/lib/gundb/lib/wave.js
  23. 9 6
      public/lib/gundb/lib/webrtc.js
  24. 1 1
      public/lib/gundb/lib/wire.js
  25. 153 0
      public/lib/gundb/lib/yson.js
  26. 156 56
      public/lib/gundb/sea.js
  27. 4 1
      public/lib/gundb/sea/aeskey.js
  28. 6 4
      public/lib/gundb/sea/base64.js
  29. 1 1
      public/lib/gundb/sea/buffer.js
  30. 1 1
      public/lib/gundb/sea/decrypt.js
  31. 2 2
      public/lib/gundb/sea/pair.js
  32. 1 0
      public/lib/gundb/sea/sea.js
  33. 6 4
      public/lib/gundb/sea/secret.js
  34. 8 1
      public/lib/gundb/sea/settings.js
  35. 11 13
      public/lib/gundb/sea/shim.js
  36. 2 2
      public/lib/gundb/sea/sign.js
  37. 5 5
      public/lib/gundb/sea/verify.js
  38. 18 13
      public/web/index-app.js

+ 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 } 
 export { Helpers } 

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

@@ -1,22 +1,17 @@
 ;(function(){
 ;(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){
 	;USE(function(module){
     if(typeof window !== "undefined"){ module.window = window }
     if(typeof window !== "undefined"){ module.window = window }
@@ -24,7 +19,7 @@
 		var AXE = tmp.AXE || function(){};
 		var AXE = tmp.AXE || function(){};
 
 
     if(AXE.window = module.window){ AXE.window.AXE = AXE }
     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;
     module.exports = AXE;
 	})(USE, './root');
 	})(USE, './root');
   
   
@@ -32,6 +27,7 @@
 
 
 		var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
 		var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
 		(Gun.AXE = AXE).GUN = AXE.Gun = Gun;
 		(Gun.AXE = AXE).GUN = AXE.Gun = Gun;
+    var ST = 0;
 
 
 		Gun.on('opt', function(at){
 		Gun.on('opt', function(at){
 			start(at);
 			start(at);
@@ -73,11 +69,12 @@
 				The mob threshold might be determined by other factors,
 				The mob threshold might be determined by other factors,
 				like how much RAM or CPU stress we have.
 				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);
 			var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
 			console.log("AXE enabled.");
 			console.log("AXE enabled.");
 
 
 			function verify(dht, msg) {
 			function verify(dht, msg) {
+				var S = (+new Date);
 				var puts = Object.keys(msg.put);
 				var puts = Object.keys(msg.put);
 				var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
 				var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
 				var subs = dht(soul);
 				var subs = dht(soul);
@@ -93,17 +90,20 @@
 				if (opt.super) {
 				if (opt.super) {
 					dht(soul, tmp.join(','));
 					dht(soul, tmp.join(','));
 				}
 				}
+				console.STAT && console.STAT(S, +new Date - S, 'axe verify');
 			}
 			}
 			function route(get){ var tmp;
 			function route(get){ var tmp;
 				if(!get){ return }
 				if(!get){ return }
 				if('string' != typeof (tmp = get['#'])){ return }
 				if('string' != typeof (tmp = get['#'])){ return }
 				return tmp;
 				return tmp;
 			}
 			}
+			// TODO: AXE NEEDS TO BE CHECKED FOR NEW CODE SYSTEM!!!!!!!!!!
 
 
 			var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
 			var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
 			at.opt.dht = Rad();
 			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 dht = opt.dht;
 				var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
 				var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
 				var get = msg.get, hash, tmp;
 				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);
 				to.next(msg);
 
 
@@ -144,7 +144,7 @@
 						});
 						});
 					});
 					});
 				}
 				}
-			});
+			};
 
 
 			//try{console.log(req.connection.remoteAddress)}catch(e){};
 			//try{console.log(req.connection.remoteAddress)}catch(e){};
 			mesh.hear['opt'] = function(msg, peer){
 			mesh.hear['opt'] = function(msg, peer){
@@ -189,6 +189,7 @@
 						var peers = routes[hash];
 						var peers = routes[hash];
 						function chat(peers, old){ // what about optimizing for directed peers?
 						function chat(peers, old){ // what about optimizing for directed peers?
 							if(!peers){ return chat(opt.peers) }
 							if(!peers){ return chat(opt.peers) }
+							var S = (+new Date); // STATS!
 							var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
 							var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
 							var meta = (msg._||yes);
 							var meta = (msg._||yes);
 							clearTimeout(meta.lack);
 							clearTimeout(meta.lack);
@@ -198,12 +199,13 @@
 								meta.turn = (meta.turn || 0) + 1;
 								meta.turn = (meta.turn || 0) + 1;
 								if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
 								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);
 							//console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
 							if(0 < c){
 							if(0 < c){
 								if(peers === opt.peers){ return } // prevent infinite lack loop.
 								if(peers === opt.peers){ return } // prevent infinite lack loop.
 								return meta.turn = 0, chat(opt.peers, peers) 
 								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(){
 							meta.lack = setTimeout(function(){
 								if(ack && hash && hash === msg['##']){ return }
 								if(ack && hash && hash === msg['##']){ return }
 								if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
 								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!
 					// TODO: PUTs need to only go to subs!
 					if(msg.put){
 					if(msg.put){
+						var S = (+new Date); // STATS!
 						var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
 						var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
 						var peers = {};
 						var peers = {};
 						Gun.obj.map(msg.put, function(node, soul){
 						Gun.obj.map(msg.put, function(node, soul){
@@ -223,6 +226,7 @@
 							if(!to){ return }
 							if(!to){ return }
 							Gun.obj.to(to, peers);
 							Gun.obj.to(to, peers);
 						});
 						});
+						console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe put');
 						mesh.say(msg, peers);
 						mesh.say(msg, peers);
 						return;
 						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 = {};
 			axe.up = {};
 			at.on('hi', function(peer){
 			at.on('hi', function(peer){
@@ -273,12 +259,14 @@
 			});
 			});
 			at.on('bye', function(peer){ this.to.next(peer);
 			at.on('bye', function(peer){ this.to.next(peer);
 				if(peer.url){ delete axe.up[peer.id] }
 				if(peer.url){ delete axe.up[peer.id] }
+				var S = +new Date;
 				Gun.obj.map(peer.routes, function(route, hash){
 				Gun.obj.map(peer.routes, function(route, hash){
 					delete route[peer.id];
 					delete route[peer.id];
 					if(Gun.obj.empty(route)){
 					if(Gun.obj.empty(route)){
 						delete axe.routes[hash];
 						delete axe.routes[hash];
 					}
 					}
 				});
 				});
+				console.STAT && console.STAT(S, +new Date - S, 'axe bye');
 			});
 			});
 
 
 			// handle rebalancing a mob of peers:
 			// 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.
 				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;
 				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?
 				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
 				//setTimeout(function(){ mesh.bye(peer) }, 9); // something with better perf? // UNCOMMENT WHEN WE ACTIVATE THIS FEATURE
 			});
 			});
 			at.on('bye', function(peer){
 			at.on('bye', function(peer){
 				this.to.next(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) {
 		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.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);
 		this.to.next(root);
 		if(root.once){ return }
 		if(root.once){ return }
 		if(typeof process == 'undefined'){ return }
 		if(typeof process == 'undefined'){ return }
-		var util = process.memoryUsage;
+		var util = process.memoryUsage, heap;
 		if(!util){ return }
 		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);
 		setInterval(check, 1000);
 		function check(){
 		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(){
 		function GC(){
+			var S = +new Date;
 			var souls = Object.keys(root.graph||empty);
 			var souls = Object.keys(root.graph||empty);
 			var toss = Math.ceil(souls.length * 0.01);
 			var toss = Math.ceil(souls.length * 0.01);
 			//var S = +new Date;
 			//var S = +new Date;
 			Gun.list.map(souls, function(soul){
 			Gun.list.map(souls, function(soul){
 				if(--toss < 0){ return }
 				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){
 		root.on('in', function(msg){

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

@@ -14,113 +14,105 @@
     }
     }
   }
   }
   if(typeof module !== "undefined"){ var common = module }
   if(typeof module !== "undefined"){ var common = module }
-
+  
 	/* UNBUILD */
 	/* UNBUILD */
 	;USE(function(module){
 	;USE(function(module){
-
 		var noop = function(){}, u;
 		var noop = function(){}, u;
 		$.fn.or = function(s){ return this.length ? this : $(s||'body') };
 		$.fn.or = function(s){ return this.length ? this : $(s||'body') };
 		var m = window.meta = {edit:[]};
 		var m = window.meta = {edit:[]};
 		var k = m.key = {};
 		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){
 		k.down = function(eve){
-		  if(eve.repeat){ return }
 			var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode;
 			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 && 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(!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));
 			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;
 		k.up = function(eve){ var tmp;
 			var key = (k.eve = m.eve = eve).which = eve.which || eve.fake || eve.keyCode;
 			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;
 			k.last = null;
-		//	if($(':focus').closest('#meta').length){ return }
 			m.check('up', key);
 			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){
 		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(){
 		m.flip.is = function(){
-			return $('#meta .meta-menu').is(':visible');
+			return m.ui.board.is(':visible');
 		}
 		}
 		m.flip.wait = 500;
 		m.flip.wait = 500;
 		m.check = function(how, key, at){
 		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];
 			var next = at[key];
 			if(!next){ return }
 			if(!next){ return }
 			var tmp = k.eve || noop;
 			var tmp = k.eve || noop;
 			if(tmp.preventDefault){ tmp.preventDefault()} // prevent typing (etc) when action found
 			if(tmp.preventDefault){ tmp.preventDefault()} // prevent typing (etc) when action found
 			if(next[how]){
 			if(next[how]){
-				//if(tmp.fake && !next.fake){
-					//m.tap.next = next;
-				//} else {
 					next[how](m.eve);
 					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('up' == how){ return }
 			if(at != next){ next.back = at }
 			if(at != next){ next.back = at }
 			(k.combo || (k.combo = [])).push(key);
 			(k.combo || (k.combo = [])).push(key);
 			m.list(next, true);
 			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){
 		m.list = function(at, opt){
 			if(!at){ return m.flip(false) }
 			if(!at){ return m.flip(false) }
-		//	m.ui.depth(m.key.combo ? m.key.combo.length : 0)
 			var l = [];
 			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 }
 			if(!l.length){ return }
 			k.at = at;
 			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')
 			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
 			$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){
 			$.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) }
 			if(opt){ m.flip(true) }
 			$ul.append($('<li>').html('&larr;').on('click', function(){
 			$ul.append($('<li>').html('&larr;').on('click', function(){
-		//	  m.key.combo.pop()
 				m.list(at.back);
 				m.list(at.back);
 			}));
 			}));
 		}
 		}
@@ -140,7 +132,6 @@
 			$put.focus();
 			$put.focus();
 		}
 		}
 		k.wipe = function(opt){
 		k.wipe = function(opt){
-		//	k.down.meta = false;
 			k.combo = [];
 			k.combo = [];
 			if(!opt){ m.flip(false) }
 			if(!opt){ m.flip(false) }
 			m.list(k.at = m.edit);
 			m.list(k.at = m.edit);
@@ -151,19 +142,19 @@
 				.or($(document.elementFromPoint(meta.tap.x||0, meta.tap.y||0)));
 				.or($(document.elementFromPoint(meta.tap.x||0, meta.tap.y||0)));
 			return on;
 			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);
 			m.list(k.at || meta.edit);
 		}
 		}
-
-
+		$.extend(meta.edit, defaults)
 	})(USE, './metaCore');
 	})(USE, './metaCore');
 	;USE(function(module){
 	;USE(function(module){
 		/* UI */
 		/* UI */
@@ -187,6 +178,7 @@
 		$m.append($('<span>').html('&#9776;').addClass('meta-start'));
 		$m.append($('<span>').html('&#9776;').addClass('meta-start'));
 		$m.append($('<div>').addClass('meta-menu meta-none').append('<ul>'));
 		$m.append($('<div>').addClass('meta-menu meta-none').append('<ul>'));
 		$(document.body).append($m);
 		$(document.body).append($m);
+		meta.ui.board = $('.meta-menu', $m);
 		css({
 		css({
 			'#meta': {
 			'#meta': {
 				display: 'block',
 				display: 'block',
@@ -196,7 +188,6 @@
 				background: 'white',
 				background: 'white',
 				'font-size': '18pt',
 				'font-size': '18pt',
 				'font-family': 'Tahoma, arial',
 				'font-family': 'Tahoma, arial',
-				//'box-shadow': '0px 0px 1px #000044',
 				'border-radius': '1em',
 				'border-radius': '1em',
 				'text-align': 'center',
 				'text-align': 'center',
 				'z-index': 999999,
 				'z-index': 999999,
@@ -204,7 +195,6 @@
 				padding: 0,
 				padding: 0,
 				width: '2em',
 				width: '2em',
 				height: '2em',
 				height: '2em',
-				opacity: 0.7,
 				outline: 'none',
 				outline: 'none',
 				color: '#000044',
 				color: '#000044',
 				overflow: 'visible',
 				overflow: 'visible',
@@ -232,31 +222,28 @@
 			},
 			},
 			'#meta .meta-menu ul li': {
 			'#meta .meta-menu ul li': {
 				display: 'block',
 				display: 'block',
+				'float': 'right',
 				background: 'white',
 				background: 'white',
+				opacity: 0.7,
 				padding: '0.5em 1em',
 				padding: '0.5em 1em',
 				'border-radius': '1em',
 				'border-radius': '1em',
 				'margin-left': '0.25em',
 				'margin-left': '0.25em',
 				'margin-top': '0.25em',
 				'margin-top': '0.25em',
-				'float': 'right',
 				'cursor':  'pointer'
 				'cursor':  'pointer'
 			},
 			},
+			'#meta .meta-menu ul li:hover': {
+				opacity: 1
+			},
 			'#meta a': {color: 'black'},
 			'#meta a': {color: 'black'},
-		//	'#meta:hover': {opacity: 1},
 			'#meta .meta-menu ul:before': {
 			'#meta .meta-menu ul:before': {
 				content: "' '",
 				content: "' '",
 				display: 'block',
 				display: 'block',
 				'min-height': '15em',
 				'min-height': '15em',
 				height: '50vh'
 				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){
 		function css(css){
 			var tmp = '';
 			var tmp = '';
@@ -271,156 +258,10 @@
 			tag.innerHTML = tmp;
 			tag.innerHTML = tmp;
 			$m.append(tag)
 			$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){
 	;USE(function(module){
 		var m = meta, k = m.key;
 		var m = meta, k = m.key;
 		$(window).on('focus', k.wipe.bind(null, false)); // .on('blur', k.wipe.bind(null, false))
 		$(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.y = eve.pageY||0;
 			m.tap.on = $(eve.target);
 			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
 		var [start, end] = 'ontouchstart' in window
 												? ['touchstart', 'touchend']
 												? ['touchstart', 'touchend']
 												: ['mousedown', 'mouseup']
 												: ['mousedown', 'mouseup']
-
 		$(document).on(start, '#meta .meta-menu li', function(eve){
 		$(document).on(start, '#meta .meta-menu li', function(eve){
 			var combo = $(this).data().combo;
 			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;
 			eve.tap = true;
 			k.down(eve);
 			k.down(eve);
 			$(document).one(end, () => k.up(eve))
 			$(document).one(end, () => k.up(eve))
 		return;
 		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);
 		$(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
 		  if (ev.target.tagName == 'LI' || ev.target.tagName == 'UL') return
 			meta.flip()
 			meta.flip()
 		})
 		})
-
-		//$(document).on('select contextmenu keyup mouseup', '[contenteditable=true]', m.text.on);
-
-
 	})(USE, './metaEvents');
 	})(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.bind({port: udp.port, exclusive: true}, function(){
     socket.setBroadcast(true);
     socket.setBroadcast(true);
     socket.setMulticastTTL(128);
     socket.setMulticastTTL(128);
-    try{ socket.addMembership(udp.address); }catch(e){}
   });
   });
 
 
   socket.on("listening", function(){
   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 = {id: udp.address + ':' + udp.port, wire: socket};
 
 
     udp.peer.say = function(raw){
     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';
     var url = 'http://' + info.address + ':' + (port || (opt.web && opt.web.address()||{}).port) + '/gun';
     if(root.opt.peers[url]){ return }
     if(root.opt.peers[url]){ return }
-  
+
     //console.log('discovered', url, message, info);
     //console.log('discovered', url, message, info);
     root.$.opt(url);
     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((tmp = root.stats) && (tmp = tmp.gap) && info){ (tmp.near || (tmp.near = {}))[info.address] = info.port || 1 } // STATS!
     if(check.on || id === pid){ return }
     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);
   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.doc = opt.doc || {};
 	opt.ids = opt.ids || {};
 	opt.ids = opt.ids || {};
 	opt.any = opt.any || cb;
 	opt.any = opt.any || cb;
+	opt.meta = opt.meta || false;
 	opt.ev = opt.ev || {off: function(){
 	opt.ev = opt.ev || {off: function(){
 		Gun.obj.map(opt.ev.s, function(e){
 		Gun.obj.map(opt.ev.s, function(e){
 			if(e){ e.off() }
 			if(e){ e.off() }
@@ -12,7 +13,9 @@ Gun.chain.open = function(cb, opt, at){
 		opt.ev.s = {};
 		opt.ev.s = {};
 	}, s:{}}
 	}, s:{}}
 	return this.on(function(data, key, ctx, ev){
 	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);
 		clearTimeout(opt.to);
 		opt.to = setTimeout(function(){
 		opt.to = setTimeout(function(){
 			if(!opt.any){ return }
 			if(!opt.any){ return }

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

@@ -16,10 +16,14 @@
 		opt.code.from = opt.code.from || '!';
 		opt.code.from = opt.code.from || '!';
 		opt.jsonify = true;
 		opt.jsonify = true;
 
 
+
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
 		function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
 		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 map = Gun.obj.map;
-		var LOG = console.LOG;
+		var obj_empty = Gun.obj.empty;
+		var ST = 0;
 
 
 		if(!opt.store){
 		if(!opt.store){
 			return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
 			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.
 			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.
 			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 || {};
 				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.
 			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,
 			Therefore it is unavoidable that a read will have to happen,
 			the question is just how long you delay it.
 			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};
 			o = ('object' == typeof o)? o : {force: o};
-			var f = function Fractal(){};
+			var f = function Fractal(){}, a, b;
 			f.text = '';
 			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(){
 			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();
 				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() }
 			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 raw;
-			var S; LOG && (S = +new Date);
+			var S = +new Date;
+			DBG && (DBG.w = S);
 			try{raw = JSON.stringify(rad.$);
 			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(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.text = raw;
 			f.write();
 			f.write();
@@ -222,81 +243,85 @@
 			if(u === o.start && u === o.end){ return tree }
 			if(u === o.start && u === o.end){ return tree }
 			if(atomic(tree)){ return tree }
 			if(atomic(tree)){ return tree }
 			var sub = Radix();
 			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('');
 			return sub('');
 		}
 		}
 
 
 		;(function(){
 		;(function(){
-			var Q = {};
-			r.read = function(key, cb, o){
+			r.read = function(key, cb, o, DBG){
 				o = o || {};
 				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.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 }
 			function rev(a,b){ return b }
 			var revo = {reverse: true};
 			var revo = {reverse: true};
@@ -310,18 +335,19 @@
 				with how much performance and scale we can get out of only one.
 				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.
 				Then we can work on the harder problem of being multi-process.
 			*/
 			*/
+			var RPC = 0;
 			var Q = {}, s = String.fromCharCode(31);
 			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;
 				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];
 					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{
 						try{
 							if(opt.pack <= data.length){
 							if(opt.pack <= data.length){
 								p.err = "Chunk too big!";
 								p.err = "Chunk too big!";
@@ -329,29 +355,55 @@
 								data = data.toString(); // If it crashes, it crashes here. How!?? We check size first!
 								data = data.toString(); // If it crashes, it crashes here. How!?? We check size first!
 							}
 							}
 						}catch(e){ p.err = e }
 						}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;
 					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{
 						try{
-							var json = JSON.parse(data);
+							var json = JSON.parse(data); // TODO: this caused a out-of-memory crash!
 							p.disk.$ = json;
 							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;
 							return;
 						}catch(e){ tmp = e }
 						}catch(e){ tmp = e }
 						if('{' === data[0]){
 						if('{' === data[0]){
 							p.err = tmp || "JSON error!";
 							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;
 					var tmp = p.split(data), pre = [], i, k, v;
 					if(!tmp || 0 !== tmp[1]){
 					if(!tmp || 0 !== tmp[1]){
 						p.err = "File '"+file+"' does not have root radix! ";
 						p.err = "File '"+file+"' does not have root radix! ";
-						return map(q, p.ack);
+						p.map(q, p.ack);
+						return; 
 					}
 					}
 					while(tmp){
 					while(tmp){
 						k = v = u;
 						k = v = u;
@@ -370,9 +422,8 @@
 						if(u !== k && u !== v){ p.disk(pre.join(''), v) }
 						if(u !== k && u !== v){ p.disk(pre.join(''), v) }
 						tmp = p.split(tmp[2]);
 						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){
 				p.split = function(t){
 					if(!t){ return }
 					if(!t){ return }
@@ -385,81 +436,76 @@
 					l[2] = t.slice(i + o.i);
 					l[2] = t.slice(i + o.i);
 					return l;
 					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);
 				opt.store.get(ename(file), p.read);
+				// TODO: What if memory disk gets filled with updates, and we get an old one back?
 			}
 			}
 		}());
 		}());
 
 
 		;(function(){
 		;(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;
 					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);
 				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){
 				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);
 					cb(u, 1);
 				}, true);
 				}, 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){
 				if(err){
 					opt.log('list', 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;
 					return;
 				}
 				}
+				if(disk){ drain(disk); return }
+				dir = dir || disk || Radix();
+				if(!opt.store.list){ drain(dir); return }
 				// import directory.
 				// import directory.
 				opt.store.list(function(file){
 				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;
 		var noop = function(){}, RAD, u;
 		Radisk.has[opt.file] = r;
 		Radisk.has[opt.file] = r;
 		return r;
 		return r;
 	}
 	}
 
 
-
-
 	;(function(){
 	;(function(){
 		var _ = String.fromCharCode(31), u;
 		var _ = String.fromCharCode(31), u;
 		Radisk.encode = function(d, o, s){ s = s || _;
 		Radisk.encode = function(d, o, s){ s = s || _;

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

@@ -2,13 +2,14 @@
 
 
 	function Radix(){
 	function Radix(){
 		var radix = function(key, val, t){
 		var radix = function(key, val, t){
-			key = ''+key;
+			radix.unit = 0;
 			if(!t && u !== val){ 
 			if(!t && u !== val){ 
-				radix.last = (key < radix.last)? radix.last : key;
+				radix.last = (''+key < radix.last)? radix.last : ''+key;
 				delete (radix.$||{})[_];
 				delete (radix.$||{})[_];
 			}
 			}
 			t = t || radix.$ || (radix.$ = {});
 			t = t || radix.$ || (radix.$ = {});
 			if(!key && Object.keys(t).length){ return t }
 			if(!key && Object.keys(t).length){ return t }
+			key = ''+key;
 			var i = 0, l = key.length-1, k = key[i], at, tmp;
 			var i = 0, l = key.length-1, k = key[i], at, tmp;
 			while(!(at = t[k]) && i < l){
 			while(!(at = t[k]) && i < l){
 				k += key[++i];
 				k += key[++i];
@@ -22,57 +23,71 @@
 					if(kk){
 					if(kk){
 						if(u === val){
 						if(u === val){
 							if(ii <= l){ return }
 							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 __ = {};
 						var __ = {};
 						__[s.slice(ii)] = r;
 						__[s.slice(ii)] = r;
 						ii = key.slice(ii);
 						ii = key.slice(ii);
 						('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val);
 						('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val);
+						//(__[_] = function $(){ $.sort = Object.keys(__).sort(); return $ }());
 						t[kk] = __;
 						t[kk] = __;
 						delete t[s];
 						delete t[s];
+						//(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }());
 						return true;
 						return true;
 					}
 					}
 				})){
 				})){
 					if(u === val){ return; }
 					if(u === val){ return; }
 					(t[k] || (t[k] = {}))[''] = val;
 					(t[k] || (t[k] = {}))[''] = val;
+					//(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }());
 				}
 				}
 				if(u === val){
 				if(u === val){
 					return tmp;
 					return tmp;
 				}
 				}
 			} else 
 			} else 
 			if(i == l){
 			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[''] = val;
+				//(at[_] = function $(){ $.sort = Object.keys(at).sort(); return $ }());
 			} else {
 			} else {
 				if(u !== val){ delete at[_] }
 				if(u !== val){ delete at[_] }
+				//at && (at[_] = function $(){ $.sort = Object.keys(at).sort(); return $ }());
 				return radix(key.slice(++i), val, at || (at = {}));
 				return radix(key.slice(++i), val, at || (at = {}));
 			}
 			}
 		}
 		}
 		return radix;
 		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;
 		var t = ('function' == typeof radix)? radix.$ || {} : radix;
+		//!opt && console.log("WHAT IS T?", JSON.stringify(t).length);
 		if(!t){ return }
 		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();
 		//var keys = Object.keys(t).sort();
 		opt = (true === opt)? {branch: true} : (opt || {});
 		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;
 		var i = 0, l = keys.length;
 		for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt;
 		for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt;
 			if(!tree || '' === key || _ === key){ continue }
 			if(!tree || '' === key || _ === key){ continue }
-			p = pre.slice(); p.push(key);
+			p = pre.slice(0); p.push(key);
 			pt = p.join('');
 			pt = p.join('');
 			if(u !== start && pt < (start||'').slice(0,pt.length)){ continue }
 			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.
 			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){ return tmp }
 			}
 			}
 			if(u !== (tmp = tree[''])){
 			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
 			} else
 			if(opt.branch){
 			if(opt.branch){
 				tmp = cb(u, pt, key, pre);
 				tmp = cb(u, pt, key, pre);
@@ -80,7 +95,7 @@
 			}
 			}
 			pre = p;
 			pre = p;
 			if(!rev){
 			if(!rev){
-				tmp = map(tree, cb, opt, pre);
+				tmp = rap(tree, cb, opt, pre);
 				if(u !== tmp){ return tmp }
 				if(u !== tmp){ return tmp }
 			}
 			}
 			pre.pop();
 			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];
 		return Store[opt.file];
 	}
 	}
 	Store[opt.file] = store;
 	Store[opt.file] = store;
+	var puts = {};
 
 
+	// TODO!!! ADD ZLIB INFLATE / DEFLATE COMPRESSION!
 	store.put = function(file, data, cb){
 	store.put = function(file, data, cb){
+		puts[file] = data;
 		var random = Math.random().toString(36).slice(-3);
 		var random = Math.random().toString(36).slice(-3);
 		var tmp = opt.file+'-'+file+'-'+random+'.tmp';
 		var tmp = opt.file+'-'+file+'-'+random+'.tmp';
 		fs.writeFile(tmp, data, function(err, ok){
 		fs.writeFile(tmp, data, function(err, ok){
+			delete puts[file];
 			if(err){ return cb(err) }
 			if(err){ return cb(err) }
 			move(tmp, opt.file+'/'+file, cb);
 			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){
 		fs.readFile(opt.file+'/'+file, function(err, data){
 			if(err){
 			if(err){
 				if('ENOENT' === (err.code||'').toUpperCase()){
 				if('ENOENT' === (err.code||'').toUpperCase()){
-					return cb(null);
+					return cb();
 				}
 				}
-				opt.log("ERROR:", err)
+				opt.log("ERROR:", err);
 			}
 			}
 			cb(err, data);
 			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;
 	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){
 	store.put = function(file, data, cb){
 		var params = {Bucket: opts.bucket, Key: file, Body: data};
 		var params = {Bucket: opts.bucket, Key: file, Body: data};
 		//console.log("RS3 PUT ---->", (data||"").slice(0,20));
 		//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 cbs = c.g[file] = [cb];
 		var params = {Bucket: opts.bucket, Key: file||''};
 		var params = {Bucket: opts.bucket, Key: file||''};
 		//console.log("RS3 GET ---->", 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));
 			//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){
 	store.list = function(cb, match, params, cbs){
@@ -98,6 +100,7 @@ function Store(opt){
     });
     });
 	};
 	};
 	//store.list(function(){ return true });
 	//store.list(function(){ return true });
+	require('./rfsmix')(opt, store); // ugly, but gotta move fast for now.
 	return store;
 	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,'/');
 		req.url = (req.url||'').replace(dot,'').replace(slash,'/');
 		if(serve(req, res)){ return } // filters GUN requests!
 		if(serve(req, res)){ return } // filters GUN requests!
 		fs.createReadStream(path.join(dir, req.url)).on('error',function(tmp){ // static files!
 		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;
 		return true;
 	}
 	}
 	if(0 <= req.url.indexOf('gun/')){
 	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;
 			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 = req.socket) && (tmp = tmp.server) && (tmp = tmp.route)){ var url;
 		if(tmp = tmp[(((req.url||'').slice(1)).split('/')[0]||'').split('.')[0]]){
 		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;
 	var Gun = require('../gun'), u;
 	Gun.serve = require('./serve');
 	Gun.serve = require('./serve');
 	//process.env.GUN_ENV = process.env.GUN_ENV || 'debug';
 	//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){
 	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;
 		root.opt.log = root.opt.log || Gun.log;
 		this.to.next(root);
 		this.to.next(root);
 	})
 	})
@@ -16,10 +15,9 @@
 	require('./wire');
 	require('./wire');
 	try{require('../sea');}catch(e){}
 	try{require('../sea');}catch(e){}
 	try{require('../axe');}catch(e){}
 	try{require('../axe');}catch(e){}
-	require('./file');
+	//require('./file');
 	require('./evict');
 	require('./evict');
 	require('./multicast');
 	require('./multicast');
 	require('./stats');
 	require('./stats');
-	if('debug' === process.env.GUN_ENV){ require('./debug') }
 	module.exports = Gun;
 	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.up.count = (root.stats.up.count || 0) + 1;
 		root.stats.stay = root.stats.stay || {};
 		root.stats.stay = root.stats.stay || {};
 		root.stats.gap = {};
 		root.stats.gap = {};
+		root.stats.over = +new Date;
 	},1);
 	},1);
 	setInterval(function(){
 	setInterval(function(){
 		if(!root.stats){ root.stats = {} }
 		if(!root.stats){ root.stats = {} }
+		var S = +new Date;
 		var stats = root.stats, tmp;
 		var stats = root.stats, tmp;
+		stats.over = S - (stats.over||S);
 		(stats.up||{}).time = process.uptime();
 		(stats.up||{}).time = process.uptime();
 		stats.memory = process.memoryUsage() || {};
 		stats.memory = process.memoryUsage() || {};
 		stats.memory.totalmem = os.totalmem();
 		stats.memory.totalmem = os.totalmem();
@@ -43,18 +46,19 @@ Gun.on('opt', function(root){
 		stats.all = all;
 		stats.all = all;
 		var dam = root.opt.mesh;
 		var dam = root.opt.mesh;
 		if(dam){
 		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
 			dam.hear.c = dam.hear.d = dam.say.c = dam.say.d = 0; // reset
 			stats.peers.time = dam.bye.time || 0;
 			stats.peers.time = dam.bye.time || 0;
-			dam.hear.long = [];
 		}
 		}
 		var rad = root.opt.store; rad = rad && rad.stats;
 		var rad = root.opt.store; rad = rad && rad.stats;
 		if(rad){
 		if(rad){
 			stats.rad = rad;
 			stats.rad = rad;
 			root.opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // reset
 			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 = {};
 		stats.gap = {};
 	}, 1000 * 15);
 	}, 1000 * 15);
 	Object.keys = Object.keys || function(o){ return Gun.obj.map(o, function(v,k,t){t(k)}) }
 	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;
 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){
 	if('number' == typeof a && 'number' == typeof b && 'string' == typeof c){
 		var tmp = (all[c] || (all[c] = []));
 		var tmp = (all[c] || (all[c] = []));
 		if(max < tmp.push([a,b])){ all[c] = [] } // reset
 		if(max < tmp.push([a,b])){ all[c] = [] } // reset
 		//return;
 		//return;
 	}
 	}
+	if(!console.LOG || log.off){ return }
 	return log.apply(Gun, arguments);
 	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 }
     if(false === opt.radisk){ return }
     var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
     var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
     var Radix = Radisk.Radix;
     var Radix = Radisk.Radix;
-    var LOG = console.LOG;
+    var ST = 0;
  
  
     opt.store = opt.store || (!Gun.window && require('./rfs')(opt));
     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){
     root.on('put', function(msg){
         this.to.next(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){
     root.on('get', function(msg){
         this.to.next(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;
         var id = msg['#'], get = msg.get, soul = msg.get['#'], has = msg.get['.']||'', o = {}, graph, lex, key, tmp, force;
         if('string' == typeof soul){
         if('string' == typeof soul){
             key = soul;
             key = soul;
@@ -78,52 +59,86 @@ Gun.on('create', function(root){
             o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1;
             o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1;
         }
         }
         if(has['-'] || (soul||{})['-']){ o.reverse = true }
         if(has['-'] || (soul||{})['-']){ o.reverse = true }
-        if((tmp = (root.next||empty)[soul]) && tmp.put){
+        if((tmp = (root.next||'')[soul]) && tmp.put){
             if(o.atom){
             if(o.atom){
-                tmp = (tmp.next||empty)[o.atom] ;
+                tmp = (tmp.next||'')[o.atom] ;
                 if(tmp && tmp.rad){ return }
                 if(tmp && tmp.rad){ return }
             } else
             } 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;
             try{opt.store.stats.get.time[statg % 50] = (+new Date) - S; ++statg;
                 opt.store.stats.get.count++;
                 opt.store.stats.get.count++;
                 if(err){ opt.store.stats.get.err = err }
                 if(err){ opt.store.stats.get.err = err }
             }catch(e){} // STATS!
             }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(typeof data !== 'string'){
                     if(o.atom){
                     if(o.atom){
                         data = u;
                         data = u;
                     } else {
                     } else {
-                        Radix.map(data, each) 
+                        Radix.map(data, each); // IS A RADIX TREE, NOT FUNCTION!
                     }
                     }
                 }
                 }
                 if(!graph && data){ each(data, '') }
                 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 }
             if(!val){ return }
             has = (key+has).split(esc);
             has = (key+has).split(esc);
             var soul = has.slice(0,1)[0];
             var soul = has.slice(0,1)[0];
             has = has.slice(-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;
             o.count = (o.count || 0) + val.length;
             var tmp = val.lastIndexOf('>');
             var tmp = val.lastIndexOf('>');
             var state = Radisk.decode(val.slice(tmp+1), null, esc);
             var state = Radisk.decode(val.slice(tmp+1), null, esc);
             val = Radisk.decode(val.slice(0,tmp), null, esc);
             val = Radisk.decode(val.slice(0,tmp), null, esc);
             (graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul);
             (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!
     opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS!
     var statg = 0, statp = 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
 // Returns a promise for the data, key of the gun call
-Gun.chain.then = function() {
+Gun.chain.then = function(cb) {
 	var gun = this;
 	var gun = this;
-  return (new Promise((res, rej)=>{
+  var p = (new Promise((res, rej)=>{
     gun.once(function (data, key) {
     gun.once(function (data, key) {
       res(data, key); //call resolve when data is returned
       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);
 }).call({}, edide);
 "use strict";edide.var = (function _var (edide) {
 "use strict";edide.var = (function _var (edide) {
 var currentReact, debugging, dependees, dependsOn, depsRequired, inInitCall, infinityCheck, initSetter, newVar, parent, setLinks, setters, updateVar, values;
 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
 setters = new Map; // varName => setter:func
 dependees = new Map; // varName => deps:
 dependees = new Map; // varName => deps:
 setLinks = new Map; // for reactiveGraph, show setters inside reactive/setter:s
 setLinks = new Map; // for reactiveGraph, show setters inside reactive/setter:s
@@ -190,7 +190,13 @@ return (initialVars = {}) => {
     }
     }
     return edide.var(func);
     return edide.var(func);
   };
   };
-  return {revar, unvar, react}; // , dom
+  return {
+    react,
+    revar,
+    unvar,
+    un: unvar,
+    re: revar // , dom
+  };
 };
 };
 return this;
 return this;
 }).call({}, edide);
 }).call({}, edide);
@@ -416,7 +422,7 @@ this.fromKeyCode = (key) => {
 };
 };
 return this;
 return this;
 }).call({}, edide);
 }).call({}, edide);
-"use strict";edide.new = (function  (edide) {
+"use strict";edide.clone = (function _clone (edide) {
 return function(...objects) {
 return function(...objects) {
   if (Array.isArray(objects[0])) {
   if (Array.isArray(objects[0])) {
     return Object.assign([], ...objects);
     return Object.assign([], ...objects);
@@ -443,7 +449,7 @@ this.hasVar = (varName) => {
 this.activateVar = (name, value) => {
 this.activateVar = (name, value) => {
   var configs;
   var configs;
   if (value != null) {
   if (value != null) {
-    configs = edide.new(mutatingVars[name]);
+    configs = edide.clone(mutatingVars[name]);
     if (edide.strParsesToNumber(value)) {
     if (edide.strParsesToNumber(value)) {
       value = parseFloat(value);
       value = parseFloat(value);
     }
     }
@@ -601,7 +607,7 @@ return this;
 }).call({}, edide);
 }).call({}, edide);
 "use strict";edide.mmPipe = (function _mmPipe (edide) {Object.defineProperty(this, 'module_name', {value:'mmPipe'});
 "use strict";edide.mmPipe = (function _mmPipe (edide) {Object.defineProperty(this, 'module_name', {value:'mmPipe'});
 var dist, lowpass, react, revar, reverber, unvar;
 var dist, lowpass, react, revar, reverber, unvar;
-({revar, unvar, react} = edide.mmState);
+({revar, unvar, react} = edide.mmState.init());
 dist = lowpass = reverber = null;
 dist = lowpass = reverber = null;
 this.initPipe = () => {
 this.initPipe = () => {
   lowpass = new Tone.Filter(unvar.lowpass, 'lowpass', -12);
   lowpass = new Tone.Filter(unvar.lowpass, 'lowpass', -12);
@@ -932,12 +938,35 @@ return sleep;
 return this;
 return this;
 }).call({}, edide);
 }).call({}, edide);
 "use strict";edide.mmInstrument = (function _mmInstrument (edide) {Object.defineProperty(this, 'module_name', {value:'mmInstrument'});
 "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);
 ({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', () => {
 react('create first instrument', () => {
   var name, ref;
   var name, ref;
   if (!(name = (ref = revar.instrumentConf) != null ? ref.name : void 0)) { // e.g. illegal instrument name
   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) => {
 cloneOrCreateInstrument = (name) => {
   var inst;
   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) => {
 getInstruments = (n) => {
   var all, free, name;
   var all, free, name;
@@ -1164,12 +1189,54 @@ this.play = (str) => {
 this.stop = () => revar.playing = false
 this.stop = () => revar.playing = false
 edide.instrumentConfigs.initInstrument()
 edide.instrumentConfigs.initInstrument()
 var initialSettings = {
 var initialSettings = {
-  "instrument": "piano"
+  "instrument": "synth-simple", //"piano"
 }
 }
 react(() => {
 react(() => {
   if(!revar.pipeReady) return
   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
 }) // TEMP init instrument
 return this;
 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);
 }).call({}, edide);

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

@@ -21,13 +21,16 @@
 		opt.RTCSessionDescription = rtcsd;
 		opt.RTCSessionDescription = rtcsd;
 		opt.RTCIceCandidate = rtcic;
 		opt.RTCIceCandidate = rtcic;
 		opt.rtc = opt.rtc || {'iceServers': [
 		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.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2};
     opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
     opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
     opt.announce = function(to){
     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);
 				opt.mesh.bye(peer);
 			});
 			});
 			wire.on('error', function(e){});
 			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(){
 ;(function(){
 
 
   /* UNBUILD */
   /* 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){
   function USE(arg, req){
     return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
     return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
       arg(mod = {exports: {}});
       arg(mod = {exports: {}});
@@ -15,7 +10,7 @@
       return p.split('/').slice(-1).toString().replace('.js','');
       return p.split('/').slice(-1).toString().replace('.js','');
     }
     }
   }
   }
-  if(typeof module !== "undefined"){ var common = module }
+  if(typeof module !== "undefined"){ var MODULE = module }
   /* UNBUILD */
   /* UNBUILD */
 
 
   ;USE(function(module){
   ;USE(function(module){
@@ -31,7 +26,7 @@
 
 
     if(SEA.window = module.window){ SEA.window.SEA = SEA }
     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;
     module.exports = SEA;
   })(USE, './root');
   })(USE, './root');
 
 
@@ -47,10 +42,12 @@
   })(USE, './https');
   })(USE, './https');
 
 
   ;USE(function(module){
   ;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');
   })(USE, './base64');
 
 
@@ -96,7 +93,7 @@
     Object.assign(SafeBuffer, {
     Object.assign(SafeBuffer, {
       // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
       // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
       from() {
       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.')
           throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
         }
         }
         const input = arguments[0]
         const input = arguments[0]
@@ -167,28 +164,26 @@
     var o = {};
     var o = {};
 
 
     if(SEA.window){
     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.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
       api.TextEncoder = window.TextEncoder;
       api.TextEncoder = window.TextEncoder;
       api.TextDecoder = window.TextDecoder;
       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{
     if(!api.crypto){try{
       var crypto = USE('crypto', 1);
       var crypto = USE('crypto', 1);
-      const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
       Object.assign(api, {
       Object.assign(api, {
         crypto,
         crypto,
-        //subtle,
-        TextEncoder,
-        TextDecoder,
         random: (len) => Buffer.from(crypto.randomBytes(len))
         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){
     }catch(e){
       console.log("text-encoding and @peculiar/webcrypto may not be included by default, please add it to your package.json!");
       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;
       TEXT_ENCODING_OR_PECULIAR_WEBCRYPTO_NOT_INSTALLED;
@@ -201,7 +196,7 @@
     var SEA = USE('./root');
     var SEA = USE('./root');
     var Buffer = USE('./buffer');
     var Buffer = USE('./buffer');
     var s = {};
     var s = {};
-    s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64};
+    s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64};
     s.ecdsa = {
     s.ecdsa = {
       pair: {name: 'ECDSA', namedCurve: 'P-256'},
       pair: {name: 'ECDSA', namedCurve: 'P-256'},
       sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
       sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
@@ -217,6 +212,13 @@
       if(d){ jwk.d = d }
       if(d){ jwk.d = d }
       return jwk;
       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 = {
     s.recall = {
       validity: 12 * 60 * 60, // internally in seconds : 12 hours
       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)
       hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props)
@@ -267,13 +269,13 @@
         cb = salt;
         cb = salt;
         salt = u;
         salt = u;
       }
       }
-      salt = salt || shim.random(9);
       data = (typeof data == 'string')? data : JSON.stringify(data);
       data = (typeof data == 'string')? data : JSON.stringify(data);
       if('sha' === (opt.name||'').toLowerCase().slice(0,3)){
       if('sha' === (opt.name||'').toLowerCase().slice(0,3)){
         var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64')
         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)} }
         if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
         return rsha;
         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 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({
       var work = await (shim.ossl || shim.subtle).deriveBits({
         name: opt.name || 'PBKDF2',
         name: opt.name || 'PBKDF2',
@@ -317,7 +319,7 @@
 
 
       var ecdhSubtle = shim.ossl || shim.subtle;
       var ecdhSubtle = shim.ossl || shim.subtle;
       // First: ECDSA keys for signing/verifying...
       // 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) => {
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         // privateKey scope doesn't leak out from here!
         //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
         //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
@@ -337,7 +339,7 @@
       // Next: ECDH keys for encryption/decryption...
       // Next: ECDH keys for encryption/decryption...
 
 
       try{
       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) => {
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         // privateKey scope doesn't leak out from here!
         var key = {};
         var key = {};
@@ -396,8 +398,8 @@
       var priv = pair.priv;
       var priv = pair.priv;
       var jwk = S.jwk(pub, priv);
       var jwk = S.jwk(pub, priv);
       var hash = await sha(json);
       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')}
       var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
       if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
       if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
 
 
@@ -431,12 +433,12 @@
       opt = opt || {};
       opt = opt || {};
       // SEA.I // verify is free! Requires no user permission.
       // SEA.I // verify is free! Requires no user permission.
       var pub = pair.pub || pair;
       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 hash = await sha(json.m);
       var buf, sig, check, tmp; try{
       var buf, sig, check, tmp; try{
         buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
         buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
         sig = new Uint8Array(buf);
         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." }
         if(!check){ throw "Signature did not match." }
       }catch(e){
       }catch(e){
         if(SEA.opt.fallback){
         if(SEA.opt.fallback){
@@ -462,27 +464,30 @@
     var keyForPair = SEA.opt.slow_leak = pair => {
     var keyForPair = SEA.opt.slow_leak = pair => {
       if (knownKeys[pair]) return knownKeys[pair];
       if (knownKeys[pair]) return knownKeys[pair];
       var jwk = S.jwk(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];
       return knownKeys[pair];
     };
     };
 
 
-
+    var O = SEA.opt;
     SEA.opt.fall_verify = async function(data, pair, cb, opt, f){
     SEA.opt.fall_verify = async function(data, pair, cb, opt, f){
       if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
       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 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 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{
       var buf; var sig; var check; try{
         buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
         buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
         sig = new Uint8Array(buf)
         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." }
         if(!check){ throw "Signature did not match." }
       }catch(e){
       }catch(e){
         buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA!
         buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA!
         sig = new Uint8Array(buf)
         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." }
         if(!check){ throw "Signature did not match." }
       }
       }
       var r = check? S.parse(json.m) : u;
       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)} }
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
       return r;
     }
     }
@@ -492,6 +497,7 @@
 
 
   ;USE(function(module){
   ;USE(function(module){
     var shim = USE('./shim');
     var shim = USE('./shim');
+    var S = USE('./settings');
     var sha256hash = USE('./sha256');
     var sha256hash = USE('./sha256');
 
 
     const importGen = async (key, salt, opt) => {
     const importGen = async (key, salt, opt) => {
@@ -499,7 +505,9 @@
       var opt = opt || {};
       var opt = opt || {};
       const combo = key + (salt || shim.random(8)).toString('utf8'); // new
       const combo = key + (salt || shim.random(8)).toString('utf8'); // new
       const hash = shim.Buffer.from(await sha256hash(combo), 'binary')
       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;
     module.exports = importGen;
   })(USE, './aeskey');
   })(USE, './aeskey');
@@ -563,7 +571,7 @@
         bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
         bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
         bufct = shim.Buffer.from(json.ct, 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...
         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)));
         }, aes, new Uint8Array(bufct)));
       }catch(e){
       }catch(e){
         if('utf8' === opt.encode){ throw "Could not decrypt" }
         if('utf8' === opt.encode){ throw "Could not decrypt" }
@@ -601,11 +609,13 @@
       var epriv = pair.epriv;
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
       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 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!
         // 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);
         return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k);
       })
       })
       var r = derived;
       var r = derived;
@@ -630,7 +640,7 @@
           jwk,
           jwk,
           { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
           { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
         ), // ??? refactor
         ), // ??? refactor
-        S.ecdh
+        {name: 'ECDH', namedCurve: 'P-256'}
       ]
       ]
     }
     }
 
 
@@ -639,13 +649,14 @@
 
 
   ;USE(function(module){
   ;USE(function(module){
     var shim = USE('./shim');
     var shim = USE('./shim');
-    // Practical examples about usage found from ./test/common.js
+    // Practical examples about usage found in tests.
     var SEA = USE('./root');
     var SEA = USE('./root');
     SEA.work = USE('./work');
     SEA.work = USE('./work');
     SEA.sign = USE('./sign');
     SEA.sign = USE('./sign');
     SEA.verify = USE('./verify');
     SEA.verify = USE('./verify');
     SEA.encrypt = USE('./encrypt');
     SEA.encrypt = USE('./encrypt');
     SEA.decrypt = USE('./decrypt');
     SEA.decrypt = USE('./decrypt');
+    SEA.opt.aeskey = USE('./aeskey'); // not official!
 
 
     SEA.random = SEA.random || shim.random;
     SEA.random = SEA.random || shim.random;
 
 
@@ -687,7 +698,7 @@
     // But all other behavior needs to be equally easy, like opinionated ways of
     // But all other behavior needs to be equally easy, like opinionated ways of
     // Adding friends (trusted public keys), sending private messages, etc.
     // Adding friends (trusted public keys), sending private messages, etc.
     // Cheers! Tell me what you think.
     // 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;
     Gun.SEA = SEA;
     SEA.GUN = SEA.Gun = Gun;
     SEA.GUN = SEA.Gun = Gun;
 
 
@@ -723,13 +734,13 @@
       if(user = root.back('user')){ return user }
       if(user = root.back('user')){ return user }
       var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
       var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
       (at = (user = at.user = gun.chain(new User))._).opt = {};
       (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;
         var id = uuid(), pub = root.user;
         if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
         if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
         id = id + '~' + pub + '.';
         id = id + '~' + pub + '.';
         if(cb && cb.call){ cb(null, id) }
         if(cb && cb.call){ cb(null, id) }
         return id;
         return id;
-      }
+      }*/
       return user;
       return user;
     }
     }
     Gun.User = User;
     Gun.User = User;
@@ -1076,17 +1087,17 @@
   })(USE, './create');
   })(USE, './create');
 
 
   ;USE(function(module){
   ;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.
     // 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)
     // 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){
     Gun.on('opt', function(at){
       if(!at.sea){ // only add SEA once per instance, on the "at" context.
       if(!at.sea){ // only add SEA once per instance, on the "at" context.
         at.sea = {own: {}};
         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.
       this.to.next(at); // make sure to call the "next" middleware adapter.
     });
     });
@@ -1135,6 +1146,86 @@
       security.call(this, msg);
       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.
     // 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.
     // 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:
     // This is broken down into some pretty clear edge cases, let's go over them:
@@ -1160,6 +1251,11 @@
         }
         }
       }
       }
       if(msg.put){
       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!!!
         // potentially parallel async operations!!!
         var check = {}, each = {}, u;
         var check = {}, each = {}, u;
         each.node = function(node, soul){
         each.node = function(node, soul){
@@ -1290,6 +1386,7 @@
       if(!s || !(s = s[1])){ return }
       if(!s || !(s = s[1])){ return }
       s = s.split('.');
       s = s.split('.');
       if(!s || 2 > s.length){ return }
       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('.');
       s = s.slice(0,2).join('.');
       return s;
       return s;
     }
     }
@@ -1298,16 +1395,18 @@
     }
     }
     SEA.opt.pack = function(d,k, n,s){ // pack for verifying
     SEA.opt.pack = function(d,k, n,s){ // pack for verifying
       if(SEA.opt.check(d)){ return d }
       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;
     SEA.opt.unpack = function(d, k, n){ var tmp;
       if(u === d){ return }
       if(u === d){ return }
       if(d && (u !== (tmp = d[':']))){ return tmp }
       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(!k || !n){ return }
       if(d === n[k]){ return d }
       if(d === n[k]){ return d }
       if(!SEA.opt.check(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])){
       if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
         return d[2];
         return d[2];
       }
       }
@@ -1319,7 +1418,8 @@
     var noop = function(){}, u;
     var noop = function(){}, u;
     var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
     var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
     var rel_is = Gun.val.rel.is;
     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.
     // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
 
 
   })(USE, './index');
   })(USE, './index');
-}());
+}());

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

@@ -1,5 +1,6 @@
 
 
     var shim = require('./shim');
     var shim = require('./shim');
+    var S = require('./settings');
     var sha256hash = require('./sha256');
     var sha256hash = require('./sha256');
 
 
     const importGen = async (key, salt, opt) => {
     const importGen = async (key, salt, opt) => {
@@ -7,7 +8,9 @@
       var opt = opt || {};
       var opt = opt || {};
       const combo = key + (salt || shim.random(8)).toString('utf8'); // new
       const combo = key + (salt || shim.random(8)).toString('utf8'); // new
       const hash = shim.Buffer.from(await sha256hash(combo), 'binary')
       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;
     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, {
     Object.assign(SafeBuffer, {
       // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
       // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
       from() {
       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.')
           throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
         }
         }
         const input = arguments[0]
         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');
         bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
         bufct = shim.Buffer.from(json.ct, 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...
         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)));
         }, aes, new Uint8Array(bufct)));
       }catch(e){
       }catch(e){
         if('utf8' === opt.encode){ throw "Could not decrypt" }
         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;
       var ecdhSubtle = shim.ossl || shim.subtle;
       // First: ECDSA keys for signing/verifying...
       // 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) => {
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         // privateKey scope doesn't leak out from here!
         //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
         //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
@@ -39,7 +39,7 @@
       // Next: ECDH keys for encryption/decryption...
       // Next: ECDH keys for encryption/decryption...
 
 
       try{
       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) => {
       .then(async (keys) => {
         // privateKey scope doesn't leak out from here!
         // privateKey scope doesn't leak out from here!
         var key = {};
         var key = {};

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

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

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

@@ -13,11 +13,13 @@
       var epriv = pair.epriv;
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
       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 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!
         // 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);
         return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k);
       })
       })
       var r = derived;
       var r = derived;
@@ -42,7 +44,7 @@
           jwk,
           jwk,
           { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
           { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
         ), // ??? refactor
         ), // ??? 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 SEA = require('./root');
     var Buffer = require('./buffer');
     var Buffer = require('./buffer');
     var s = {};
     var s = {};
-    s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64};
+    s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64};
     s.ecdsa = {
     s.ecdsa = {
       pair: {name: 'ECDSA', namedCurve: 'P-256'},
       pair: {name: 'ECDSA', namedCurve: 'P-256'},
       sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
       sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
@@ -18,6 +18,13 @@
       if(d){ jwk.d = d }
       if(d){ jwk.d = d }
       return jwk;
       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 = {
     s.recall = {
       validity: 12 * 60 * 60, // internally in seconds : 12 hours
       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)
       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 = {};
     var o = {};
 
 
     if(SEA.window){
     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.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
       api.TextEncoder = window.TextEncoder;
       api.TextEncoder = window.TextEncoder;
       api.TextDecoder = window.TextDecoder;
       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{
     if(!api.crypto){try{
       var crypto = require('crypto', 1);
       var crypto = require('crypto', 1);
-      const { TextEncoder, TextDecoder } = require('text-encoding', 1)
       Object.assign(api, {
       Object.assign(api, {
         crypto,
         crypto,
-        //subtle,
-        TextEncoder,
-        TextDecoder,
         random: (len) => Buffer.from(crypto.randomBytes(len))
         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){
     }catch(e){
       console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!");
       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;
       OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;

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

@@ -24,8 +24,8 @@
       var priv = pair.priv;
       var priv = pair.priv;
       var jwk = S.jwk(pub, priv);
       var jwk = S.jwk(pub, priv);
       var hash = await sha(json);
       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')}
       var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
       if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
       if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
 
 

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

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

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

@@ -655,19 +655,24 @@ class IndexApp {
                                             "label": 'Sign IN',
                                             "label": 'Sign IN',
                                             "onclick": function (e) {
                                             "onclick": function (e) {
                                                 e.preventDefault();
                                                 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