|  | @@ -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;
 | 
	
		
			
				|  |  |                  }
 |