diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 00000000..7df76dc9
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,2 @@
+node_modules/*
+npm-debug.log
diff --git a/examples/admin/old_gun_for_slinger.js b/examples/admin/old_gun_for_slinger.js
index 18b1dfe5..70a662e4 100644
--- a/examples/admin/old_gun_for_slinger.js
+++ b/examples/admin/old_gun_for_slinger.js
@@ -1,1062 +1,1062 @@
-;(function(){
- function Gun(opt){
- var gun = this;
- if(!Gun.is(gun)){
- return new Gun(opt);
- }
- gun.opt(opt);
- }
- Gun._ = {
- soul: '#'
- ,meta: '_'
- ,HAM: '>'
- }
- ;(function(Gun){
- Gun.is = function(gun){ return (gun instanceof Gun)? true : false }
- Gun.version = 0.8;
- Gun.union = function(graph, prime){
- var context = Gun.shot();
- context.nodes = {};
- context('done');context('change');
- Gun.obj.map(prime, function(node, soul){
- var vertex = graph[soul], env;
- if(!vertex){ // disjoint
- context.nodes[node._[Gun._.soul]] = graph[node._[Gun._.soul]] = node;
- context('change').fire(node);
- return;
- }
- env = Gun.HAM(vertex, node, function(current, field, deltaValue){
- if(!current){ return }
- var change = {};
- current[field] = change[field] = deltaValue; // current and vertex are the same
- current._[Gun._.HAM][field] = node._[Gun._.HAM][field];
- change._ = current._;
- context.nodes[change._[Gun._.soul]] = change;
- context('change').fire(change);
- }).upper(function(c){
- context.err = c.err;
- context.up -= 1;
- if(!context.up){
- context('done').fire(context.err, context);
- }
- });
- context.up += env.up;
- });
- if(!context.up){
- context('done').fire(context.err, context);
- }
- return context;
- }
- Gun.HAM = function(current, delta, each){ // HAM only handles primitives values, all other data structures need to be built ontop and reduce to HAM.
- 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};
- }
- if(incomingState < currentState){
- // the incoming value is within the boundary of the machine's state, but not within the range.
- return {quarantineState: true};
- }
- if(currentState < incomingState){
- // the incoming value is within both the boundary and the range of the machine's state.
- return {converge: true, incoming: true};
- }
- if(incomingState === currentState){
- if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different
- return {state: true};
- }
- /*
- The following is a naive implementation, but will always work.
- Never change it unless you have specific needs that absolutely require it.
- If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same.
- As a result, it is highly discouraged to modify despite the fact that it is naive,
- because convergence (data integrity) is generally more important.
- Any difference in this algorithm must be given a new and different name.
- */
- if(String(incomingValue) < String(currentValue)){ // String only works on primitive values!
- return {converge: true, current: true};
- }
- if(String(currentValue) < String(incomingValue)){ // String only works on primitive values!
- return {converge: true, incoming: true};
- }
- }
- return {err: "you have not properly handled recursion through your data or filtered it as JSON"};
- }
- var context = Gun.shot();
- context.HAM = {};
- context.states = {};
- context.states.delta = delta._[Gun._.HAM];
- context.states.current = current._[Gun._.HAM] = current._[Gun._.HAM] || {};
- context('lower');context('upper');context.up = context.up || 0;
- Gun.obj.map(delta, function update(deltaValue, field){
- if(field === Gun._.meta){ return }
- if(!Gun.obj.has(current, field)){ // does not need to be applied through HAM
- each.call({incoming: true, converge: true}, current, field, deltaValue);
- return;
- }
- var serverState = Gun.time.is();
- // add more checks?
- var state = HAM(serverState, context.states.delta[field], context.states.current[field], deltaValue, current[field]);
- //console.log("HAM:", field, deltaValue, context.states.delta[field], context.states.current[field], 'the', state, (context.states.delta[field] - serverState));
- if(state.err){
- Gun.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err);
- return;
- }
- if(state.state || state.quarantineState || state.current){
- context('lower').fire(context, state, current, field, deltaValue);
- return;
- }
- if(state.incoming){
- each.call(state, current, field, deltaValue);
- return;
- }
- if(state.amnesiaQuarantine){
- context.up += 1;
- Gun.schedule(context.states.delta[field], function(){
- update(deltaValue, field);
- context.up -= 1;
- context('upper').fire(context, state, current, field, deltaValue);
- });
- }
- });
- if(!context.up){
- context('upper').fire(context, {});
- }
- return context;
- }
- Gun.roulette = function(l, c){
- var gun = Gun.is(this)? this : {};
- if(gun._ && gun.__.opt && gun.__.opt.uuid){
- if(Gun.fns.is(gun.__.opt.uuid)){
- return gun.__.opt.uuid(l, c);
- }
- l = l || gun.__.opt.uuid.length;
- }
- return Gun.text.random(l, c);
- }
- Gun.log = function(a, b, c, d, e, f){
- //console.log(a, b, c, d, e, f);
- //console.log.apply(console, arguments);
- }
- }(Gun));
- ;(function(Chain){
- Chain.opt = function(opt, stun){ // idempotently update or set options
- var gun = this;
- gun._ = gun._ || {};
- gun.__ = gun.__ || {};
- gun.shot = Gun.shot();
- gun.shot('then');
- gun.shot('err');
- if(!opt){ return gun }
- gun.__.opt = gun.__.opt || {};
- gun.__.keys = gun.__.keys || {};
- gun.__.graph = gun.__.graph || {};
- gun.__.on = gun.__.on || Gun.on.create();
- if(Gun.text.is(opt)){ opt = {peers: opt} }
- if(Gun.list.is(opt)){ opt = {peers: opt} }
- if(Gun.text.is(opt.peers)){ opt.peers = [opt.peers] }
- if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) }
- gun.__.opt.peers = opt.peers || gun.__.opt.peers || {};
- gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {};
- gun.__.opt.hooks = gun.__.opt.hooks || {};
- Gun.obj.map(opt.hooks, function(h, f){
- if(!Gun.fns.is(h)){ return }
- gun.__.opt.hooks[f] = h;
- });
- if(!stun){ Gun.on('opt').emit(gun, opt) }
- return gun;
- }
- Chain.chain = function(from){
- var gun = Gun();
- from = from || this;
- gun.back = from;
- gun.__ = from.__;
- gun._ = {};
- Gun.obj.map(from._, function(val, field){
- gun._[field] = val;
- });
- return gun;
- }
- Chain.load = function(key, cb, opt){
- var gun = this.chain();
- cb = cb || function(){};
- gun.shot.then(function(){ cb.apply(gun, arguments) });
- cb.soul = (key||{})[Gun._.soul];
- if(cb.soul){
- cb.node = gun.__.graph[cb.soul];
- } else {
- gun._.key = key;
- cb.node = gun.__.keys[key];
- }
- if(cb.node){ // set this to the current node, too!
- Gun.log("from gun"); // remember to do all the same stack stuff here also!
- var freeze = Gun.obj.copy(gun._.node = cb.node);
- gun.shot('then').fire(freeze); // freeze now even though internals use this? OK for now.
- return gun; // TODO: BUG: This needs to react the same as below!
- }
- cb.fn = function(){}
- // missing: hear shots!
- if(Gun.fns.is(gun.__.opt.hooks.load)){
- gun.__.opt.hooks.load(key, function(err, data){
- gun._.loaded = (gun._.loaded || 0) + 1; // TODO: loading should be idempotent even if we got an err or no data
- if(err){ return (gun._.dud||cb.fn)(err) }
- if(!data){ return (gun._.blank||cb.fn)() }
- var context = gun.union(data); // safely transform the data
- if(context.err){ return (gun._.dud||cb.fn)(context.err) }
- gun._.node = gun.__.graph[data._[Gun._.soul]]; // don't wait for the union to be done because we want the immediate state not the intended state.
- if(!cb.soul){ gun.__.keys[key] = gun._.node }
- var freeze = Gun.obj.copy(gun._.node);
- gun.shot('then').fire(freeze); // freeze now even though internals use this? OK for now.
- }, opt);
- } else {
- Gun.log("Warning! You have no persistence layer to load from!");
- }
- return gun;
- }
- Chain.key = function(key, cb){
- var gun = this;
- gun.shot.then(function(){
- Gun.log("make key", key);
- cb = cb || function(){};
- cb.node = gun.__.keys[key] = gun._.node;
- if(!cb.node){ return gun }
- if(Gun.fns.is(gun.__.opt.hooks.key)){
- gun.__.opt.hooks.key(key, cb.node._[Gun._.soul], function(err, data){
- Gun.log("key made", key);
- if(err){ return cb(err) }
- return cb(null);
- });
- } else {
- Gun.log("Warning! You have no key hook!");
- }
- });
- if(!gun.back){ gun.shot('then').fire() }
- return gun;
- }
- /*
- how many different ways can we get something?
- Find via a singular path
- .path('blah').get(blah);
- Find via multiple paths with the callback getting called many times
- .path('foo', 'bar').get(foorOrBar);
- Find via multiple paths with the callback getting called once with matching arguments
- .path('foo', 'bar').get(foo, bar)
- Find via multiple paths with the result aggregated into an object of pre-given fields
- .path('foo', 'bar').get({foo: foo, bar: bar}) || .path({a: 'foo', b: 'bar'}).get({a: foo, b: bar})
- Find via multiple paths where the fields and values must match
- .path({foo: val, bar: val}).get({})
- */
- Chain.path = function(path){ // The focal point follows the path
- var gun = this.chain();
- path = (path || '').split('.');
- gun.back.shot.then(function trace(node){ // should handle blank and err! Err already handled?
- //console.log("shot path", path, node);
- gun.field = null;
- gun._.node = node;
- if(!path.length){ // if the path resolves to another node, we finish here
- return gun.shot('then').fire(node); // already frozen from loaded.
- }
- var field = path.shift()
- , val = node[field];
- gun.field = field;
- if(Gun.ify.is.soul(val)){ // we might end on a link, so we must resolve
- return gun.load(val).shot.then(trace);
- } else
- if(path.length){ // we cannot go any further, despite the fact there is more path, which means the thing we wanted does not exist
- gun.shot('then').fire();
- } else { // we are done, and this should be the value we wanted.
- gun.shot('then').fire(val); // primitive values are passed as copies in JS.
- }
- });
- // if(!gun.back){ gun.shot('then').fire() } // replace below with this? maybe???
- if(gun.back && gun.back._ && gun.back._.loaded){
- gun._.node = gun.back._.node;
- gun.back.shot('then').fire(gun.back._.node);
- }
- return gun;
- }
- Chain.get = function(cb){
- var gun = this;
- gun.shot.then(function(val){
- cb.call(gun, val); // frozen from done.
- gun.__.on(gun._.node._[Gun._.soul]).event(function(delta){
- if(!delta){ return }
- if(!gun.field){
- cb.call(gun, Gun.obj.copy(gun._.node));
- return;
- }
- if(Gun.obj.has(delta, gun.field)){
- cb.call(gun, delta[gun.field]);
- }
- })
- });
- return gun;
- }
- /*
- ACID compliant, unfortunately the vocabulary is vague, as such the following is an explicit definition:
- A - Atomic, if you set a full node, or nodes of nodes, if any value is in error then nothing will be set.
- If you want sets to be independent of each other, you need to set each piece of the data individually.
- C - Consistency, if you use any reserved symbols or similar, the operation will be rejected as it could lead to an invalid read and thus an invalid state.
- I - Isolation, the conflict resolution algorithm guarantees idempotent transactions, across every peer, regardless of any partition,
- including a peer acting by itself or one having been disconnected from the network.
- D - Durability, if the acknowledgement receipt is received, then the state at which the final persistence hook was called on is guaranteed to have been written.
- The live state at point of confirmation may or may not be different than when it was called.
- If this causes any application-level concern, it can compare against the live data by immediately reading it, or accessing the logs if enabled.
- */
- Chain.set = function(val, cb, opt){ // TODO: need to turn deserializer into a trampolining function so stackoverflow doesn't happen.
- opt = opt || {};
- var gun = this, set;
- gun.shot.then(function(){
- if(gun.field){ // a field cannot be 0!
- set = {}; // in case we are doing a set on a field, not on a node
- set[gun.field] = val; // we create a blank node with the field/value to be set
- val = set;
- } // TODO: should be able to handle val being a relation or a gun context or a gun promise.
- val._ = Gun.ify.soul.call(gun, {}, gun._.node); // and then set their souls to be the same that way they will merge correctly for us during the union!
- cb = Gun.fns.is(cb)? cb : function(){};
- set = Gun.ify.call(gun, val);
- cb.root = set.root;
- if(set.err){ return cb(set.err), gun }
- set = Gun.ify.state(set.nodes, Gun.time.is()); // set time state on nodes?
- if(set.err){ return cb(set.err), gun }
- Gun.union(gun.__.graph, set.nodes); // while this maybe should return a list of the nodes that were changed, we want to send the actual delta
- gun._.node = gun.__.graph[cb.root._[Gun._.soul]] || cb.root;
- // TODO? ^ Maybe BUG! if val is a new node on a field, _.node should now be that! Or will that happen automatically?
- if(Gun.fns.is(gun.__.opt.hooks.set)){
- gun.__.opt.hooks.set(set.nodes, function(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved
- //Gun.log("gun set hook callback called");
- if(err){ return cb(err) }
- return cb(null);
- });
- } else {
- Gun.log("Warning! You have no persistence layer to save to!");
- }
- });
- if(!gun.back){ gun.shot('then').fire() }
- return gun;
- }
- Chain.union = function(prime, cb){
- var tmp, gun = this, context = Gun.shot();
- context.nodes = {};
- cb = cb || function(){}
- if(!prime){
- context.err = {err: "No data to merge!"};
- } else
- if(prime._ && prime._[Gun._.soul]){
- tmp = {};
- tmp[prime._[Gun._.soul]] = prime;
- prime = tmp;
- }
- if(!gun || context.err){
- cb(context.err = context.err || {err: "No gun instance!", corrupt: true}, context);
- return context;
- }
- Gun.obj.map(prime, function(node){ // map over the prime graph, to get each node that has been modified
- var set = Gun.ify.call(gun, node);
- if(set.err){ return context.err = set.err } // check to see if the node is valid
- Gun.obj.map(set.nodes, function(node, soul){ // if so, map over it, and any other nodes that were deserialized from it
- context.nodes[soul] = node; // into a valid context we'll actually do a union on.
- });
- });
- if(context.err){ return cb(context.err, context), context } // if any errors happened in the previous steps, then fail.
- Gun.union(gun.__.graph, context.nodes).done(function(err, env){ // now merge prime into the graph
- context.err = err || env.err;
- cb(context.err, context || {});
- }).change(function(delta){
- if(!delta || !delta._ || !delta._[Gun._.soul]){ return }
- gun.__.on(delta._[Gun._.soul]).emit(Gun.obj.copy(delta)); // this is in reaction to HAM
- });
- return context;
- }
- Chain.match = function(){ // same as path, except using objects
- return this;
- }
- Chain.blank = function(blank){
- this._.blank = Gun.fns.is(blank)? blank : function(){};
- return this;
- }
- Chain.dud = function(dud){
- this._.dud = Gun.fns.is(dud)? dud : function(){};
- return this;
- }
- }(Gun.chain = Gun.prototype));
- ;(function(Util){
- Util.fns = {};
- Util.fns.is = function(fn){ return (fn instanceof Function)? true : false }
- Util.bi = {};
- Util.bi.is = function(b){ return (b instanceof Boolean || typeof b == 'boolean')? true : false }
- Util.num = {};
- Util.num.is = function(n){
- return ((n===0)? true : (!isNaN(n) && !Util.bi.is(n) && !Util.list.is(n) && !Util.text.is(n))? true : false );
- }
- Util.text = {};
- Util.text.is = function(t){ return typeof t == 'string'? true : false }
- Util.text.ify = function(t){
- if(Util.text.is(t)){ return t }
- if(JSON){ return JSON.stringify(t) }
- return (t && t.toString)? t.toString() : t;
- }
- Util.text.random = function(l, c){
- var s = '';
- l = l || 24; // you are not going to make a 0 length random number, so no need to check type
- c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghiklmnopqrstuvwxyz';
- while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- }
- return s;
- }
- Util.list = {};
- Util.list.is = function(l){ return (l instanceof Array)? true : false }
- Util.list.slit = Array.prototype.slice;
- Util.list.sort = function(k){ // creates a new sort function based off some field
- return function(A,B){
- if(!A || !B){ return 0 } A = A[k]; B = B[k];
- if(A < B){ return -1 }else if(A > B){ return 1 }
- else { return 0 }
- }
- }
- Util.list.map = function(l, c, _){ return Util.obj.map(l, c, _) }
- Util.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation
- Util.obj = {};
- Util.obj.is = function(o){ return (o instanceof Object && !Util.list.is(o) && !Util.fns.is(o))? true : false }
- Util.obj.del = function(o, k){
- if(!o){ return }
- o[k] = null;
- delete o[k];
- return true;
- }
- Util.obj.ify = function(o){
- if(Util.obj.is(o)){ return o }
- try{o = JSON.parse(o);
- }catch(e){o={}};
- return o;
- }
- Util.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2
- return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways!
- }
- Util.obj.has = function(o, t){ return Object.prototype.hasOwnProperty.call(o, t) }
- Util.obj.map = function(l, c, _){
- var u, i = 0, ii = 0, x, r, rr, f = Util.fns.is(c),
- t = function(k,v){
- if(v !== u){
- rr = rr || {};
- rr[k] = v;
- return;
- } rr = rr || [];
- rr.push(k);
- };
- if(Util.list.is(l)){
- x = l.length;
- for(;i < x; i++){
- ii = (i + Util.list.index);
- if(f){
- r = _? c.call(_, l[i], ii, t) : c(l[i], ii, t);
- if(r !== u){ return r }
- } else {
- //if(Util.test.is(c,l[i])){ return ii } // should implement deep equality testing!
- if(c === l[i]){ return ii } // use this for now
- }
- }
- } else {
- for(i in l){
- if(f){
- if(Util.obj.has(l,i)){
- r = _? c.call(_, l[i], i, t) : c(l[i], i, t);
- if(r !== u){ return r }
- }
- } else {
- //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing!
- if(c === l[i]){ return i }
- }
- }
- }
- return f? rr : Util.list.index? 0 : -1;
- }
- Util.time = {};
- Util.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) }
- }(Gun));
- ;Gun.shot=(function(){
- // I hate the idea of using setTimeouts in my code to do callbacks (promises and sorts)
- // as there is no way to guarantee any type of state integrity or the completion of callback.
- // However, I have fallen. HAM is suppose to assure side effect free safety of unknown states.
- var setImmediate = setImmediate || function(cb){setTimeout(cb,0)}
- function Flow(){
- var chain = new Flow.chain();
- return chain.$ = function(where){
- (chain._ = chain._ || {})[where] = chain._[where] || [];
- chain.$[where] = chain.$[where] || function(fn){
- (chain._[where]||[]).push(fn);
- return chain.$;
- }
- chain.where = where;
- return chain;
- }
- }
- Flow.is = function(flow){ return (Flow instanceof flow)? true : false }
- ;Flow.chain=(function(){
- function Chain(){
- if(!(this instanceof Chain)){
- return new Chain();
- }
- }
- Chain.chain = Chain.prototype;
- Chain.chain.pipe = function(a,s,d,f){
- var me = this
- , where = me.where
- , args = Array.prototype.slice.call(arguments);
- setImmediate(function(){
- if(!me || !me._ || !me._[where]){ return }
- while(0 < me._[where].length){
- (me._[where].shift()||function(){}).apply(me, args);
- }
- // do a done? That would be nice. :)
- });
- return me;
- }
- return Chain;
- }());
- return Flow;
- }());Gun.shot.chain.chain.fire=Gun.shot.chain.chain.pipe;
- ;Gun.on=(function(){
- function On(where){
- if(where){
- return (On.event = On.event || On.create())(where);
- }
- return On.create();
- }
- On.is = function(on){ return (On instanceof on)? true : false }
- On.create = function(){
- var chain = new On.chain();
- return chain.$ = function(where){
- chain.where = where;
- return chain;
- }
- }
- On.sort = Gun.list.sort('i');
- ;On.chain=(function(){
- function Chain(){
- if(!(this instanceof Chain)){
- return new Chain();
- }
- }
- Chain.chain = Chain.prototype;
- Chain.chain.emit = function(what){
- var me = this
- , where = me.where
- , args = arguments
- , on = (me._ = me._ || {})[where] = me._[where] || [];
- if(!(me._[where] = Gun.list.map(on, function(hear, i, map){
- if(!hear || !hear.as){ return }
- map(hear);
- hear.as.apply(hear, args);
- }))){ Gun.obj.del(on, where) }
- }
- Chain.chain.event = function(as, i){
- if(!as){ return }
- var me = this
- , where = me.where
- , args = arguments
- , on = (me._ = me._ || {})[where] = me._[where] || []
- , e = {as: as, i: i || 0, off: function(){ return !(e.as = false) }};
- return on.push(e), on.sort(On.sort), e;
- }
- Chain.chain.once = function(as, i){
- var me = this
- , once = function(){
- this.off();
- as.apply(this, arguments)
- }
- return me.event(once, i)
- }
- return Chain;
- }());
- return On;
- }());
- ;(function(schedule){ // maybe use lru-cache
- schedule.waiting = [];
- schedule.soonest = Infinity;
- schedule.sort = Gun.list.sort('when');
- schedule.set = function(future){
- var now = Gun.time.is();
- future = (future <= now)? 0 : (future - now);
- clearTimeout(schedule.id);
- schedule.id = setTimeout(schedule.check, future);
- }
- schedule.check = function(){
- var now = Gun.time.is(), soonest = Infinity;
- schedule.waiting.sort(schedule.sort);
- schedule.waiting = Gun.list.map(schedule.waiting, function(wait, i, map){
- if(!wait){ return }
- if(wait.when <= now){
- if(Gun.fns.is(wait.event)){
- wait.event();
- }
- } else {
- soonest = (soonest < wait.when)? soonest : wait.when;
- map(wait);
- }
- }) || [];
- schedule.set(soonest);
- }
- Gun.schedule = function(state, cb){
- schedule.waiting.push({when: state, event: cb});
- if(schedule.soonest < state){ return }
- schedule.set(state);
- }
- }({}));
- ;(function(Serializer){
- Gun.ify = function(data){ // TODO: BUG: Modify lists to include HAM state
- var gun = Gun.is(this)? this : {}
- , context = {
- nodes: {}
- ,seen: []
- ,_seen: []
- }, nothing;
- function ify(data, context, sub){
- sub = sub || {};
- sub.path = sub.path || '';
- context = context || {};
- context.nodes = context.nodes || {};
- if((sub.simple = Gun.ify.is(data)) && !(sub._ && Gun.text.is(sub.simple))){
- return data;
- } else
- if(Gun.obj.is(data)){
- var value = {}, symbol = {}, seen
- , err = {err: "Metadata does not support external or circular references at " + sub.path, meta: true};
- context.root = context.root || value;
- if(seen = ify.seen(context._seen, data)){
- //Gun.log("seen in _", sub._, sub.path, data);
- context.err = err;
- return;
- } else
- if(seen = ify.seen(context.seen, data)){
- //Gun.log("seen in data", sub._, sub.path, data);
- if(sub._){
- context.err = err;
- return;
- }
- symbol = Gun.ify.soul.call(gun, symbol, seen);
- return symbol;
- } else {
- //Gun.log("seen nowhere", sub._, sub.path, data);
- if(sub._){
- context.seen.push({data: data, node: value});
- } else {
- value._ = Gun.ify.soul.call(gun, {}, data);
- context.seen.push({data: data, node: value});
- context.nodes[value._[Gun._.soul]] = value;
- }
- }
- Gun.obj.map(data, function(val, field){
- var subs = {path: sub.path + field + '.', _: sub._ || (field == Gun._.meta)? true : false };
- val = ify(val, context, subs);
- //Gun.log('>>>>', sub.path + field, 'is', val);
- if(context.err){ return true }
- if(nothing === val){ return }
- // TODO: check field validity
- value[field] = val;
- });
- if(sub._){ return value }
- if(!value._ || !value._[Gun._.soul]){ return }
- symbol[Gun._.soul] = value._[Gun._.soul];
- return symbol;
- } else
- if(Gun.list.is(data)){
- var unique = {}, edges
- , err = {err: "Arrays cause data corruption at " + sub.path, array: true}
- edges = Gun.list.map(data, function(val, i, map){
- val = ify(val, context, sub);
- if(context.err){ return true }
- if(!Gun.obj.is(val)){
- context.err = err;
- return true;
- }
- return Gun.obj.map(val, function(soul, field){
- if(field !== Gun._.soul){
- context.err = err;
- return true;
- }
- if(unique[soul]){ return }
- unique[soul] = 1;
- map(val);
- });
- });
- if(context.err){ return }
- return edges;
- } else {
- context.err = {err: "Data type not supported at " + sub.path, invalid: true};
- }
- }
- ify.seen = function(seen, data){
- // unfortunately, using seen[data] = true will cause false-positives for data's children
- return Gun.list.map(seen, function(check){
- if(check.data === data){ return check.node }
- });
- }
- ify(data, context);
- return context;
- }
- Gun.ify.state = function(nodes, now){
- var context = {};
- context.nodes = nodes;
- context.now = now = (now === 0)? now : now || Gun.time.is();
- Gun.obj.map(context.nodes, function(node, soul){
- if(!node || !soul || !node._ || !node._[Gun._.soul] || node._[Gun._.soul] !== soul){
- return context.err = {err: "There is a corruption of nodes and or their souls", corrupt: true};
- }
- var states = node._[Gun._.HAM] = node._[Gun._.HAM] || {};
- Gun.obj.map(node, function(val, field){
- if(field == Gun._.meta){ return }
- val = states[field];
- states[field] = (val === 0)? val : val || now;
- });
- });
- return context;
- }
- Gun.ify.soul = function(to, from){
- var gun = this;
- to = to || {};
- if(Gun.ify.soul.is(from)){
- to[Gun._.soul] = from._[Gun._.soul];
- return to;
- }
- to[Gun._.soul] = Gun.roulette.call(gun);
- return to;
- }
- Gun.ify.soul.is = function(o){
- if(o && o._ && o._[Gun._.soul]){
- return true;
- }
- }
- Gun.ify.is = function(v){ // null, binary, number (!Infinity), text, or a rel.
- if(v === null){ return true } // deletes
- if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face.
- if(Gun.bi.is(v)
- || Gun.num.is(v)
- || Gun.text.is(v)){
- return true; // simple values
- }
- var yes;
- if(yes = Gun.ify.is.soul(v)){
- return yes;
- }
- return false;
- }
- Gun.ify.is.soul = function(v){
- if(Gun.obj.is(v)){
- var yes;
- Gun.obj.map(v, function(soul, field){
- if(yes){ return yes = false }
- if(field === Gun._.soul && Gun.text.is(soul)){
- yes = soul;
- }
- });
- if(yes){
- return yes;
- }
- }
- return false;
- }
- }());
- if(typeof window !== "undefined"){
- window.Gun = Gun;
- } else {
- module.exports = Gun;
- }
-}({}));
-
-;(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('opt').event(function(gun, opt){
- tab.server = tab.server || function(req, res, next){
-
- }
- window.tab = tab; // window.XMLHttpRequest = null; // for debugging purposes
- (function(){
- tab.store = {};
- var store = window.localStorage || {setItem: function(){}, removeItem: function(){}, getItem: function(){}};
- tab.store.set = function(key, val){console.log('setting', key); return store.setItem(key, Gun.text.ify(val)) }
- tab.store.get = function(key){ return Gun.obj.ify(store.getItem(key)) }
- tab.store.del = function(key){ return store.removeItem(key) }
- }());
- tab.load = tab.load || function(key, cb, opt){
- if(!key){ return }
- cb = cb || function(){};
- opt = opt || {};
- if(key[Gun._.soul]){
- key = '_' + tab.query(key);
- }
- Gun.obj.map(gun.__.opt.peers, function(peer, url){
- tab.ajax(url + '/' + key, null, function(err, reply){
- console.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'] || tab.subscribe.sub;
- //console.log("We are sub", tab.subscribe.sub);
- var data = reply.body;
- if(!data || !data._){ return }
- tab.subscribe(data._[Gun._.soul]);
- }());
- }, {headers: {'Gun-Sub': tab.subscribe.sub || ''}, header: {'Gun-Sub': 1}});
- });
- }
- tab.url = function(nodes){
- return;
- console.log("urlify delta", nodes);
- var s = ''
- , uri = encodeURIComponent;
- Gun.obj.map(nodes, function(delta, soul){
- var ham;
- if(!delta || !delta._ || !(ham = delta._[Gun._.HAM])){ return }
- s += uri('#') + '=' + uri(soul) + '&';
- Gun.obj.map(delta, function(val, field){
- if(field === Gun._.meta){ return }
- s += uri(field) + '=' + uri(Gun.text.ify(val)) + uri('>') + uri(ham[field]) + '&';
- })
- });
- console.log(s);
- return s;
- }
- tab.set = tab.set || function(nodes, cb){
- cb = cb || function(){};
- // TODO: batch and throttle later.
- //tab.store.set(cb.id = 'send/' + Gun.text.random(), nodes);
- //tab.url(nodes);
- Gun.obj.map(gun.__.opt.peers, function(peer, url){
- tab.ajax(url, nodes, function respond(err, reply, id){
- var body = reply && reply.body;
- respond.id = respond.id || cb.id;
- Gun.obj.del(tab.set.defer, id); // handle err with a retry? Or make a system auto-do it?
- if(!body){ return }
- if(body.defer){
- //console.log("deferring post", body.defer);
- tab.set.defer[body.defer] = respond;
- }
- if(body.reply){
- respond(null, {headers: reply.headers, body: body.reply });
- }
- if(body.refed){
- console.log("-------post-reply-all--------->", 1 || reply, err);
- Gun.obj.map(body.refed, function(r, id){
- var cb;
- if(cb = tab.set.defer[id]){
- cb(null, {headers: reply.headers, body: r}, id);
- }
- });
- // TODO: should be able to do some type of "checksum" that every request cleared, and if not, figure out what is wrong/wait for finish.
- return;
- }
- if(body.reply || body.defer || body.refed){ return }
- //tab.store.del(respond.id);
- }, {headers: {'Gun-Sub': tab.subscribe.sub || ''}});
- });
- Gun.obj.map(nodes, function(node, soul){
- gun.__.on(soul).emit(node, true); // should we emit difference between local and not?
- });
- }
- tab.set.defer = {};
- tab.subscribe = function(soul){ // TODO: BUG!!! ERROR! Handle disconnection (onerror)!!!!
- tab.subscribe.to = tab.subscribe.to || {};
- if(soul){
- tab.subscribe.to[soul] = 1;
- }
- var opt = {
- header: {'Gun-Sub': 1},
- headers: {
- 'Gun-Sub': tab.subscribe.sub || ''
- }
- }, query = tab.subscribe.sub? '' : tab.query(tab.subscribe.to);
- console.log("subscribing poll", tab.subscribe.sub);
- Gun.obj.map(gun.__.opt.peers, function(peer, url){
- tab.ajax(url + query, null, function(err, reply){
- if(err || !reply || !reply.body || reply.body.err){ // not interested in any null/0/''/undefined values
- //console.log(err, reply);
- return;
- }
- console.log("poll", 1 || reply);
- tab.subscribe.poll();
- if(reply.headers){
- tab.subscribe.sub = reply.headers['gun-sub'] || tab.subscribe.sub;
- }
- if(!reply.body){ return } // do anything?
- gun.union(reply.body); // safely transform data
- }, 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.query = function(params){
- var s = '?'
- , uri = encodeURIComponent;
- Gun.obj.map(params, function(val, field){
- s += uri(field) + '=' + uri(val || '') + '&';
- });
- return s;
- }
- tab.ajax = (function(){
- function ajax(url, data, cb, opt){
- var u;
- opt = opt || {};
- opt.header = opt.header || {};
- opt.header["Content-Type"] = 1;
- opt.headers = opt.headers || {};
- if(data === u || data === null){
- data = u;
- } else {
- try{data = JSON.stringify(data);
- opt.headers["Content-Type"] = "application/json";
- }catch(e){}
- }
- opt.method = opt.method || (data? 'POST' : 'GET');
- var xhr = ajax.xhr() || ajax.jsonp() // TODO: BUG: JSONP push is working, but not post
- , clean = function(){
- if(!xhr){ return }
- xhr.onreadystatechange = xhr.onerror = null;
- try{xhr.abort();
- }catch(e){}
- xhr = null;
- }
- xhr.onerror = function(){
- if(cb){
- cb({err: err || 'Unknown error.', status: xhr.status });
- }
- clean(xhr.status === 200 ? 'network' : 'permanent');
- };
- xhr.onreadystatechange = function(){
- if(!xhr){ return }
- var reply, status;
- try{reply = xhr.responseText;
- status = xhr.status;
- }catch(e){}
- if(status === 1223){ status = 204 }
- if(xhr.readyState === 3){
- if(reply && 0 < reply.length){
- opt.ondata(status, reply);
- }
- } else
- if(xhr.readyState === 4){
- opt.ondata(status, reply, true);
- clean(status === 200? 'network' : 'permanent');
- }
- };
- opt.ondata = opt.ondata || function(status, chunk, end){
- if(status !== 200){ return }
- try{ajax.each(opt.header, function(val, i){
- (xhr.responseHeader = xhr.responseHeader||{})[i.toLowerCase()] = xhr.getResponseHeader(i);
- });
- }catch(e){}
- var data, buf, pos = 1;
- while(pos || end){ // in order to end
- if(u !== data){ // we need at least one loop
- opt.onload({
- headers: xhr.responseHeader || {}
- ,body: data
- });
- end = false; // now both pos and end will be false
- }
- if(ajax.string(chunk)){
- buf = chunk.slice(xhr.index = xhr.index || 0);
- pos = buf.indexOf('\n') + 1;
- data = pos? buf.slice(0, pos - 1) : buf;
- xhr.index += pos;
- } else {
- data = chunk;
- pos = 0;
- }
- }
- }
- opt.onload = opt.onload || function(reply){
- if(!reply){ return }
- if( reply.headers && ("application/json" === reply.headers["content-type"])){
- var body;
- try{body = JSON.parse(reply.body);
- }catch(e){body = reply.body}
- reply.body = body;
- }
- if(cb){
- cb(null, reply);
- }
- }
- if(opt.cookies || opt.credentials || opt.withCredentials){
- xhr.withCredentials = true;
- }
- opt.headers["X-Requested-With"] = xhr.transport || "XMLHttpRequest";
- try{xhr.open(opt.method, url, true);
- }catch(e){ return xhr.onerror("Open failed.") }
- if(opt.headers){
- try{ajax.each(opt.headers, function(val, i){
- xhr.setRequestHeader(i, val);
- });
- }catch(e){ return xhr.onerror("Invalid headers.") }
- }
- try{xhr.send(data);
- }catch(e){ return xhr.onerror("Failed to send request.") }
- }
- ajax.xhr = function(xhr){
- return (window.XMLHttpRequest && "withCredentials" in (xhr = new XMLHttpRequest()))? xhr : null;
- }
- ajax.jsonp = function(xhr){
- xhr = {};
- xhr.transport = "jsonp";
- xhr.open = function(method, url){
- xhr.url = url;
- }
- xhr.send = function(){
- xhr.url += ((xhr.url.indexOf('?') + 1)? '&' : '?') + 'jsonp=' + xhr.js.id;
- ajax.each(xhr.headers, function(val, i){
- xhr.url += '&' + encodeURIComponent(i) + "=" + encodeURIComponent(val);
- });
- xhr.js.src = xhr.url = xhr.url.replace(/%20/g, "+");
- document.getElementsByTagName('head')[0].appendChild(xhr.js);
- }
- xhr.setRequestHeader = function(i, val){
- (xhr.headers = xhr.headers||{})[i] = val;
- }
- xhr.getResponseHeader = function(i){ return (xhr.responseHeaders||{})[i] }
- xhr.js = document.createElement('script');
- window[xhr.js.id = 'P'+Math.floor((Math.random()*65535)+1)] = function(reply){
- xhr.status = 200;
- if(reply.chunks && reply.chunks.length){
- xhr.readyState = 3
- while(0 < reply.chunks.length){
- xhr.responseText = reply.chunks.shift();
- xhr.onreadystatechange();
- }
- }
- xhr.responseHeaders = reply.headers || {};
- xhr.readyState = 4;
- xhr.responseText = reply.body;
- xhr.onreadystatechange();
- xhr.id = xhr.js.id;
- xhr.js.parentNode.removeChild(xhr.js);
- window[xhr.id] = null;
- try{delete window[xhr.id];
- }catch(e){}
-
- }
- xhr.abort = function(){} // clean up?
- xhr.js.async = true;
- return xhr;
- }
- ajax.string = function(s){ return (typeof s == 'string') }
- ajax.each = function(obj, cb){
- if(!obj || !cb){ return }
- for(var i in obj){
- if(obj.hasOwnProperty(i)){
- cb(obj[i], i);
- }
- }
- }
- return ajax;
- }());
- gun.__.opt.hooks.load = gun.__.opt.hooks.load || tab.load;
- gun.__.opt.hooks.set = gun.__.opt.hooks.set || tab.set;
- });
+;(function(){
+ function Gun(opt){
+ var gun = this;
+ if(!Gun.is(gun)){
+ return new Gun(opt);
+ }
+ gun.opt(opt);
+ }
+ Gun._ = {
+ soul: '#'
+ ,meta: '_'
+ ,HAM: '>'
+ }
+ ;(function(Gun){
+ Gun.is = function(gun){ return (gun instanceof Gun)? true : false }
+ Gun.version = 0.8;
+ Gun.union = function(graph, prime){
+ var context = Gun.shot();
+ context.nodes = {};
+ context('done');context('change');
+ Gun.obj.map(prime, function(node, soul){
+ var vertex = graph[soul], env;
+ if(!vertex){ // disjoint
+ context.nodes[node._[Gun._.soul]] = graph[node._[Gun._.soul]] = node;
+ context('change').fire(node);
+ return;
+ }
+ env = Gun.HAM(vertex, node, function(current, field, deltaValue){
+ if(!current){ return }
+ var change = {};
+ current[field] = change[field] = deltaValue; // current and vertex are the same
+ current._[Gun._.HAM][field] = node._[Gun._.HAM][field];
+ change._ = current._;
+ context.nodes[change._[Gun._.soul]] = change;
+ context('change').fire(change);
+ }).upper(function(c){
+ context.err = c.err;
+ context.up -= 1;
+ if(!context.up){
+ context('done').fire(context.err, context);
+ }
+ });
+ context.up += env.up;
+ });
+ if(!context.up){
+ context('done').fire(context.err, context);
+ }
+ return context;
+ }
+ Gun.HAM = function(current, delta, each){ // HAM only handles primitives values, all other data structures need to be built ontop and reduce to HAM.
+ 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};
+ }
+ if(incomingState < currentState){
+ // the incoming value is within the boundary of the machine's state, but not within the range.
+ return {quarantineState: true};
+ }
+ if(currentState < incomingState){
+ // the incoming value is within both the boundary and the range of the machine's state.
+ return {converge: true, incoming: true};
+ }
+ if(incomingState === currentState){
+ if(incomingValue === currentValue){ // Note: while these are practically the same, the deltas could be technically different
+ return {state: true};
+ }
+ /*
+ The following is a naive implementation, but will always work.
+ Never change it unless you have specific needs that absolutely require it.
+ If changed, your data will diverge unless you guarantee every peer's algorithm has also been changed to be the same.
+ As a result, it is highly discouraged to modify despite the fact that it is naive,
+ because convergence (data integrity) is generally more important.
+ Any difference in this algorithm must be given a new and different name.
+ */
+ if(String(incomingValue) < String(currentValue)){ // String only works on primitive values!
+ return {converge: true, current: true};
+ }
+ if(String(currentValue) < String(incomingValue)){ // String only works on primitive values!
+ return {converge: true, incoming: true};
+ }
+ }
+ return {err: "you have not properly handled recursion through your data or filtered it as JSON"};
+ }
+ var context = Gun.shot();
+ context.HAM = {};
+ context.states = {};
+ context.states.delta = delta._[Gun._.HAM];
+ context.states.current = current._[Gun._.HAM] = current._[Gun._.HAM] || {};
+ context('lower');context('upper');context.up = context.up || 0;
+ Gun.obj.map(delta, function update(deltaValue, field){
+ if(field === Gun._.meta){ return }
+ if(!Gun.obj.has(current, field)){ // does not need to be applied through HAM
+ each.call({incoming: true, converge: true}, current, field, deltaValue);
+ return;
+ }
+ var serverState = Gun.time.is();
+ // add more checks?
+ var state = HAM(serverState, context.states.delta[field], context.states.current[field], deltaValue, current[field]);
+ //console.log("HAM:", field, deltaValue, context.states.delta[field], context.states.current[field], 'the', state, (context.states.delta[field] - serverState));
+ if(state.err){
+ Gun.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", state.err);
+ return;
+ }
+ if(state.state || state.quarantineState || state.current){
+ context('lower').fire(context, state, current, field, deltaValue);
+ return;
+ }
+ if(state.incoming){
+ each.call(state, current, field, deltaValue);
+ return;
+ }
+ if(state.amnesiaQuarantine){
+ context.up += 1;
+ Gun.schedule(context.states.delta[field], function(){
+ update(deltaValue, field);
+ context.up -= 1;
+ context('upper').fire(context, state, current, field, deltaValue);
+ });
+ }
+ });
+ if(!context.up){
+ context('upper').fire(context, {});
+ }
+ return context;
+ }
+ Gun.roulette = function(l, c){
+ var gun = Gun.is(this)? this : {};
+ if(gun._ && gun.__.opt && gun.__.opt.uuid){
+ if(Gun.fns.is(gun.__.opt.uuid)){
+ return gun.__.opt.uuid(l, c);
+ }
+ l = l || gun.__.opt.uuid.length;
+ }
+ return Gun.text.random(l, c);
+ }
+ Gun.log = function(a, b, c, d, e, f){
+ //console.log(a, b, c, d, e, f);
+ //console.log.apply(console, arguments);
+ }
+ }(Gun));
+ ;(function(Chain){
+ Chain.opt = function(opt, stun){ // idempotently update or set options
+ var gun = this;
+ gun._ = gun._ || {};
+ gun.__ = gun.__ || {};
+ gun.shot = Gun.shot();
+ gun.shot('then');
+ gun.shot('err');
+ if(!opt){ return gun }
+ gun.__.opt = gun.__.opt || {};
+ gun.__.keys = gun.__.keys || {};
+ gun.__.graph = gun.__.graph || {};
+ gun.__.on = gun.__.on || Gun.on.create();
+ if(Gun.text.is(opt)){ opt = {peers: opt} }
+ if(Gun.list.is(opt)){ opt = {peers: opt} }
+ if(Gun.text.is(opt.peers)){ opt.peers = [opt.peers] }
+ if(Gun.list.is(opt.peers)){ opt.peers = Gun.obj.map(opt.peers, function(n,f,m){ m(n,{}) }) }
+ gun.__.opt.peers = opt.peers || gun.__.opt.peers || {};
+ gun.__.opt.uuid = opt.uuid || gun.__.opt.uuid || {};
+ gun.__.opt.hooks = gun.__.opt.hooks || {};
+ Gun.obj.map(opt.hooks, function(h, f){
+ if(!Gun.fns.is(h)){ return }
+ gun.__.opt.hooks[f] = h;
+ });
+ if(!stun){ Gun.on('opt').emit(gun, opt) }
+ return gun;
+ }
+ Chain.chain = function(from){
+ var gun = Gun();
+ from = from || this;
+ gun.back = from;
+ gun.__ = from.__;
+ gun._ = {};
+ Gun.obj.map(from._, function(val, field){
+ gun._[field] = val;
+ });
+ return gun;
+ }
+ Chain.load = function(key, cb, opt){
+ var gun = this.chain();
+ cb = cb || function(){};
+ gun.shot.then(function(){ cb.apply(gun, arguments) });
+ cb.soul = (key||{})[Gun._.soul];
+ if(cb.soul){
+ cb.node = gun.__.graph[cb.soul];
+ } else {
+ gun._.key = key;
+ cb.node = gun.__.keys[key];
+ }
+ if(cb.node){ // set this to the current node, too!
+ Gun.log("from gun"); // remember to do all the same stack stuff here also!
+ var freeze = Gun.obj.copy(gun._.node = cb.node);
+ gun.shot('then').fire(freeze); // freeze now even though internals use this? OK for now.
+ return gun; // TODO: BUG: This needs to react the same as below!
+ }
+ cb.fn = function(){}
+ // missing: hear shots!
+ if(Gun.fns.is(gun.__.opt.hooks.load)){
+ gun.__.opt.hooks.load(key, function(err, data){
+ gun._.loaded = (gun._.loaded || 0) + 1; // TODO: loading should be idempotent even if we got an err or no data
+ if(err){ return (gun._.dud||cb.fn)(err) }
+ if(!data){ return (gun._.blank||cb.fn)() }
+ var context = gun.union(data); // safely transform the data
+ if(context.err){ return (gun._.dud||cb.fn)(context.err) }
+ gun._.node = gun.__.graph[data._[Gun._.soul]]; // don't wait for the union to be done because we want the immediate state not the intended state.
+ if(!cb.soul){ gun.__.keys[key] = gun._.node }
+ var freeze = Gun.obj.copy(gun._.node);
+ gun.shot('then').fire(freeze); // freeze now even though internals use this? OK for now.
+ }, opt);
+ } else {
+ Gun.log("Warning! You have no persistence layer to load from!");
+ }
+ return gun;
+ }
+ Chain.key = function(key, cb){
+ var gun = this;
+ gun.shot.then(function(){
+ Gun.log("make key", key);
+ cb = cb || function(){};
+ cb.node = gun.__.keys[key] = gun._.node;
+ if(!cb.node){ return gun }
+ if(Gun.fns.is(gun.__.opt.hooks.key)){
+ gun.__.opt.hooks.key(key, cb.node._[Gun._.soul], function(err, data){
+ Gun.log("key made", key);
+ if(err){ return cb(err) }
+ return cb(null);
+ });
+ } else {
+ Gun.log("Warning! You have no key hook!");
+ }
+ });
+ if(!gun.back){ gun.shot('then').fire() }
+ return gun;
+ }
+ /*
+ how many different ways can we get something?
+ Find via a singular path
+ .path('blah').get(blah);
+ Find via multiple paths with the callback getting called many times
+ .path('foo', 'bar').get(foorOrBar);
+ Find via multiple paths with the callback getting called once with matching arguments
+ .path('foo', 'bar').get(foo, bar)
+ Find via multiple paths with the result aggregated into an object of pre-given fields
+ .path('foo', 'bar').get({foo: foo, bar: bar}) || .path({a: 'foo', b: 'bar'}).get({a: foo, b: bar})
+ Find via multiple paths where the fields and values must match
+ .path({foo: val, bar: val}).get({})
+ */
+ Chain.path = function(path){ // The focal point follows the path
+ var gun = this.chain();
+ path = (path || '').split('.');
+ gun.back.shot.then(function trace(node){ // should handle blank and err! Err already handled?
+ //console.log("shot path", path, node);
+ gun.field = null;
+ gun._.node = node;
+ if(!path.length){ // if the path resolves to another node, we finish here
+ return gun.shot('then').fire(node); // already frozen from loaded.
+ }
+ var field = path.shift()
+ , val = node[field];
+ gun.field = field;
+ if(Gun.ify.is.soul(val)){ // we might end on a link, so we must resolve
+ return gun.load(val).shot.then(trace);
+ } else
+ if(path.length){ // we cannot go any further, despite the fact there is more path, which means the thing we wanted does not exist
+ gun.shot('then').fire();
+ } else { // we are done, and this should be the value we wanted.
+ gun.shot('then').fire(val); // primitive values are passed as copies in JS.
+ }
+ });
+ // if(!gun.back){ gun.shot('then').fire() } // replace below with this? maybe???
+ if(gun.back && gun.back._ && gun.back._.loaded){
+ gun._.node = gun.back._.node;
+ gun.back.shot('then').fire(gun.back._.node);
+ }
+ return gun;
+ }
+ Chain.get = function(cb){
+ var gun = this;
+ gun.shot.then(function(val){
+ cb.call(gun, val); // frozen from done.
+ gun.__.on(gun._.node._[Gun._.soul]).event(function(delta){
+ if(!delta){ return }
+ if(!gun.field){
+ cb.call(gun, Gun.obj.copy(gun._.node));
+ return;
+ }
+ if(Gun.obj.has(delta, gun.field)){
+ cb.call(gun, delta[gun.field]);
+ }
+ })
+ });
+ return gun;
+ }
+ /*
+ ACID compliant, unfortunately the vocabulary is vague, as such the following is an explicit definition:
+ A - Atomic, if you set a full node, or nodes of nodes, if any value is in error then nothing will be set.
+ If you want sets to be independent of each other, you need to set each piece of the data individually.
+ C - Consistency, if you use any reserved symbols or similar, the operation will be rejected as it could lead to an invalid read and thus an invalid state.
+ I - Isolation, the conflict resolution algorithm guarantees idempotent transactions, across every peer, regardless of any partition,
+ including a peer acting by itself or one having been disconnected from the network.
+ D - Durability, if the acknowledgement receipt is received, then the state at which the final persistence hook was called on is guaranteed to have been written.
+ The live state at point of confirmation may or may not be different than when it was called.
+ If this causes any application-level concern, it can compare against the live data by immediately reading it, or accessing the logs if enabled.
+ */
+ Chain.set = function(val, cb, opt){ // TODO: need to turn deserializer into a trampolining function so stackoverflow doesn't happen.
+ opt = opt || {};
+ var gun = this, set;
+ gun.shot.then(function(){
+ if(gun.field){ // a field cannot be 0!
+ set = {}; // in case we are doing a set on a field, not on a node
+ set[gun.field] = val; // we create a blank node with the field/value to be set
+ val = set;
+ } // TODO: should be able to handle val being a relation or a gun context or a gun promise.
+ val._ = Gun.ify.soul.call(gun, {}, gun._.node); // and then set their souls to be the same that way they will merge correctly for us during the union!
+ cb = Gun.fns.is(cb)? cb : function(){};
+ set = Gun.ify.call(gun, val);
+ cb.root = set.root;
+ if(set.err){ return cb(set.err), gun }
+ set = Gun.ify.state(set.nodes, Gun.time.is()); // set time state on nodes?
+ if(set.err){ return cb(set.err), gun }
+ Gun.union(gun.__.graph, set.nodes); // while this maybe should return a list of the nodes that were changed, we want to send the actual delta
+ gun._.node = gun.__.graph[cb.root._[Gun._.soul]] || cb.root;
+ // TODO? ^ Maybe BUG! if val is a new node on a field, _.node should now be that! Or will that happen automatically?
+ if(Gun.fns.is(gun.__.opt.hooks.set)){
+ gun.__.opt.hooks.set(set.nodes, function(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved
+ //Gun.log("gun set hook callback called");
+ if(err){ return cb(err) }
+ return cb(null);
+ });
+ } else {
+ Gun.log("Warning! You have no persistence layer to save to!");
+ }
+ });
+ if(!gun.back){ gun.shot('then').fire() }
+ return gun;
+ }
+ Chain.union = function(prime, cb){
+ var tmp, gun = this, context = Gun.shot();
+ context.nodes = {};
+ cb = cb || function(){}
+ if(!prime){
+ context.err = {err: "No data to merge!"};
+ } else
+ if(prime._ && prime._[Gun._.soul]){
+ tmp = {};
+ tmp[prime._[Gun._.soul]] = prime;
+ prime = tmp;
+ }
+ if(!gun || context.err){
+ cb(context.err = context.err || {err: "No gun instance!", corrupt: true}, context);
+ return context;
+ }
+ Gun.obj.map(prime, function(node){ // map over the prime graph, to get each node that has been modified
+ var set = Gun.ify.call(gun, node);
+ if(set.err){ return context.err = set.err } // check to see if the node is valid
+ Gun.obj.map(set.nodes, function(node, soul){ // if so, map over it, and any other nodes that were deserialized from it
+ context.nodes[soul] = node; // into a valid context we'll actually do a union on.
+ });
+ });
+ if(context.err){ return cb(context.err, context), context } // if any errors happened in the previous steps, then fail.
+ Gun.union(gun.__.graph, context.nodes).done(function(err, env){ // now merge prime into the graph
+ context.err = err || env.err;
+ cb(context.err, context || {});
+ }).change(function(delta){
+ if(!delta || !delta._ || !delta._[Gun._.soul]){ return }
+ gun.__.on(delta._[Gun._.soul]).emit(Gun.obj.copy(delta)); // this is in reaction to HAM
+ });
+ return context;
+ }
+ Chain.match = function(){ // same as path, except using objects
+ return this;
+ }
+ Chain.blank = function(blank){
+ this._.blank = Gun.fns.is(blank)? blank : function(){};
+ return this;
+ }
+ Chain.dud = function(dud){
+ this._.dud = Gun.fns.is(dud)? dud : function(){};
+ return this;
+ }
+ }(Gun.chain = Gun.prototype));
+ ;(function(Util){
+ Util.fns = {};
+ Util.fns.is = function(fn){ return (fn instanceof Function)? true : false }
+ Util.bi = {};
+ Util.bi.is = function(b){ return (b instanceof Boolean || typeof b == 'boolean')? true : false }
+ Util.num = {};
+ Util.num.is = function(n){
+ return ((n===0)? true : (!isNaN(n) && !Util.bi.is(n) && !Util.list.is(n) && !Util.text.is(n))? true : false );
+ }
+ Util.text = {};
+ Util.text.is = function(t){ return typeof t == 'string'? true : false }
+ Util.text.ify = function(t){
+ if(Util.text.is(t)){ return t }
+ if(JSON){ return JSON.stringify(t) }
+ return (t && t.toString)? t.toString() : t;
+ }
+ Util.text.random = function(l, c){
+ var s = '';
+ l = l || 24; // you are not going to make a 0 length random number, so no need to check type
+ c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghiklmnopqrstuvwxyz';
+ while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- }
+ return s;
+ }
+ Util.list = {};
+ Util.list.is = function(l){ return (l instanceof Array)? true : false }
+ Util.list.slit = Array.prototype.slice;
+ Util.list.sort = function(k){ // creates a new sort function based off some field
+ return function(A,B){
+ if(!A || !B){ return 0 } A = A[k]; B = B[k];
+ if(A < B){ return -1 }else if(A > B){ return 1 }
+ else { return 0 }
+ }
+ }
+ Util.list.map = function(l, c, _){ return Util.obj.map(l, c, _) }
+ Util.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation
+ Util.obj = {};
+ Util.obj.is = function(o){ return (o instanceof Object && !Util.list.is(o) && !Util.fns.is(o))? true : false }
+ Util.obj.del = function(o, k){
+ if(!o){ return }
+ o[k] = null;
+ delete o[k];
+ return true;
+ }
+ Util.obj.ify = function(o){
+ if(Util.obj.is(o)){ return o }
+ try{o = JSON.parse(o);
+ }catch(e){o={}};
+ return o;
+ }
+ Util.obj.copy = function(o){ // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2
+ return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways!
+ }
+ Util.obj.has = function(o, t){ return Object.prototype.hasOwnProperty.call(o, t) }
+ Util.obj.map = function(l, c, _){
+ var u, i = 0, ii = 0, x, r, rr, f = Util.fns.is(c),
+ t = function(k,v){
+ if(v !== u){
+ rr = rr || {};
+ rr[k] = v;
+ return;
+ } rr = rr || [];
+ rr.push(k);
+ };
+ if(Util.list.is(l)){
+ x = l.length;
+ for(;i < x; i++){
+ ii = (i + Util.list.index);
+ if(f){
+ r = _? c.call(_, l[i], ii, t) : c(l[i], ii, t);
+ if(r !== u){ return r }
+ } else {
+ //if(Util.test.is(c,l[i])){ return ii } // should implement deep equality testing!
+ if(c === l[i]){ return ii } // use this for now
+ }
+ }
+ } else {
+ for(i in l){
+ if(f){
+ if(Util.obj.has(l,i)){
+ r = _? c.call(_, l[i], i, t) : c(l[i], i, t);
+ if(r !== u){ return r }
+ }
+ } else {
+ //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing!
+ if(c === l[i]){ return i }
+ }
+ }
+ }
+ return f? rr : Util.list.index? 0 : -1;
+ }
+ Util.time = {};
+ Util.time.is = function(t){ return t? t instanceof Date : (+new Date().getTime()) }
+ }(Gun));
+ ;Gun.shot=(function(){
+ // I hate the idea of using setTimeouts in my code to do callbacks (promises and sorts)
+ // as there is no way to guarantee any type of state integrity or the completion of callback.
+ // However, I have fallen. HAM is suppose to assure side effect free safety of unknown states.
+ var setImmediate = setImmediate || function(cb){setTimeout(cb,0)}
+ function Flow(){
+ var chain = new Flow.chain();
+ return chain.$ = function(where){
+ (chain._ = chain._ || {})[where] = chain._[where] || [];
+ chain.$[where] = chain.$[where] || function(fn){
+ (chain._[where]||[]).push(fn);
+ return chain.$;
+ }
+ chain.where = where;
+ return chain;
+ }
+ }
+ Flow.is = function(flow){ return (Flow instanceof flow)? true : false }
+ ;Flow.chain=(function(){
+ function Chain(){
+ if(!(this instanceof Chain)){
+ return new Chain();
+ }
+ }
+ Chain.chain = Chain.prototype;
+ Chain.chain.pipe = function(a,s,d,f){
+ var me = this
+ , where = me.where
+ , args = Array.prototype.slice.call(arguments);
+ setImmediate(function(){
+ if(!me || !me._ || !me._[where]){ return }
+ while(0 < me._[where].length){
+ (me._[where].shift()||function(){}).apply(me, args);
+ }
+ // do a done? That would be nice. :)
+ });
+ return me;
+ }
+ return Chain;
+ }());
+ return Flow;
+ }());Gun.shot.chain.chain.fire=Gun.shot.chain.chain.pipe;
+ ;Gun.on=(function(){
+ function On(where){
+ if(where){
+ return (On.event = On.event || On.create())(where);
+ }
+ return On.create();
+ }
+ On.is = function(on){ return (On instanceof on)? true : false }
+ On.create = function(){
+ var chain = new On.chain();
+ return chain.$ = function(where){
+ chain.where = where;
+ return chain;
+ }
+ }
+ On.sort = Gun.list.sort('i');
+ ;On.chain=(function(){
+ function Chain(){
+ if(!(this instanceof Chain)){
+ return new Chain();
+ }
+ }
+ Chain.chain = Chain.prototype;
+ Chain.chain.emit = function(what){
+ var me = this
+ , where = me.where
+ , args = arguments
+ , on = (me._ = me._ || {})[where] = me._[where] || [];
+ if(!(me._[where] = Gun.list.map(on, function(hear, i, map){
+ if(!hear || !hear.as){ return }
+ map(hear);
+ hear.as.apply(hear, args);
+ }))){ Gun.obj.del(on, where) }
+ }
+ Chain.chain.event = function(as, i){
+ if(!as){ return }
+ var me = this
+ , where = me.where
+ , args = arguments
+ , on = (me._ = me._ || {})[where] = me._[where] || []
+ , e = {as: as, i: i || 0, off: function(){ return !(e.as = false) }};
+ return on.push(e), on.sort(On.sort), e;
+ }
+ Chain.chain.once = function(as, i){
+ var me = this
+ , once = function(){
+ this.off();
+ as.apply(this, arguments)
+ }
+ return me.event(once, i)
+ }
+ return Chain;
+ }());
+ return On;
+ }());
+ ;(function(schedule){ // maybe use lru-cache
+ schedule.waiting = [];
+ schedule.soonest = Infinity;
+ schedule.sort = Gun.list.sort('when');
+ schedule.set = function(future){
+ var now = Gun.time.is();
+ future = (future <= now)? 0 : (future - now);
+ clearTimeout(schedule.id);
+ schedule.id = setTimeout(schedule.check, future);
+ }
+ schedule.check = function(){
+ var now = Gun.time.is(), soonest = Infinity;
+ schedule.waiting.sort(schedule.sort);
+ schedule.waiting = Gun.list.map(schedule.waiting, function(wait, i, map){
+ if(!wait){ return }
+ if(wait.when <= now){
+ if(Gun.fns.is(wait.event)){
+ wait.event();
+ }
+ } else {
+ soonest = (soonest < wait.when)? soonest : wait.when;
+ map(wait);
+ }
+ }) || [];
+ schedule.set(soonest);
+ }
+ Gun.schedule = function(state, cb){
+ schedule.waiting.push({when: state, event: cb});
+ if(schedule.soonest < state){ return }
+ schedule.set(state);
+ }
+ }({}));
+ ;(function(Serializer){
+ Gun.ify = function(data){ // TODO: BUG: Modify lists to include HAM state
+ var gun = Gun.is(this)? this : {}
+ , context = {
+ nodes: {}
+ ,seen: []
+ ,_seen: []
+ }, nothing;
+ function ify(data, context, sub){
+ sub = sub || {};
+ sub.path = sub.path || '';
+ context = context || {};
+ context.nodes = context.nodes || {};
+ if((sub.simple = Gun.ify.is(data)) && !(sub._ && Gun.text.is(sub.simple))){
+ return data;
+ } else
+ if(Gun.obj.is(data)){
+ var value = {}, symbol = {}, seen
+ , err = {err: "Metadata does not support external or circular references at " + sub.path, meta: true};
+ context.root = context.root || value;
+ if(seen = ify.seen(context._seen, data)){
+ //Gun.log("seen in _", sub._, sub.path, data);
+ context.err = err;
+ return;
+ } else
+ if(seen = ify.seen(context.seen, data)){
+ //Gun.log("seen in data", sub._, sub.path, data);
+ if(sub._){
+ context.err = err;
+ return;
+ }
+ symbol = Gun.ify.soul.call(gun, symbol, seen);
+ return symbol;
+ } else {
+ //Gun.log("seen nowhere", sub._, sub.path, data);
+ if(sub._){
+ context.seen.push({data: data, node: value});
+ } else {
+ value._ = Gun.ify.soul.call(gun, {}, data);
+ context.seen.push({data: data, node: value});
+ context.nodes[value._[Gun._.soul]] = value;
+ }
+ }
+ Gun.obj.map(data, function(val, field){
+ var subs = {path: sub.path + field + '.', _: sub._ || (field == Gun._.meta)? true : false };
+ val = ify(val, context, subs);
+ //Gun.log('>>>>', sub.path + field, 'is', val);
+ if(context.err){ return true }
+ if(nothing === val){ return }
+ // TODO: check field validity
+ value[field] = val;
+ });
+ if(sub._){ return value }
+ if(!value._ || !value._[Gun._.soul]){ return }
+ symbol[Gun._.soul] = value._[Gun._.soul];
+ return symbol;
+ } else
+ if(Gun.list.is(data)){
+ var unique = {}, edges
+ , err = {err: "Arrays cause data corruption at " + sub.path, array: true}
+ edges = Gun.list.map(data, function(val, i, map){
+ val = ify(val, context, sub);
+ if(context.err){ return true }
+ if(!Gun.obj.is(val)){
+ context.err = err;
+ return true;
+ }
+ return Gun.obj.map(val, function(soul, field){
+ if(field !== Gun._.soul){
+ context.err = err;
+ return true;
+ }
+ if(unique[soul]){ return }
+ unique[soul] = 1;
+ map(val);
+ });
+ });
+ if(context.err){ return }
+ return edges;
+ } else {
+ context.err = {err: "Data type not supported at " + sub.path, invalid: true};
+ }
+ }
+ ify.seen = function(seen, data){
+ // unfortunately, using seen[data] = true will cause false-positives for data's children
+ return Gun.list.map(seen, function(check){
+ if(check.data === data){ return check.node }
+ });
+ }
+ ify(data, context);
+ return context;
+ }
+ Gun.ify.state = function(nodes, now){
+ var context = {};
+ context.nodes = nodes;
+ context.now = now = (now === 0)? now : now || Gun.time.is();
+ Gun.obj.map(context.nodes, function(node, soul){
+ if(!node || !soul || !node._ || !node._[Gun._.soul] || node._[Gun._.soul] !== soul){
+ return context.err = {err: "There is a corruption of nodes and or their souls", corrupt: true};
+ }
+ var states = node._[Gun._.HAM] = node._[Gun._.HAM] || {};
+ Gun.obj.map(node, function(val, field){
+ if(field == Gun._.meta){ return }
+ val = states[field];
+ states[field] = (val === 0)? val : val || now;
+ });
+ });
+ return context;
+ }
+ Gun.ify.soul = function(to, from){
+ var gun = this;
+ to = to || {};
+ if(Gun.ify.soul.is(from)){
+ to[Gun._.soul] = from._[Gun._.soul];
+ return to;
+ }
+ to[Gun._.soul] = Gun.roulette.call(gun);
+ return to;
+ }
+ Gun.ify.soul.is = function(o){
+ if(o && o._ && o._[Gun._.soul]){
+ return true;
+ }
+ }
+ Gun.ify.is = function(v){ // null, binary, number (!Infinity), text, or a rel.
+ if(v === null){ return true } // deletes
+ if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face.
+ if(Gun.bi.is(v)
+ || Gun.num.is(v)
+ || Gun.text.is(v)){
+ return true; // simple values
+ }
+ var yes;
+ if(yes = Gun.ify.is.soul(v)){
+ return yes;
+ }
+ return false;
+ }
+ Gun.ify.is.soul = function(v){
+ if(Gun.obj.is(v)){
+ var yes;
+ Gun.obj.map(v, function(soul, field){
+ if(yes){ return yes = false }
+ if(field === Gun._.soul && Gun.text.is(soul)){
+ yes = soul;
+ }
+ });
+ if(yes){
+ return yes;
+ }
+ }
+ return false;
+ }
+ }());
+ if(typeof window !== "undefined"){
+ window.Gun = Gun;
+ } else {
+ module.exports = Gun;
+ }
+}({}));
+
+;(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('opt').event(function(gun, opt){
+ tab.server = tab.server || function(req, res, next){
+
+ }
+ window.tab = tab; // window.XMLHttpRequest = null; // for debugging purposes
+ (function(){
+ tab.store = {};
+ var store = window.localStorage || {setItem: function(){}, removeItem: function(){}, getItem: function(){}};
+ tab.store.set = function(key, val){console.log('setting', key); return store.setItem(key, Gun.text.ify(val)) }
+ tab.store.get = function(key){ return Gun.obj.ify(store.getItem(key)) }
+ tab.store.del = function(key){ return store.removeItem(key) }
+ }());
+ tab.load = tab.load || function(key, cb, opt){
+ if(!key){ return }
+ cb = cb || function(){};
+ opt = opt || {};
+ if(key[Gun._.soul]){
+ key = '_' + tab.query(key);
+ }
+ Gun.obj.map(gun.__.opt.peers, function(peer, url){
+ tab.ajax(url + '/' + key, null, function(err, reply){
+ console.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'] || tab.subscribe.sub;
+ //console.log("We are sub", tab.subscribe.sub);
+ var data = reply.body;
+ if(!data || !data._){ return }
+ tab.subscribe(data._[Gun._.soul]);
+ }());
+ }, {headers: {'Gun-Sub': tab.subscribe.sub || ''}, header: {'Gun-Sub': 1}});
+ });
+ }
+ tab.url = function(nodes){
+ return;
+ console.log("urlify delta", nodes);
+ var s = ''
+ , uri = encodeURIComponent;
+ Gun.obj.map(nodes, function(delta, soul){
+ var ham;
+ if(!delta || !delta._ || !(ham = delta._[Gun._.HAM])){ return }
+ s += uri('#') + '=' + uri(soul) + '&';
+ Gun.obj.map(delta, function(val, field){
+ if(field === Gun._.meta){ return }
+ s += uri(field) + '=' + uri(Gun.text.ify(val)) + uri('>') + uri(ham[field]) + '&';
+ })
+ });
+ console.log(s);
+ return s;
+ }
+ tab.set = tab.set || function(nodes, cb){
+ cb = cb || function(){};
+ // TODO: batch and throttle later.
+ //tab.store.set(cb.id = 'send/' + Gun.text.random(), nodes);
+ //tab.url(nodes);
+ Gun.obj.map(gun.__.opt.peers, function(peer, url){
+ tab.ajax(url, nodes, function respond(err, reply, id){
+ var body = reply && reply.body;
+ respond.id = respond.id || cb.id;
+ Gun.obj.del(tab.set.defer, id); // handle err with a retry? Or make a system auto-do it?
+ if(!body){ return }
+ if(body.defer){
+ //console.log("deferring post", body.defer);
+ tab.set.defer[body.defer] = respond;
+ }
+ if(body.reply){
+ respond(null, {headers: reply.headers, body: body.reply });
+ }
+ if(body.refed){
+ console.log("-------post-reply-all--------->", 1 || reply, err);
+ Gun.obj.map(body.refed, function(r, id){
+ var cb;
+ if(cb = tab.set.defer[id]){
+ cb(null, {headers: reply.headers, body: r}, id);
+ }
+ });
+ // TODO: should be able to do some type of "checksum" that every request cleared, and if not, figure out what is wrong/wait for finish.
+ return;
+ }
+ if(body.reply || body.defer || body.refed){ return }
+ //tab.store.del(respond.id);
+ }, {headers: {'Gun-Sub': tab.subscribe.sub || ''}});
+ });
+ Gun.obj.map(nodes, function(node, soul){
+ gun.__.on(soul).emit(node, true); // should we emit difference between local and not?
+ });
+ }
+ tab.set.defer = {};
+ tab.subscribe = function(soul){ // TODO: BUG!!! ERROR! Handle disconnection (onerror)!!!!
+ tab.subscribe.to = tab.subscribe.to || {};
+ if(soul){
+ tab.subscribe.to[soul] = 1;
+ }
+ var opt = {
+ header: {'Gun-Sub': 1},
+ headers: {
+ 'Gun-Sub': tab.subscribe.sub || ''
+ }
+ }, query = tab.subscribe.sub? '' : tab.query(tab.subscribe.to);
+ console.log("subscribing poll", tab.subscribe.sub);
+ Gun.obj.map(gun.__.opt.peers, function(peer, url){
+ tab.ajax(url + query, null, function(err, reply){
+ if(err || !reply || !reply.body || reply.body.err){ // not interested in any null/0/''/undefined values
+ //console.log(err, reply);
+ return;
+ }
+ console.log("poll", 1 || reply);
+ tab.subscribe.poll();
+ if(reply.headers){
+ tab.subscribe.sub = reply.headers['gun-sub'] || tab.subscribe.sub;
+ }
+ if(!reply.body){ return } // do anything?
+ gun.union(reply.body); // safely transform data
+ }, 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.query = function(params){
+ var s = '?'
+ , uri = encodeURIComponent;
+ Gun.obj.map(params, function(val, field){
+ s += uri(field) + '=' + uri(val || '') + '&';
+ });
+ return s;
+ }
+ tab.ajax = (function(){
+ function ajax(url, data, cb, opt){
+ var u;
+ opt = opt || {};
+ opt.header = opt.header || {};
+ opt.header["Content-Type"] = 1;
+ opt.headers = opt.headers || {};
+ if(data === u || data === null){
+ data = u;
+ } else {
+ try{data = JSON.stringify(data);
+ opt.headers["Content-Type"] = "application/json";
+ }catch(e){}
+ }
+ opt.method = opt.method || (data? 'POST' : 'GET');
+ var xhr = ajax.xhr() || ajax.jsonp() // TODO: BUG: JSONP push is working, but not post
+ , clean = function(){
+ if(!xhr){ return }
+ xhr.onreadystatechange = xhr.onerror = null;
+ try{xhr.abort();
+ }catch(e){}
+ xhr = null;
+ }
+ xhr.onerror = function(){
+ if(cb){
+ cb({err: err || 'Unknown error.', status: xhr.status });
+ }
+ clean(xhr.status === 200 ? 'network' : 'permanent');
+ };
+ xhr.onreadystatechange = function(){
+ if(!xhr){ return }
+ var reply, status;
+ try{reply = xhr.responseText;
+ status = xhr.status;
+ }catch(e){}
+ if(status === 1223){ status = 204 }
+ if(xhr.readyState === 3){
+ if(reply && 0 < reply.length){
+ opt.ondata(status, reply);
+ }
+ } else
+ if(xhr.readyState === 4){
+ opt.ondata(status, reply, true);
+ clean(status === 200? 'network' : 'permanent');
+ }
+ };
+ opt.ondata = opt.ondata || function(status, chunk, end){
+ if(status !== 200){ return }
+ try{ajax.each(opt.header, function(val, i){
+ (xhr.responseHeader = xhr.responseHeader||{})[i.toLowerCase()] = xhr.getResponseHeader(i);
+ });
+ }catch(e){}
+ var data, buf, pos = 1;
+ while(pos || end){ // in order to end
+ if(u !== data){ // we need at least one loop
+ opt.onload({
+ headers: xhr.responseHeader || {}
+ ,body: data
+ });
+ end = false; // now both pos and end will be false
+ }
+ if(ajax.string(chunk)){
+ buf = chunk.slice(xhr.index = xhr.index || 0);
+ pos = buf.indexOf('\n') + 1;
+ data = pos? buf.slice(0, pos - 1) : buf;
+ xhr.index += pos;
+ } else {
+ data = chunk;
+ pos = 0;
+ }
+ }
+ }
+ opt.onload = opt.onload || function(reply){
+ if(!reply){ return }
+ if( reply.headers && ("application/json" === reply.headers["content-type"])){
+ var body;
+ try{body = JSON.parse(reply.body);
+ }catch(e){body = reply.body}
+ reply.body = body;
+ }
+ if(cb){
+ cb(null, reply);
+ }
+ }
+ if(opt.cookies || opt.credentials || opt.withCredentials){
+ xhr.withCredentials = true;
+ }
+ opt.headers["X-Requested-With"] = xhr.transport || "XMLHttpRequest";
+ try{xhr.open(opt.method, url, true);
+ }catch(e){ return xhr.onerror("Open failed.") }
+ if(opt.headers){
+ try{ajax.each(opt.headers, function(val, i){
+ xhr.setRequestHeader(i, val);
+ });
+ }catch(e){ return xhr.onerror("Invalid headers.") }
+ }
+ try{xhr.send(data);
+ }catch(e){ return xhr.onerror("Failed to send request.") }
+ }
+ ajax.xhr = function(xhr){
+ return (window.XMLHttpRequest && "withCredentials" in (xhr = new XMLHttpRequest()))? xhr : null;
+ }
+ ajax.jsonp = function(xhr){
+ xhr = {};
+ xhr.transport = "jsonp";
+ xhr.open = function(method, url){
+ xhr.url = url;
+ }
+ xhr.send = function(){
+ xhr.url += ((xhr.url.indexOf('?') + 1)? '&' : '?') + 'jsonp=' + xhr.js.id;
+ ajax.each(xhr.headers, function(val, i){
+ xhr.url += '&' + encodeURIComponent(i) + "=" + encodeURIComponent(val);
+ });
+ xhr.js.src = xhr.url = xhr.url.replace(/%20/g, "+");
+ document.getElementsByTagName('head')[0].appendChild(xhr.js);
+ }
+ xhr.setRequestHeader = function(i, val){
+ (xhr.headers = xhr.headers||{})[i] = val;
+ }
+ xhr.getResponseHeader = function(i){ return (xhr.responseHeaders||{})[i] }
+ xhr.js = document.createElement('script');
+ window[xhr.js.id = 'P'+Math.floor((Math.random()*65535)+1)] = function(reply){
+ xhr.status = 200;
+ if(reply.chunks && reply.chunks.length){
+ xhr.readyState = 3
+ while(0 < reply.chunks.length){
+ xhr.responseText = reply.chunks.shift();
+ xhr.onreadystatechange();
+ }
+ }
+ xhr.responseHeaders = reply.headers || {};
+ xhr.readyState = 4;
+ xhr.responseText = reply.body;
+ xhr.onreadystatechange();
+ xhr.id = xhr.js.id;
+ xhr.js.parentNode.removeChild(xhr.js);
+ window[xhr.id] = null;
+ try{delete window[xhr.id];
+ }catch(e){}
+
+ }
+ xhr.abort = function(){} // clean up?
+ xhr.js.async = true;
+ return xhr;
+ }
+ ajax.string = function(s){ return (typeof s == 'string') }
+ ajax.each = function(obj, cb){
+ if(!obj || !cb){ return }
+ for(var i in obj){
+ if(obj.hasOwnProperty(i)){
+ cb(obj[i], i);
+ }
+ }
+ }
+ return ajax;
+ }());
+ gun.__.opt.hooks.load = gun.__.opt.hooks.load || tab.load;
+ gun.__.opt.hooks.set = gun.__.opt.hooks.set || tab.set;
+ });
}({}));
\ No newline at end of file
diff --git a/examples/admin/slinger-t.html b/examples/admin/slinger-t.html
index 7cd5e2d3..9400166d 100644
--- a/examples/admin/slinger-t.html
+++ b/examples/admin/slinger-t.html
@@ -1,229 +1,229 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
GUN SLINGER
-
Select!
-
-
-
Next game available in 15 seconds or less...
-
Fastest draw in the west, no seconds, by nobody.
-
Previous duel won in no seconds, by no one.
-
-
-
GET READY!
-
-
-
FIRE!
-
by tapping this screen
-
-
-
STOP!
-
...waiting for the other player...
-
-
-
DISQUALIFIED!
-
-
-
YOU DIED!
-
-
-
YOU BOTH DIED!
-
-
-
-
YOU WON!
-
-
-
-
YOU WON!
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
GUN SLINGER
+
Select!
+
+
+
Next game available in 15 seconds or less...
+
Fastest draw in the west, no seconds, by nobody.
+
Previous duel won in no seconds, by no one.
+
+
+
GET READY!
+
+
+
FIRE!
+
by tapping this screen
+
+
+
STOP!
+
...waiting for the other player...
+
+
+
DISQUALIFIED!
+
+
+
YOU DIED!
+
+
+
YOU BOTH DIED!
+
+
+
+
YOU WON!
+
+
+
+
YOU WON!
+
+
+
+
\ No newline at end of file
diff --git a/examples/admin/slinger.html b/examples/admin/slinger.html
index af3a074d..9fc14674 100644
--- a/examples/admin/slinger.html
+++ b/examples/admin/slinger.html
@@ -1,229 +1,229 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
GUN SLINGER
-
Select!
-
-
-
Next game available in 15 seconds or less...
-
Fastest draw in the west, no seconds, by nobody.
-
Previous duel won in no seconds, by no one.
-
-
-
GET READY!
-
-
-
FIRE!
-
by tapping this screen
-
-
-
STOP!
-
...waiting for the other player...
-
-
-
DISQUALIFIED!
-
-
-
YOU DIED!
-
-
-
YOU BOTH DIED!
-
-
-
-
YOU WON!
-
-
-
-
YOU WON!
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
GUN SLINGER
+
Select!
+
+
+
Next game available in 15 seconds or less...
+
Fastest draw in the west, no seconds, by nobody.
+
Previous duel won in no seconds, by no one.
+
+
+
GET READY!
+
+
+
FIRE!
+
by tapping this screen
+
+
+
STOP!
+
...waiting for the other player...
+
+
+
DISQUALIFIED!
+
+
+
YOU DIED!
+
+
+
YOU BOTH DIED!
+
+
+
+
YOU WON!
+
+
+
+
YOU WON!
+
+
+
+
\ No newline at end of file
diff --git a/examples/admin/slinger_.html b/examples/admin/slinger_.html
index 77e1ee71..f9fb6c83 100644
--- a/examples/admin/slinger_.html
+++ b/examples/admin/slinger_.html
@@ -1,195 +1,195 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
GUN SLINGER
-
Select!
-
-
-
Next game available in 15 seconds or less...
-
-
-
GET READY!
-
-
-
FIRE!
-
by tapping this screen
-
-
-
STOP!
-
...waiting for the other player...
-
-
-
DISQUALIFIED!
-
-
-
YOU DIED!
-
-
-
YOU BOTH DIED!
-
-
-
-
YOU WON!
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
GUN SLINGER
+
Select!
+
+
+
Next game available in 15 seconds or less...
+
+
+
GET READY!
+
+
+
FIRE!
+
by tapping this screen
+
+
+
STOP!
+
...waiting for the other player...
+
+
+
DISQUALIFIED!
+
+
+
YOU DIED!
+
+
+
YOU BOTH DIED!
+
+
+
+
YOU WON!
+
+
+
+
\ No newline at end of file
diff --git a/examples/all.js b/examples/all.js
index c668f272..83295ffb 100644
--- a/examples/all.js
+++ b/examples/all.js
@@ -1,15 +1,15 @@
-console.log("If modules not found, run `npm install` in /example folder!"); // git subtree push -P examples heroku master
-var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || 8888;
-
-var express = require('express');
-var app = express();
-
-var Gun = require('gun');
-var gun = Gun({
- s3: (process.env.NODE_ENV === 'production')? null : require('../test/shotgun') // replace this with your own keys!
-});
-
-gun.attach(app);
-app.use(express.static(__dirname)).listen(port);
-
-console.log('Server started on port ' + port + ' with /gun');
\ No newline at end of file
+console.log("If modules not found, run `npm install` in /example folder!"); // git subtree push -P examples heroku master
+var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 80;
+
+var express = require('express');
+var app = express();
+
+var Gun = require('gun');
+var gun = Gun({
+ s3: (process.env.NODE_ENV === 'production')? null : require('../test/shotgun') // replace this with your own keys!
+});
+
+gun.attach(app);
+app.use(express.static(__dirname)).listen(port);
+
+console.log('Server started on port ' + port + ' with /gun');
diff --git a/examples/angular/index.html b/examples/angular/index.html
index 60303b81..0680fbca 100644
--- a/examples/angular/index.html
+++ b/examples/angular/index.html
@@ -1,76 +1,76 @@
-
-
-
-
-
-
-
-
-
-
-
Admin JSON Editor
- This is a live view of your data, you can edit it in realtime or add new key/values.
-
-
-
- {{key}}:
- {{val}}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
Admin JSON Editor
+ This is a live view of your data, you can edit it in realtime or add new key/values.
+
+
+
+ {{key}}:
+ {{val}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/cats/get.js b/examples/cats/get.js
index c9f0d9f7..926aa14b 100644
--- a/examples/cats/get.js
+++ b/examples/cats/get.js
@@ -1,5 +1,5 @@
-var gun = require('gun')({
- s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
-});
-
+var gun = require('gun')({
+ s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
+});
+
gun.load('kitten/hobbes').path('servant.cat.servant.name').get(function(name){ console.log(name) })
\ No newline at end of file
diff --git a/examples/cats/load.js b/examples/cats/load.js
index 6d909304..b5a9a5af 100644
--- a/examples/cats/load.js
+++ b/examples/cats/load.js
@@ -1,13 +1,13 @@
-var gun = require('gun')({
- s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
-});
-
-gun.load('email/mark@gundb.io').get(function(Mark){
- console.log("Hello ", Mark);
- this.path('username').set('amark'); // because we hadn't saved it yet!
- this.path('cat').get(function(Hobbes){ // `this` is context of the nodes you explore via path
- console.log(Hobbes);
- this.set({ servant: Mark, coat: "tabby" }); // oh no! Hobbes has become Mark's master.
- this.key('kitten/hobbes'); // cats are taking over the internet! Better make an index for them.
- });
+var gun = require('gun')({
+ s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
+});
+
+gun.load('email/mark@gundb.io').get(function(Mark){
+ console.log("Hello ", Mark);
+ this.path('username').set('amark'); // because we hadn't saved it yet!
+ this.path('cat').get(function(Hobbes){ // `this` is context of the nodes you explore via path
+ console.log(Hobbes);
+ this.set({ servant: Mark, coat: "tabby" }); // oh no! Hobbes has become Mark's master.
+ this.key('kitten/hobbes'); // cats are taking over the internet! Better make an index for them.
+ });
});
\ No newline at end of file
diff --git a/examples/cats/set.js b/examples/cats/set.js
index 66a2a30c..4ba48876 100644
--- a/examples/cats/set.js
+++ b/examples/cats/set.js
@@ -1,7 +1,7 @@
-var gun = require('gun')({
- s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
-});
-
-gun.set({ name: "Mark Nadal", email: "mark@gunDB.io", cat: { name: "Hobbes", species: "kitty" } })
- .key('email/mark@gundb.io')
+var gun = require('gun')({
+ s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
+});
+
+gun.set({ name: "Mark Nadal", email: "mark@gunDB.io", cat: { name: "Hobbes", species: "kitty" } })
+ .key('email/mark@gundb.io')
;
\ No newline at end of file
diff --git a/examples/examples.sh b/examples/examples.sh
new file mode 100644
index 00000000..a9b8e4c5
--- /dev/null
+++ b/examples/examples.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# curl -fsSL http://gunDB.io/example.sh | bash -c
+
+echo ""
+echo ""
+
+echo "updating apt-get..."
+sudo bash -c "apt-get update -qq -y < /dev/null" > /dev/null
+
+# git, wget, curl, build-essential
+if [ -z "$(which git | grep git)" ] || [ -z "$(which wget | grep wget)" ] || [ -z "$(which curl | grep curl)" ] || [ -z "$(which gcc | grep gcc)" ] || [ -z "$(which rsync | grep rsync)" ]
+then
+ echo "installing git, wget, curl, build-essential, rsync..."
+ sudo bash -c "apt-get install -qq -y git wget curl build-essential rsync < /dev/null" > /dev/null 2>/dev/null
+fi
+
+# node
+NODE_VER=v0.11.14
+CUR_NODE_VER=$(node -v 2>/dev/null)
+if [ -n "$(which node | grep node)" ] && [ "${NODE_VER}" == "$(node -v 2>/dev/null)" ]; then
+#if [ "${NODE_VER}" == "${CUR_NODE_VER}" ]; then
+ echo node ${NODE_VER} already installed
+else
+ echo "installing node ${NODE_VER}..."
+ curl -fsSL "http://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-linux-x64.tar.gz" \
+ -o "/tmp/node-${NODE_VER}-linux-x64.tar.gz"
+ pushd /tmp
+ tar xf /tmp/node-${NODE_VER}-linux-x64.tar.gz
+ rm node-${NODE_VER}-linux-x64/{LICENSE,ChangeLog,README.md}
+ sudo rsync -a /tmp/node-${NODE_VER}-linux-x64/ /usr/local/
+ sudo chown -R $(whoami) /usr/local
+fi
+
+mkdir -p ~/gun
+cd ~/gun
+npm install gun
+cd ./node_modules/gun/examples
+npm install .
+node all.js
+
+echo ""
\ No newline at end of file
diff --git a/examples/hello-world.js b/examples/hello-world.js
index 6b52dc21..052bd6f9 100644
--- a/examples/hello-world.js
+++ b/examples/hello-world.js
@@ -1,18 +1,18 @@
-var Gun = require('gun');
-var gun = Gun({
- s3: {
- key: '', // AWS Access Key
- secret: '', // AWS Secret Token
- bucket: '' // The bucket you want to save into
- }
-});
-gun.set({ hello: 'world' }).key('my/first/data');
-
-var http = require('http');
-http.createServer(function (req, res) {
- gun.load('my/first/data', function(err, data){
- res.writeHead(200, {'Content-Type': 'application/json'});
- res.end(JSON.stringify(data));
- });
-}).listen(1337, '127.0.0.1');
+var Gun = require('gun');
+var gun = Gun({
+ s3: {
+ key: '', // AWS Access Key
+ secret: '', // AWS Secret Token
+ bucket: '' // The bucket you want to save into
+ }
+});
+gun.set({ hello: 'world' }).key('my/first/data');
+
+var http = require('http');
+http.createServer(function (req, res) {
+ gun.load('my/first/data', function(err, data){
+ res.writeHead(200, {'Content-Type': 'application/json'});
+ res.end(JSON.stringify(data));
+ });
+}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
\ No newline at end of file
diff --git a/examples/node_modules/gun/index.js b/examples/node_modules/gun/index.js
deleted file mode 100644
index bc0fc4b7..00000000
--- a/examples/node_modules/gun/index.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require(__dirname + '/../../../index');
\ No newline at end of file
diff --git a/examples/package.json b/examples/package.json
index d0eb45da..6021dfc3 100644
--- a/examples/package.json
+++ b/examples/package.json
@@ -1,17 +1,17 @@
-{
- "name": "examples",
- "main": "all.js",
- "description": "Example gun apps"
-, "version": "0.0.1"
-, "engines": {
- "node": "~>0.6.6"
- }
-, "dependencies": {
- "express": "~>4.9.0",
- "gun": "~>0.0.8"
- }
-, "scripts": {
- "start": "node all.js",
- "test": "mocha"
- }
-}
\ No newline at end of file
+{
+ "name": "examples",
+ "main": "all.js",
+ "description": "Example gun apps"
+, "version": "0.0.1"
+, "engines": {
+ "node": "~>0.10.34"
+ }
+, "dependencies": {
+ "express": "~>4.9.0",
+ "gun": "~>0.0.9a"
+ }
+, "scripts": {
+ "start": "node all.js",
+ "test": "mocha"
+ }
+}
diff --git a/examples/start.sh b/examples/start.sh
new file mode 100644
index 00000000..485fc0c5
--- /dev/null
+++ b/examples/start.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+#debian/ubuntu
+sudo apt-get update -y
+sudo apt-get install curl git git-core -y
+#fedora/openSUSE
+sudo yum check-update -y
+sudo yum install curl git git-core -y
+
+#install nodejs
+git clone http://github.com/isaacs/nave.git
+sudo ./nave/nave.sh usemain stable
+
+npm install gun
+
+cd ./node_modules/gun/examples
+
+npm install .
+
+sudo /usr/local/bin/node ./all.js
diff --git a/examples/todo/index.html b/examples/todo/index.html
index e4a3a0ab..7bfbdd55 100644
--- a/examples/todo/index.html
+++ b/examples/todo/index.html
@@ -1,15 +1,15 @@
-
-
- ToDo
-
-
-
- Todo List
-
-
-
+
+
+ ToDo
+
+
+
+ Todo List
+
+
+
\ No newline at end of file
diff --git a/gun.js b/gun.js
index 0f4b2669..1c97fb4e 100644
--- a/gun.js
+++ b/gun.js
@@ -237,9 +237,9 @@
}
Chain.load = function(key, cb, opt){
var gun = this.chain();
- cb = cb || {};
gun.shot('done');
gun.shot.done(function(){
+ cb = cb || function(){};
cb.soul = (key||{})[Gun._.soul];
if(cb.soul){
cb.node = gun.__.graph[cb.soul];
@@ -282,10 +282,14 @@
gun.shot.then(function(){
//Gun.log.call(gun, "make key", key);
cb = cb || function(){};
- cb.node = gun.__.keys[key] = gun._.node;
- if(!cb.node){ return gun }
+ if(Gun.obj.is(key)){ // if key is an object then we get the soul directly from it because the node might not exist in cache.
+ Gun.obj.map(key, function(soul, field){ return key = field, cb.soul = soul });
+ } else { // else the node does exist in cache and we will get the soul from it instead, plus link the key.
+ cb.node = gun.__.keys[key] = gun._.node;
+ }
if(Gun.fns.is(gun.__.opt.hooks.key)){
- gun.__.opt.hooks.key(key, cb.node._[Gun._.soul], function(err, data){
+ console.log("UGLY UGLY UGLY UGLY", key, cb.soul, cb.node);
+ gun.__.opt.hooks.key(key, cb.soul || (cb.node||{_:{}})._[Gun._.soul], function(err, data){
//Gun.log.call(gun, "key made", key);
if(err){ return cb(err) }
return cb(null);
@@ -954,6 +958,7 @@
var ws = window.WebSocket || window.mozWebSocket || window.webkitWebSocket;
if(!ws){ return }
if(ws = r.ws.peers[opt.base]){
+ if(!ws.readyState){ return setTimeout(function(){ r.ws(opt, cb) },10), true }
var req = {};
if(opt.headers){ req.headers = opt.headers }
if(opt.body){ req.body = opt.body }
diff --git a/lib/aws.js b/lib/aws.js
index b6cec9ec..c2ef79f7 100644
--- a/lib/aws.js
+++ b/lib/aws.js
@@ -1,155 +1,155 @@
-;module.exports = (function(a, own){
-
- function s3(opt){
- if(!(a.fns.is(this) || this instanceof s3)){
- return new s3(opt);
- }
- var s = this;
- s.on = a.on.create();
- s.mime = require('mime');
- s.AWS = require('aws-sdk');
- s.config = {};
- opt = opt || {};
- s.AWS.config.bucket = s.config.bucket = opt.bucket || opt.Bucket || s.config.bucket || process.env.AWS_S3_BUCKET;
- s.AWS.config.region = s.config.region = opt.region || s.config.region || process.env.AWS_REGION || "us-east-1";
- s.AWS.config.accessKeyId = s.config.accessKeyId = opt.key || opt.accessKeyId || s.config.accessKeyId || process.env.AWS_ACCESS_KEY_ID;
- s.AWS.config.secretAccessKey = s.config.secretAccessKey = opt.secret || opt.secretAccessKey || s.config.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY;
- if(s.config.fakes3 = s.config.fakes3 || opt.fakes3 || process.env.fakes3){
- s.AWS.config.endpoint = s.config.endpoint = opt.fakes3 || s.config.fakes3 || process.env.fakes3;
- s.AWS.config.sslEnabled = s.config.sslEnabled = false;
- s.AWS.config.bucket = s.config.bucket = s.config.bucket.replace('.','p');
- }
- s.AWS.config.update(s.config);
- s.S3 = function(){
- var s = new this.AWS.S3();
- if(this.config.fakes3){
- s.endpoint = config.endpoint;
- }
- return s;
- }
- return s;
- };
- s3.id = function(m){ return m.Bucket +'/'+ m.Key }
- s3.chain = s3.prototype;
- s3.chain.put = function(key, o, cb, m){
- if(!key){ return }
- m = m || {}
- m.Bucket = m.Bucket || this.config.bucket;
- m.Key = m.Key || key;
- if(a.obj.is(o) || a.list.is(o)){
- m.Body = a.text.ify(o);
- m.ContentType = this.mime.lookup('json')
- } else {
- m.Body = a.text.is(o)? o : a.text.ify(o);
- }
- this.S3().putObject(m, function(e,r){
- //console.log('saved', e,r);
- if(!cb){ return }
- cb(e,r);
- });
- return this;
- }
- s3.chain.get = function(key, cb, o){
- if(!key){ return }
- var s = this
- , m = {
- Bucket: s.config.bucket
- ,Key: key
- }, id = s3.id(m);
- s.on(id).once(function(e,d,t,m,r){
- delete s.batch[id];
- if(!a.fns.is(cb)){ return }
- try{ cb(e,d,t,m,r);
- }catch(e){
- console.log(e);
- }
- });
- s.batch = s.batch || {};
- if(s.batch[id]){ return s }
- s.batch[id] = (s.batch[id] || 0) + 1;
- console.log("no batch!");
- s.S3().getObject(m, function(e,r){
- var d, t, m;
- r = r || (this && this.httpResponse);
- if(e || !r){ return s.on(id).emit(e) }
- r.Text = r.text = t = (r.Body||r.body||'').toString('utf8');
- r.Type = r.type = r.ContentType || (r.headers||{})['content-type'];
- if(r.type && 'json' === s.mime.extension(r.type)){
- d = a.obj.ify(t);
- }
- m = r.Metadata;
- s.on(id).emit(e, d, t, m, r); // Warning about the r parameter, is is the raw response and may result in stupid SAX errors.
- });
- return s;
- }
- s3.chain.del = function(key, cb){
- if(!key){ return }
- var m = {
- Bucket: this.config.bucket
- ,Key: key
- }
- this.S3().deleteObject(m, function(e,r){
- if(!cb){ return }
- cb(e, r);
- });
- return this;
- }
- s3.chain.dbs = function(o, cb){
- cb = cb || o;
- var m = {}
- this.S3().listBuckets(m, function(e,r){
- //console.log('dbs',e);
- a.list.map((r||{}).Contents, function(v){console.log(v);});
- //console.log('---end list---');
- if(!a.fns.is(cb)) return;
- cb(e,r);
- });
- return this;
- }
- s3.chain.keys = function(from, upto, cb){
- cb = cb || upto || from;
- var m = {
- Bucket: this.config.bucket
- }
- if(a.text.is(from)){
- m.Prefix = from;
- }
- if(a.text.is(upto)){
- m.Delimiter = upto;
- }
- this.S3().listObjects(m, function(e,r){
- //console.log('list',e);
- a.list.each((r||{}).Contents, function(v){console.log(v)});
- //console.log('---end list---');
- if(!a.fns.is(cb)) return;
- cb(e,r);
- });
- return this;
- }
- return s3;
-})(require('gun/gun'), {});
-/**
-Knox S3 Config is:
-knox.createClient({
- key: ''
- , secret: ''
- , bucket: ''
- , endpoint: 'us-standard'
- , port: 0
- , secure: true
- , token: ''
- , style: ''
- , agent: ''
-});
-
-aws-sdk for s3 is:
-{ "accessKeyId": "akid", "secretAccessKey": "secret", "region": "us-west-2" }
-AWS.config.loadFromPath('./config.json');
- {
- accessKeyId: process.env.AWS_ACCESS_KEY_ID = ''
- ,secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY = ''
- ,Bucket: process.env.s3Bucket = ''
- ,region: process.env.AWS_REGION = "us-east-1"
- ,sslEnabled: ''
-}
-**/
\ No newline at end of file
+;module.exports = (function(a, own){
+
+ function s3(opt){
+ if(!(a.fns.is(this) || this instanceof s3)){
+ return new s3(opt);
+ }
+ var s = this;
+ s.on = a.on.create();
+ s.mime = require('mime');
+ s.AWS = require('aws-sdk');
+ s.config = {};
+ opt = opt || {};
+ s.AWS.config.bucket = s.config.bucket = opt.bucket || opt.Bucket || s.config.bucket || process.env.AWS_S3_BUCKET;
+ s.AWS.config.region = s.config.region = opt.region || s.config.region || process.env.AWS_REGION || "us-east-1";
+ s.AWS.config.accessKeyId = s.config.accessKeyId = opt.key || opt.accessKeyId || s.config.accessKeyId || process.env.AWS_ACCESS_KEY_ID;
+ s.AWS.config.secretAccessKey = s.config.secretAccessKey = opt.secret || opt.secretAccessKey || s.config.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY;
+ if(s.config.fakes3 = s.config.fakes3 || opt.fakes3 || process.env.fakes3){
+ s.AWS.config.endpoint = s.config.endpoint = opt.fakes3 || s.config.fakes3 || process.env.fakes3;
+ s.AWS.config.sslEnabled = s.config.sslEnabled = false;
+ s.AWS.config.bucket = s.config.bucket = s.config.bucket.replace('.','p');
+ }
+ s.AWS.config.update(s.config);
+ s.S3 = function(){
+ var s = new this.AWS.S3();
+ if(this.config.fakes3){
+ s.endpoint = config.endpoint;
+ }
+ return s;
+ }
+ return s;
+ };
+ s3.id = function(m){ return m.Bucket +'/'+ m.Key }
+ s3.chain = s3.prototype;
+ s3.chain.put = function(key, o, cb, m){
+ if(!key){ return }
+ m = m || {}
+ m.Bucket = m.Bucket || this.config.bucket;
+ m.Key = m.Key || key;
+ if(a.obj.is(o) || a.list.is(o)){
+ m.Body = a.text.ify(o);
+ m.ContentType = this.mime.lookup('json')
+ } else {
+ m.Body = a.text.is(o)? o : a.text.ify(o);
+ }
+ this.S3().putObject(m, function(e,r){
+ //console.log('saved', e,r);
+ if(!cb){ return }
+ cb(e,r);
+ });
+ return this;
+ }
+ s3.chain.get = function(key, cb, o){
+ if(!key){ return }
+ var s = this
+ , m = {
+ Bucket: s.config.bucket
+ ,Key: key
+ }, id = s3.id(m);
+ s.on(id).once(function(e,d,t,m,r){
+ delete s.batch[id];
+ if(!a.fns.is(cb)){ return }
+ try{ cb(e,d,t,m,r);
+ }catch(e){
+ console.log(e);
+ }
+ });
+ s.batch = s.batch || {};
+ if(s.batch[id]){ return s }
+ s.batch[id] = (s.batch[id] || 0) + 1;
+ console.log("no batch!");
+ s.S3().getObject(m, function(e,r){
+ var d, t, m;
+ r = r || (this && this.httpResponse);
+ if(e || !r){ return s.on(id).emit(e) }
+ r.Text = r.text = t = (r.Body||r.body||'').toString('utf8');
+ r.Type = r.type = r.ContentType || (r.headers||{})['content-type'];
+ if(r.type && 'json' === s.mime.extension(r.type)){
+ d = a.obj.ify(t);
+ }
+ m = r.Metadata;
+ s.on(id).emit(e, d, t, m, r); // Warning about the r parameter, is is the raw response and may result in stupid SAX errors.
+ });
+ return s;
+ }
+ s3.chain.del = function(key, cb){
+ if(!key){ return }
+ var m = {
+ Bucket: this.config.bucket
+ ,Key: key
+ }
+ this.S3().deleteObject(m, function(e,r){
+ if(!cb){ return }
+ cb(e, r);
+ });
+ return this;
+ }
+ s3.chain.dbs = function(o, cb){
+ cb = cb || o;
+ var m = {}
+ this.S3().listBuckets(m, function(e,r){
+ //console.log('dbs',e);
+ a.list.map((r||{}).Contents, function(v){console.log(v);});
+ //console.log('---end list---');
+ if(!a.fns.is(cb)) return;
+ cb(e,r);
+ });
+ return this;
+ }
+ s3.chain.keys = function(from, upto, cb){
+ cb = cb || upto || from;
+ var m = {
+ Bucket: this.config.bucket
+ }
+ if(a.text.is(from)){
+ m.Prefix = from;
+ }
+ if(a.text.is(upto)){
+ m.Delimiter = upto;
+ }
+ this.S3().listObjects(m, function(e,r){
+ //console.log('list',e);
+ a.list.each((r||{}).Contents, function(v){console.log(v)});
+ //console.log('---end list---');
+ if(!a.fns.is(cb)) return;
+ cb(e,r);
+ });
+ return this;
+ }
+ return s3;
+})(require('../gun'), {});
+/**
+Knox S3 Config is:
+knox.createClient({
+ key: ''
+ , secret: ''
+ , bucket: ''
+ , endpoint: 'us-standard'
+ , port: 0
+ , secure: true
+ , token: ''
+ , style: ''
+ , agent: ''
+});
+
+aws-sdk for s3 is:
+{ "accessKeyId": "akid", "secretAccessKey": "secret", "region": "us-west-2" }
+AWS.config.loadFromPath('./config.json');
+ {
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID = ''
+ ,secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY = ''
+ ,Bucket: process.env.s3Bucket = ''
+ ,region: process.env.AWS_REGION = "us-east-1"
+ ,sslEnabled: ''
+}
+**/
diff --git a/lib/http.js b/lib/http.js
index e7f27ca1..e7dc1eb4 100644
--- a/lib/http.js
+++ b/lib/http.js
@@ -1,50 +1,50 @@
-var Gun = require('gun/gun')
-, formidable = require('formidable')
-, url = require('url');
-module.exports = function(req, res, next){
- next = next || function(){};
- if(!req || !res){ return next() }
- if(!req.url){ return next() }
- if(!req.method){ return next() }
- var msg = {};
- msg.url = url.parse(req.url, true);
- msg.method = (req.method||'').toLowerCase();
- msg.headers = req.headers;
- var u, body
- , form = new formidable.IncomingForm()
- , post = function(err, body){
- if(u !== body){ msg.body = body }
- next(msg, function(reply){
- if(!res){ return }
- if(!reply){ return res.end() }
- if(Gun.obj.has(reply, 'statusCode') || Gun.obj.has(reply, 'status')){
- res.statusCode = reply.statusCode || reply.status;
- }
- if(reply.headers){
- if(!res._headerSent){
- Gun.obj.map(reply.headers, function(val, field){
- res.setHeader(field, val);
- });
- }
- }
- if(Gun.obj.has(reply,'chunk') || Gun.obj.has(reply,'write')){
- res.write(Gun.text.ify(reply.chunk || reply.write) || '');
- }
- if(Gun.obj.has(reply,'body') || Gun.obj.has(reply,'end')){
- res.end(Gun.text.ify(reply.body || reply.end) || '');
- }
- });
- }
- form.on('field',function(k,v){
- (body = body || {})[k] = v;
- }).on('file',function(k,v){
- return; // files not supported in gun yet
- }).on('error',function(e){
- if(form.done){ return }
- post(e);
- }).on('end', function(){
- if(form.done){ return }
- post(null, body);
- });
- form.parse(req);
-}
\ No newline at end of file
+var Gun = require('../gun')
+, formidable = require('formidable')
+, url = require('url');
+module.exports = function(req, res, next){
+ next = next || function(){};
+ if(!req || !res){ return next() }
+ if(!req.url){ return next() }
+ if(!req.method){ return next() }
+ var msg = {};
+ msg.url = url.parse(req.url, true);
+ msg.method = (req.method||'').toLowerCase();
+ msg.headers = req.headers;
+ var u, body
+ , form = new formidable.IncomingForm()
+ , post = function(err, body){
+ if(u !== body){ msg.body = body }
+ next(msg, function(reply){
+ if(!res){ return }
+ if(!reply){ return res.end() }
+ if(Gun.obj.has(reply, 'statusCode') || Gun.obj.has(reply, 'status')){
+ res.statusCode = reply.statusCode || reply.status;
+ }
+ if(reply.headers){
+ if(!res._headerSent){
+ Gun.obj.map(reply.headers, function(val, field){
+ res.setHeader(field, val);
+ });
+ }
+ }
+ if(Gun.obj.has(reply,'chunk') || Gun.obj.has(reply,'write')){
+ res.write(Gun.text.ify(reply.chunk || reply.write) || '');
+ }
+ if(Gun.obj.has(reply,'body') || Gun.obj.has(reply,'end')){
+ res.end(Gun.text.ify(reply.body || reply.end) || '');
+ }
+ });
+ }
+ form.on('field',function(k,v){
+ (body = body || {})[k] = v;
+ }).on('file',function(k,v){
+ return; // files not supported in gun yet
+ }).on('error',function(e){
+ if(form.done){ return }
+ post(e);
+ }).on('end', function(){
+ if(form.done){ return }
+ post(null, body);
+ });
+ form.parse(req);
+}
diff --git a/lib/jsonp.js b/lib/jsonp.js
index 5f86fac6..4c92a307 100644
--- a/lib/jsonp.js
+++ b/lib/jsonp.js
@@ -1,37 +1,37 @@
-var Gun = require('gun/gun');
-module.exports = function(req, cb){
- if(!req.url || !req.url.query || !req.url.query.jsonp){ return cb }
- cb.jsonp = req.url.query.jsonp;
- delete req.url.query.jsonp;
- Gun.obj.map(Gun.obj.ify(req.url.query['`']), function(val, i){
- req.headers[i] = val;
- });
- delete req.url.query['`'];
- if(req.url.query.$){
- req.body = req.url.query.$;
- if(!Gun.obj.has(req.url.query, '^') || 'json' == req.url.query['^']){
- req.body = Gun.obj.ify(req.body); // TODO: BUG! THIS IS WRONG! This doesn't handle multipart chunking, and will fail!
- }
- }
- delete req.url.query.$;
- delete req.url.query['^'];
- delete req.url.query['%'];
- var reply = {headers:{}};
- return function(res){
- if(!res){ return }
- if(res.headers){
- Gun.obj.map(res.headers, function(val, field){
- reply.headers[field] = val;
- });
- }
- reply.headers['Content-Type'] = "text/javascript";
- if(Gun.obj.has(res,'chunk') && (!reply.body || Gun.list.is(reply.chunks))){
- (reply.chunks = reply.chunks || []).push(res.chunk);
- }
- if(Gun.obj.has(res,'body')){
- reply.body = res.body; // self-reference yourself so on the client we can get the headers and body.
- reply.body = ';'+ cb.jsonp + '(' + Gun.text.ify(reply) + ');'; // javascriptify it! can't believe the client trusts us.
- cb(reply);
- }
- }
-}
\ No newline at end of file
+var Gun = require('../gun');
+module.exports = function(req, cb){
+ if(!req.url || !req.url.query || !req.url.query.jsonp){ return cb }
+ cb.jsonp = req.url.query.jsonp;
+ delete req.url.query.jsonp;
+ Gun.obj.map(Gun.obj.ify(req.url.query['`']), function(val, i){
+ req.headers[i] = val;
+ });
+ delete req.url.query['`'];
+ if(req.url.query.$){
+ req.body = req.url.query.$;
+ if(!Gun.obj.has(req.url.query, '^') || 'json' == req.url.query['^']){
+ req.body = Gun.obj.ify(req.body); // TODO: BUG! THIS IS WRONG! This doesn't handle multipart chunking, and will fail!
+ }
+ }
+ delete req.url.query.$;
+ delete req.url.query['^'];
+ delete req.url.query['%'];
+ var reply = {headers:{}};
+ return function(res){
+ if(!res){ return }
+ if(res.headers){
+ Gun.obj.map(res.headers, function(val, field){
+ reply.headers[field] = val;
+ });
+ }
+ reply.headers['Content-Type'] = "text/javascript";
+ if(Gun.obj.has(res,'chunk') && (!reply.body || Gun.list.is(reply.chunks))){
+ (reply.chunks = reply.chunks || []).push(res.chunk);
+ }
+ if(Gun.obj.has(res,'body')){
+ reply.body = res.body; // self-reference yourself so on the client we can get the headers and body.
+ reply.body = ';'+ cb.jsonp + '(' + Gun.text.ify(reply) + ');'; // javascriptify it! can't believe the client trusts us.
+ cb(reply);
+ }
+ }
+}
diff --git a/lib/s3.js b/lib/s3.js
index 72139de0..8073fdea 100644
--- a/lib/s3.js
+++ b/lib/s3.js
@@ -1,113 +1,113 @@
-;(function(){
- var Gun = require('gun/gun');
- var S3 = require('./aws');
-
- Gun.on('opt').event(function(gun, opt){
- opt.s3 = opt.s3 || {};
- 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 || 15;
- gun.__.opt.disconnect = opt.disconnect || gun.__.opt.disconnect || 5;
- s3.load = s3.load || function(key, cb, opt){
- if(!key){ return }
- cb = cb || function(){};
- opt = opt || {};
- if(key[Gun._.soul]){
- key = s3.prefix + s3.prenode + key[Gun._.soul];
- } else {
- key = s3.prefix + s3.prekey + key;
- }
- s3.get(key, function(err, data, text, meta){
- console.log('via s3', key, err);
- if(meta && meta[Gun._.soul]){
- return s3.load(meta, cb);
- }
- if(err && err.statusCode == 404){
- err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong).
- }
- cb(err, data);
- });
- }
- s3.set = s3.set || function(nodes, cb){
- s3.batching += 1;
- cb = cb || function(){};
- cb.count = 0;
- var next = s3.next
- , ack = Gun.text.random(8)
- , batch = s3.batch[next] = s3.batch[next] || {};
- s3.on(ack).once(cb);
- Gun.obj.map(nodes, function(node, soul){
- cb.count += 1;
- batch[soul] = (batch[soul] || 0) + 1;
- //console.log("set listener for", next + ':' + soul, batch[soul], cb.count);
- s3.on(next + ':' + soul).event(function(){
- cb.count -= 1;
- //console.log("transaction", cb.count);
- if(!cb.count){
- s3.on(ack).emit();
- this.off(); // MEMORY LEAKS EVERYWHERE!!!!!!!!!!!!!!!! FIX THIS!!!!!!!!!
- }
- });
- });
- if(gun.__.opt.batch < s3.batching){
- return s3.set.now();
- }
- if(!gun.__.opt.throttle){
- return s3.set.now();
- }
- s3.wait = s3.wait || setTimeout(s3.set.now, gun.__.opt.throttle * 1000); // in seconds
- }
- s3.set.now = s3.set.now || function(){
- clearTimeout(s3.wait);
- s3.batching = 0;
- s3.wait = null;
- var now = s3.next
- , batch = s3.batch[s3.next];
- s3.next = Gun.time.is();
- Gun.obj.map(batch, function put(exists, soul){
- var node = gun.__.graph[soul]; // the batch does not actually have the nodes, but what happens when we do cold data? Could this be gone?
- s3.put(s3.prefix + s3.prenode + soul, node, function(err, reply){
- console.log("s3 put reply", soul, err, reply);
- if(err || !reply){
- put(exists, soul); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
- return;
- }
- s3.on(now + ':' + soul).emit(200);
- });
- });
- }
- s3.next = s3.next || Gun.time.is();
- s3.on = s3.on || Gun.on.create();
- s3.batching = s3.batching || 0;
- s3.batched = s3.batched || {};
- s3.batch = s3.batch || {};
- s3.persisted = s3.persisted || {};
- s3.wait = s3.wait || null;
-
- s3.key = s3.key || function(key, soul, cb){
- var meta = {};
- meta[Gun._.soul] = soul = Gun.text.is(soul)? soul : (soul||{})[Gun._.soul];
- if(!soul){
- return cb({err: "No soul!"});
- }
- s3.put(s3.prefix + s3.prekey + key, '', function(err, reply){ // key is 2 bytes??? Should be smaller. Wait HUH? What did I mean by this?
- console.log("s3 put reply", soul, err, reply);
- if(err || !reply){
- s3.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
- return;
- }
- cb();
- }, {Metadata: meta});
- }
-
- opt.hooks = opt.hooks || {};
- gun.opt({hooks: {
- load: opt.hooks.load || s3.load
- ,set: opt.hooks.set || s3.set
- ,key: opt.hooks.key || s3.key
- }}, true);
- });
-}());
\ No newline at end of file
+;(function(){
+ var Gun = require('../gun');
+ var S3 = require('./aws');
+
+ Gun.on('opt').event(function(gun, opt){
+ opt.s3 = opt.s3 || {};
+ 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 || 15;
+ gun.__.opt.disconnect = opt.disconnect || gun.__.opt.disconnect || 5;
+ s3.load = s3.load || function(key, cb, opt){
+ if(!key){ return }
+ cb = cb || function(){};
+ opt = opt || {};
+ if(key[Gun._.soul]){
+ key = s3.prefix + s3.prenode + key[Gun._.soul];
+ } else {
+ key = s3.prefix + s3.prekey + key;
+ }
+ s3.get(key, function(err, data, text, meta){
+ console.log('via s3', key, err);
+ if(meta && meta[Gun._.soul]){
+ return s3.load(meta, cb);
+ }
+ if(err && err.statusCode == 404){
+ err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong).
+ }
+ cb(err, data);
+ });
+ }
+ s3.set = s3.set || function(nodes, cb){
+ s3.batching += 1;
+ cb = cb || function(){};
+ cb.count = 0;
+ var next = s3.next
+ , ack = Gun.text.random(8)
+ , batch = s3.batch[next] = s3.batch[next] || {};
+ s3.on(ack).once(cb);
+ Gun.obj.map(nodes, function(node, soul){
+ cb.count += 1;
+ batch[soul] = (batch[soul] || 0) + 1;
+ //console.log("set listener for", next + ':' + soul, batch[soul], cb.count);
+ s3.on(next + ':' + soul).event(function(){
+ cb.count -= 1;
+ //console.log("transaction", cb.count);
+ if(!cb.count){
+ s3.on(ack).emit();
+ this.off(); // MEMORY LEAKS EVERYWHERE!!!!!!!!!!!!!!!! FIX THIS!!!!!!!!!
+ }
+ });
+ });
+ if(gun.__.opt.batch < s3.batching){
+ return s3.set.now();
+ }
+ if(!gun.__.opt.throttle){
+ return s3.set.now();
+ }
+ s3.wait = s3.wait || setTimeout(s3.set.now, gun.__.opt.throttle * 1000); // in seconds
+ }
+ s3.set.now = s3.set.now || function(){
+ clearTimeout(s3.wait);
+ s3.batching = 0;
+ s3.wait = null;
+ var now = s3.next
+ , batch = s3.batch[s3.next];
+ s3.next = Gun.time.is();
+ Gun.obj.map(batch, function put(exists, soul){
+ var node = gun.__.graph[soul]; // the batch does not actually have the nodes, but what happens when we do cold data? Could this be gone?
+ s3.put(s3.prefix + s3.prenode + soul, node, function(err, reply){
+ console.log("s3 put reply", soul, err, reply);
+ if(err || !reply){
+ put(exists, soul); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
+ return;
+ }
+ s3.on(now + ':' + soul).emit(200);
+ });
+ });
+ }
+ s3.next = s3.next || Gun.time.is();
+ s3.on = s3.on || Gun.on.create();
+ s3.batching = s3.batching || 0;
+ s3.batched = s3.batched || {};
+ s3.batch = s3.batch || {};
+ s3.persisted = s3.persisted || {};
+ s3.wait = s3.wait || null;
+
+ s3.key = s3.key || function(key, soul, cb){
+ var meta = {};
+ meta[Gun._.soul] = soul = Gun.text.is(soul)? soul : (soul||{})[Gun._.soul];
+ if(!soul){
+ return cb({err: "No soul!"});
+ }
+ s3.put(s3.prefix + s3.prekey + key, '', function(err, reply){ // key is 2 bytes??? Should be smaller. Wait HUH? What did I mean by this?
+ console.log("s3 put reply", soul, err, reply);
+ if(err || !reply){
+ s3.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
+ return;
+ }
+ cb();
+ }, {Metadata: meta});
+ }
+
+ opt.hooks = opt.hooks || {};
+ gun.opt({hooks: {
+ load: opt.hooks.load || s3.load
+ ,set: opt.hooks.set || s3.set
+ ,key: opt.hooks.key || s3.key
+ }}, true);
+ });
+}());
diff --git a/lib/server.js b/lib/server.js
index 54b4a22c..6d13363b 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -1,7 +1,7 @@
-;(function(){
- console.log("Hello wonderful person! :) I'm mark@gunDB.io, message me for help or with hatemail. I want to hear from you! <3");
- var Gun = require('../gun');
- require('./s3');
- require('./wsp');
- module.exports = Gun;
+;(function(){
+ console.log("Hello wonderful person! :) I'm mark@gunDB.io, message me for help or with hatemail. I want to hear from you! <3");
+ var Gun = require('../gun');
+ require('./s3');
+ require('./wsp');
+ module.exports = Gun;
}());
\ No newline at end of file
diff --git a/lib/ws.js b/lib/ws.js
index 3b7077ae..eb919a21 100644
--- a/lib/ws.js
+++ b/lib/ws.js
@@ -1,37 +1,37 @@
-var Gun = require('gun/gun')
-, url = require('url');
-module.exports = function(wss, server){
- wss.on('connection', function(ws){
- var req = {};
- ws.upgradeReq = ws.upgradeReq || {};
- req.url = url.parse(ws.upgradeReq.url||'');
- req.method = (ws.upgradeReq.method||'').toLowerCase();
- req.headers = ws.upgradeReq.headers || {};
- //console.log("wsReq", req);
- ws.on('message', function(msg){
- msg = Gun.obj.ify(msg);
- msg.url = msg.url || {};
- msg.url.pathname = (req.url.pathname||'') + (msg.url.pathname||'');
- Gun.obj.map(req.url, function(val, i){
- msg.url[i] = msg.url[i] || val; // reattach url
- });
- msg.method = msg.method || req.method;
- msg.headers = msg.headers || {};
- Gun.obj.map(req.headers, function(val, i){
- msg.headers[i] = msg.headers[i] || val; // reattach headers
- });
- server.call(ws, msg, function(reply){
- if(!ws || !ws.send){ return }
- reply = reply || {};
- reply.wsrid = msg.wsrid;
- ws.send(Gun.text.ify(reply));
- });
- });
- ws.off = function(m){
- console.log("ws.off", m);
- ws.send = null;
- }
- ws.on('close', ws.off);
- ws.on('error', ws.off);
- });
-}
\ No newline at end of file
+var Gun = require('../gun')
+, url = require('url');
+module.exports = function(wss, server){
+ wss.on('connection', function(ws){
+ var req = {};
+ ws.upgradeReq = ws.upgradeReq || {};
+ req.url = url.parse(ws.upgradeReq.url||'');
+ req.method = (ws.upgradeReq.method||'').toLowerCase();
+ req.headers = ws.upgradeReq.headers || {};
+ //console.log("wsReq", req);
+ ws.on('message', function(msg){
+ msg = Gun.obj.ify(msg);
+ msg.url = msg.url || {};
+ msg.url.pathname = (req.url.pathname||'') + (msg.url.pathname||'');
+ Gun.obj.map(req.url, function(val, i){
+ msg.url[i] = msg.url[i] || val; // reattach url
+ });
+ msg.method = msg.method || req.method;
+ msg.headers = msg.headers || {};
+ Gun.obj.map(req.headers, function(val, i){
+ msg.headers[i] = msg.headers[i] || val; // reattach headers
+ });
+ server.call(ws, msg, function(reply){
+ if(!ws || !ws.send){ return }
+ reply = reply || {};
+ reply.wsrid = msg.wsrid;
+ ws.send(Gun.text.ify(reply));
+ });
+ });
+ ws.off = function(m){
+ console.log("ws.off", m);
+ ws.send = null;
+ }
+ ws.on('close', ws.off);
+ ws.on('error', ws.off);
+ });
+}
diff --git a/lib/wsp.js b/lib/wsp.js
index a8655bb8..f5f79c75 100644
--- a/lib/wsp.js
+++ b/lib/wsp.js
@@ -1,153 +1,161 @@
-;(function(wsp){
- var Gun = require('gun/gun')
- , formidable = require('formidable')
- , ws = require('ws').Server
- , http = require('./http')
- , url = require('url');
- Gun.on('opt').event(function(gun, opt){
- gun.__.opt.ws = opt.ws = gun.__.opt.ws || opt.ws || {};
- gun.attach = gun.attach || function(app){
- if(app.use){
- app.use(gun.server);
- }
- var listen = app.listen;
- app.listen = function(port){
- var server = listen.apply(app, arguments);
- gun.__.opt.ws.server = gun.__.opt.ws.server || opt.ws.server || server;
- gun.__.opt.ws.path = gun.__.opt.ws.path || opt.ws.path || '/gun';
- require('./ws')(gun.server.websocket = gun.server.websocket || new ws(gun.__.opt.ws), function(req, res){
- var ws = this;
- req.headers['gun-tid'] = ws.tid = ws.tid? ws.tid : req.headers['gun-tid'];
- ws.sub = ws.sub || gun.server.on('network').event(function(msg){
- if(!ws || !ws.send){ return this.off() }
- if(!msg || (msg.headers && msg.headers['gun-tid'] === ws.tid)){ return }
- delete msg.wsrid; // depreciate this!
- ws.send(Gun.text.ify(msg));
- });
- gun.__.opt.hooks.transport(req, res);
- });
- gun.__.opt.ws.port = port || opt.ws.port || gun.__.opt.ws.port || 80;
- return server;
- }
- return gun;
- }
- gun.server = gun.server || function(req, res, next){
- //console.log("\n\n GUN SERVER!", req);
- next = next || function(){};
- if(!req || !res){ return next() }
- if(!req.url){ return next() }
- if(!req.method){ return next() }
- var msg = {};
- msg.url = url.parse(req.url, true);
- if(!gun.server.regex.test(msg.url.pathname)){ return next() }
- if(msg.url.pathname.replace(gun.server.regex,'').slice(0,3).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;
- }
- http(req, res, function(req, res){
- if(!req){ return next() }
- var tab, cb = res = require('./jsonp')(req, res);
- if(req.headers && (tab = req.headers['gun-tid'])){
- tab = (gun.server.peers = gun.server.peers || {})[tab] = gun.server.peers[tab] || {tid: tab};
- tab.sub = tab.sub || gun.server.on('network').event(function(req){
- if(!tab){ return this.off() } // self cleans up after itself!
- if(!req || (req.headers && req.headers['gun-tid'] === tab.tid)){ return }
- (tab.queue = tab.queue || []).push(req);
- tab.drain(tab.reply);
- });
- cb = function(r){ (r.headers||{}).poll = gun.__.opt.poll; res(r) }
- tab.drain = tab.drain || function(res){
- if(!res || !tab || !tab.queue || !tab.queue.length){ return }
- res({headers: {'gun-tid': tab.tid}, body: tab.queue });
- tab.off = setTimeout(function(){ tab = null }, gun.__.opt.pull);
- tab.reply = tab.queue = null;
- return true;
- }
- clearTimeout(tab.off);
- if(req.headers.pull){
- if(tab.drain(cb)){ return }
- return tab.reply = cb;
- }
- }
- gun.__.opt.hooks.transport(req, cb);
- });
- }
- gun.server.on = gun.server.on || Gun.on.create();
- gun.__.opt.poll = gun.__.opt.poll || opt.poll || 1;
- gun.__.opt.pull = gun.__.opt.pull || opt.pull || gun.__.opt.poll * 1000;
- gun.server.regex = gun.__.opt.route = gun.__.opt.route || opt.route || /^\/gun/i;
- if((gun.__.opt.maxSockets = opt.maxSockets || gun.__.opt.maxSockets) !== false){
- require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = gun.__.opt.maxSockets || Infinity; // WARNING: Document this!
- }
- /* gun.server.xff = function(r){
- if(!r){ return '' }
- var req = {headers: r.headers || {}, connection: r.connection || {}, socket: r.socket || {}};
- return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || (req.connection.socket || {}).remoteAddress || '';
- } */
- gun.server.transport = gun.server.transport || (function(){
- // all streams, technically PATCH but implemented as POST, are forwarded to other trusted peers
- // except for the ones that are listed in the message as having already been sending to.
- // all states, implemented with GET, are replied to the source that asked for it.
- function tran(req, cb){
- //console.log("gun.server", req);
- req.method = req.body? 'post' : 'get'; // post or get is based on whether there is a body or not
- req.url.key = req.url.pathname.replace(gun.server.regex,'').replace(/^\//i,'') || '';
- if('get' == req.method){ return tran.load(req, cb) }
- if('post' == req.method){ return tran.post(req, cb) }
- cb({body: {hello: 'world'}});
- }
- tran.load = function(req, cb){
- var key = req.url.key
- , reply = {headers: {'Content-Type': tran.json}};
- if(!key){
- if(!Gun.obj.has(req.url.query, Gun._.soul)){
- return cb({headers: reply.headers, body: {err: "No key or soul to load."}});
- }
- key = {};
- key[Gun._.soul] = req.url.query[Gun._.soul];
- }
- gun.load(key, function(err, node){
- //tran.sub.scribe(req.tab, node._[Gun._.soul]);
- cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : node || null)});
- });
- }
- tran.post = function(req, cb){
- // NOTE: It is highly recommended you do your own POSTs through your own API that then saves to gun manually.
- // This will give you much more fine-grain control over security, transactions, and what not.
- var reply = {headers: {'Content-Type': tran.json}};
- if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
- if(req.url.key && Gun.obj.has(req.body, Gun._.soul)){ // key hook!
- console.log("TODO: BUG! IMPLEMENT KEY TRANSPORT HOOK!");
- return;
- }
- // saving
- Gun.obj.map(req.body, function(node, soul){
- if(soul != Gun.is.soul.on(node)){ return this.end("No soul!") }
- gun.load(node._, this.add(soul));
- }, Gun.fns.sum(function(err){
- if(err){ return cb({headers: reply.headers, body: {err: err}}) }
- gun.union(req.body, function(err, context){
- if(err || context.err || !context.nodes){ return cb({headers: reply.headers, body: {err: err || context.err || "Union failed." }}) }
- if(!Gun.fns.is(gun.__.opt.hooks.set)){ return cb({headers: reply.headers, body: {err: "Persistence not supported." }}) }
- gun.__.opt.hooks.set(context.nodes, function(err, data){ // since we've already manually done the union, we can now directly call the persistence layer.
- if(err){ return cb({headers: reply.headers, body: {err: err || "Persistence failed." }}) }
- cb({headers: reply.headers, body: {ok: "Persisted."}});
- });
- });
- }));
- gun.server.on('network').emit(req);
- }
- gun.server.on('network').event(function(req){
- // TODO: MARK! You should move the networking events to here, not in WSS only.
- });
- tran.json = 'application/json';
- return tran;
- }());
-
- opt.hooks = opt.hooks || {};
- gun.opt({hooks: {
- transport: opt.hooks.transport || gun.server.transport
- }}, true);
- });
-}({}));
\ No newline at end of file
+;(function(wsp){
+ var Gun = require('../gun')
+ , formidable = require('formidable')
+ , ws = require('ws').Server
+ , http = require('./http')
+ , url = require('url');
+ Gun.on('opt').event(function(gun, opt){
+ gun.__.opt.ws = opt.ws = gun.__.opt.ws || opt.ws || {};
+ gun.attach = gun.attach || function(app){
+ if(app.use){
+ app.use(gun.server);
+ }
+ var listen = app.listen;
+ app.listen = function(port){
+ var server = listen.apply(app, arguments);
+ gun.__.opt.ws.server = gun.__.opt.ws.server || opt.ws.server || server;
+ gun.__.opt.ws.path = gun.__.opt.ws.path || opt.ws.path || '/gun';
+ require('./ws')(gun.server.websocket = gun.server.websocket || new ws(gun.__.opt.ws), function(req, res){
+ var ws = this;
+ req.headers['gun-tid'] = ws.tid = ws.tid? ws.tid : req.headers['gun-tid'];
+ ws.sub = ws.sub || gun.server.on('network').event(function(msg){
+ if(!ws || !ws.send){ return this.off() }
+ if(!msg || (msg.headers && msg.headers['gun-tid'] === ws.tid)){ return }
+ delete msg.wsrid; // depreciate this!
+ ws.send(Gun.text.ify(msg));
+ });
+ gun.__.opt.hooks.transport(req, res);
+ });
+ gun.__.opt.ws.port = port || opt.ws.port || gun.__.opt.ws.port || 80;
+ return server;
+ }
+ return gun;
+ }
+ gun.server = gun.server || function(req, res, next){
+ //console.log("\n\n GUN SERVER!", req);
+ next = next || function(){};
+ if(!req || !res){ return next() }
+ if(!req.url){ return next() }
+ if(!req.method){ return next() }
+ var msg = {};
+ msg.url = url.parse(req.url, true);
+ if(!gun.server.regex.test(msg.url.pathname)){ return next() }
+ if(msg.url.pathname.replace(gun.server.regex,'').slice(0,3).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;
+ }
+ http(req, res, function(req, res){
+ if(!req){ return next() }
+ var tab, cb = res = require('./jsonp')(req, res);
+ if(req.headers && (tab = req.headers['gun-tid'])){
+ tab = (gun.server.peers = gun.server.peers || {})[tab] = gun.server.peers[tab] || {tid: tab};
+ tab.sub = tab.sub || gun.server.on('network').event(function(req){
+ if(!tab){ return this.off() } // self cleans up after itself!
+ if(!req || (req.headers && req.headers['gun-tid'] === tab.tid)){ return }
+ (tab.queue = tab.queue || []).push(req);
+ tab.drain(tab.reply);
+ });
+ cb = function(r){ (r.headers||{}).poll = gun.__.opt.poll; res(r) }
+ tab.drain = tab.drain || function(res){
+ if(!res || !tab || !tab.queue || !tab.queue.length){ return }
+ res({headers: {'gun-tid': tab.tid}, body: tab.queue });
+ tab.off = setTimeout(function(){ tab = null }, gun.__.opt.pull);
+ tab.reply = tab.queue = null;
+ return true;
+ }
+ clearTimeout(tab.off);
+ if(req.headers.pull){
+ if(tab.drain(cb)){ return }
+ return tab.reply = cb;
+ }
+ }
+ gun.__.opt.hooks.transport(req, cb);
+ });
+ }
+ gun.server.on = gun.server.on || Gun.on.create();
+ gun.__.opt.poll = gun.__.opt.poll || opt.poll || 1;
+ gun.__.opt.pull = gun.__.opt.pull || opt.pull || gun.__.opt.poll * 1000;
+ gun.server.regex = gun.__.opt.route = gun.__.opt.route || opt.route || /^\/gun/i;
+ if((gun.__.opt.maxSockets = opt.maxSockets || gun.__.opt.maxSockets) !== false){
+ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = gun.__.opt.maxSockets || Infinity; // WARNING: Document this!
+ }
+ /* gun.server.xff = function(r){
+ if(!r){ return '' }
+ var req = {headers: r.headers || {}, connection: r.connection || {}, socket: r.socket || {}};
+ return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || (req.connection.socket || {}).remoteAddress || '';
+ } */
+ gun.server.transport = gun.server.transport || (function(){
+ // all streams, technically PATCH but implemented as POST, are forwarded to other trusted peers
+ // except for the ones that are listed in the message as having already been sending to.
+ // all states, implemented with GET, are replied to the source that asked for it.
+ function tran(req, cb){
+ //console.log("gun.server", req);
+ req.method = req.body? 'post' : 'get'; // post or get is based on whether there is a body or not
+ req.url.key = req.url.pathname.replace(gun.server.regex,'').replace(/^\//i,'') || '';
+ if('get' == req.method){ return tran.load(req, cb) }
+ if('post' == req.method){ return tran.post(req, cb) }
+ cb({body: {hello: 'world'}});
+ }
+ tran.load = function(req, cb){
+ var key = req.url.key
+ , reply = {headers: {'Content-Type': tran.json}};
+ if(!key){
+ if(!Gun.obj.has(req.url.query, Gun._.soul)){
+ return cb({headers: reply.headers, body: {err: "No key or soul to load."}});
+ }
+ key = {};
+ key[Gun._.soul] = req.url.query[Gun._.soul];
+ }
+ gun.load(key, function(err, node){
+ //tran.sub.scribe(req.tab, node._[Gun._.soul]);
+ cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : node || null)});
+ });
+ }
+ tran.post = function(req, cb){
+ // NOTE: It is highly recommended you do your own POSTs through your own API that then saves to gun manually.
+ // This will give you much more fine-grain control over security, transactions, and what not.
+ var reply = {headers: {'Content-Type': tran.json}};
+ if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
+ if(tran.post.key(req, cb)){ return }
+ // saving
+ Gun.obj.map(req.body, function(node, soul){
+ if(soul != Gun.is.soul.on(node)){ return this.end("No soul!") }
+ gun.load(node._, this.add(soul));
+ }, Gun.fns.sum(function(err){
+ if(err){ return cb({headers: reply.headers, body: {err: err}}) }
+ gun.union(req.body, function(err, context){
+ if(err || context.err || !context.nodes){ return cb({headers: reply.headers, body: {err: err || context.err || "Union failed." }}) }
+ if(!Gun.fns.is(gun.__.opt.hooks.set)){ return cb({headers: reply.headers, body: {err: "Persistence not supported." }}) }
+ gun.__.opt.hooks.set(context.nodes, function(err, data){ // since we've already manually done the union, we can now directly call the persistence layer.
+ if(err){ return cb({headers: reply.headers, body: {err: err || "Persistence failed." }}) }
+ cb({headers: reply.headers, body: {ok: "Persisted."}});
+ });
+ });
+ }));
+ gun.server.on('network').emit(req);
+ }
+ tran.post.key = function(req, cb){ // key hook!
+ if(!req || !req.url || !req.url.key || !Gun.obj.has(req.body, Gun._.soul)){ return }
+ var index = {};
+ index[req.url.key] = req.body[Gun._.soul];
+ console.log("NASTY INDEX", index);
+ gun.key(index, function(err, reply){
+ if(err){ return cb({headers: {'Content-Type': tran.json}, body: {err: err}}) }
+ cb({headers: {'Content-Type': tran.json}, body: reply});
+ });
+ return true;
+ }
+ gun.server.on('network').event(function(req){
+ // TODO: MARK! You should move the networking events to here, not in WSS only.
+ });
+ tran.json = 'application/json';
+ return tran;
+ }());
+
+ opt.hooks = opt.hooks || {};
+ gun.opt({hooks: {
+ transport: opt.hooks.transport || gun.server.transport
+ }}, true);
+ });
+}({}));
diff --git a/lib/xhr.js b/lib/xhr.js
index cbaaf54d..16e9e843 100644
--- a/lib/xhr.js
+++ b/lib/xhr.js
@@ -1,354 +1,354 @@
-;(function(){
- var Gun = require('gun/gun')
- , formidable = require('formidable')
- , url = require('url')
- , meta = {};
- Gun.on('opt').event(function(gun, opt){
- gun.server = gun.server || function(req, res, next){ // this whole function needs refactoring and modularization
- //console.log("\n\n GUN SERVER!");
- next = next || function(){};
- if(!req || !res){ return next() }
- if(!req.url){ return next() }
- if(!req.method){ return next() }
- 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;
- }
- msg.url.key = msg.url.key.replace(/^\//i,'') || ''; // strip the base slash
- msg.method = (req.method||'').toLowerCase();
- msg.headers = req.headers;
- var body
- , form = new formidable.IncomingForm()
- , post = function(err, body){
- msg.body = body;
- gun.__.opt.hooks.transport(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);
- });
- }
- }
- meta.CORS(req, res); // add option to disable this
- if(Gun.obj.has(reply,'chunk')){
- res.write(Gun.text.ify(reply.chunk) || '');
- }
- if(Gun.obj.has(reply,'body')){
- res.end(Gun.text.ify(reply.body) || '');
- }
- });
- }
- form.on('field',function(k,v){
- (body = body || {})[k] = v;
- }).on('file',function(k,v){
- return; // files not supported in gun yet
- }).on('error',function(e){
- if(form.done){ return }
- post(e);
- }).on('end', function(){
- if(form.done){ return }
- post(null, body);
- });
- form.parse(req);
- }
- gun.server.regex = /^\/gun/i;
- if(!gun.__.opt.keepMaxSockets){ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = Infinity } // WARNING: Document this!
-
- gun.server.transport = (function(){
- function tran(req, cb){
- //console.log(req);
- req.sub = req.headers['gun-sub']; // grab the sub
- req.tab = tran.sub.s[req.sub] || {}; // check to see if there already is a tab associated with it, or create one
- req.tab.sub = req.sub = req.sub || Gun.text.random(); // Generate a session id if we don't already have one
- req.tran = tran.xhr(req, cb) || tran.jsonp(req, cb); // polyfill transport layer
- clearTimeout(req.tab.timeout);
- // raw test for now, no auth:
- if(!req.tran){ return cb({headers: {"Content-Type": tran.json}, body: {err: "No transport layer!"}}) }
- if('post' === req.method || 'patch' === req.method){ return tran.post(req, req.tran) } // TODO: Handle JSONP emulated POST via GET
- if('get' !== req.method){ return req.tran({body: {err: "Invalid method"}}) }
- if(!req.url.key){ return tran.sub(req, req.tran) } // get acts as sub, too.
- return tran.load(req, req.tran); // else load the state for the tab!
- }
- tran.load = function(req, cb){
- var reply = {}, key;
- reply.headers = {'Content-Type': tran.json};
- reply.headers['Gun-Sub'] = req.tab.sub = req.sub;
- key = (Gun._.meta == req.url.key)? req.url.query : req.url.key;
- console.log("Loading", req.url.key, 'for', req.tab);
- gun.load(key, function(node){
- tran.sub.scribe(req.tab, node._[Gun._.soul]);
- cb({
- headers: reply.headers
- ,body: node
- });
- }).blank(function(){
- cb({
- headers: reply.headers
- ,body: null
- });
- }).dud(function(err){
- cb({
- headers: reply.headers
- ,body: {err: err || "Unknown error."}
- });
- });
- }
- tran.post = function(req, cb){ // post is used as patch, sad that patch has such poor support
- if(!req.body){ return cb({body: {err: "No body"}}) }
- if(req.url.key && Gun.obj.has(req.body, Gun._.soul)){ // key hook!
- req.tab = tran.sub.s[req.sub] || {};
- req.tab.sub = req.sub;
- console.log("key.hook", req.tab);
- tran.sub.scribe(req.tab, req.body[Gun._.soul]);
- return gun.load(req.body).get(function(frozen){
- // NEED to allow a security callback so server can tamper with this!
- this.key(req.url.key, function(err, reply){
- reply = reply || {};
- if(err){ reply.err = err }
- reply = {body: reply};
- reply.headers = {'Content-Type': tran.json};
- reply.headers['Gun-Sub'] = req.tab.sub;
- cb(reply);
- });
- }); // do I need to handle the blank case? :/ Not sure.
- }
- // raw test for now, no auth:
- // should probably load all the nodes first? YES.
- var context = gun.union(req.body, function(err, context){ // data safely transformed
- cb = cb || function(){};
- if(err || context.err){ return cb({body: {err: context.err}}) }
- if(Gun.fns.is(gun.__.opt.hooks.set)){
- gun.__.opt.hooks.set(context.nodes, function saved(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved
- var body = {};
- if(err){
- body.err = err ;
- }
- if(!req.sub){
- if(!err){
- body = defer.map({}, context.nodes, 1);
- }
- return cb({body: body});
- }
- var now = tran.post.s[req.sub]; // begin our stupid Chrome fix, we should abstract this out into defer (where it belogns) to keep things clean.
- if(!now){ return } // utoh we've lost our reply to the tab!
- clearTimeout(now.timeout);
- now.body = now.body || {}; // make sure we have a body for our multi-response in a single response.
- if(req.wait){ // did this request get deferred?
- (now.body.refed = now.body.refed || {})[req.wait] = err? {err: err} : defer.map({}, context.nodes, 1); // then reply to it "here".
- } else {
- now.body.reply = err? {err: err} : defer.map({}, context.nodes, 1); // else this is the original POST that had to be upgraded.
- }
- if(0 < (now.count = ((now.count || 0) - 1))){
- // Don't reply till all deferred POSTs have successfully heard back from S3. (Sarcasm: Like counting guarantees that)
- return now.timeout = setTimeout(saved, gun.__.opt.throttle * 2 * 1000); // reply not guaranteed, so time it out, in seconds.
- }
- if(Gun.fns.is(now)){
- now({body: now.body}); // FINALLY reply for ALL the POSTs for that session that accumulated.
- } else {
- // console.log("Error! We deleted our response!");
- }
- Gun.obj.del(tran.post.s, req.sub); // clean up our memory.
- // need to rewrite that if Stream is enabled that both Stream + State save are guaranteed before replying.
- });
- // stuff past this point is just stupid implementation optimizations.
- function defer(nodes, req){ // because Chrome can only handle 4 requests at a time, sad face.
- if(!req.sub){
- return;
- }
- var next = tran.post.s[req.sub];
- if(!next){ // was there a previous POST? If not, we become the previous POST.
- //cb({chunk: ''}); // because on some services (heroku) you need to reply starting a stream to keep the connection open.
- return tran.post.s[req.sub] = cb;
- }
- next.count = (next.count || 1) + 1; // start counting how many we accumulate
- next.body = next.body || {}; // this becomes the polyfill for all the posts
- next.body.refed = next.body.refed || {}; // where we refeed the responses for the deferred POSTs.
- req.wait = Gun.text.random(); // generate an random id for this deferred POST.
- next.body.refed[req.wait] = false; // establish that we are incomplete.
- cb({body: {defer: req.wait}}); // end this POST immediately so Chrome only ever uses a couple connections.
- cb = null; // null it out so we don't accidentally reply to it once we hear back from S3.
- }
- defer.map = function(now, nodes, val){ // shortcut for maping which nodes were saved successfully
- if(!now){ return }
- Gun.obj.map(nodes, function(node, soul, map){
- now[soul] = val;
- });
- return now;
- }
- defer(context.nodes, req); // actually do the weird stuff to make Chrome not be slow
- } else {
- context.err = "Warning! You have no persistence layer to save to!";
- Gun.log(context.err);
- cb({body: {err: "Server has no persistence layer!"}});
- }
- });
- if(context.err){
- cb({body: {err: context.err}});
- return cb = null;
- }
- Gun.obj.map(context.nodes, function(node, soul){ // live push the stream out in realtime to every tab subscribed
- var msg = {};
- msg.headers = req.headers; // polyfill the delta as its own message.
- msg.body = node;
- console.log("emit delta", soul);
- tran.push(soul).emit(msg);
- });
- }
- tran.post.s = {};
- tran.sub = function(req, cb){
- //console.log("<-- ", req.sub, req.tran ," -->");
- req.tab = tran.sub.s[req.sub];
- if(!req.tab){
- cb({
- headers: {'Gun-Sub': ''}
- ,body: {err: "Please re-initialize sub."}
- });
- return;
- }
- //console.log("\n\n\n THE CURRENT STATUS IS");console.log(req.tab);
- if(req.tab.queue && req.tab.queue.length){
- tran.clean(req.tab); // We flush their data now, if they don't come back for more within timeout, we remove their session
- console.log("_____ NOW PUSHING YOUR DATA ______", req.sub);
- cb({ headers: {'Gun-Sub': req.sub} });
- while(1 < req.tab.queue.length){
- cb({ chunk: req.tab.queue.shift() });
- }
- cb({ body: req.tab.queue.shift() });
- } else {
- cb({chunk: ''}); // same thing as the defer code, initialize a stream to support some services (heroku).
- req.tab.reply = cb;
- console.log("_____ STANDING BY, WAITING FOR DATA ______", req.sub);
- }
- }
- tran.sub.s = {};
- tran.clean = function(tab, mult){
- if(!tab){ return }
- mult = mult || 1;
- clearTimeout(tab.timeout);
- tab.timeout = setTimeout(function(){
- if(!tab){ return }
- if(tab.reply){ tab.reply({body: {err: "Connection timed out"}}) }
- console.log("!!!! DISCONNECTING CLIENT !!!!!", tab.sub);
- Gun.obj.del(tran.sub.s, tab.sub)
- }, gun.__.opt.disconnect * mult * 1000); // in seconds
- }
- tran.sub.scribe = function(tab, soul){
- tran.sub.s[tab.sub] = tab;
- tab.subs = tab.subs || {};
- console.log("meow subscribes", soul);
- tab.subs[soul] = tab.subs[soul] || tran.push(soul).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
- console.log('FROM:', req.sub, "TO:", tab.sub);
- tran.clean(tab);
- if(tab.reply){
- tab.reply({
- headers: {'Gun-Sub': tab.sub}
- ,body: req.body
- });
- tab.reply = null;
- return;
- }
- (tab.queue = tab.queue || []).push(req.body);
- });
- tran.clean(tab, 2);
- }
- tran.xhr = function(req, cb){ // Streaming Long Polling
- return req.tran || (req.headers['x-requested-with'] === 'XMLHttpRequest'? transport : null);
- function transport(res){
- if(!res){ return }
- var reply = {headers: {}};
- if(res.headers){
- Gun.obj.map(res.headers, function(val, field){
- reply.headers[field] = val;
- });
- }
- reply.headers["Content-Type"] = tran.json;
- if(Gun.obj.has(res,'chunk')){
- cb({
- headers: reply.headers
- ,chunk: Gun.text.ify(res.chunk) + '\n'
- })
- }
- if(Gun.obj.has(res,'body')){
- cb({
- headers: reply.headers
- ,body: Gun.text.ify(res.body)
- })
- }
- }
- }
- tran.jsonp = function(req, cb){
- var reply = {headers: {}};
- if(req.tran || req.headers['x-requested-with']){ return }
- if((req.url.query||{}).jsonp){
- cb.jsonp = req.url.query.jsonp;
- Gun.obj.del(req.url.query, 'jsonp');
- req.headers['x-requested-with'] = 'jsonp'; // polyfill
- req.sub = req.headers['gun-sub'] = req.headers['gun-sub'] || req.url.query['Gun-Sub'] || req.url.query['gun-sub'];
- Gun.obj.del(req.url.query, 'Gun-Sub');
- Gun.obj.del(req.url.query, 'gun-sub');
- return transport;
- }
- function transport(res){
- if(!res){ return }
- if(res.headers){
- Gun.obj.map(res.headers, function(val, field){
- reply.headers[field] = val;
- });
- }
- if(Gun.obj.has(res,'chunk') && (!reply.body || Gun.list.is(reply.chunks))){
- (reply.chunks = reply.chunks || []).push(res.chunk);
- }
- if(Gun.obj.has(res,'body')){
- reply.body = res.body; // self-reference yourself so on the client we can get the headers and body.
- reply.body = ';'+ cb.jsonp + '(' + Gun.text.ify(reply) + ');'; // javascriptify it! can't believe the client trusts us.
- cb(reply);
- }
- }
- }
- tran.json = 'application/json';
- tran.push = Gun.on.create();
- return tran;
- }());
-
- opt.hooks = opt.hooks || {};
- gun.opt({hooks: {
- transport: opt.hooks.transport || gun.server.transport
- }}, true);
- });
- meta.json = 'application/json';
- meta.JSON = function(res, data, multi){
- if(res && !res._headerSent){
- res.setHeader('Content-Type', meta.json);
- }
- if(!data && multi){
- res.write(Gun.text.ify(multi||'') + '\n');
- return;
- }
- return res.end(Gun.text.ify(data||''));
- };
- meta.CORS = function(req, res){
- if(!res || res.CORSHeader || res._headerSent){ return }
- res.setHeader("Access-Control-Allow-Origin", "*");
- res.setHeader("Access-Control-Allow-Methods", ["POST", "GET", "PUT", "DELETE", "OPTIONS"]);
- res.setHeader("Access-Control-Allow-Credentials", false);
- res.setHeader("Access-Control-Max-Age", 1000 * 60 * 60 * 24);
- res.setHeader("Access-Control-Allow-Headers", ["X-Requested-With", "X-HTTP-Method-Override", "Content-Type", "Accept", "Gun-Sub"]);
- res.setHeader("Access-Control-Expose-Headers", ["Content-Type", "Gun-Sub"]);
- res.CORSHeader = true;
- if(req && req.method === 'OPTIONS'){
- res.end();
- return true;
- }
- };
-}());
\ No newline at end of file
+;(function(){
+ var Gun = require('../gun')
+ , formidable = require('formidable')
+ , url = require('url')
+ , meta = {};
+ Gun.on('opt').event(function(gun, opt){
+ gun.server = gun.server || function(req, res, next){ // this whole function needs refactoring and modularization
+ //console.log("\n\n GUN SERVER!");
+ next = next || function(){};
+ if(!req || !res){ return next() }
+ if(!req.url){ return next() }
+ if(!req.method){ return next() }
+ 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;
+ }
+ msg.url.key = msg.url.key.replace(/^\//i,'') || ''; // strip the base slash
+ msg.method = (req.method||'').toLowerCase();
+ msg.headers = req.headers;
+ var body
+ , form = new formidable.IncomingForm()
+ , post = function(err, body){
+ msg.body = body;
+ gun.__.opt.hooks.transport(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);
+ });
+ }
+ }
+ meta.CORS(req, res); // add option to disable this
+ if(Gun.obj.has(reply,'chunk')){
+ res.write(Gun.text.ify(reply.chunk) || '');
+ }
+ if(Gun.obj.has(reply,'body')){
+ res.end(Gun.text.ify(reply.body) || '');
+ }
+ });
+ }
+ form.on('field',function(k,v){
+ (body = body || {})[k] = v;
+ }).on('file',function(k,v){
+ return; // files not supported in gun yet
+ }).on('error',function(e){
+ if(form.done){ return }
+ post(e);
+ }).on('end', function(){
+ if(form.done){ return }
+ post(null, body);
+ });
+ form.parse(req);
+ }
+ gun.server.regex = /^\/gun/i;
+ if(!gun.__.opt.keepMaxSockets){ require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = Infinity } // WARNING: Document this!
+
+ gun.server.transport = (function(){
+ function tran(req, cb){
+ //console.log(req);
+ req.sub = req.headers['gun-sub']; // grab the sub
+ req.tab = tran.sub.s[req.sub] || {}; // check to see if there already is a tab associated with it, or create one
+ req.tab.sub = req.sub = req.sub || Gun.text.random(); // Generate a session id if we don't already have one
+ req.tran = tran.xhr(req, cb) || tran.jsonp(req, cb); // polyfill transport layer
+ clearTimeout(req.tab.timeout);
+ // raw test for now, no auth:
+ if(!req.tran){ return cb({headers: {"Content-Type": tran.json}, body: {err: "No transport layer!"}}) }
+ if('post' === req.method || 'patch' === req.method){ return tran.post(req, req.tran) } // TODO: Handle JSONP emulated POST via GET
+ if('get' !== req.method){ return req.tran({body: {err: "Invalid method"}}) }
+ if(!req.url.key){ return tran.sub(req, req.tran) } // get acts as sub, too.
+ return tran.load(req, req.tran); // else load the state for the tab!
+ }
+ tran.load = function(req, cb){
+ var reply = {}, key;
+ reply.headers = {'Content-Type': tran.json};
+ reply.headers['Gun-Sub'] = req.tab.sub = req.sub;
+ key = (Gun._.meta == req.url.key)? req.url.query : req.url.key;
+ console.log("Loading", req.url.key, 'for', req.tab);
+ gun.load(key, function(node){
+ tran.sub.scribe(req.tab, node._[Gun._.soul]);
+ cb({
+ headers: reply.headers
+ ,body: node
+ });
+ }).blank(function(){
+ cb({
+ headers: reply.headers
+ ,body: null
+ });
+ }).dud(function(err){
+ cb({
+ headers: reply.headers
+ ,body: {err: err || "Unknown error."}
+ });
+ });
+ }
+ tran.post = function(req, cb){ // post is used as patch, sad that patch has such poor support
+ if(!req.body){ return cb({body: {err: "No body"}}) }
+ if(req.url.key && Gun.obj.has(req.body, Gun._.soul)){ // key hook!
+ req.tab = tran.sub.s[req.sub] || {};
+ req.tab.sub = req.sub;
+ console.log("key.hook", req.tab);
+ tran.sub.scribe(req.tab, req.body[Gun._.soul]);
+ return gun.load(req.body).get(function(frozen){
+ // NEED to allow a security callback so server can tamper with this!
+ this.key(req.url.key, function(err, reply){
+ reply = reply || {};
+ if(err){ reply.err = err }
+ reply = {body: reply};
+ reply.headers = {'Content-Type': tran.json};
+ reply.headers['Gun-Sub'] = req.tab.sub;
+ cb(reply);
+ });
+ }); // do I need to handle the blank case? :/ Not sure.
+ }
+ // raw test for now, no auth:
+ // should probably load all the nodes first? YES.
+ var context = gun.union(req.body, function(err, context){ // data safely transformed
+ cb = cb || function(){};
+ if(err || context.err){ return cb({body: {err: context.err}}) }
+ if(Gun.fns.is(gun.__.opt.hooks.set)){
+ gun.__.opt.hooks.set(context.nodes, function saved(err, data){ // now iterate through those nodes to S3 and get a callback once all are saved
+ var body = {};
+ if(err){
+ body.err = err ;
+ }
+ if(!req.sub){
+ if(!err){
+ body = defer.map({}, context.nodes, 1);
+ }
+ return cb({body: body});
+ }
+ var now = tran.post.s[req.sub]; // begin our stupid Chrome fix, we should abstract this out into defer (where it belogns) to keep things clean.
+ if(!now){ return } // utoh we've lost our reply to the tab!
+ clearTimeout(now.timeout);
+ now.body = now.body || {}; // make sure we have a body for our multi-response in a single response.
+ if(req.wait){ // did this request get deferred?
+ (now.body.refed = now.body.refed || {})[req.wait] = err? {err: err} : defer.map({}, context.nodes, 1); // then reply to it "here".
+ } else {
+ now.body.reply = err? {err: err} : defer.map({}, context.nodes, 1); // else this is the original POST that had to be upgraded.
+ }
+ if(0 < (now.count = ((now.count || 0) - 1))){
+ // Don't reply till all deferred POSTs have successfully heard back from S3. (Sarcasm: Like counting guarantees that)
+ return now.timeout = setTimeout(saved, gun.__.opt.throttle * 2 * 1000); // reply not guaranteed, so time it out, in seconds.
+ }
+ if(Gun.fns.is(now)){
+ now({body: now.body}); // FINALLY reply for ALL the POSTs for that session that accumulated.
+ } else {
+ // console.log("Error! We deleted our response!");
+ }
+ Gun.obj.del(tran.post.s, req.sub); // clean up our memory.
+ // need to rewrite that if Stream is enabled that both Stream + State save are guaranteed before replying.
+ });
+ // stuff past this point is just stupid implementation optimizations.
+ function defer(nodes, req){ // because Chrome can only handle 4 requests at a time, sad face.
+ if(!req.sub){
+ return;
+ }
+ var next = tran.post.s[req.sub];
+ if(!next){ // was there a previous POST? If not, we become the previous POST.
+ //cb({chunk: ''}); // because on some services (heroku) you need to reply starting a stream to keep the connection open.
+ return tran.post.s[req.sub] = cb;
+ }
+ next.count = (next.count || 1) + 1; // start counting how many we accumulate
+ next.body = next.body || {}; // this becomes the polyfill for all the posts
+ next.body.refed = next.body.refed || {}; // where we refeed the responses for the deferred POSTs.
+ req.wait = Gun.text.random(); // generate an random id for this deferred POST.
+ next.body.refed[req.wait] = false; // establish that we are incomplete.
+ cb({body: {defer: req.wait}}); // end this POST immediately so Chrome only ever uses a couple connections.
+ cb = null; // null it out so we don't accidentally reply to it once we hear back from S3.
+ }
+ defer.map = function(now, nodes, val){ // shortcut for maping which nodes were saved successfully
+ if(!now){ return }
+ Gun.obj.map(nodes, function(node, soul, map){
+ now[soul] = val;
+ });
+ return now;
+ }
+ defer(context.nodes, req); // actually do the weird stuff to make Chrome not be slow
+ } else {
+ context.err = "Warning! You have no persistence layer to save to!";
+ Gun.log(context.err);
+ cb({body: {err: "Server has no persistence layer!"}});
+ }
+ });
+ if(context.err){
+ cb({body: {err: context.err}});
+ return cb = null;
+ }
+ Gun.obj.map(context.nodes, function(node, soul){ // live push the stream out in realtime to every tab subscribed
+ var msg = {};
+ msg.headers = req.headers; // polyfill the delta as its own message.
+ msg.body = node;
+ console.log("emit delta", soul);
+ tran.push(soul).emit(msg);
+ });
+ }
+ tran.post.s = {};
+ tran.sub = function(req, cb){
+ //console.log("<-- ", req.sub, req.tran ," -->");
+ req.tab = tran.sub.s[req.sub];
+ if(!req.tab){
+ cb({
+ headers: {'Gun-Sub': ''}
+ ,body: {err: "Please re-initialize sub."}
+ });
+ return;
+ }
+ //console.log("\n\n\n THE CURRENT STATUS IS");console.log(req.tab);
+ if(req.tab.queue && req.tab.queue.length){
+ tran.clean(req.tab); // We flush their data now, if they don't come back for more within timeout, we remove their session
+ console.log("_____ NOW PUSHING YOUR DATA ______", req.sub);
+ cb({ headers: {'Gun-Sub': req.sub} });
+ while(1 < req.tab.queue.length){
+ cb({ chunk: req.tab.queue.shift() });
+ }
+ cb({ body: req.tab.queue.shift() });
+ } else {
+ cb({chunk: ''}); // same thing as the defer code, initialize a stream to support some services (heroku).
+ req.tab.reply = cb;
+ console.log("_____ STANDING BY, WAITING FOR DATA ______", req.sub);
+ }
+ }
+ tran.sub.s = {};
+ tran.clean = function(tab, mult){
+ if(!tab){ return }
+ mult = mult || 1;
+ clearTimeout(tab.timeout);
+ tab.timeout = setTimeout(function(){
+ if(!tab){ return }
+ if(tab.reply){ tab.reply({body: {err: "Connection timed out"}}) }
+ console.log("!!!! DISCONNECTING CLIENT !!!!!", tab.sub);
+ Gun.obj.del(tran.sub.s, tab.sub)
+ }, gun.__.opt.disconnect * mult * 1000); // in seconds
+ }
+ tran.sub.scribe = function(tab, soul){
+ tran.sub.s[tab.sub] = tab;
+ tab.subs = tab.subs || {};
+ console.log("meow subscribes", soul);
+ tab.subs[soul] = tab.subs[soul] || tran.push(soul).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
+ console.log('FROM:', req.sub, "TO:", tab.sub);
+ tran.clean(tab);
+ if(tab.reply){
+ tab.reply({
+ headers: {'Gun-Sub': tab.sub}
+ ,body: req.body
+ });
+ tab.reply = null;
+ return;
+ }
+ (tab.queue = tab.queue || []).push(req.body);
+ });
+ tran.clean(tab, 2);
+ }
+ tran.xhr = function(req, cb){ // Streaming Long Polling
+ return req.tran || (req.headers['x-requested-with'] === 'XMLHttpRequest'? transport : null);
+ function transport(res){
+ if(!res){ return }
+ var reply = {headers: {}};
+ if(res.headers){
+ Gun.obj.map(res.headers, function(val, field){
+ reply.headers[field] = val;
+ });
+ }
+ reply.headers["Content-Type"] = tran.json;
+ if(Gun.obj.has(res,'chunk')){
+ cb({
+ headers: reply.headers
+ ,chunk: Gun.text.ify(res.chunk) + '\n'
+ })
+ }
+ if(Gun.obj.has(res,'body')){
+ cb({
+ headers: reply.headers
+ ,body: Gun.text.ify(res.body)
+ })
+ }
+ }
+ }
+ tran.jsonp = function(req, cb){
+ var reply = {headers: {}};
+ if(req.tran || req.headers['x-requested-with']){ return }
+ if((req.url.query||{}).jsonp){
+ cb.jsonp = req.url.query.jsonp;
+ Gun.obj.del(req.url.query, 'jsonp');
+ req.headers['x-requested-with'] = 'jsonp'; // polyfill
+ req.sub = req.headers['gun-sub'] = req.headers['gun-sub'] || req.url.query['Gun-Sub'] || req.url.query['gun-sub'];
+ Gun.obj.del(req.url.query, 'Gun-Sub');
+ Gun.obj.del(req.url.query, 'gun-sub');
+ return transport;
+ }
+ function transport(res){
+ if(!res){ return }
+ if(res.headers){
+ Gun.obj.map(res.headers, function(val, field){
+ reply.headers[field] = val;
+ });
+ }
+ if(Gun.obj.has(res,'chunk') && (!reply.body || Gun.list.is(reply.chunks))){
+ (reply.chunks = reply.chunks || []).push(res.chunk);
+ }
+ if(Gun.obj.has(res,'body')){
+ reply.body = res.body; // self-reference yourself so on the client we can get the headers and body.
+ reply.body = ';'+ cb.jsonp + '(' + Gun.text.ify(reply) + ');'; // javascriptify it! can't believe the client trusts us.
+ cb(reply);
+ }
+ }
+ }
+ tran.json = 'application/json';
+ tran.push = Gun.on.create();
+ return tran;
+ }());
+
+ opt.hooks = opt.hooks || {};
+ gun.opt({hooks: {
+ transport: opt.hooks.transport || gun.server.transport
+ }}, true);
+ });
+ meta.json = 'application/json';
+ meta.JSON = function(res, data, multi){
+ if(res && !res._headerSent){
+ res.setHeader('Content-Type', meta.json);
+ }
+ if(!data && multi){
+ res.write(Gun.text.ify(multi||'') + '\n');
+ return;
+ }
+ return res.end(Gun.text.ify(data||''));
+ };
+ meta.CORS = function(req, res){
+ if(!res || res.CORSHeader || res._headerSent){ return }
+ res.setHeader("Access-Control-Allow-Origin", "*");
+ res.setHeader("Access-Control-Allow-Methods", ["POST", "GET", "PUT", "DELETE", "OPTIONS"]);
+ res.setHeader("Access-Control-Allow-Credentials", false);
+ res.setHeader("Access-Control-Max-Age", 1000 * 60 * 60 * 24);
+ res.setHeader("Access-Control-Allow-Headers", ["X-Requested-With", "X-HTTP-Method-Override", "Content-Type", "Accept", "Gun-Sub"]);
+ res.setHeader("Access-Control-Expose-Headers", ["Content-Type", "Gun-Sub"]);
+ res.CORSHeader = true;
+ if(req && req.method === 'OPTIONS'){
+ res.end();
+ return true;
+ }
+ };
+}());
diff --git a/package.json b/package.json
index c22c07eb..11d6b5ed 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{ "name": "gun"
-, "version": "0.0.9"
+, "version": "0.0.9a"
, "author": "Mark Nadal"
, "description": "Graph engine."
, "engines": {
@@ -19,4 +19,4 @@
"start": "node init.js",
"test": "mocha"
}
-}
\ No newline at end of file
+}
diff --git a/test/s3pricing.html b/test/s3pricing.html
index cf13f4fb..3f8bb98e 100644
--- a/test/s3pricing.html
+++ b/test/s3pricing.html
@@ -1,64 +1,64 @@
-
-
-
-
-
-
-
-
-
-
-
Seconds in month:
-
-
-
-
-
Persist every seconds:
-
-
-
-
-
Over how many objects:
-
-
-
-
-
Cost $:
-
-
-
-
-
- This page is for throughput calculation, it assumes continuous load non-stop.
-
-
-
+
+
+
+
+
+
+
+
+
+
+
Seconds in month:
+
+
+
+
+
Persist every seconds:
+
+
+
+
+
Over how many objects:
+
+
+
+
+
Cost $:
+
+
+
+
+
+ This page is for throughput calculation, it assumes continuous load non-stop.
+
+
+
\ No newline at end of file
diff --git a/web/deck/deck.html b/web/deck/deck.html
index 17fdccc9..dc0331b7 100644
--- a/web/deck/deck.html
+++ b/web/deck/deck.html
@@ -1,46 +1,46 @@
-
-
-
-
Problem
-
-
-
- Hassle to deploy slow databases that are costly to scale
-
-
-
Solution
-
-
the NoDB database
-
-
-
How
-
-
peer to peer algorithms that mimic reality
-
-
-
Now
-
-
fastest growing trends ever in software history
-
-
-
Team
-
-
-
-
-
we@gunDB.io are raising $500k
+
+
+
+
Problem
+
+
+
+ Hassle to deploy slow databases that are costly to scale
+
+
+
Solution
+
+
the NoDB database
+
+
+
How
+
+
peer to peer algorithms that mimic reality
+
+
+
Now
+
+
fastest growing trends ever in software history
+
+
+
Team
+
+
+
+
+
we@gunDB.io are raising $500k
\ No newline at end of file
diff --git a/web/img/devices.svg b/web/img/devices.svg
new file mode 100644
index 00000000..90090d07
--- /dev/null
+++ b/web/img/devices.svg
@@ -0,0 +1,335 @@
+
+
+
+
diff --git a/web/img/gun.svg b/web/img/gun.svg
index 02575d93..673c91e6 100644
--- a/web/img/gun.svg
+++ b/web/img/gun.svg
@@ -1,62 +1,62 @@
-
-
-
-
+
+
+
+
diff --git a/web/img/logo.html b/web/img/logo.html
index 04c30795..33b87637 100644
--- a/web/img/logo.html
+++ b/web/img/logo.html
@@ -1,32 +1,32 @@
-
-
- gun
-
-
-
-
-
-
+
+
+ gun
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/notes.txt b/web/notes.txt
index 5221833d..f4014f36 100644
--- a/web/notes.txt
+++ b/web/notes.txt
@@ -1,93 +1,93 @@
-HOOKS:
- - TRANSPORT:
- Has to basically re-implement set/load/key over some transport.
- REMEMBER that you have to subscribe on all set/load/key (especially key).
-
-
-Notes:
-
-Messaging
-Every message has who/what/when/where/why/how metadata.
-Who is information that identifies the potential keys or locations a reply should be made to, such that a user can receive it no matter where they are
-What is the message/delta itself
-When is when the message was sent, this may have no relation to a timestamp on a delta transmission
-Where is the destination that the message is trying to get to, and the paths it has taken to get there
-Why is any arbitrary comment
-How is the transformation that caused the message, and potentially the expected transformation to happen
-
-Data
-Because data can be in more than one place at a time, its identifier should not be mixed with any whereabouts even if you are planning on only using it within your walled garden.
-Instead, the data itself should express its particular locality - information about a statue in Rome should not be identified a being Roman, but express it is Roman.
-Therefore, preferrably the identifier for a thing should be globally unique, in case it is ever shared by multiple services - that way conflict won't happen.
-This is important because that way any service is forced to do a scan on its own dataset to match with this service's own identifier and link them together - or start fresh if no match exists.
-Information is not deleted, mostly just dereferenced - which may eventually cause cold storage, and loss of data as every system has physical limitations.
-Arrays cannot be supported because cardinality is actually an expression of the data, not an identifier - for if it were, it would cause concurrency problems resulting in divergence.
-
-Identifiers
-Reality seems to be lacking any true identifiers because we perceive things in incredibly mutable states.
-Often times attributes that may appear to be static are in fact not - take for instance the color of a car.
-One generally assumes that the color will not change, since it is not observed much - however the lifetime of a program may outlive the developer. Even the sun will go dark one day.
-This is the power of graphs, particularly in a functional setting - they are searchable, potentially timelessly.
-As such identifiers should only be created in response to an attempt to match incoming unfound data with data stored.
-If successful, the service is free and should make as many extra identifiers to reference and link that data again as to avoid doing slow scans again.
-The most likely candidate for that identifier is the identifier that was given to the service, as there is high probability that the requester will make such requests again.
-Throught his means, an identifier can spread like a virus through multiple services, where if it exists it can be retrieved immediately.
-Thus, no one identifier should potentially conflict with any other identifier, as that would produce an erroneous lookup and a faulty response.
-
-Do identifiers point to subsets or nodes, or both? Where do identifiers live? Ever in the graph itself? A developer of course would want an identifier to point to both.
-However this is a little tricky, because a single node by itself is fairly useless (hermeneutics) because it has no context, especially if it is composed of many parts.
-So an identifier referencing a "single node" should technically pull in its relations as well, into the subset. But what then do we return to the developer? The subset or the node?
-If you assume the node, then identifiers "point" to different types of data - unless we allow the node the same methods as the subset, where map returns itself.
-If we return the subset, it is inconvenient that you then have to scan for the thing you were already identifying, unless the map method also accepts traversal strings.
-Identifiers are suppose to intentionally allow humans to think in a document based perspective, however there is no guarantee that the pieces in their system will remain that way.
-Take for example a car, you'd assume that the wheel is part of the car and should be referenced as such. But what happens when the tire pops? It is removed and then used as a swing.
-Now tell me, the "belongingness" of the tire is now in the hierarchy of the tree that it was hung on. Hierarchy is imposed by directional relations, not as static structures.
-So there must be a way to evolve the system, for if we assume the identifier now references the subset, giving context to the original node, we need all of them to be uniquely referenced.
-The unique references are the system's internal IDs, not the human friendly identifiers, these must be system-wide guaranteed to be unique.
-But if everything has one, no matter how small, then that increases the odds quite substantially of conflicts. Meaning larger IDs need to be generated, which is never pretty.
-This also then causes scanning issues, cause now countless tiny nodes are floating around in the system that will get mapped over, that is unpleasant for the developer.
-Unless of course there are really good tools that allow them to map with string traversals (known paths) and value traversal (matching structures), in addition to the callback.
-Meaning everything should be its own object, but really good search tools should be available to make this easy.
-If this is the case, enforce the many-solution, that you alway have a subset and never a single node - a single node is just a subset of one.
-Or is it? Or is it the subset of the things that compose the node? Do you hide these from the developer? Expose them?
-It seems like subset of one works fine when you already have the larger subset, but not when you are using the initial identifier.
-So can we mitigate that by providing really good priority loading tools?
-
-Special Characters
-These are reserved for gun, to include meta-data on the data itself.
-/ is used as a delimiter in service specific identifiers
-# is used to define the transition from a graph identifier, into the nodes themselves
-. represents walking the path through a node's scope
-_ represents non-human data in the graph that the amnesia machine uses to process stuff
-> and < are dedicated to comparing timestamp states, timelessly to avoid malicious abuse (this should always come last, because requests will be rejected without it)
-
-{
- who: {}
- ,what: {'#": 'email/hi@example.com'}
- ,when: {'>': 1406184438192 }
- ,where: {}
- ,how: '*'
-}
-{
- _: {
- #: 'email/hi@example.com'
- >: 1406184438192
- }
-}
-
-Stream
-If we receive a message with data at a symbolic scope that doesn't exist we should still save it.
-However if we attempt to read it, we should be unable to access it because the symbols haven't been connected.
-That way idempotently, when the symbols are connected, it'll then be readable if they haven't already been overwritten.
-Grouping of symbols always inherit and merge, if all of them need to be dereferenced you must explicitly dereference it.
-How do we do memory management / clean up then of old dead bad data?
-
-Init
-You initialize with a node(s) you want to connect to.
-
-Evolution
-var node = gun.create('person/joe', {}); // this creates a 'new' node with an identifier to this node
-node('name', 'joe'); // changes properties
-node('age', 24);
-node('eyes', {}); // this internally creates a new node within the same subset as joe,
+HOOKS:
+ - TRANSPORT:
+ Has to basically re-implement set/load/key over some transport.
+ REMEMBER that you have to subscribe on all set/load/key (especially key).
+
+
+Notes:
+
+Messaging
+Every message has who/what/when/where/why/how metadata.
+Who is information that identifies the potential keys or locations a reply should be made to, such that a user can receive it no matter where they are
+What is the message/delta itself
+When is when the message was sent, this may have no relation to a timestamp on a delta transmission
+Where is the destination that the message is trying to get to, and the paths it has taken to get there
+Why is any arbitrary comment
+How is the transformation that caused the message, and potentially the expected transformation to happen
+
+Data
+Because data can be in more than one place at a time, its identifier should not be mixed with any whereabouts even if you are planning on only using it within your walled garden.
+Instead, the data itself should express its particular locality - information about a statue in Rome should not be identified a being Roman, but express it is Roman.
+Therefore, preferrably the identifier for a thing should be globally unique, in case it is ever shared by multiple services - that way conflict won't happen.
+This is important because that way any service is forced to do a scan on its own dataset to match with this service's own identifier and link them together - or start fresh if no match exists.
+Information is not deleted, mostly just dereferenced - which may eventually cause cold storage, and loss of data as every system has physical limitations.
+Arrays cannot be supported because cardinality is actually an expression of the data, not an identifier - for if it were, it would cause concurrency problems resulting in divergence.
+
+Identifiers
+Reality seems to be lacking any true identifiers because we perceive things in incredibly mutable states.
+Often times attributes that may appear to be static are in fact not - take for instance the color of a car.
+One generally assumes that the color will not change, since it is not observed much - however the lifetime of a program may outlive the developer. Even the sun will go dark one day.
+This is the power of graphs, particularly in a functional setting - they are searchable, potentially timelessly.
+As such identifiers should only be created in response to an attempt to match incoming unfound data with data stored.
+If successful, the service is free and should make as many extra identifiers to reference and link that data again as to avoid doing slow scans again.
+The most likely candidate for that identifier is the identifier that was given to the service, as there is high probability that the requester will make such requests again.
+Throught his means, an identifier can spread like a virus through multiple services, where if it exists it can be retrieved immediately.
+Thus, no one identifier should potentially conflict with any other identifier, as that would produce an erroneous lookup and a faulty response.
+
+Do identifiers point to subsets or nodes, or both? Where do identifiers live? Ever in the graph itself? A developer of course would want an identifier to point to both.
+However this is a little tricky, because a single node by itself is fairly useless (hermeneutics) because it has no context, especially if it is composed of many parts.
+So an identifier referencing a "single node" should technically pull in its relations as well, into the subset. But what then do we return to the developer? The subset or the node?
+If you assume the node, then identifiers "point" to different types of data - unless we allow the node the same methods as the subset, where map returns itself.
+If we return the subset, it is inconvenient that you then have to scan for the thing you were already identifying, unless the map method also accepts traversal strings.
+Identifiers are suppose to intentionally allow humans to think in a document based perspective, however there is no guarantee that the pieces in their system will remain that way.
+Take for example a car, you'd assume that the wheel is part of the car and should be referenced as such. But what happens when the tire pops? It is removed and then used as a swing.
+Now tell me, the "belongingness" of the tire is now in the hierarchy of the tree that it was hung on. Hierarchy is imposed by directional relations, not as static structures.
+So there must be a way to evolve the system, for if we assume the identifier now references the subset, giving context to the original node, we need all of them to be uniquely referenced.
+The unique references are the system's internal IDs, not the human friendly identifiers, these must be system-wide guaranteed to be unique.
+But if everything has one, no matter how small, then that increases the odds quite substantially of conflicts. Meaning larger IDs need to be generated, which is never pretty.
+This also then causes scanning issues, cause now countless tiny nodes are floating around in the system that will get mapped over, that is unpleasant for the developer.
+Unless of course there are really good tools that allow them to map with string traversals (known paths) and value traversal (matching structures), in addition to the callback.
+Meaning everything should be its own object, but really good search tools should be available to make this easy.
+If this is the case, enforce the many-solution, that you alway have a subset and never a single node - a single node is just a subset of one.
+Or is it? Or is it the subset of the things that compose the node? Do you hide these from the developer? Expose them?
+It seems like subset of one works fine when you already have the larger subset, but not when you are using the initial identifier.
+So can we mitigate that by providing really good priority loading tools?
+
+Special Characters
+These are reserved for gun, to include meta-data on the data itself.
+/ is used as a delimiter in service specific identifiers
+# is used to define the transition from a graph identifier, into the nodes themselves
+. represents walking the path through a node's scope
+_ represents non-human data in the graph that the amnesia machine uses to process stuff
+> and < are dedicated to comparing timestamp states, timelessly to avoid malicious abuse (this should always come last, because requests will be rejected without it)
+
+{
+ who: {}
+ ,what: {'#": 'email/hi@example.com'}
+ ,when: {'>': 1406184438192 }
+ ,where: {}
+ ,how: '*'
+}
+{
+ _: {
+ #: 'email/hi@example.com'
+ >: 1406184438192
+ }
+}
+
+Stream
+If we receive a message with data at a symbolic scope that doesn't exist we should still save it.
+However if we attempt to read it, we should be unable to access it because the symbols haven't been connected.
+That way idempotently, when the symbols are connected, it'll then be readable if they haven't already been overwritten.
+Grouping of symbols always inherit and merge, if all of them need to be dereferenced you must explicitly dereference it.
+How do we do memory management / clean up then of old dead bad data?
+
+Init
+You initialize with a node(s) you want to connect to.
+
+Evolution
+var node = gun.create('person/joe', {}); // this creates a 'new' node with an identifier to this node
+node('name', 'joe'); // changes properties
+node('age', 24);
+node('eyes', {}); // this internally creates a new node within the same subset as joe,
node('eyes.color', 'blue');
\ No newline at end of file
diff --git a/web/time.html b/web/time.html
index b81ac0b5..54db60b6 100644
--- a/web/time.html
+++ b/web/time.html
@@ -1,210 +1,210 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Easiest . Database . Ever
-
Scale without pain, because it is decentralized.
-
-
-
- As easy as 1, 2, 3!
-
-
Connect to a gun server.
-
Save some data.
-
Create a key to open it up later.
-
-
var gun = Gun('http://localhost:8888/gun');
-gun.set({hello: 'world'}).key('my/first/data');
-
- Disclaimer: This is a demo only! Data is deleted every 24 hours.
-
-