@gooddollar/gun
Version:
A realtime, decentralized, offline-first, graph data synchronization engine.
150 lines (138 loc) • 5.29 kB
JavaScript
if(typeof Gun === 'undefined'){ return } // TODO: localStorage is Browser only. But it would be nice if it could somehow plugin into NodeJS compatible localStorage APIs?
var root, noop = function(){}, store, u;
try{store = (Gun.window||noop).localStorage}catch(e){}
if(!store){
Gun.log("Warning: No localStorage exists to persist data to!");
store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}};
}
/*
NOTE: Both `lib/file.js` and `lib/memdisk.js` are based on this design!
If you update anything here, consider updating the other adapters as well.
*/
Gun.on('create', function(root){
// This code is used to queue offline writes for resync.
// See the next 'opt' code below for actual saving of data.
var ev = this.to, opt = root.opt;
if(root.once){ return ev.next(root) }
if(false === opt.localStorage){ return ev.next(root) } // we want offline resynce queue regardless! // actually, this doesn't help, per @go1dfish 's observation. Disabling for now, will need better solution later.
opt.prefix = opt.file || 'gun/';
var gap = Gun.obj.ify(store.getItem('gap/'+opt.prefix)) || {};
var empty = Gun.obj.empty, id, to, go;
// add re-sync command.
if(!empty(gap)){
var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}, send = {};
Gun.obj.map(gap, function(node, soul){
Gun.obj.map(node, function(val, key){
send[soul] = Gun.state.to(disk[soul], key, send[soul]);
});
});
setTimeout(function(){
// TODO: Holy Grail dangling by this thread! If gap / offline resync doesn't trigger, it doesn't work. Ouch, and this is a localStorage specific adapter. :(
root.on('out', {put: send, '#': root.ask(ack)});
},1);
}
root.on('out', function(msg){
if(msg.lS){ return } // TODO: for IndexedDB and others, shouldn't send to peers ACKs to our own GETs. // THIS IS BLOCKING BROWSERS REPLYING TO REQUESTS, NO??? CHANGE THIS SOON!! UNDER CONTROLLED CIRCUMSTANCES!! Or maybe in-memory already doe sit?
if(Gun.is(msg.$) && msg.put && !msg['@']){
id = msg['#'];
Gun.graph.is(msg.put, null, map);
if(!to){ to = setTimeout(flush, opt.wait || 1) }
}
this.to.next(msg);
});
root.on('ack', ack);
function ack(ack){ // TODO: This is experimental, not sure if we should keep this type of event hook.
if(ack.err || !ack.ok){ return }
var id = ack['@'];
setTimeout(function(){
Gun.obj.map(gap, function(node, soul){
Gun.obj.map(node, function(val, key){
if(id !== val){ return }
delete node[key];
});
if(empty(node)){
delete gap[soul];
}
});
flush();
}, opt.wait || 1);
};
ev.next(root);
var map = function(val, key, node, soul){
(gap[soul] || (gap[soul] = {}))[key] = id;
}
var flush = function(){
clearTimeout(to);
to = false;
try{store.setItem('gap/'+opt.prefix, JSON.stringify(gap));
}catch(e){ Gun.log(err = e || "localStorage failure") }
}
});
Gun.on('create', function(root){
this.to.next(root);
var opt = root.opt;
if(root.once){ return }
if(false === opt.localStorage){ return }
opt.prefix = opt.file || 'gun/';
var graph = root.graph, acks = {}, count = 0, to;
var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {};
var lS = function(){}, u;
root.on('localStorage', disk); // NON-STANDARD EVENT!
root.on('put', function(msg){
this.to.next(msg);
var put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], tmp;
disk[soul] = Gun.state.ify(disk[soul], key, state, val, soul);
if(!msg['@']){ (acks[msg['#']] = (tmp = (msg._||'').lot || {})).lS = (tmp.lS||0)+1; } // only ack non-acks.
count += 1;
if(count >= (opt.batch || 1000)){
return flush();
}
if(to){ return }
to = setTimeout(flush, opt.wait || 1);
});
root.on('get', function(msg){
this.to.next(msg);
var lex = msg.get, soul, data, u;
function to(){
if(!lex || !(soul = lex['#'])){ return }
//if(0 >= msg.cap){ return }
var has = lex['.'];
data = disk[soul] || u;
if(data && has){
data = Gun.state.to(data, has);
}
//if(!data && !Gun.obj.empty(opt.peers)){ return } // if data not found, don't ack if there are peers. // Hmm, what if we have peers but we are disconnected?
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), lS:1});// || root.$});
};
Gun.debug? setTimeout(to,1) : to();
});
var map = function(val, key, node, soul){
disk[soul] = Gun.state.to(node, key, disk[soul]);
}
var flush = function(data){
var err;
count = 0;
clearTimeout(to);
to = false;
var ack = acks;
acks = {};
if(data){ disk = data }
try{store.setItem(opt.prefix, JSON.stringify(disk));
}catch(e){
Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install");
root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush});
}
if(!err && !Gun.obj.empty(opt.peers)){ return } // only ack if there are no peers.
Gun.obj.map(ack, function(yes, id){
if(yes){
if(yes.more){ acks[id] = yes; return }
if(yes.s !== yes.lS){ err = "localStorage batch not same." }
}
root.on('in', {
'@': id,
err: err,
ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number.
});
});
}
});