live push, albeit needs improvements

This commit is contained in:
Mark Nadal 2014-09-16 07:31:59 -07:00
parent 8024d51950
commit cabd7f21da
5 changed files with 250 additions and 90 deletions

View File

@ -17,8 +17,8 @@ app.listen(port);
console.log('Express started on port ' + port + ' with /gun'); console.log('Express started on port ' + port + ' with /gun');
gun.load('blob/data', function(){ // ugly little idempotent initializer, in case no data is set gun.load('blob/data', function(){ // ugh need to initialize the data if there is none, what a waste of LOC!
gun.set({_:{'#': "yVbyf7BqlXVQQUOE5cw9rf8h",'>':{hello: 1407328713707,from: 1407328713707}}, // this is an actual gun object, this won't overwrite any new changes gun.set({_:{'#': "yVbyf7BqlXVQQUOE5cw9rf8h",'>':{hello: 1407328713707,from: 1407328713707}}, // this is a nasty trick to force the ID to overwrite itself
hello: "world", hello: "world",
from: "Mark Nadal" from: "Mark Nadal"
}).key('blob/data'); }).key('blob/data');

View File

@ -55,6 +55,12 @@
$scope.$data = gun.load('blob/data', function(data){ $scope.$data = gun.load('blob/data', function(data){
$scope.data = data; $scope.data = data;
$scope.$apply(); $scope.$apply();
Gun.on(data._[Gun.sym.id]).event(function(node){ // one liner-ify this!
Gun.obj.map(node, function(val, field){
$scope.data[field] = val;
});
$scope.$apply();
});
}); });
$scope.add = function(a,b,c){ $scope.add = function(a,b,c){
$scope.$data.path($scope.field).set( $scope.$data.path($scope.field).set(

148
gun.js
View File

@ -4,12 +4,12 @@
if(!Gun.is(gun)){ if(!Gun.is(gun)){
return new Gun(opt); return new Gun(opt);
} }
gun.init(opt); gun.opt(opt);
} }
Gun.is = function(gun){ return (gun instanceof Gun)? true : false } Gun.is = function(gun){ return (gun instanceof Gun)? true : false }
Gun._ = {}; Gun._ = {};
Gun.chain = Gun.prototype; Gun.chain = Gun.prototype;
Gun.chain.init = function(opt, stun){ // idempotently update or set options Gun.chain.opt = function(opt, stun){ // idempotently update or set options
var gun = this; var gun = this;
gun._ = gun._ || {}; gun._ = gun._ || {};
gun.__ = gun.__ || {}; gun.__ = gun.__ || {};
@ -28,7 +28,7 @@
if(!Gun.fns.is(h)){ return } if(!Gun.fns.is(h)){ return }
gun.__.opt.hook[f] = h; gun.__.opt.hook[f] = h;
}); });
if(!stun){ Gun.on('init').emit(gun, opt) } if(!stun){ Gun.on('opt').emit(gun, opt) }
return gun; return gun;
} }
Gun.chain.chain = function(from){ Gun.chain.chain = function(from){
@ -225,6 +225,7 @@
Gun.text = {}; Gun.text = {};
Gun.text.is = function(t){ return typeof t == 'string'? true : false } Gun.text.is = function(t){ return typeof t == 'string'? true : false }
Gun.text.ify = function(t){ Gun.text.ify = function(t){
if(Gun.text.is(t)){ return t }
if(JSON){ return JSON.stringify(t) } if(JSON){ return JSON.stringify(t) }
return (t && t.toString)? t.toString() : t; return (t && t.toString)? t.toString() : t;
} }
@ -363,7 +364,7 @@
}); });
} }
Gun.HAM = function(current, delta, some){ // TODO: BUG! HAM on sub-graphs has not yet been put into code, thus divergences could occur - this is alpha! Gun.HAM = function(current, delta, some){ // TODO: BUG! HAM on sub-graphs has not yet been put into code, thus divergences could occur - this is alpha!
function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ function HAM(machineState, incomingState, currentState, incomingValue, currentValue){ // TODO: Lester's comments on roll backs could be vulnerable to divergence, investigate!
if(machineState < incomingState){ if(machineState < incomingState){
// the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state. // the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state.
return {amnesiaQuarantine: true}; return {amnesiaQuarantine: true};
@ -609,34 +610,94 @@
} }
}({})); }({}));
;(function(Page){ ;(function(tab){
if(!this.Gun){ return } if(!this.Gun){ return }
if(!window.JSON){ Gun.log("Include JSON first: ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js") } // for old IE use if(!window.JSON){ Gun.log("Include JSON first: ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js") } // for old IE use
Gun.on('init').event(function(gun, opt){ Gun.on('opt').event(function(gun, opt){
Page.load = function(key, cb, opt){ tab.server = tab.server || function(req, res, next){
}
tab.load = tab.load || function(key, cb, opt){
cb = cb || function(){}; cb = cb || function(){};
opt = opt || {}; opt = opt || {};
Gun.obj.map(gun.__.opt.peers, function(peer, url){ Gun.obj.map(gun.__.opt.peers, function(peer, url){
Page.ajax(url + '/' + key, null, function(data){ tab.ajax(url + '/' + key, null, function(reply){
Gun.log('via', url, key, data); Gun.log('via', url, key, reply);
// alert(data + data.hello + data.from + data._); if(!reply){ return } // handle reconnect?
cb(null, data); if(reply.body && reply.body.err){
if(!data || !data._){ return } cb(reply.body.err);
Page.subscribe(data._[Gun.sym.id]); } else {
}); cb(null, reply.body);
}
(function(){
tab.subscribe.sub = (reply.headers || {})['Gun-Sub'];
var data = reply.body;
if(!data || !data._){ return }
tab.subscribe(data._[Gun.sym.id]);
}());
}, {head: {'Gun-Sub': 1}});
}); });
} }
Page.set = function(nodes, cb){ tab.set = tab.set || function(nodes, cb){
cb = cb || function(){}; cb = cb || function(){};
// TODO: batch and throttle later. // TODO: batch and throttle later.
console.log('ajax set', nodes); console.log('ajax set', nodes);
Gun.obj.map(gun.__.opt.peers, function(peer, url){ Gun.obj.map(gun.__.opt.peers, function(peer, url){
Page.ajax(url, nodes, function(reply){ tab.ajax(url, nodes, function(reply){
console.log("set confirmed?", reply); console.log("set confirmed?", reply);
}); });
}); });
} }
Page.query = function(params){ tab.subscribe = function(id){
tab.subscribe.to = tab.subscribe.to || {};
if(id){
tab.subscribe.to[id] = 1;
}
var opt = {
//head: {'Gun-Sub': 1},
headers: {
'Gun-Transport': 'XHR-SLP',
'Gun-Sub': tab.subscribe.sub || ''
}
}, query = tab.subscribe.sub? '' : tab.subscribe.query(tab.subscribe.to);
console.log("SUB", tab.subscribe.sub);
Gun.obj.map(gun.__.opt.peers, function(peer, url){
tab.ajax(url + query, null, function(reply){
tab.subscribe.poll();
if(!reply){ return } // do anything?
if(reply.headers){
tab.subscribe.sub = reply.headers['Gun-Sub'] || tab.subscribe.sub;
}
var data = reply.body
, union = function(node){ // maybe we shouldn't have this type of logic, below, in a hook?
// should we pass it off to a gun API? same with everywhere else this shows up then.
if(!node || !node._ || !node._[Gun.sym.id]){ return } // do anything?
var context = {nodes: {}};
context.nodes[node._[Gun.sym.id]] = node;
context = Gun.chain.set.now.union.call(gun, context.nodes);
if(context.err){ return } // do anything?
Gun.obj.map(context.nodes, function(node, id){
Gun.on(id).emit(node); // TODO: we shouldn't use Gun's global event namespace like this, change to local
});
}
if(!data){ return } // do anything?
if(data.err){ return } // do anything?
if(data._){
union(data);
} else {
Gun.obj.map(data, function(node, id){
union(node);
});
}
}, opt);
});
}
tab.subscribe.poll = function(){
clearTimeout(tab.subscribe.poll.id);
tab.subscribe.poll.id = setTimeout(tab.subscribe, 1); // 1000 * 10); // should enable some server-side control of this.
}
tab.subscribe.query = function(params){
var s = '?' var s = '?'
, uri = encodeURIComponent; , uri = encodeURIComponent;
Gun.obj.map(params, function(val, field){ Gun.obj.map(params, function(val, field){
@ -644,18 +705,7 @@
}); });
return s; return s;
} }
Page.subscribe = function(id){ tab.ajax =
Page.subscribe.to = Page.subscribe.to || {};
Page.subscribe.to[id] = 1;
var query = Page.query(Page.subscribe.to) || '';
Gun.obj.map(gun.__.opt.peers, function(peer, url){
Page.ajax(url + query, null, function(data){
console.log("subscribe reply!", data);
});
});
console.log("live", query);
}
Page.ajax =
window.ajax = window.ajax =
function(url, data, cb, opt){ function(url, data, cb, opt){
/* /*
@ -668,11 +718,13 @@
*/ */
var u; var u;
opt = opt || {}; opt = opt || {};
opt.head = opt.head || {};
opt.reshead = {};
opt.headers = opt.headers || {};
if(data === u || data === null){ if(data === u || data === null){
data = u; data = u;
} else { } else {
try{data = JSON.stringify(data); try{data = JSON.stringify(data);
opt.headers = opt.headers || {};
opt.headers["Content-Type"] = "application/json;charset=utf-8"; opt.headers["Content-Type"] = "application/json;charset=utf-8";
}catch(e){} }catch(e){}
} }
@ -694,20 +746,24 @@
} }
opt.xhr = null; opt.xhr = null;
} }
opt.data = opt.data || function(d){ opt.data = opt.data || function(d, head){
var t; var reply = {};
try{t = JSON.parse(d) || d; reply.headers = head;
try{reply.body = JSON.parse(d) || d;
}catch(e){ }catch(e){
t = d; reply.body = d;
} }
if(cb){ cb(t) } if(cb){ cb(reply) }
} }
opt.chunk = function(status, text, force){ opt.chunk = function(status, text, force){
if(status !== 200){ return } if(status !== 200){ return }
opt.each(opt.head, function(val, i){
opt.reshead[i] = opt.xhr.getResponseHeader(i);
});
var d, b, p = 1; var d, b, p = 1;
while(p || force){ while(p || force){
if(u !== d){ if(u !== d){
opt.data(d); opt.data(d, opt.reshead);
force = false; force = false;
} }
b = text.slice(opt.i = opt.i || 0); b = text.slice(opt.i = opt.i || 0);
@ -771,12 +827,18 @@
opt.error(); opt.error();
return; return;
} }
if(opt.headers){ opt.each = function(obj, cb){
try{for(var i in opt.headers){ if(!obj || !cb){ return }
if(opt.headers.hasOwnProperty(i)){ for(var i in obj){
opt.xhr.setRequestHeader(i, opt.headers[i]); if(obj.hasOwnProperty(i)){
} cb(obj[i], i);
} }
}
}
if(opt.headers){
try{opt.each(opt.headers, function(val, i){
opt.xhr.setRequestHeader(i, val);
});
} catch(e) { } catch(e) {
opt.error(); opt.error();
return; return;
@ -788,7 +850,7 @@
} }
return opt; return opt;
} }
gun.__.opt.hook.load = gun.__.opt.hook.load || Page.load; gun.__.opt.hook.load = gun.__.opt.hook.load || tab.load;
gun.__.opt.hook.set = gun.__.opt.hook.set || Page.set; gun.__.opt.hook.set = gun.__.opt.hook.set || tab.set;
}); });
}({})); }({}));

