diff --git a/examples/contact/index.html b/examples/contact/index.html
index 19dd8169..38679545 100644
--- a/examples/contact/index.html
+++ b/examples/contact/index.html
@@ -153,7 +153,7 @@
// for how to build a simplified version
// of this example: https://scrimba.com/c/c2gBgt4
var gun = Gun(location.origin+'/gun');
- var app = gun.get('example/contacts/6');
+ var app = gun.get('example/contacts/7');
var user = gun.user();
var c = window.c = {};
(function(){
@@ -169,7 +169,7 @@
Gun.node.is(at.put.users, function(val, key){
Gun.SEA.verify(val, false, function(val){
//Gun.SEA.read(val, false, function(val){
- if('alias/'+key === Gun.val.rel.is(val)){ return }
+ if('~@'+key === Gun.val.rel.is(val)){ return }
no = true;
})
if(no){ return no }
@@ -207,7 +207,7 @@
if(!ack.wait){ but.removeClass('pulse') }
if(ack.err){ c.tell(ack.err); return }
if(ack.pub){
- gun.get('users').get(data.alias).put(gun.get('alias/'+data.alias));
+ gun.get('users').get(data.alias).put(gun.get('~@'+data.alias));
}
session(data);
user.auth(data.alias, data.pass);
@@ -276,7 +276,7 @@
if(!user.is){ return as.route('sign') }
var pub = location.hash.split('/').slice(-1)[0];
(pub === user._.pub? $('#say').show() : $('#say').hide());
- as('#person', window.PUB = gun.get('pub/'+pub));
+ as('#person', window.PUB = gun.get('~'+pub));
});
diff --git a/gun.js b/gun.js
index 725830d8..79e4dd46 100644
--- a/gun.js
+++ b/gun.js
@@ -1296,7 +1296,7 @@
var gun = this, at = (gun._), root = at.root.gun, tmp;
as = as || {};
as.data = data;
- as.gun = as.gun || gun;
+ as.via = as.gun = as.via || as.gun || gun;
if(typeof cb === 'string'){
as.soul = cb;
} else {
@@ -1311,9 +1311,9 @@
if(as.res){ as.res() }
return gun;
}
- as.soul = as.soul || (as.not = Gun.node.soul(as.data) || ((root._).opt.uuid || Gun.text.random)());
+ as.soul = as.soul || (as.not = Gun.node.soul(as.data) || (as.via.back('opt.uuid') || Gun.text.random)());
if(!as.soul){ // polyfill async uuid for SEA
- (root._).opt.uuid(function(err, soul){ // TODO: improve perf without anonymous callback
+ as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback
if(err){ return Gun.log(err) } // TODO: Handle error!
(as.ref||as.gun).put(as.data, as.soul = soul, as);
});
@@ -1415,10 +1415,10 @@
ref = ref.get(path[i]);
}
if(Gun.node.soul(at.obj)){
- var id = Gun.node.soul(at.obj) || (ref.back('opt.uuid') || Gun.text.random)();
+ var id = Gun.node.soul(at.obj) || (as.via.back('opt.uuid') || Gun.text.random)();
if(!id){ // polyfill async uuid for SEA
(as.stun = as.stun || {})[path] = true; // make DRY
- ref.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback
+ as.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback
if(err){ return Gun.log(err) } // TODO: Handle error.
ref.back(-1).get(id);
at.soul(id);
@@ -1443,9 +1443,9 @@
var _id = (msg.put||empty)['#'];
ev.off();
at = (msg.gun._.back); // go up 1!
- var id = id || Gun.node.soul(cat.obj) || Gun.node.soul(at.put) || Gun.val.rel.is(at.put) || _id || at_._id || (as.gun.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous?
+ var id = id || Gun.node.soul(cat.obj) || Gun.node.soul(at.put) || Gun.val.rel.is(at.put) || _id || at_._id || (as.via.back('opt.uuid') || Gun.text.random)(); // TODO: BUG!? Do we really want the soul of the object given to us? Could that be dangerous?
if(!id){ // polyfill async uuid for SEA
- at.gun.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback
+ at.via.back('opt.uuid')(function(err, id){ // TODO: improve perf without anonymous callback
if(err){ return Gun.log(err) } // TODO: Handle error.
solve(at, at_._id = at_._id || id, cat, as);
});
@@ -1495,16 +1495,16 @@
}
if(!as.not && !(as.soul = Gun.node.soul(data))){
if(as.path && obj_is(as.data)){ // Apparently necessary
- as.soul = (opt.uuid || cat.root.opt.uuid || Gun.text.random)();
+ as.soul = (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)();
} else {
//as.data = obj_put({}, as.gun._.get, as.data);
if(node_ == at.get){
as.soul = (at.put||empty)['#'] || at._id;
}
- as.soul = as.soul || at.soul || cat.soul || (opt.uuid || cat.root.opt.uuid || Gun.text.random)();
+ as.soul = as.soul || at.soul || cat.soul || (opt.uuid || as.via.back('opt.uuid') || Gun.text.random)();
}
if(!as.soul){ // polyfill async uuid for SEA
- as.ref.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback
+ as.via.back('opt.uuid')(function(err, soul){ // TODO: improve perf without anonymous callback
if(err){ return Gun.log(err) } // Handle error.
as.ref.put(as.data, as.soul = soul, as);
});
@@ -1727,11 +1727,11 @@
opt = opt || {}; opt.item = opt.item || item;
if(soul = Gun.node.soul(item)){ return gun.set(gun.back(-1).get(soul), cb, opt) }
if(!Gun.is(item)){
- var id = gun._.root.opt.uuid();
+ var id = gun.back('opt.uuid')();
if(id && Gun.obj.is(item)){
return gun.set(gun._.root.gun.put(item, id), cb, opt);
}
- return gun.get(id || (Gun.state.lex() + Gun.text.random(12))).put(item, cb, opt);
+ return gun.get((Gun.state.lex() + Gun.text.random(7))).put(item, cb, opt);
}
item.get('_').get(function(at, ev){
if(!at.gun || !at.gun._.back){ return }
diff --git a/lib/store.js b/lib/store.js
index 359c8607..bdf031e2 100644
--- a/lib/store.js
+++ b/lib/store.js
@@ -63,7 +63,7 @@ function Store(opt){
var random = Math.random().toString(36).slice(-3)
fs.writeFile(opt.file+'-'+random+'.tmp', data, function(err, ok){
if(err){ return cb(err) }
- fs.rename(opt.file+'-'+random+'.tmp', opt.file+'/'+file, cb);
+ move(opt.file+'-'+random+'.tmp', opt.file+'/'+file, cb);
});
};
store.get = function(file, cb){
@@ -88,6 +88,30 @@ function Store(opt){
return store;
}
+function move(oldPath, newPath, cb) {
+ fs.rename(oldPath, newPath, function (err) {
+ if (err) {
+ if (err.code === 'EXDEV') {
+ var readStream = fs.createReadStream(oldPath);
+ var writeStream = fs.createWriteStream(newPath);
+
+ readStream.on('error', cb);
+ writeStream.on('error', cb);
+
+ readStream.on('close', function () {
+ fs.unlink(oldPath, cb);
+ });
+
+ readStream.pipe(writeStream);
+ } else {
+ cb(err);
+ }
+ } else {
+ cb();
+ }
+ });
+};
+
module.exports = Store;
diff --git a/package.json b/package.json
index 009460b0..f99dcede 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gun",
- "version": "0.9.998",
+ "version": "0.9.999",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"main": "index.js",
"browser": "gun.min.js",
diff --git a/sea.js b/sea.js
index 775da383..23fa57ab 100644
--- a/sea.js
+++ b/sea.js
@@ -108,7 +108,7 @@
} else if (enc === 'binary') {
buf = SeaArray.from(input)
} else {
- console.info(`SafeBuffer.from unknown encoding: '${enc}'`)
+ console.info('SafeBuffer.from unknown encoding: '+enc)
}
return buf
}
@@ -639,7 +639,7 @@
// 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/${alias}`).get((rat, rev) => {
+ gunRoot.get('~@'+alias).get((rat, rev) => {
rev.off();
if (!rat.put) {
// if no user, don't do anything.
@@ -652,14 +652,14 @@
let c = 0
// TODO: how about having real chainable map without callback ?
Gun.obj.map(rat.put, (at, pub) => {
- if (!pub.slice || 'pub/' !== pub.slice(0, 4)) {
+ 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) => {
- pub = pub.slice(4)
+ pub = pub.slice(1)
ev.off()
--c
if (at.put){
@@ -835,7 +835,9 @@
const { user } = gunRoot._
// add our credentials in-memory only to our root gun instance
//var tmp = user._.tag;
- user._ = key.at.gun._
+ var opt = user._.opt;
+ user._ = key.at.gun._;
+ user._.opt = opt;
//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)
@@ -989,7 +991,7 @@
} catch (e) { // TODO: right log message ?
Gun.log('Failed to finalize login with new password!')
const { err = '' } = e || {}
- throw { err: `Finalizing new password login failed! Reason: ${err}` }
+ throw { err: 'Finalizing new password login failed! Reason: '+err }
}
}
module.exports = authRecall
@@ -1042,9 +1044,19 @@
// let's extend the gun chain with a `user` function.
// only one user can be logged in at a time, per gun instance.
Gun.chain.user = function(pub){
- var gun = this, root = gun.back(-1);
- if(pub){ return root.get('pub/'+pub) }
- return root.back('user') || ((root._).user = gun.chain(new User));
+ var gun = this, root = gun.back(-1), user;
+ if(pub){ return root.get('~'+pub) }
+ if(user = root.back('user')){ return user }
+ var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
+ (at = (user = at.user = gun.chain(new User))._).opt = {};
+ at.opt.uuid = function(cb){
+ var id = uuid(), pub = root.user;
+ if(!pub || !(pub = (pub._).sea) || !(pub = pub.pub)){ return id }
+ id = id + '~' + pub + '.';
+ if(cb && cb.call){ cb(null, id) }
+ return id;
+ }
+ return user;
}
module.exports = User;
})(USE, './user');
@@ -1078,7 +1090,7 @@
var resolve = function(){}, reject = resolve;
// Because more than 1 user might have the same username, we treat the alias as a list of those users.
if(cb){ resolve = reject = cb }
- gunRoot.get(`alias/${username}`).get(async (at, ev) => {
+ gunRoot.get('~@'+username).get(async (at, ev) => {
ev.off()
if (at.put) {
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
@@ -1109,14 +1121,14 @@
// )
).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) })
const user = { alias, pub, epub, auth }
- const tmp = `pub/${pairs.pub}`
+ const tmp = '~'+pairs.pub;
// awesome, now we can actually save the user with their public key as their ID.
try{
gunRoot.get(tmp).put(user)
}catch(e){console.log(e)}
// next up, we want to associate the alias with the public key. So we add it to the alias list.
- gunRoot.get(`alias/${username}`).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)))
+ gunRoot.get('~@'+username).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)))
// callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it.
} catch (e) {
@@ -1157,7 +1169,7 @@
const putErr = (msg) => (e) => {
const { message, err = message || '' } = e
Gun.log(msg)
- var error = { err: `${msg} Reason: ${err}` }
+ var error = { err: msg+' Reason: '+err }
return cat.ing = false, gun.leave(), cb(error), gun;
}
@@ -1186,7 +1198,7 @@
epub: signedEpub
}
// awesome, now we can update the user using public key ID.
- gunRoot.get(`pub/${user.pub}`).put(user)
+ gunRoot.get('~'+user.pub).put(user)
// then we're done
const login = finalizeLogin(alias, keys, gunRoot, { pin })
login.catch(putErr('Failed to finalize login with new password!'))
@@ -1219,7 +1231,7 @@
const { pub } = await authenticate(alias, pass, gunRoot)
await authLeave(gunRoot, alias)
// Delete user data
- gunRoot.get(`pub/${pub}`).put(null)
+ gunRoot.get('~'+pub).put(null)
// Wipe user data from memory
const { user = { _: {} } } = gunRoot._;
// TODO: is this correct way to 'logout' user from Gun.User ?
@@ -1354,14 +1366,6 @@
Gun.on('opt', function(at){
if(!at.sea){ // only add SEA once per instance, on the "at" context.
at.sea = {own: {}};
- var uuid = at.opt.uuid || Gun.state.lex;
- at.opt.uuid = function(cb){ // TODO: consider async/await and drop callback pattern...
- var id = uuid(), pub = at.user;
- if(!pub || !(pub = at.user._.sea) || !(pub = pub.pub)){ return id }
- id = id + '~' + pub;
- if(cb && cb.call){ cb(null, id) }
- return id;
- }
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);
@@ -1376,7 +1380,7 @@
// Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc.
// This data is self-enforced (the value can only match its ID), but that is handled in the `security` function.
// From the self-enforced data, we can see all the edges in the graph that belong to a public key.
- // Example: pub/ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
+ // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
// its encrypted private key, but it might also have other signed values on it like `profile = ` edge.
// Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys.
// Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
@@ -1420,7 +1424,7 @@
if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
return to.next(msg); // yes.
} else
- if('alias/' === soul.slice(0,6)){ // Allow reading the list of public keys associated with an alias?
+ if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias?
return to.next(msg); // yes.
} else { // Allow reading everything?
return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on.
@@ -1437,29 +1441,29 @@
each.way = function(val, key){
var soul = this.soul, node = this.node, tmp;
if('_' === key){ return } // ignore meta data
- if('alias' === soul){ // special case for shared system data, the list of aliases.
+ if('~@' === soul){ // special case for shared system data, the list of aliases.
each.alias(val, key, node, soul); return;
}
- if('alias/' === soul.slice(0,6)){ // special case for shared system data, the list of public keys for an alias.
+ if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
each.pubs(val, key, node, soul); return;
}
- if('pub/' === soul.slice(0,4)){ // special case, account data for a public key.
- each.pub(val, key, node, soul, soul.slice(4), msg.user); return;
+ if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
+ each.pub(val, key, node, soul, tmp, msg.user); return;
}
each.any(val, key, node, soul, msg.user); return;
return each.end({err: "No other data allowed!"});
};
- each.alias = function(val, key, node, soul){ // Example: {_:#alias, alias/alice: {#alias/alice}}
+ each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist
- if('alias/'+key === Gun.val.rel.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
+ if('~@'+key === Gun.val.rel.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
each.end({err: "Mismatching alias."}); // if it isn't, reject.
};
- each.pubs = function(val, key, node, soul){ // Example: {_:#alias/alice, pub/asdf: {#pub/asdf}}
+ each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist
if(key === Gun.val.rel.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
};
- each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#pub/asdf, hello:SEA['world',fdsa]}
+ each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}}
if('pub' === key){
if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
return each.end({err: "Account must match!"});
@@ -1479,30 +1483,34 @@
// TODO: Handle error!!!!
return;
}
- // TODO: consider async/await and drop callback pattern...
SEA.verify(val, pub, function(data){ var rel, tmp;
if(u === data){ // make sure the signature matches the account it claims to be on.
return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
}
- if((rel = Gun.val.rel.is(data)) && (tmp = rel.split('~')) && 2 === tmp.length){
- if(pub === tmp[1]){
- (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
- }
+ if((rel = Gun.val.rel.is(data)) && pub === relpub(rel)){
+ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['user'+soul+key] = 0;
each.end({ok: 1});
});
};
+ function relpub(s){
+ if(!s){ return }
+ s = s.split('~');
+ if(!s || !(s = s[1])){ return }
+ s = s.split('.');
+ if(!s || 2 > s.length){ return }
+ s = s.slice(0,2).join('.');
+ return s;
+ }
each.any = function(val, key, node, soul, user){ var tmp, pub;
if(!user || !(user = user._) || !(user = user.sea)){
- if((tmp = soul.split('~')) && 2 == tmp.length){
+ if(tmp = relpub(soul)){
check['any'+soul+key] = 1;
- SEA.verify(val, (pub = tmp[1]), function(data){ var rel;
+ SEA.verify(val, pub = tmp, function(data){ var rel;
if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) }
- if((rel = Gun.val.rel.is(data)) && (tmp = rel.split('~')) && 2 === tmp.length){
- if(pub === tmp[1]){
- (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
- }
+ if((rel = Gun.val.rel.is(data)) && pub === relpub(rel)){
+ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['any'+soul+key] = 0;
each.end({ok: 1});
@@ -1518,7 +1526,7 @@
//each.end({err: "Data cannot be modified."});
return;
}
- if(!(tmp = soul.split('~')) || 2 !== tmp.length){
+ if(!(tmp = relpub(soul))){
if(at.opt.secure){
each.end({err: "Soul is missing public key at '" + key + "'."});
return;
@@ -1528,16 +1536,16 @@
each.end({ok: 1});
return;
}
- check['any'+soul+key] = 1;
- SEA.sign(val, user, function(data){
- if(u === data){ return each.end({err: 'Any signature failed.'}) }
- node[key] = data;
+ //check['any'+soul+key] = 1;
+ //SEA.sign(val, user, function(data){
+ // if(u === data){ return each.end({err: 'Any signature failed.'}) }
+ // node[key] = data;
check['any'+soul+key] = 0;
each.end({ok: 1});
- });
+ //});
return;
}
- var pub = tmp[1];
+ var pub = tmp;
if(pub !== user.pub){
each.any(val, key, node, soul);
return;