Browse Source

update gundb, some fixes, user can delete worlds/states

Nikolay Suslov 6 years ago
parent
commit
43c2efa3e6

+ 108 - 1
public/app.js

@@ -952,6 +952,10 @@ class App {
 
     //let objName = loadInfo[ 'save_name' ] +'/'+ "savestate_" + loadInfo[ 'save_revision' ];
 
+    if(!loadInfo.save_name){
+        return undefined
+    }
+
     let objName = "savestate_" + loadInfo['application_path'] + '/' + loadInfo['save_name'] + '_vwf_json';
     let objNameRev = "savestate_" + loadInfo['save_revision'] + loadInfo['application_path'] + '/' + loadInfo['save_name'] + '_vwf_json';
 
@@ -1047,6 +1051,7 @@ class App {
     console.log('clone: ' + worldName + 'to: ' + worldID);
 
     let newOwner = _LCSUSER.is.pub;
+    let created = new Date().valueOf();
 
     let worldObj = {
       'owner': newOwner,
@@ -1064,7 +1069,7 @@ class App {
       let res = await _LCSDB.user(userPub).get('worlds').get(worldName).get(fn).once().then();
       let data = {
         'file': res.file,
-        'modified': res.modified
+        'modified': created
       }
       worldObj[fn] = data;
     }
@@ -1373,7 +1378,109 @@ class App {
     }
   }
 
+  // SUPPORT of DELETE USER WORLDS & SAVE STATES (experimental)
+  // TODO: manual garbage collection
+
+  async deleteWorldState(worldName, indexState) {
+
+    let revs = await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').once().then();
+    if (revs) {
+      for (const el of Object.keys(revs)) {
+        if (el !== '_') {
+          let doc = await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').get(el).once().then();
+          for (const rev of Object.keys(doc)) {
+            if (rev !== '_') {
+              await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').get(el).get(rev).put(null).then();
+            }
+          }
+          await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').get(el).put(null).then();
+        }
+      }
+    }
+
+    // clear all state params
+    let stateDoc = await _LCSUSER.get('documents').get(worldName).get(indexState).once().then();
+    for (const state of Object.keys(stateDoc)) {
+      if (state !== '_' && state !== 'revs') {
+        await _LCSUSER.get('documents').get(worldName).get(indexState).get(state).put(null).then();
+      }
+    }
+
+    await _LCSUSER.get('documents').get(worldName).get(indexState).get('revs').put(null).then();
+    await _LCSUSER.get('documents').get(worldName).get(indexState).put(null).then();
+
+  }
+
+  async deleteWorld(name, type) {
+
+    if (type == 'proto') {
+
+      let worldName = name;
+      //TODO check for states (ask for deleting all states first...)
+      //delete states
+
+      let documents = await _LCSUSER.get('documents').once().then();
+      if (documents) {
+        let states = await _LCSUSER.get('documents').get(worldName).once().then();
+        if (states) {
+          for (const el of Object.keys(states)) {
+            if (el !== '_') {
+              if (states[el]) {
+                await this.deleteWorldState(worldName, el);
+              }
+
+            }
+          }
+        }
+      }
+
+      let worldFiles = await _LCSUSER.get('worlds').get(worldName).once().then();
+      if (worldFiles) {
+        for (const el of Object.keys(worldFiles)) {
+          if (el !== '_') {
+            let doc = await _LCSUSER.get('worlds').get(worldName).get(el).once().then();
+            if (doc.file) {
+              for (const fEl of Object.keys(doc)) {
+                if (fEl !== '_') {
+                  await _LCSUSER.get('worlds').get(worldName).get(el).get(fEl).put(null).then();
+                }
+              }
+              await _LCSUSER.get('worlds').get(worldName).get(el).put(null).then();
+            } else {
+              await _LCSUSER.get('worlds').get(worldName).get(el).put(null).then()
+            }
+          }
+        }
+      }
+
+      //  _LCSUSER.get('worlds').get(worldName).map((res, index) => {
+
+      //       if(typeof res == 'object'){
+      //         _LCSUSER.get('worlds').get(worldName).get(index)
+      //         .get('file').put("null")
+      //         .back(1)
+      //         .get('modified').put("null")
+      //         .back(1)
+      //         .get('created').put("null")
+      //         .back(1).put("null")
+      //       } else {
+      //         _LCSUSER.get('worlds').get(worldName).get(index).put("null")
+      //       }
+      //  })
 
+      await _LCSUSER.get('worlds').get(worldName).put(null).then();
+
+    } else if (type == 'state') {
+
+      let worldName = name.split('/')[0];
+      let stateName = name.split('/')[2];
+
+      let stateEntryInfo = 'savestate_/' + worldName + '/' + stateName + '_info_vwf_json';
+      let stateEntry = 'savestate_/' + worldName + '/' + stateName + '_vwf_json';
+      await this.deleteWorldState(worldName, stateEntryInfo);
+      await this.deleteWorldState(worldName, stateEntry);
+    }
+  }
 
 
 }

+ 21 - 20
public/lib/gundb/gun.js

@@ -1,22 +1,22 @@
 ;(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){
-		return 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 */
+  var root;
+  if(typeof window !== "undefined"){ root = window }
+  if(typeof global !== "undefined"){ root = global }
+  root = root || {};
+  var console = root.console || {log: function(){}};
+  function USE(arg, req){
+    return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
+      arg(mod = {exports: {}});
+      USE[R(path)] = mod.exports;
+    }
+    function R(p){
+      return p.split('/').slice(-1).toString().replace('.js','');
+    }
+  }
+  if(typeof module !== "undefined"){ var common = module }
+  /* UNBUILD */
 
 	;USE(function(module){
 		// Generic javascript utilities.
@@ -1661,7 +1661,8 @@
 				data = tmp.put;
 			}
 			if((tmp = eve.wait) && (tmp = tmp[at.id])){ clearTimeout(tmp) }
-			if(!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))){
+			if((!to && (u === data || at.soul || at.link || (link && !(0 < link.ack))))
+			|| (u === data && (tmp = (obj_map(at.root.opt.peers, function(v,k,t){t(k)})||[]).length) && (link||at).ack <= tmp)){
 				tmp = (eve.wait = {})[at.id] = setTimeout(function(){
 					val.call({as:opt}, msg, eve, tmp || 1);
 				}, opt.wait || 99);
@@ -2162,7 +2163,7 @@
 
 			var wire = opt.wire;
 			opt.wire = open;
-			function open(peer){
+			function open(peer){ try{
 				if(!peer || !peer.url){ return wire && wire(peer) }
 				var url = peer.url.replace('http', 'ws');
 				var wire = peer.wire = new opt.WebSocket(url);
@@ -2185,7 +2186,7 @@
 					opt.mesh.hear(msg.data || msg, peer);
 				};
 				return wire;
-			}
+			}catch(e){}}
 
 			function reconnect(peer){
 				clearTimeout(peer.defer);

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


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

@@ -11,7 +11,7 @@
 		opt.code.from = opt.code.from || '!';
 
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
-		map = Gun.obj.map;
+		var map = Gun.obj.map;
 
 		if(!opt.store){
 			return Gun.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");

+ 36 - 10
public/lib/gundb/lib/rindexed.js

@@ -34,11 +34,7 @@
 
     store.put = function(file, data, cb){
       cb = cb || function(){};
-      if(!db){
-        var es = 'ERROR: RAD IndexedDB not yet ready.'
-        console.log(es);
-        cb(es, undefined);
-      } else {
+      var doPut = function(){
         // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action.
         var transaction = db.transaction([opt.file], 'readwrite');
 
@@ -60,15 +56,20 @@
           cb(es, undefined);
         };
       }
+      if(!db){
+        waitDbReady(doGet, 100, function(){
+          var es = 'ERROR: Timeout: RAD IndexedDB not ready.';
+          console.log(es);
+          cb(es, undefined);
+        }, 10)
+      } else {
+        doPut();
+      }
     };
 
     store.get = function(file, cb){
       cb = cb || function(){};
-      if(!db){
-        var es = 'ERROR: RAD IndexedDB not yet ready.';
-        console.log(es);
-        cb(es, undefined);
-      } else {
+      var doGet = function(){
         // Start a transaction. The transaction will be automaticallt closed when the last success/error handler took no new action.
         var transaction = db.transaction([opt.file], 'readwrite');
 
@@ -90,6 +91,31 @@
           cb(es, undefined);
         };
       }
+      if(!db){
+        waitDbReady(doGet, 100, function(){
+          var es = 'ERROR: Timeout: RAD IndexedDB not ready.';
+          console.log(es);
+          cb(es, undefined);
+        }, 10)
+      } else {
+        doGet();
+      }
+    };
+
+    var waitDbReady = function(readyFunc, checkInterval, timeoutFunc, timeoutSecs){
+      var startTime = new Date();
+      var checkFunc = function(){
+        if(db){
+          readyFunc(); 
+        } else {
+          if((new Date() - startTime) / 1000 >= timeoutSecs){
+            timeoutFunc();
+          } else {
+            setTimeout(checkFunc, checkInterval);
+          }
+        }
+      };
+      checkFunc();
     };
 
     return store;

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

@@ -4,9 +4,10 @@ var path = require('path');
 function CDN(dir){
 	return function(req, res){
 		if(serve(req, res)){ return } // filters GUN requests!
-		fs.createReadStream(path.join(dir, req.url)).on('error',function(){ // 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){}
 			res.writeHead(200, {'Content-Type': 'text/html'});
-			res.end(fs.readFileSync(path.join(dir, 'index.html'))); // or default to index
+			res.end(tmp+''); // or default to index
 		}).pipe(res); // stream
 	}
 }

+ 107 - 85
public/lib/gundb/sea.js

@@ -6,8 +6,8 @@
   if(typeof global !== "undefined"){ root = global }
   root = root || {};
   var console = root.console || {log: function(){}};
-  function USE(arg){
-    return arg.slice? USE[R(arg)] : function(mod, path){
+  function USE(arg, req){
+    return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
       arg(mod = {exports: {}});
       USE[R(path)] = mod.exports;
     }
@@ -20,12 +20,24 @@
 
   ;USE(function(module){
     // Security, Encryption, and Authorization: SEA.js
-    // MANDATORY READING: http://gun.js.org/explainers/data/security.html
+    // MANDATORY READING: https://gun.eco/explainers/data/security.html
+    // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
     // THIS IS AN EARLY ALPHA!
 
-    function SEA(){}
-    if(typeof window !== "undefined"){ (SEA.window = window).SEA = SEA }
+    if(typeof window !== "undefined"){ module.window = window }
 
+    var tmp = module.window || module;
+    var SEA = tmp.SEA || function(){};
+
+    if(SEA.window = module.window){ try{
+      SEA.window.SEA = SEA;
+      tmp = document.createEvent('CustomEvent');
+      tmp.initCustomEvent('extension', false, false, {type: "SEA"});
+      (window.dispatchEvent || window.fireEvent)(tmp);
+      window.postMessage({type: "SEA"}, '*');
+    } catch(e){} }
+
+    try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
     module.exports = SEA;
   })(USE, './root');
 
@@ -148,7 +160,7 @@
     const Buffer = USE('./buffer')
     const api = {Buffer: Buffer}
 
-    if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') {
+    if (typeof window !== 'undefined') {
       var crypto = window.crypto || window.msCrypto;
       var subtle = crypto.subtle || crypto.webkitSubtle;
       const TextEncoder = window.TextEncoder
@@ -162,9 +174,9 @@
       })
     } else {
       try{
-        var crypto = require('crypto');
-        const { subtle } = require('@trust/webcrypto')             // All but ECDH
-        const { TextEncoder, TextDecoder } = require('text-encoding')
+        var crypto = USE('crypto', 1);
+        const { subtle } = USE('@trust/webcrypto', 1)             // All but ECDH
+        const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
         Object.assign(api, {
           crypto,
           subtle,
@@ -173,7 +185,7 @@
           random: (len) => Buffer.from(crypto.randomBytes(len))
         });
         //try{
-          const WebCrypto = require('node-webcrypto-ossl')
+          const WebCrypto = USE('node-webcrypto-ossl', 1)
           api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
         //}catch(e){
           //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
@@ -189,6 +201,7 @@
   })(USE, './shim');
 
   ;USE(function(module){
+    const SEA = USE('./root');
     const Buffer = USE('./buffer')
     const settings = {}
     // Encryption parameters
@@ -225,6 +238,7 @@
       jwk: keysToEcdsaJwk,
       recall: authsettings
     })
+    SEA.opt = settings;
     module.exports = settings
   })(USE, './settings');
 
@@ -267,49 +281,40 @@
     var SEA = USE('./root');
     var shim = USE('./shim');
     var S = USE('./settings');
+    var sha = USE('./sha256');
     var u;
 
-    SEA.work = async (data, pair, cb) => { try { // used to be named `proof`
-      var salt = pair.epub || pair; // epub not recommended, salt should be random!
+    SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
+      var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
+      var opt = opt || {};
       if(salt instanceof Function){
         cb = salt;
         salt = u;
       }
       salt = salt || shim.random(9);
-      if (SEA.window) {
-        // For browser subtle works fine
-        const key = await shim.subtle.importKey(
-          'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits']
-        )
-        const result = await shim.subtle.deriveBits({
-          name: 'PBKDF2',
-          iterations: S.pbkdf2.iter,
-          salt: new shim.TextEncoder().encode(salt),
-          hash: S.pbkdf2.hash,
-        }, key, S.pbkdf2.ks * 8)
-        data = shim.random(data.length)  // Erase data in case of passphrase
-        const r = shim.Buffer.from(result, 'binary').toString('utf8')
-        if(cb){ try{ cb(r) }catch(e){console.log(e)} }
-        return r;
+      if('SHA-256' === opt.name){
+        var rsha = shim.Buffer.from(await sha(data), 'binary').toString('utf8')
+        if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
+        return rsha;
       }
-      // For NodeJS crypto.pkdf2 rocks
-      const crypto = shim.crypto;
-      const hash = crypto.pbkdf2Sync(
-        data,
-        new shim.TextEncoder().encode(salt),
-        S.pbkdf2.iter,
-        S.pbkdf2.ks,
-        S.pbkdf2.hash.replace('-', '').toLowerCase()
+      const key = await (shim.ossl || shim.subtle).importKey(
+        'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits']
       )
-      data = shim.random(data.length)  // Erase passphrase for app
-      const r = hash && hash.toString('utf8')
+      const result = await (shim.ossl || shim.subtle).deriveBits({
+        name: opt.name || 'PBKDF2',
+        iterations: opt.iterations || S.pbkdf2.iter,
+        salt: new shim.TextEncoder().encode(opt.salt || salt),
+        hash: opt.hash || S.pbkdf2.hash,
+      }, key, opt.length || (S.pbkdf2.ks * 8))
+      data = shim.random(data.length)  // Erase data in case of passphrase
+      const r = shim.Buffer.from(result, 'binary').toString('utf8')
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.work;
   })(USE, './work');
@@ -321,7 +326,7 @@
     var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
 
     //SEA.pair = async (data, proof, cb) => { try {
-    SEA.pair = async (cb) => { try {
+    SEA.pair = SEA.pair || (async (cb) => { try {
 
       const ecdhSubtle = shim.ossl || shim.subtle
       // First: ECDSA keys for signing/verifying...
@@ -372,7 +377,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.pair;
   })(USE, './pair');
@@ -383,7 +388,7 @@
     var S = USE('./settings');
     var sha256hash = USE('./sha256');
 
-    SEA.sign = async (data, pair, cb) => { try {
+    SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
       if(data && data.slice
       && 'SEA{' === data.slice(0,4)
       && '"m":' === data.slice(4,8)){
@@ -409,7 +414,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.sign;
   })(USE, './sign');
@@ -422,7 +427,7 @@
     var parse = USE('./parse');
     var u;
 
-    SEA.verify = async (data, pair, cb) => { try {
+    SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
       const json = parse(data)
       if(false === pair){ // don't verify!
         const raw = (json !== data)? 
@@ -447,7 +452,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.verify;
   })(USE, './verify');
@@ -472,13 +477,13 @@
     var S = USE('./settings');
     var aeskey = USE('./aeskey');
 
-    SEA.encrypt = async (data, pair, cb, opt) => { try {
+    SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const msg = JSON.stringify(data)
       const rand = {s: shim.random(8), iv: shim.random(16)};
       const ct = await aeskey(key, rand.s, opt)
-      .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
       }, aes, new shim.TextEncoder().encode(msg)))
       const r = 'SEA'+JSON.stringify({
@@ -493,7 +498,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.encrypt;
   })(USE, './encrypt');
@@ -505,23 +510,22 @@
     var aeskey = USE('./aeskey');
     var parse = USE('./parse');
 
-    SEA.decrypt = async (data, pair, cb, opt) => { try {
+    SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const json = parse(data)
       const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
-      .then((aes) => shim.subtle.decrypt({  // Keeping aesKey scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({  // Keeping aesKey scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8'))
       }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8'))))
       const r = parse(new shim.TextDecoder('utf8').decode(ct))
-      
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.decrypt;
   })(USE, './decrypt');
@@ -531,7 +535,7 @@
     var shim = USE('./shim');
     var S = USE('./settings');
     // Derive shared secret from other's pub and my epub/epriv
-    SEA.secret = async (key, pair, cb) => { try {
+    SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
       const pub = key.epub || key
       const epub = pair.epub
       const epriv = pair.epriv
@@ -555,7 +559,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     const keysToEcdhJwk = (pub, d) => { // d === priv
       //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old
@@ -605,7 +609,7 @@
     SEA.encrypt = USE('./encrypt');
     SEA.decrypt = USE('./decrypt');
 
-    SEA.random = getRandomBytes;
+    SEA.random = SEA.random || getRandomBytes;
 
     // This is easy way to use IndexedDB, all methods are Promises
     // Note: Not all SEA interfaces have to support this.
@@ -613,7 +617,7 @@
 
     // This is Buffer used in SEA and usable from Gun/SEA application also.
     // For documentation see https://nodejs.org/api/buffer.html
-    SEA.Buffer = Buffer;
+    SEA.Buffer = SEA.Buffer || Buffer;
 
     // These SEA functions support now ony Promises or
     // async/await (compatible) code, use those like Promises.
@@ -621,7 +625,7 @@
     // Creates a wrapper library around Web Crypto API
     // for various AES, ECDSA, PBKDF2 functions we called above.
     // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
-    SEA.keyid = async (pub) => {
+    SEA.keyid = SEA.keyid || (async (pub) => {
       try {
         // base64('base64(x):base64(y)') => Buffer(xy)
         const pb = Buffer.concat(
@@ -639,7 +643,7 @@
         console.log(e)
         throw e
       }
-    }
+    });
     // all done!
     // Obviously it is missing MANY necessary features. This is only an alpha release.
     // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
@@ -649,7 +653,7 @@
     // But all other behavior needs to be equally easy, like opinionated ways of
     // Adding friends (trusted public keys), sending private messages, etc.
     // Cheers! Tell me what you think.
-    var Gun = (SEA.window||{}).Gun || require('./gun');
+    var Gun = (SEA.window||{}).Gun || USE('./gun', 1);
     Gun.SEA = SEA;
     SEA.Gun = Gun;
 
@@ -662,9 +666,9 @@
     // This is internal func queries public key(s) for alias.
     const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
       // load all public keys associated with the username alias we want to log in with.
-      gunRoot.get('~@'+alias).get((rat, rev) => {
-        rev.off();
-        if (!rat.put) {
+      gunRoot.get('~@'+alias).once((data, key) => {
+        //rev.off();
+        if (!data) {
           // if no user, don't do anything.
           const err = 'No user!'
           Gun.log(err)
@@ -674,19 +678,18 @@
         const aliases = []
         let c = 0
         // TODO: how about having real chainable map without callback ?
-        Gun.obj.map(rat.put, (at, pub) => {
+        Gun.obj.map(data, (at, pub) => {
           if (!pub.slice || '~' !== pub.slice(0, 1)) {
             // TODO: ... this would then be .filter((at, pub))
             return
           }
           ++c
           // grab the account associated with this public key.
-          gunRoot.get(pub).get((at, ev) => {
+          gunRoot.get(pub).once(data => {
             pub = pub.slice(1)
-            ev.off()
             --c
-            if (at.put){
-              aliases.push({ pub, at })
+            if (data){
+              aliases.push({ pub, put: data })
             }
             if (!c && (c = -1)) {
               resolve(aliases)
@@ -710,7 +713,7 @@
     const authenticate = async (alias, pass, gunRoot) => {
       // load all public keys associated with the username alias we want to log in with.
       const aliases = (await queryGunAliases(alias, gunRoot))
-      .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put)
+      .filter(a => !!a.pub && !!a.put)
       // Got any?
       if (!aliases.length) {
         throw { err: 'Public key does not exist!' }
@@ -718,14 +721,14 @@
       let err
       // then attempt to log into each one until we find ours!
       // (if two users have the same username AND the same password... that would be bad)
-      const users = await Promise.all(aliases.map(async ({ at: at, pub: pub }, i) => {
+      const users = await Promise.all(aliases.map(async (a, i) => {
         // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
-        const auth = parseProps(at.put.auth)
+        const auth = parseProps(a.put.auth)
       // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
       // SEA.verify(at.put.auth, pub).then(function(auth){
         try {
           const proof = await SEA.work(pass, auth.s)
-          const props = { pub: pub, proof: proof, at: at }
+          //const props = { pub: pub, proof: proof, at: at }
           // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
           /*
           MARK TO @mhelander : pub vs epub!???
@@ -733,24 +736,24 @@
           const salt = auth.salt
           const sea = await SEA.decrypt(auth.ek, proof)
           if (!sea) {
-            err = 'Failed to decrypt secret! ' + i +'/'+aliases.length;
+            err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
             return
           }
           // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
           // if we were successful, then that meanswe're logged in!
           const priv = sea.priv
           const epriv = sea.epriv
-          const epub = at.put.epub
+          const epub = a.put.epub
           // TODO: 'salt' needed?
           err = null
-          if(typeof window !== 'undefined'){
-            var tmp = window.sessionStorage;
+          if(SEA.window){
+            var tmp = SEA.window.sessionStorage;
             if(tmp && gunRoot._.opt.remember){
-              window.sessionStorage.alias = alias;
-              window.sessionStorage.tmp = pass;
+              SEA.window.sessionStorage.alias = alias;
+              SEA.window.sessionStorage.tmp = pass;
             }
           }
-          return Object.assign(props, { priv: priv, salt: salt, epub: epub, epriv: epriv })
+          return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
         } catch (e) {
           err = 'Failed to decrypt secret!'
           throw { err }
@@ -862,10 +865,25 @@
     const finalizeLogin = async (alias, key, gunRoot, opts) => {
       const user = gunRoot._.user
       // add our credentials in-memory only to our root gun instance
-      //var tmp = user._.tag;
+      var tmp = user._.tag;
       var opt = user._.opt;
-      user._ = key.at.$._;
+      user._ = gunRoot.get('~'+key.pub)._;
       user._.opt = opt;
+      var tags = user._.tag;
+      /*Object.values && Object.values(tmp).forEach(function(tag){
+        // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
+        var t = tags[tag.tag];
+        console.log("hm??", tag, t);
+        if(!t){
+          tags[tag.tag] = tag;
+          return;
+        }
+        if(tag.last){
+          tag.last.to = t.to;
+          t.last = tag.last = t.last || tag.last;
+        }
+        t.to = tag.to;
+      })*/
       //user._.tag = tmp || user._.tag;
       // so that way we can use the credentials to encrypt/decrypt data
       // that is input/output through gun (see below)
@@ -880,7 +898,8 @@
       //await authPersist(user._, key.proof, opts) // temporarily disabled
       // emit an auth event, useful for page redirects and stuff.  
       try {
-        gunRoot._.on('auth', user._)
+        gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
+        //user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
       } catch (e) {
         console.log('Your \'auth\' callback crashed with:', e)
       }
@@ -1300,7 +1319,8 @@
     }
     // If authentication is to be remembered over reloads or browser closing,
     // set validity time in minutes.
-    User.prototype.recall = async function(setvalidity, options){ 
+    User.prototype.recall = function(setvalidity, options){
+      var gun = this;
       const gunRoot = this.back(-1)
 
       let validity
@@ -1317,7 +1337,7 @@
             }
           }
         }
-        return this;
+        return gun;
       }
 
       if (!Gun.val.is(setvalidity)) {
@@ -1340,13 +1360,15 @@
         authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
         ? opts.hook : _initial_authsettings.hook
         // All is good. Should we do something more with actual recalled data?
-        return await authRecall(gunRoot)
+        (async function(){ await authRecall(gunRoot) }());
+        return gun;
       } catch (e) {
         const err = 'No session!'
         Gun.log(err)
         // NOTE! It's fine to resolve recall with reason why not successful
         // instead of rejecting...
-        return { err: (e && e.err) || err }
+        //return { err: (e && e.err) || err }
+        return gun;
       }
     }
     User.prototype.alive = async function(){
@@ -1563,7 +1585,7 @@
             if(tmp = relpub(soul)){
               check['any'+soul+key] = 1;
               SEA.verify(val, pub = tmp, function(data){ var rel;
-                if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) }
+                if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
                 if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
                   (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
                 }

+ 11 - 11
public/lib/gundb/sea/authenticate.js

@@ -7,7 +7,7 @@
     const authenticate = async (alias, pass, gunRoot) => {
       // load all public keys associated with the username alias we want to log in with.
       const aliases = (await queryGunAliases(alias, gunRoot))
-      .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put)
+      .filter(a => !!a.pub && !!a.put)
       // Got any?
       if (!aliases.length) {
         throw { err: 'Public key does not exist!' }
@@ -15,14 +15,14 @@
       let err
       // then attempt to log into each one until we find ours!
       // (if two users have the same username AND the same password... that would be bad)
-      const users = await Promise.all(aliases.map(async ({ at: at, pub: pub }, i) => {
+      const users = await Promise.all(aliases.map(async (a, i) => {
         // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.)
-        const auth = parseProps(at.put.auth)
+        const auth = parseProps(a.put.auth)
       // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here.
       // SEA.verify(at.put.auth, pub).then(function(auth){
         try {
           const proof = await SEA.work(pass, auth.s)
-          const props = { pub: pub, proof: proof, at: at }
+          //const props = { pub: pub, proof: proof, at: at }
           // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
           /*
           MARK TO @mhelander : pub vs epub!???
@@ -30,24 +30,24 @@
           const salt = auth.salt
           const sea = await SEA.decrypt(auth.ek, proof)
           if (!sea) {
-            err = 'Failed to decrypt secret! ' + i +'/'+aliases.length;
+            err = 'Failed to decrypt secret! ' + (i+1) +'/'+aliases.length;
             return
           }
           // now we have AES decrypted the private key, from when we encrypted it with the proof at registration.
           // if we were successful, then that meanswe're logged in!
           const priv = sea.priv
           const epriv = sea.epriv
-          const epub = at.put.epub
+          const epub = a.put.epub
           // TODO: 'salt' needed?
           err = null
-          if(typeof window !== 'undefined'){
-            var tmp = window.sessionStorage;
+          if(SEA.window){
+            var tmp = SEA.window.sessionStorage;
             if(tmp && gunRoot._.opt.remember){
-              window.sessionStorage.alias = alias;
-              window.sessionStorage.tmp = pass;
+              SEA.window.sessionStorage.alias = alias;
+              SEA.window.sessionStorage.tmp = pass;
             }
           }
-          return Object.assign(props, { priv: priv, salt: salt, epub: epub, epriv: epriv })
+          return {priv: priv, pub: a.put.pub, salt: salt, epub: epub, epriv: epriv };
         } catch (e) {
           err = 'Failed to decrypt secret!'
           throw { err }

+ 7 - 4
public/lib/gundb/sea/create.js

@@ -202,7 +202,8 @@
     }
     // If authentication is to be remembered over reloads or browser closing,
     // set validity time in minutes.
-    User.prototype.recall = async function(setvalidity, options){ 
+    User.prototype.recall = function(setvalidity, options){
+      var gun = this;
       const gunRoot = this.back(-1)
 
       let validity
@@ -219,7 +220,7 @@
             }
           }
         }
-        return this;
+        return gun;
       }
 
       if (!Gun.val.is(setvalidity)) {
@@ -242,13 +243,15 @@
         authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
         ? opts.hook : _initial_authsettings.hook
         // All is good. Should we do something more with actual recalled data?
-        return await authRecall(gunRoot)
+        (async function(){ await authRecall(gunRoot) }());
+        return gun;
       } catch (e) {
         const err = 'No session!'
         Gun.log(err)
         // NOTE! It's fine to resolve recall with reason why not successful
         // instead of rejecting...
-        return { err: (e && e.err) || err }
+        //return { err: (e && e.err) || err }
+        return gun;
       }
     }
     User.prototype.alive = async function(){

+ 3 - 4
public/lib/gundb/sea/decrypt.js

@@ -5,23 +5,22 @@
     var aeskey = require('./aeskey');
     var parse = require('./parse');
 
-    SEA.decrypt = async (data, pair, cb, opt) => { try {
+    SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const json = parse(data)
       const ct = await aeskey(key, shim.Buffer.from(json.s, 'utf8'), opt)
-      .then((aes) => shim.subtle.decrypt({  // Keeping aesKey scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({  // Keeping aesKey scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8'))
       }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8'))))
       const r = parse(new shim.TextDecoder('utf8').decode(ct))
-      
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.decrypt;
   

+ 3 - 3
public/lib/gundb/sea/encrypt.js

@@ -4,13 +4,13 @@
     var S = require('./settings');
     var aeskey = require('./aeskey');
 
-    SEA.encrypt = async (data, pair, cb, opt) => { try {
+    SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
       var opt = opt || {};
       const key = pair.epriv || pair;
       const msg = JSON.stringify(data)
       const rand = {s: shim.random(8), iv: shim.random(16)};
       const ct = await aeskey(key, rand.s, opt)
-      .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible...
+      .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
         name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
       }, aes, new shim.TextEncoder().encode(msg)))
       const r = 'SEA'+JSON.stringify({
@@ -25,7 +25,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.encrypt;
   

+ 2 - 1
public/lib/gundb/sea/index.js

@@ -62,6 +62,7 @@
         // if there is a request to read data from us, then...
         var soul = msg.get['#'];
         if(soul){ // for now, only allow direct IDs to be read.
+          if(soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
           if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
             return to.next(msg); // yes.
           } else
@@ -149,7 +150,7 @@
             if(tmp = relpub(soul)){
               check['any'+soul+key] = 1;
               SEA.verify(val, pub = tmp, function(data){ var rel;
-                if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) }
+                if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
                 if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
                   (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
                 }

+ 19 - 3
public/lib/gundb/sea/login.js

@@ -4,10 +4,25 @@
     const finalizeLogin = async (alias, key, gunRoot, opts) => {
       const user = gunRoot._.user
       // add our credentials in-memory only to our root gun instance
-      //var tmp = user._.tag;
+      var tmp = user._.tag;
       var opt = user._.opt;
-      user._ = key.at.$._;
+      user._ = gunRoot.get('~'+key.pub)._;
       user._.opt = opt;
+      var tags = user._.tag;
+      /*Object.values && Object.values(tmp).forEach(function(tag){
+        // TODO: This is ugly & buggy code, it needs to be refactored & tested into a event "merge" utility.
+        var t = tags[tag.tag];
+        console.log("hm??", tag, t);
+        if(!t){
+          tags[tag.tag] = tag;
+          return;
+        }
+        if(tag.last){
+          tag.last.to = t.to;
+          t.last = tag.last = t.last || tag.last;
+        }
+        t.to = tag.to;
+      })*/
       //user._.tag = tmp || user._.tag;
       // so that way we can use the credentials to encrypt/decrypt data
       // that is input/output through gun (see below)
@@ -22,7 +37,8 @@
       //await authPersist(user._, key.proof, opts) // temporarily disabled
       // emit an auth event, useful for page redirects and stuff.  
       try {
-        gunRoot._.on('auth', user._)
+        gunRoot._.on('auth', user._) // TODO: Deprecate this, emit on user instead! Update docs when you do.
+        //user._.on('auth', user._) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
       } catch (e) {
         console.log('Your \'auth\' callback crashed with:', e)
       }

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

@@ -5,7 +5,7 @@
     var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
 
     //SEA.pair = async (data, proof, cb) => { try {
-    SEA.pair = async (cb) => { try {
+    SEA.pair = SEA.pair || (async (cb) => { try {
 
       const ecdhSubtle = shim.ossl || shim.subtle
       // First: ECDSA keys for signing/verifying...
@@ -56,7 +56,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.pair;
   

+ 7 - 8
public/lib/gundb/sea/query.js

@@ -4,9 +4,9 @@
     // This is internal func queries public key(s) for alias.
     const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => {
       // load all public keys associated with the username alias we want to log in with.
-      gunRoot.get('~@'+alias).get((rat, rev) => {
-        rev.off();
-        if (!rat.put) {
+      gunRoot.get('~@'+alias).once((data, key) => {
+        //rev.off();
+        if (!data) {
           // if no user, don't do anything.
           const err = 'No user!'
           Gun.log(err)
@@ -16,19 +16,18 @@
         const aliases = []
         let c = 0
         // TODO: how about having real chainable map without callback ?
-        Gun.obj.map(rat.put, (at, pub) => {
+        Gun.obj.map(data, (at, pub) => {
           if (!pub.slice || '~' !== pub.slice(0, 1)) {
             // TODO: ... this would then be .filter((at, pub))
             return
           }
           ++c
           // grab the account associated with this public key.
-          gunRoot.get(pub).get((at, ev) => {
+          gunRoot.get(pub).once(data => {
             pub = pub.slice(1)
-            ev.off()
             --c
-            if (at.put){
-              aliases.push({ pub, at })
+            if (data){
+              aliases.push({ pub, put: data })
             }
             if (!c && (c = -1)) {
               resolve(aliases)

+ 15 - 3
public/lib/gundb/sea/root.js

@@ -1,10 +1,22 @@
 
     // Security, Encryption, and Authorization: SEA.js
-    // MANDATORY READING: http://gun.js.org/explainers/data/security.html
+    // MANDATORY READING: https://gun.eco/explainers/data/security.html
+    // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
     // THIS IS AN EARLY ALPHA!
 
-    function SEA(){}
-    if(typeof window !== "undefined"){ (SEA.window = window).SEA = SEA }
+    if(typeof window !== "undefined"){ module.window = window }
 
+    var tmp = module.window || module;
+    var SEA = tmp.SEA || function(){};
+
+    if(SEA.window = module.window){ try{
+      SEA.window.SEA = SEA;
+      tmp = document.createEvent('CustomEvent');
+      tmp.initCustomEvent('extension', false, false, {type: "SEA"});
+      (window.dispatchEvent || window.fireEvent)(tmp);
+      window.postMessage({type: "SEA"}, '*');
+    } catch(e){} }
+
+    try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
     module.exports = SEA;
   

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

@@ -29,7 +29,7 @@
     SEA.encrypt = require('./encrypt');
     SEA.decrypt = require('./decrypt');
 
-    SEA.random = getRandomBytes;
+    SEA.random = SEA.random || getRandomBytes;
 
     // This is easy way to use IndexedDB, all methods are Promises
     // Note: Not all SEA interfaces have to support this.
@@ -37,7 +37,7 @@
 
     // This is Buffer used in SEA and usable from Gun/SEA application also.
     // For documentation see https://nodejs.org/api/buffer.html
-    SEA.Buffer = Buffer;
+    SEA.Buffer = SEA.Buffer || Buffer;
 
     // These SEA functions support now ony Promises or
     // async/await (compatible) code, use those like Promises.
@@ -45,7 +45,7 @@
     // Creates a wrapper library around Web Crypto API
     // for various AES, ECDSA, PBKDF2 functions we called above.
     // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
-    SEA.keyid = async (pub) => {
+    SEA.keyid = SEA.keyid || (async (pub) => {
       try {
         // base64('base64(x):base64(y)') => Buffer(xy)
         const pb = Buffer.concat(
@@ -63,7 +63,7 @@
         console.log(e)
         throw e
       }
-    }
+    });
     // all done!
     // Obviously it is missing MANY necessary features. This is only an alpha release.
     // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
@@ -73,7 +73,7 @@
     // But all other behavior needs to be equally easy, like opinionated ways of
     // Adding friends (trusted public keys), sending private messages, etc.
     // Cheers! Tell me what you think.
-    var Gun = (SEA.window||{}).Gun || require('./gun');
+    var Gun = (SEA.window||{}).Gun || require('./gun', 1);
     Gun.SEA = SEA;
     SEA.Gun = Gun;
 

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

@@ -3,7 +3,7 @@
     var shim = require('./shim');
     var S = require('./settings');
     // Derive shared secret from other's pub and my epub/epriv
-    SEA.secret = async (key, pair, cb) => { try {
+    SEA.secret = SEA.secret || (async (key, pair, cb) => { try {
       const pub = key.epub || key
       const epub = pair.epub
       const epriv = pair.epriv
@@ -27,7 +27,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     const keysToEcdhJwk = (pub, d) => { // d === priv
       //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old

+ 2 - 0
public/lib/gundb/sea/settings.js

@@ -1,4 +1,5 @@
 
+    const SEA = require('./root');
     const Buffer = require('./buffer')
     const settings = {}
     // Encryption parameters
@@ -35,5 +36,6 @@
       jwk: keysToEcdsaJwk,
       recall: authsettings
     })
+    SEA.opt = settings;
     module.exports = settings
   

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

@@ -2,7 +2,7 @@
     const Buffer = require('./buffer')
     const api = {Buffer: Buffer}
 
-    if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') {
+    if (typeof window !== 'undefined') {
       var crypto = window.crypto || window.msCrypto;
       var subtle = crypto.subtle || crypto.webkitSubtle;
       const TextEncoder = window.TextEncoder
@@ -16,9 +16,9 @@
       })
     } else {
       try{
-        var crypto = require('crypto');
-        const { subtle } = require('@trust/webcrypto')             // All but ECDH
-        const { TextEncoder, TextDecoder } = require('text-encoding')
+        var crypto = require('crypto', 1);
+        const { subtle } = require('@trust/webcrypto', 1)             // All but ECDH
+        const { TextEncoder, TextDecoder } = require('text-encoding', 1)
         Object.assign(api, {
           crypto,
           subtle,
@@ -27,7 +27,7 @@
           random: (len) => Buffer.from(crypto.randomBytes(len))
         });
         //try{
-          const WebCrypto = require('node-webcrypto-ossl')
+          const WebCrypto = require('node-webcrypto-ossl', 1)
           api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
         //}catch(e){
           //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");

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

@@ -4,8 +4,8 @@
     var S = require('./settings');
     var sha256hash = require('./sha256');
 
-    SEA.sign = async (data, pair, cb) => { try {
-      if(data.slice
+    SEA.sign = SEA.sign || (async (data, pair, cb) => { try {
+      if(data && data.slice
       && 'SEA{' === data.slice(0,4)
       && '"m":' === data.slice(4,8)){
         // TODO: This would prevent pair2 signing pair1's signature.
@@ -19,8 +19,8 @@
       const jwk = S.jwk(pub, priv)
       const msg = JSON.stringify(data)
       const hash = await sha256hash(msg)
-      const sig = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
-      .then((key) => shim.subtle.sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
+      const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
+      .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
       const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')});
 
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
@@ -30,7 +30,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.sign;
   

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

@@ -6,7 +6,7 @@
     var parse = require('./parse');
     var u;
 
-    SEA.verify = async (data, pair, cb) => { try {
+    SEA.verify = SEA.verify || (async (data, pair, cb) => { try {
       const json = parse(data)
       if(false === pair){ // don't verify!
         const raw = (json !== data)? 
@@ -31,7 +31,7 @@
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.verify;
   

+ 19 - 28
public/lib/gundb/sea/work.js

@@ -2,49 +2,40 @@
     var SEA = require('./root');
     var shim = require('./shim');
     var S = require('./settings');
+    var sha = require('./sha256');
     var u;
 
-    SEA.work = async (data, pair, cb) => { try { // used to be named `proof`
-      var salt = pair.epub || pair; // epub not recommended, salt should be random!
+    SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
+      var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
+      var opt = opt || {};
       if(salt instanceof Function){
         cb = salt;
         salt = u;
       }
       salt = salt || shim.random(9);
-      if (SEA.window) {
-        // For browser subtle works fine
-        const key = await shim.subtle.importKey(
-          'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits']
-        )
-        const result = await shim.subtle.deriveBits({
-          name: 'PBKDF2',
-          iterations: S.pbkdf2.iter,
-          salt: new shim.TextEncoder().encode(salt),
-          hash: S.pbkdf2.hash,
-        }, key, S.pbkdf2.ks * 8)
-        data = shim.random(data.length)  // Erase data in case of passphrase
-        const r = shim.Buffer.from(result, 'binary').toString('utf8')
-        if(cb){ try{ cb(r) }catch(e){console.log(e)} }
-        return r;
+      if('SHA-256' === opt.name){
+        var rsha = shim.Buffer.from(await sha(data), 'binary').toString('utf8')
+        if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
+        return rsha;
       }
-      // For NodeJS crypto.pkdf2 rocks
-      const crypto = shim.crypto;
-      const hash = crypto.pbkdf2Sync(
-        data,
-        new shim.TextEncoder().encode(salt),
-        S.pbkdf2.iter,
-        S.pbkdf2.ks,
-        S.pbkdf2.hash.replace('-', '').toLowerCase()
+      const key = await (shim.ossl || shim.subtle).importKey(
+        'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits']
       )
-      data = shim.random(data.length)  // Erase passphrase for app
-      const r = hash && hash.toString('utf8')
+      const result = await (shim.ossl || shim.subtle).deriveBits({
+        name: opt.name || 'PBKDF2',
+        iterations: opt.iterations || S.pbkdf2.iter,
+        salt: new shim.TextEncoder().encode(opt.salt || salt),
+        hash: opt.hash || S.pbkdf2.hash,
+      }, key, opt.length || (S.pbkdf2.ks * 8))
+      data = shim.random(data.length)  // Erase data in case of passphrase
+      const r = shim.Buffer.from(result, 'binary').toString('utf8')
       if(cb){ try{ cb(r) }catch(e){console.log(e)} }
       return r;
     } catch(e) { 
       SEA.err = e;
       if(cb){ cb() }
       return;
-    }}
+    }});
 
     module.exports = SEA.work;
   

+ 56 - 4
public/web/index-app.js

@@ -567,9 +567,18 @@ class IndexApp {
             },
             _worldCardDef: function (desc) {
 
-                var userGUI = [];
+                let userGUI = [];
+                let cardInfo = {
+                    "title": ""
+                };
 
+                if (desc[3] == 'saveState') {
+                    cardInfo.title = desc[0].split('/')[2];
+                }
+                
                 if (desc[3] == 'proto') {
+                    cardInfo.title = desc[0];
+
                     userGUI.push(
 
                         {
@@ -620,7 +629,36 @@ class IndexApp {
                                     }
                                 }
                             );
+
+                            userGUI.push(
+                                {
+                                    $type: "a",
+                                    class: "mdc-button mdc-button--compact mdc-card__action",
+                                    $text: "Delete",
+                                    //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                                    onclick: function (e) {
+                                        _app.deleteWorld(desc[0], 'proto');
+                                    }
+                                }
+                            );
+                        }
+
+
+                        if(desc[3] == 'saveState'){
+                            userGUI.push(
+                                {
+                                    $type: "a",
+                                    class: "mdc-button mdc-button--compact mdc-card__action",
+                                    $text: "Delete",
+                                    //href: "/" + desc[2] + '/worlds/' + desc[0] + '/edit', ///:user/worlds/:name/edit
+                                    onclick: function (e) {
+                                        _app.deleteWorld(desc[0], 'state');
+                                    }
+                                }
+                            );
                         }
+
+
                     }
 
                     if (desc[3] == 'proto') {
@@ -638,6 +676,8 @@ class IndexApp {
 
                         )
                     } else if (desc[3] == 'saveState') {
+
+                        
                         // userGUI.push(
                         //     {
                         //         $type: "a",
@@ -684,6 +724,14 @@ class IndexApp {
                                     class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
                                     $text: desc[1].text 
                                 },
+                                {
+                                    $type: "span",
+                                    class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
+                                    $text: 'id: ' + cardInfo.title
+                                },
+                                {
+                                    $type: "p", 
+                                },
                                 {
                                     $type: "span",
                                     class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
@@ -878,7 +926,9 @@ class IndexApp {
                 if (w) {
                     db.get('worlds').get(index).get('info_json').once(res => {
 
-                        if (res) {
+                        if (res && res !== 'null') {
+
+                            if (res.file && res.file !== 'null') {
 
                             let created = res.created ? res.created: res.modified;
 
@@ -890,6 +940,7 @@ class IndexApp {
                             world[root].modified = res.modified;
                             this.worlds[index] = world[root];
                             document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
+                            }
                         }
 
                     })
@@ -942,7 +993,8 @@ class IndexApp {
 
                                     db.get('documents').get(index).get(el).once(res => {
 
-                                        if (res) {
+                                        if (res ) {
+                                            if(res.file && res.file !== "null") {
 
                                             let created = res.created ? res.created: res.modified;
 
@@ -955,7 +1007,7 @@ class IndexApp {
                                             world[root].modified = res.modified;
                                             this.worlds[index + '/load/' + fileName] = world[root];
                                             document.querySelector("#main")._jsonData = Object.assign({}, this.worlds);
-
+                                            }
                                         }
                                     })
                                 })

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