View File

@ -1,5 +1,5 @@
{ "name": "gun" { "name": "gun"
, "version": "0.0.2b" , "version": "0.0.3"
, "author": "Mark Nadal" , "author": "Mark Nadal"
, "description": "Graph engine." , "description": "Graph engine."
, "engines": { , "engines": {

180
shots.js
View File

@ -3,62 +3,56 @@
, S3 = require(__dirname+'/gate/s3') // redis has been removed, can be replaced with a disk system , S3 = require(__dirname+'/gate/s3') // redis has been removed, can be replaced with a disk system
, url = require('url') , url = require('url')
, meta = {}; , meta = {};
Gun.on('init').event(function(gun, opt){ Gun.on('opt').event(function(gun, opt){
gun.server = gun.server || function(req, res, next){ // this whole function needs refactoring and modularization gun.server = gun.server || function(req, res, next){ // this whole function needs refactoring and modularization
next = next || function(){}; next = next || function(){};
if(!req || !res){ return next() } if(!req || !res){ return next() }
if(!req.url){ return next() } if(!req.url){ return next() }
if(!req.method){ return next() } if(!req.method){ return next() }
var tmp = {}; var msg = {};
tmp.url = url.parse(req.url, true); msg.url = url.parse(req.url, true);
if(!gun.server.regex.test(tmp.url.pathname)){ return next() } if(!gun.server.regex.test(msg.url.pathname)){ return next() }
tmp.key = tmp.url.pathname.replace(gun.server.regex,'') || ''; msg.url.key = msg.url.pathname.replace(gun.server.regex,'') || '';
if(tmp.key.toLowerCase() === '.js'){ if(msg.url.key.toLowerCase() === '.js'){
res.writeHead(200, {'Content-Type': 'text/javascript'}); res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(gun.server.js = gun.server.js || require('fs').readFileSync(__dirname + '/gun.js')); // gun server is caching the gun library for the client res.end(gun.server.js = gun.server.js || require('fs').readFileSync(__dirname + '/gun.js')); // gun server is caching the gun library for the client
return; return;
} }
console.log("\ngun server has requests!", req.method, req.url, req.headers, req.body); msg.url.key = msg.url.key.replace(/^\//i,'') || ''; // strip the base
tmp.key = tmp.key.replace(/^\//i,'') || ''; // strip the base msg.method = (req.method||'').toLowerCase();
tmp.method = (req.method||'').toLowerCase(); msg.headers = req.headers;
if('get' === tmp.method){ // get is used as subscribe msg.body = req.body; // TODO: include body-parser here?
console.log("URL?", tmp.url); if('get' === msg.method){ // get is used as subscribe
if(tmp.url && tmp.url.query){ gun.__.opt.hook.sub(msg, function(reply){
/* if(!res){ return }
long polling! Idea: On data-flush or res.end, issue a timeout token, if(!reply){ return res.end() }
that keeps the 'connection' alive even while disconnected. if(reply.headers){
Subsequent requests use the timeout token and thus continue off as before, seamlessly. if(!res._headerSent){
If after the timeout no follow up has been made, we assume the client has dropped / disconnected. Gun.obj.map(reply.headers, function(val, field){
*/ res.setHeader(field, val);
Gun.obj.map(tmp.url.query, function(){ });
tmp.query = true; }
// subscribe this req/res to the ids, then make POSTS publish to them and reply!
// MARK! COME BACK HERE
});
if(tmp.query){
return; // we'll wait until we get updates before we reply. Long polling (should probably be implemented as a hook itself! so websockets can replace it)
} }
} meta.CORS(req, res); // add option to disable this
if(!tmp.key){ if(reply.chunk){
return meta.JSON(res, {gun: true}); res.write(Gun.text.ify(reply.chunk));
} }
// raw test for now, no auth: if(reply.body){
gun.load(tmp.key, function(err, data){ res.end(Gun.text.ify(reply.body));
meta.CORS(req, res); }
return meta.JSON(res, data || err); });
}) return;
} else } else
if('post' === tmp.method || 'patch' === tmp.method){ // post is used as patch, sad that patch has such poor support if('post' === msg.method || 'patch' === msg.method){ // post is used as patch, sad that patch has such poor support
if(!req.body){ if(!msg.body){
console.log("Warn: No body on POST?"); console.log("Warn: No body on POST?");
} }
// raw test for now, no auth: // raw test for now, no auth:
// should probably load all the nodes first? // should probably load all the nodes first?
var context = Gun.chain.set.now.union.call(gun, req.body); // data safely transformed var context = Gun.chain.set.now.union.call(gun, msg.body); // data safely transformed
if(context.err){ if(context.err){
return meta.JSON(res, context.err); // need to standardize errors more return meta.JSON(res, context.err); // need to use the now standardized errors
} }
// console.log("-------- union ---------");Gun.obj.map(gun.__.nodes, function(node){ console.log(node); });console.log("------------------------");
/* /*
WARNING! TODO: BUG! Do not send OK confirmation if amnesiaQuaratine is activated! Not until after it has actually been processed!!! WARNING! TODO: BUG! Do not send OK confirmation if amnesiaQuaratine is activated! Not until after it has actually been processed!!!
*/ */
@ -73,15 +67,26 @@
context.err = "Warning! You have no persistence layer to save to!"; context.err = "Warning! You have no persistence layer to save to!";
Gun.log(context.err); Gun.log(context.err);
} }
var diff = msg.body
msg.body = null;
Gun.obj.map(context.nodes, function(node, id){
var req = Gun.obj.copy(msg);
msg.body = node;
gun.server.push.on(id).emit(msg);
});
msg.body = diff;
} }
} }
gun.server.regex = /^\/gun/i; gun.server.regex = /^\/gun/i;
gun.server.clients = {};
gun.server.push = Gun.on.split();
var s3 = gun.__.opt.s3 = gun.__.opt.s3 || S3(opt && opt.s3); var s3 = gun.__.opt.s3 = gun.__.opt.s3 || S3(opt && opt.s3);
s3.prefix = s3.prefix || opt.s3.prefix || ''; s3.prefix = s3.prefix || opt.s3.prefix || '';
s3.prekey = s3.prekey || opt.s3.prekey || ''; s3.prekey = s3.prekey || opt.s3.prekey || '';
s3.prenode = s3.prenode || opt.s3.prenode || '_/nodes/'; s3.prenode = s3.prenode || opt.s3.prenode || '_/nodes/';
gun.__.opt.batch = opt.batch || gun.__.opt.batch || 10; gun.__.opt.batch = opt.batch || gun.__.opt.batch || 10;
gun.__.opt.throttle = opt.throttle || gun.__.opt.throttle || 2; gun.__.opt.throttle = opt.throttle || gun.__.opt.throttle || 15;
if(!gun.__.opt.keepMaxSockets){ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = Infinity } // WARNING: Document this! if(!gun.__.opt.keepMaxSockets){ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = Infinity } // WARNING: Document this!
s3.load = s3.load || function(key, cb, opt){ s3.load = s3.load || function(key, cb, opt){
@ -174,17 +179,104 @@
}, {Metadata: {'#': id}}); }, {Metadata: {'#': id}});
} }
gun.server.sub = (function(){
function sub(req, cb){
//console.log("\n\n\n", req);
req.sub = req.headers['gun-sub'];
req.transport = req.headers['gun-transport'];
if(req.transport === 'XHR-SLP'){ return sub.SLP(req, cb) }
if(!req.url.key){ return sub.keyless(req, cb) }
// raw test for now, no auth:
req.tab = sub.s[req.sub] || {};
cb.header = {'Content-Type': sub.json};
cb.header['Gun-Sub'] = req.tab.sub =
req.sub = req.tab.sub || req.sub || Gun.text.random();
gun.load(req.url.key, function(node){
sub.scribe(req.tab, node._[Gun.sym.id]);
cb({
headers: cb.header
,body: Gun.text.ify(node)
});
}).blank(function(){
cb({
headers: cb.header
,body: Gun.text.ify(null)
});
}).dud(function(err){
cb({
headers: cb.header
,body: Gun.text.ify({err: err || "Unknown error."})
});
});
}
sub.s = {};
sub.scribe = function(tab, id){
sub.s[tab.sub] = tab;
tab.subs = tab.subs || {};
tab.subs[id] = tab.subs[id] || gun.server.push.on(id).event(function(req){
if(!req){ return }
if(!tab){ return this.off() } // resolve any dangling callbacks
req.sub = req.sub || req.headers['gun-sub'];
if(req.sub === tab.sub){ return } // do not send back to the tab that sent it
if(Gun.fns.is(tab.reply)){
tab.reply({
headers: {'Content-Type': sub.json, 'Gun-Sub': tab.sub}
,body: Gun.text.ify(req.body)
})
tab.reply = null;
return;
}
(tab.queue = tab.queue || []).push(req.body);
});
}
sub.SLP = function(req, cb){ // Streaming Long Polling
//console.log("<-- ", req.sub, req.transport ," -->");
req.tab = sub.s[req.sub];
if(!req.tab){
cb({
headers: {'Content-Type': sub.json, 'Gun-Sub': ''}
,body: Gun.text.ify({err: "Please re-initialize sub."})
});
return;
}
req.tab.sub = req.tab.sub || req.sub;
if(req.tab.queue && req.tab.queue.length){
cb({ headers: {'Content-Type': sub.json, 'Gun-Sub': req.sub} });
while(1 < req.tab.queue.length){
cb({ chunk: Gun.text.ify(req.tab.queue.shift() + '\n') });
}
cb({ body: Gun.text.ify(req.tab.queue.shift()) });
} else {
req.tab.reply = cb;
}
}
sub.keyless = function(req, cb){
cb({
headers: {'Content-Type': sub.json}
,body: {gun: true}
});
}
sub.json = 'application/json';
return sub;
}());
opt.hook = opt.hook || {}; opt.hook = opt.hook || {};
gun.init({hook: { gun.opt({hook: {
load: opt.hook.load || s3.load load: opt.hook.load || s3.load
,set: opt.hook.set || s3.set ,set: opt.hook.set || s3.set
,key: opt.hook.key || s3.key ,key: opt.hook.key || s3.key
,sub: opt.hook.sub || gun.server.sub
}}, true); }}, true);
}); });
meta.json = 'application/json'; meta.json = 'application/json';
meta.JSON = function(res, data){ meta.JSON = function(res, data, multi){
if(!res || res._headerSent){ return } if(res && !res._headerSent){
res.setHeader('Content-Type', meta.json); res.setHeader('Content-Type', meta.json);
}
if(!data && multi){
res.write(JSON.stringify(multi||'')+'\n');
return;
}
return res.end(JSON.stringify(data||'')); return res.end(JSON.stringify(data||''));
}; };
meta.CORS = function(req, res){ meta.CORS = function(req, res){