mirror of
https://github.com/amark/gun.git
synced 2025-06-06 14:16:44 +00:00
live push, albeit needs improvements
This commit is contained in:
parent
8024d51950
commit
cabd7f21da
@ -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');
|
||||
|
@ -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
148
gun.js
@ -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;
|
||||
});
|
||||
}({}));
|
@ -1,5 +1,5 @@
|
||||
{ "name": "gun"
|
||||
, "version": "0.0.2b"
|
||||
, "version": "0.0.3"
|
||||
, "author": "Mark Nadal"
|
||||
, "description": "Graph engine."
|
||||
, "engines": {
|
||||
|
180
shots.js
180
shots.js
@ -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){
|
||||
|
Loading…
x
Reference in New Issue
Block a user