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');
gun.load('blob/data', function(){ // ugly little idempotent initializer, in case no data is set
gun.set({_:{'#': "yVbyf7BqlXVQQUOE5cw9rf8h",'>':{hello: 1407328713707,from: 1407328713707}}, // this is an actual gun object, this won't overwrite any new changes
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 a nasty trick to force the ID to overwrite itself
hello: "world",
from: "Mark Nadal"
}).key('blob/data');

View File

@ -55,6 +55,12 @@
$scope.$data = gun.load('blob/data', function(data){
$scope.data = data;
$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.$data.path($scope.field).set(

148
gun.js
View File

@ -4,12 +4,12 @@
if(!Gun.is(gun)){
return new Gun(opt);
}
gun.init(opt);
gun.opt(opt);
}
Gun.is = function(gun){ return (gun instanceof Gun)? true : false }
Gun._ = {};
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;
gun._ = gun._ || {};
gun.__ = gun.__ || {};
@ -28,7 +28,7 @@
if(!Gun.fns.is(h)){ return }
gun.__.opt.hook[f] = h;
});
if(!stun){ Gun.on('init').emit(gun, opt) }
if(!stun){ Gun.on('opt').emit(gun, opt) }
return gun;
}
Gun.chain.chain = function(from){
@ -225,6 +225,7 @@
Gun.text = {};
Gun.text.is = function(t){ return typeof t == 'string'? true : false }
Gun.text.ify = function(t){
if(Gun.text.is(t)){ return t }
if(JSON){ return JSON.stringify(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!
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){
// the incoming value is outside the boundary of the machine's state, it must be reprocessed in another state.
return {amnesiaQuarantine: true};
@ -609,34 +610,94 @@
}
}({}));
;(function(Page){
;(function(tab){
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
Gun.on('init').event(function(gun, opt){
Page.load = function(key, cb, opt){
Gun.on('opt').event(function(gun, opt){
tab.server = tab.server || function(req, res, next){
}
tab.load = tab.load || function(key, cb, opt){
cb = cb || function(){};
opt = opt || {};
Gun.obj.map(gun.__.opt.peers, function(peer, url){
Page.ajax(url + '/' + key, null, function(data){
Gun.log('via', url, key, data);
// alert(data + data.hello + data.from + data._);
cb(null, data);
if(!data || !data._){ return }
Page.subscribe(data._[Gun.sym.id]);
});
tab.ajax(url + '/' + key, null, function(reply){
Gun.log('via', url, key, reply);
if(!reply){ return } // handle reconnect?
if(reply.body && reply.body.err){
cb(reply.body.err);
} 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(){};
// TODO: batch and throttle later.
console.log('ajax set', nodes);
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);
});
});
}
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 = '?'
, uri = encodeURIComponent;
Gun.obj.map(params, function(val, field){
@ -644,18 +705,7 @@
});
return s;
}
Page.subscribe = function(id){
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 =
tab.ajax =
window.ajax =
function(url, data, cb, opt){
/*
@ -668,11 +718,13 @@
*/
var u;
opt = opt || {};
opt.head = opt.head || {};
opt.reshead = {};
opt.headers = opt.headers || {};
if(data === u || data === null){
data = u;
} else {
try{data = JSON.stringify(data);
opt.headers = opt.headers || {};
opt.headers["Content-Type"] = "application/json;charset=utf-8";
}catch(e){}
}
@ -694,20 +746,24 @@
}
opt.xhr = null;
}
opt.data = opt.data || function(d){
var t;
try{t = JSON.parse(d) || d;
opt.data = opt.data || function(d, head){
var reply = {};
reply.headers = head;
try{reply.body = JSON.parse(d) || d;
}catch(e){
t = d;
reply.body = d;
}
if(cb){ cb(t) }
if(cb){ cb(reply) }
}
opt.chunk = function(status, text, force){
if(status !== 200){ return }
opt.each(opt.head, function(val, i){
opt.reshead[i] = opt.xhr.getResponseHeader(i);
});
var d, b, p = 1;
while(p || force){
if(u !== d){
opt.data(d);
opt.data(d, opt.reshead);
force = false;
}
b = text.slice(opt.i = opt.i || 0);
@ -771,12 +827,18 @@
opt.error();
return;
}
if(opt.headers){
try{for(var i in opt.headers){
if(opt.headers.hasOwnProperty(i)){
opt.xhr.setRequestHeader(i, opt.headers[i]);
}
opt.each = function(obj, cb){
if(!obj || !cb){ return }
for(var i in obj){
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) {
opt.error();
return;
@ -788,7 +850,7 @@
}
return opt;
}
gun.__.opt.hook.load = gun.__.opt.hook.load || Page.load;
gun.__.opt.hook.set = gun.__.opt.hook.set || Page.set;
gun.__.opt.hook.load = gun.__.opt.hook.load || tab.load;
gun.__.opt.hook.set = gun.__.opt.hook.set || tab.set;
});
}({}));

View File

@ -1,5 +1,5 @@
{ "name": "gun"
, "version": "0.0.2b"
, "version": "0.0.3"
, "author": "Mark Nadal"
, "description": "Graph engine."
, "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
, url = require('url')
, 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
next = next || function(){};
if(!req || !res){ return next() }
if(!req.url){ return next() }
if(!req.method){ return next() }
var tmp = {};
tmp.url = url.parse(req.url, true);
if(!gun.server.regex.test(tmp.url.pathname)){ return next() }
tmp.key = tmp.url.pathname.replace(gun.server.regex,'') || '';
if(tmp.key.toLowerCase() === '.js'){
var msg = {};
msg.url = url.parse(req.url, true);
if(!gun.server.regex.test(msg.url.pathname)){ return next() }
msg.url.key = msg.url.pathname.replace(gun.server.regex,'') || '';
if(msg.url.key.toLowerCase() === '.js'){
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
return;
}
console.log("\ngun server has requests!", req.method, req.url, req.headers, req.body);
tmp.key = tmp.key.replace(/^\//i,'') || ''; // strip the base
tmp.method = (req.method||'').toLowerCase();
if('get' === tmp.method){ // get is used as subscribe
console.log("URL?", tmp.url);
if(tmp.url && tmp.url.query){
/*
long polling! Idea: On data-flush or res.end, issue a timeout token,
that keeps the 'connection' alive even while disconnected.
Subsequent requests use the timeout token and thus continue off as before, seamlessly.
If after the timeout no follow up has been made, we assume the client has dropped / disconnected.
*/
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)
msg.url.key = msg.url.key.replace(/^\//i,'') || ''; // strip the base
msg.method = (req.method||'').toLowerCase();
msg.headers = req.headers;
msg.body = req.body; // TODO: include body-parser here?
if('get' === msg.method){ // get is used as subscribe
gun.__.opt.hook.sub(msg, function(reply){
if(!res){ return }
if(!reply){ return res.end() }
if(reply.headers){
if(!res._headerSent){
Gun.obj.map(reply.headers, function(val, field){
res.setHeader(field, val);
});
}
}
}
if(!tmp.key){
return meta.JSON(res, {gun: true});
}
// raw test for now, no auth:
gun.load(tmp.key, function(err, data){
meta.CORS(req, res);
return meta.JSON(res, data || err);
})
meta.CORS(req, res); // add option to disable this
if(reply.chunk){
res.write(Gun.text.ify(reply.chunk));
}
if(reply.body){
res.end(Gun.text.ify(reply.body));
}
});
return;
} else
if('post' === tmp.method || 'patch' === tmp.method){ // post is used as patch, sad that patch has such poor support
if(!req.body){
if('post' === msg.method || 'patch' === msg.method){ // post is used as patch, sad that patch has such poor support
if(!msg.body){
console.log("Warn: No body on POST?");
}
// raw test for now, no auth:
// 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){
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!!!
*/
@ -73,15 +67,26 @@
context.err = "Warning! You have no persistence layer to save to!";
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.clients = {};
gun.server.push = Gun.on.split();
var s3 = gun.__.opt.s3 = gun.__.opt.s3 || S3(opt && opt.s3);
s3.prefix = s3.prefix || opt.s3.prefix || '';
s3.prekey = s3.prekey || opt.s3.prekey || '';
s3.prenode = s3.prenode || opt.s3.prenode || '_/nodes/';
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!
s3.load = s3.load || function(key, cb, opt){
@ -174,17 +179,104 @@
}, {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 || {};
gun.init({hook: {
gun.opt({hook: {
load: opt.hook.load || s3.load
,set: opt.hook.set || s3.set
,key: opt.hook.key || s3.key
,sub: opt.hook.sub || gun.server.sub
}}, true);
});
meta.json = 'application/json';
meta.JSON = function(res, data){
if(!res || res._headerSent){ return }
res.setHeader('Content-Type', meta.json);
meta.JSON = function(res, data, multi){
if(res && !res._headerSent){
res.setHeader('Content-Type', meta.json);
}
if(!data && multi){
res.write(JSON.stringify(multi||'')+'\n');
return;
}
return res.end(JSON.stringify(data||''));
};
meta.CORS = function(req, res){