mirror of
				https://github.com/amark/gun.git
				synced 2025-10-14 00:59:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1153 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1153 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| ;(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.version = 0.9;
 | |
| 		Gun.is = function(gun){ return (gun instanceof Gun)? true : false }
 | |
| 		Gun.is.value = 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 id;
 | |
| 			if(id = Gun.is.soul(v)){
 | |
| 				return id;
 | |
| 			}
 | |
| 			return false;
 | |
| 		}
 | |
| 		Gun.is.soul = function(v){
 | |
| 			if(Gun.obj.is(v)){
 | |
| 				var id;
 | |
| 				Gun.obj.map(v, function(soul, field){
 | |
| 					if(id){ return id = false } // if ID is already defined AND we're still looping through the object, it is invalid.
 | |
| 					if(field == Gun._.soul && Gun.text.is(soul)){
 | |
| 						id = soul; // we found the soul!
 | |
| 					} else {
 | |
| 						return id = false; // if there exists anything else on the object, that isn't the soul, then it is invalid.
 | |
| 					}
 | |
| 				});
 | |
| 				if(id){
 | |
| 					return id;
 | |
| 				}
 | |
| 			}
 | |
| 			return false;
 | |
| 		}
 | |
| 		Gun.is.soul.on = function(n){ return (n && n._ && n._[Gun._.soul]) || false }
 | |
| 		Gun.is.node = function(node, cb){
 | |
| 			if(!Gun.obj.is(node)){ return false }
 | |
| 			if(Gun.is.soul.on(node)){
 | |
| 				return !Gun.obj.map(node, function(value, field){ // need to invert this, because the way we check for this is via a negation.
 | |
| 					if(field == Gun._.meta){ return } // skip this.
 | |
| 					if(!Gun.is.value(value)){ return true } // it is true that this is an invalid node.
 | |
| 					if(cb){ cb(value, field) }
 | |
| 				});
 | |
| 			}
 | |
| 			return false;
 | |
| 		}
 | |
| 		Gun.is.graph = function(graph, cb, fn){
 | |
| 			var exist = false;
 | |
| 			if(!Gun.obj.is(graph)){ return false }
 | |
| 			return !Gun.obj.map(graph, function(node, soul){ // need to invert this, because the way we check for this is via a negation.
 | |
| 				if(!node || soul !== Gun.is.soul.on(node) || !Gun.is.node(node, fn)){ return true } // it is true that this is an invalid graph.
 | |
| 				if(cb){ cb(node, soul) }
 | |
| 				exist = true;
 | |
| 			}) && exist;
 | |
| 		}
 | |
| 		// Gun.ify // the serializer is too long for right here, it has been relocated towards the bottom.
 | |
| 		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); // done synchronously
 | |
| 					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){
 | |
| 					console.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); // done synchronously
 | |
| 					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){
 | |
| 			var gun = this;
 | |
| 			if(!gun || !gun.__ || !gun.__.opt){
 | |
| 				return console.log.apply(console, arguments);
 | |
| 			}
 | |
| 			if(gun.__.opt.quiet){ return }
 | |
| 			return 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('then', 'err');
 | |
| 			if(opt === null){ return gun }
 | |
| 			opt = opt || {};
 | |
| 			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.__.hook = Gun.shot('then','end');
 | |
| 			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(null);
 | |
| 			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();
 | |
| 			gun.shot('done');
 | |
| 			gun.shot.done(function(){
 | |
| 				opt = opt || {};
 | |
| 				cb = cb || function(){};
 | |
| 				cb.soul = (key||{})[Gun._.soul]; // is this a key or a soul?
 | |
| 				if(cb.soul){ // if a soul...
 | |
| 					cb.node = gun.__.graph[cb.soul]; // then attempt to grab it directly from cache in the graph
 | |
| 				} else { // if not...
 | |
| 					cb.node = gun.__.keys[key]; // attempt to grab it directly from our key cache
 | |
| 					(gun._.keys = gun._.keys || {})[key] = cb.node? 1 : 0; // set a key marker on it
 | |
| 				}
 | |
| 				if(!opt.force && cb.node){ // if it was in cache, then...
 | |
| 					Gun.log.call(gun, "load via gun");
 | |
| 					gun._.node = cb.node; // assign it to this context
 | |
| 					cb.call(gun, null, Gun.obj.copy(gun._.node)); // frozen copy
 | |
| 					gun.shot('then').fire();
 | |
| 					return;
 | |
| 				}
 | |
| 				cb.fn = function(){}
 | |
| 				// missing: hear shots! I now hook this up in other places, but we could get async/latency issues?
 | |
| 				// We need to subscribe early? Or the transport layer handle this for us?
 | |
| 				if(Gun.fns.is(gun.__.opt.hooks.load)){
 | |
| 					gun.__.opt.hooks.load(key, function(err, data){
 | |
| 						if(err){ return cb.call(gun, err), (gun._.err||cb.fn).call(gun, err) }
 | |
| 						if(!data){ return cb.call(gun, err, data), gun.shot('then').fire() } // if no data, emit without any contexxt change
 | |
| 						var context = gun.union(data); // safely transform the data into the current context
 | |
| 						if(context.err){ return cb.call(gun, context.err), (gun._.err||cb.fn).call(gun, context.err) } // but validate it in case of errors
 | |
| 						gun._.node = gun.__.graph[data._[Gun._.soul]]; // immediately use the state in cache, no waiting for union to be done.
 | |
| 						if(!cb.soul){ // and if we had loaded with a key rather than a soul
 | |
| 							gun._.keys[key] = 1; // then set a marker that this key matches
 | |
| 							gun.__.keys[key] = gun._.node; // and cache a pointer to the node
 | |
| 						}
 | |
| 						cb.call(gun, null, Gun.obj.copy(gun._.node)); // frozen copy
 | |
| 						gun.shot('then').fire();
 | |
| 					}, opt);
 | |
| 				} else {
 | |
| 					Gun.log.call(gun, "Warning! You have no persistence layer to load from!");
 | |
| 				}
 | |
| 			});
 | |
| 			gun.shot('done').fire(); // because we are loading, we always fire!
 | |
| 			return gun;
 | |
| 		}
 | |
| 		Chain.key = function(key, cb){
 | |
| 			var gun = this;
 | |
| 			gun.shot.then(function(){
 | |
| 				cb = cb || function(){};
 | |
| 				if(Gun.obj.is(key)){ // if key is an object then we get the soul directly from it
 | |
| 					Gun.obj.map(key, function(soul, field){ return cb.key = field, cb.soul = soul });
 | |
| 				} else { // or else
 | |
| 					cb.key = key; // the key is the key
 | |
| 				}
 | |
| 				if(gun._.node){ // if it is in cache
 | |
| 					cb.soul = gun._.node._[Gun._.soul];
 | |
| 					(gun._.keys = gun._.keys || {})[cb.key] = 1; // clear the marker in this context
 | |
| 					(gun.__.keys = gun.__.keys || {})[cb.key] = gun._.node; // and create the pointer
 | |
| 				} else { // if it is not in cache
 | |
| 					(gun._.keys = gun._.keys || {})[cb.key] = 0; // then set a marker on this context
 | |
| 				}
 | |
| 				if(Gun.fns.is(gun.__.opt.hooks.key)){
 | |
| 					gun.__.opt.hooks.key(cb.key, cb.soul, function(err, data){
 | |
| 						if(err){ return cb.call(gun, err) }
 | |
| 						return cb.call(gun, null);
 | |
| 					});
 | |
| 				} else {
 | |
| 					Gun.log.call(gun, "Warning! You have no key hook!");
 | |
| 				}
 | |
| 			});
 | |
| 			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({})
 | |
| 			Path ultimately should call .get each time, individually, for what it finds.
 | |
| 			Things that wait and merge many things together should be an abstraction ontop of path.
 | |
| 		*/
 | |
| 		Chain.path = function(path){ // The focal point follows the path
 | |
| 			var gun = this.chain();
 | |
| 			path = (path || '').split('.');
 | |
| 			gun.back.shot.then(function trace(){ // should handle blank and err! Err already handled?
 | |
| 				if(!gun._.node && !gun.back._.node){ return gun.shot('then').fire() } // TODO: BUG? ERROR? MAYBE? MARK?
 | |
| 				gun._.field = null;
 | |
| 				gun._.node = gun._.node || gun.back._.node;
 | |
| 				if(!path.length){ // if the path resolves to another node, we finish here.
 | |
| 					return gun.shot('then').fire(); // this is not frozen yet, but it is still used for internals so keep it unfrozen.
 | |
| 				}
 | |
| 				var field = Gun.text.ify(path.shift())
 | |
| 				, val = gun._.node[field];
 | |
| 				gun._.field = field;
 | |
| 				if(Gun.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();
 | |
| 				}
 | |
| 			});
 | |
| 			return gun;
 | |
| 		}
 | |
| 		Chain.get = function(cb){
 | |
| 			var gun = this;
 | |
| 			gun.shot.then(function(){
 | |
| 				cb = cb || function(){};
 | |
| 				if(!gun._.node){ return }
 | |
| 				cb.call(gun, gun._.field? gun._.node[gun._.field] : Gun.obj.copy(gun._.node), gun._.field); // frozen copy
 | |
| 				// TODO: BUG! Maybe? Interesting, if delta is an object, I might have to .load!
 | |
| 			});
 | |
| 			return gun;
 | |
| 		}
 | |
| 		Chain.on = function(cb){
 | |
| 			var gun = this;
 | |
| 			gun.shot.then(function(){
 | |
| 				cb = cb || function(){};
 | |
| 				if(!gun._.node){ return }
 | |
| 				cb.call(gun, gun._.field? gun._.node[gun._.field] : Gun.obj.copy(gun._.node), gun._.field); // frozen copy
 | |
| 				gun.__.on(gun._.node._[Gun._.soul]).event(function(delta){
 | |
| 					if(!delta){ return }
 | |
| 					if(!gun._.field){
 | |
| 						cb.call(gun, Gun.obj.copy(gun._.node)); // frozen copy
 | |
| 						return;
 | |
| 					}
 | |
| 					if(Gun.obj.has(delta, gun._.field)){
 | |
| 						delta = delta[gun._.field];
 | |
| 						cb.call(gun, Gun.obj.is(delta)? Gun.obj.copy(delta) : delta, gun._.field); // frozen copy
 | |
| 						// TODO: BUG! Maybe? Interesting, if delta is an object, I might have to .load!
 | |
| 					}
 | |
| 				})
 | |
| 			});
 | |
| 			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.
 | |
| 			var gun = this, set = {};
 | |
| 			gun.shot.then(function(){
 | |
| 				opt = opt || {};
 | |
| 				cb = cb || function(){};
 | |
| 				if(gun._.field){ // if a field exists, it should always be a string
 | |
| 					var partial = {}; // in case we are doing a set on a field, not on a node
 | |
| 					partial[gun._.field] = val; // we create a blank node with the field/value to be set
 | |
| 					val = partial;
 | |
| 				} else
 | |
| 				if(!Gun.obj.is(val)){
 | |
| 					return cb.call(gun, {err: "No field exists to set the " + (typeof val) + " on."});
 | |
| 				}
 | |
| 				// TODO: should be able to handle val being a relation or a gun context or a gun promise.
 | |
| 				// TODO: BUG: IF we are setting an object, doing a partial merge, and they are reusing a frozen copy, we need to do a DIFF to update the HAM! Or else we'll get "old" HAM.
 | |
| 				val._ = Gun.ify.soul.call(gun, {}, gun._.node || val); // set their souls to be the same that way they will merge correctly for us during the union!
 | |
| 				set = Gun.ify.call(gun, val, set);
 | |
| 				cb.root = set.root;
 | |
| 				if(set.err || !cb.root){ return cb.call(gun, set.err || {err: "No root object!"}) }
 | |
| 				set = Gun.ify.state(set.nodes, Gun.time.is()); // set time state on nodes?
 | |
| 				if(set.err){ return cb.call(gun, set.err) }
 | |
| 				gun.union(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;
 | |
| 				if(!gun._.field){
 | |
| 					Gun.obj.map(gun._.keys, function(yes, key){
 | |
| 						if(yes){ return }
 | |
| 						gun.key(key); // TODO: Feature? what about these callbacks?
 | |
| 					});
 | |
| 				}
 | |
| 				if(Gun.fns.is(gun.__.opt.hooks.set)){
 | |
| 					gun.__.opt.hooks.set(set.nodes, function(err, data){ // now iterate through those nodes to a persistence layer and get a callback once all are saved
 | |
| 						if(err){ return cb.call(gun, err) }
 | |
| 						return cb.call(gun, null);
 | |
| 					});
 | |
| 				} else {
 | |
| 					Gun.log.call(gun, "Warning! You have no persistence layer to save to!");
 | |
| 				}
 | |
| 			});
 | |
| 			if(!gun.back){
 | |
| 				gun.shot('then').fire();
 | |
| 			}
 | |
| 			return gun;
 | |
| 		}
 | |
| 		Chain.insert = function(obj, cb, opt){
 | |
| 			var gun = this;
 | |
| 			opt = opt || {};
 | |
| 			cb = cb || function(){};
 | |
| 			gun = gun.set({}); // insert assumes a graph node. So either create it or merge with the existing one.
 | |
| 			var error, item = Gun(null).set(obj, function(err){ // create the new item in its own context.
 | |
| 				error = err; // if this happens, it should get called before the .get
 | |
| 			}).get(function(val){
 | |
| 				if(error){ return cb.call(gun, error) } // which in case it is, allows us to fail fast.
 | |
| 				var list = {}, soul = Gun.is.soul.on(val);
 | |
| 				if(!soul){ return cb.call(gun, {err: "No soul!"}) }
 | |
| 				list[soul] = val; // other wise, let's then
 | |
| 				gun.set(list, cb); // merge with the graph node.
 | |
| 			});
 | |
| 			return gun;
 | |
| 		}
 | |
| 		Chain.map = function(cb, opt){
 | |
| 			var gun = this;
 | |
| 			gun.get(function(val){
 | |
| 				opt = opt || {};
 | |
| 				cb = cb || function(){};
 | |
| 				Gun.obj.map(val, function(val, field){  // by default it only maps over nodes
 | |
| 					if(Gun._.meta == field){ return }
 | |
| 					if(Gun.is.soul(val)){
 | |
| 						gun.load(val).get(function(val){ // should map have support for blank?
 | |
| 							cb.call(this, val, field);
 | |
| 						});
 | |
| 					} else {
 | |
| 						if(!opt.all){ return } // {all: true} maps over everything
 | |
| 						cb.call(gun, val, field);
 | |
| 					}
 | |
| 				});
 | |
| 			});
 | |
| 			return gun;
 | |
| 		}
 | |
| 		// Union is different than set. Set casts non-gun style of data into a gun compatible data.
 | |
| 		// Union takes already gun compatible data and validates it for a merge.
 | |
| 		// Meaning it is more low level, such that even set uses union internally.
 | |
| 		Chain.union = function(prime, cb){
 | |
| 			var tmp = {}, gun = this, context = Gun.shot();
 | |
| 			cb = cb || function(){}
 | |
| 			context.nodes = {};
 | |
| 			if(!prime){
 | |
| 				context.err = {err: "No data to merge!"};
 | |
| 			} else
 | |
| 			if(Gun.is.soul.on(prime)){
 | |
| 				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;
 | |
| 			}
 | |
| 			if(!Gun.is.graph(prime, function(node, soul){
 | |
| 				context.nodes[soul] = node;
 | |
| 			})){
 | |
| 				cb(context.err = context.err || {err: "Invalid graph!", corrupt: true}, context);
 | |
| 				return context;
 | |
| 			}
 | |
| 			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(!Gun.is.soul.on(delta)){ return }
 | |
| 				gun.__.on(delta._[Gun._.soul]).emit(Gun.obj.copy(delta)); // this is in reaction to HAM. frozen copy here?
 | |
| 			});
 | |
| 			return context;
 | |
| 		}
 | |
| 		Chain.blank = function(blank){
 | |
| 			var tmp = this;
 | |
| 			var gun = this.chain();
 | |
| 			tmp.shot.then(function(){
 | |
| 				if(tmp._.node){ // if it does indeed exist
 | |
| 					gun._ = tmp._; // switch back to the original context
 | |
| 					return gun.shot('then').fire(); // yet fire off the chain
 | |
| 				}
 | |
| 				blank.call(tmp); // call the blank function with the original context
 | |
| 				tmp.shot.then(function(){ // then after the blank logic is done...
 | |
| 					gun._ = tmp._; // inherit those changes
 | |
| 					gun.shot('then').fire(); // and fire off the chain
 | |
| 				});
 | |
| 			});
 | |
| 			return gun;
 | |
| 		}
 | |
| 		Chain.err = function(dud){ // WARNING: dud was depreciated.
 | |
| 			this._.err = 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.fns.sum = function(done){ // combine with Util.obj.map for some easy parallel async operations!
 | |
| 			var context = {task: {}, data: {}};
 | |
| 			context.end = function(e,v){ return done(e,v), done = function(){} };
 | |
| 			context.add = function(fn, id){
 | |
| 				context.task[id = id || (Gun.text.is(fn)? fn : Gun.text.random())] = false;
 | |
| 				var each = function(err, val){
 | |
| 					context.task[id] = true;
 | |
| 					if(err){ (context.err = context.err || {})[id] = err }
 | |
| 					context.data[id] = val;
 | |
| 					if(!Gun.obj.map(context.task, function(val){ if(!val){ return true } })){ // it is true if we are NOT done yet, then invert.
 | |
| 						done(context.err, context.data);
 | |
| 					}
 | |
| 				}, c = context;
 | |
| 				return Gun.fns.is(fn)? function(){ return fn.apply({task: c.task, data: c.data, end: c.end, done: each}, arguments) } : each;
 | |
| 			}
 | |
| 			return context;
 | |
| 		}
 | |
| 		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();
 | |
| 			chain.$ = function(where){
 | |
| 				(chain._ = chain._ || {})[where] = chain._[where] || [];
 | |
| 				chain.$[where] = chain.$[where] || function(fn){
 | |
| 					if(chain.args){
 | |
| 						fn.apply(chain, chain.args);
 | |
| 					} else {
 | |
| 						(chain._[where]||[]).push(fn);
 | |
| 					}
 | |
| 					return chain.$;
 | |
| 				}
 | |
| 				chain.where = where;
 | |
| 				return chain;
 | |
| 			}
 | |
| 			Gun.list.map(Array.prototype.slice.call(arguments), function(where){ chain.$(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 }
 | |
| 					me.args = args;
 | |
| 					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(){
 | |
| 		// events are fundamentally different, being synchronously 1 to N fan out,
 | |
| 		// than req/res/callback/promise flow, which are asynchronously 1 to 1 into a sink.
 | |
| 		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, bind){ // TODO: BUG: Modify lists to include HAM state
 | |
| 			if(Gun.obj.map(bind, function(){ return true })){ return {err: "Bind must be an empty object."} }
 | |
| 			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.is.value(data)) && !(sub._ && Gun.text.is(sub.simple))){
 | |
| 					return data;
 | |
| 				} else
 | |
| 				if(Gun.obj.is(data)){
 | |
| 					var value = bind || {}, symbol = {}, seen
 | |
| 					, err = {err: "Metadata does not support external or circular references at " + sub.path, meta: true};
 | |
| 					context.root = context.root || value;
 | |
| 					bind = null;
 | |
| 					if(seen = ify.seen(context._seen, data)){
 | |
| 						//Gun.log.call(gun, "seen in _", sub._, sub.path, data);
 | |
| 						context.err = err;
 | |
| 						return;
 | |
| 					} else
 | |
| 					if(seen = ify.seen(context.seen, data)){
 | |
| 						//Gun.log.call(gun, "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.call(gun, "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.call(gun, '>>>>', 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.is.soul.on(from)){
 | |
| 				to[Gun._.soul] = from._[Gun._.soul];
 | |
| 				return to;
 | |
| 			}
 | |
| 			to[Gun._.soul] = Gun.roulette.call(gun);
 | |
| 			return to;
 | |
| 		}
 | |
| 	}());
 | |
| 	if(typeof window !== "undefined"){
 | |
| 		window.Gun = Gun;
 | |
| 	} else {
 | |
| 		module.exports = Gun;
 | |
| 	}
 | |
| }({}));
 | |
| 
 | |
| ;(function(tab){
 | |
| 	if(!this.Gun){ return }
 | |
| 	if(!window.JSON){ throw new Error("Include JSON first: ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js") } // for old IE use
 | |
| 	Gun.on('opt').event(function(gun, opt){
 | |
| 		window.tab = tab; // for debugging purposes
 | |
| 		opt = opt || {};
 | |
| 		tab.headers = opt.headers || {};
 | |
| 		tab.headers['gun-tid'] = tab.headers['gun-tid'] || Gun.text.random();
 | |
| 		tab.load = tab.load || function(key, cb, opt){
 | |
| 			if(!key){ return }
 | |
| 			cb = cb || function(){};
 | |
| 			opt = opt || {};
 | |
| 			opt.url = opt.url || {};
 | |
| 			opt.headers = tab.headers;
 | |
| 			if(key[Gun._.soul]){
 | |
| 				opt.url.query = key;
 | |
| 			} else {
 | |
| 				opt.url.pathname = '/' + key;
 | |
| 			}
 | |
| 			console.log("gun load", key);
 | |
| 			Gun.obj.map(gun.__.opt.peers, function(peer, url){
 | |
| 				request(url, null, function(err, reply){
 | |
| 					//console.log('via', url, key, reply);
 | |
| 					if(err || !reply){ return } // handle reconnect?
 | |
| 					if(reply.body && reply.body.err){
 | |
| 						cb(reply.body.err);
 | |
| 					} else {
 | |
| 						cb(null, reply.body);
 | |
| 					}
 | |
| 				}, opt);
 | |
| 				cb.peers = true;
 | |
| 			});
 | |
| 			if(!cb.peers){ // there are no peers! this is a local only instance
 | |
| 				Gun.log.call(gun, "Warning! You have no peers to connect to!");
 | |
| 				setTimeout(function(){cb({err: "No peers!"})},0);
 | |
| 			}
 | |
| 		}
 | |
| 		tab.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!"});
 | |
| 			}
 | |
| 			Gun.obj.map(gun.__.opt.peers, function(peer, url){
 | |
| 				request(url, meta, function(err, reply){
 | |
| 					//console.log("gun key done", soul, err, reply);
 | |
| 					if(err || !reply){
 | |
| 						Gun.log.call(gun, err = err || "Error: Key failed to be made on " + url);
 | |
| 						// tab.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
 | |
| 						cb(err);
 | |
| 					} else {
 | |
| 						cb();
 | |
| 					}
 | |
| 				}, {url: {pathname: '/' + key }, headers: tab.headers});
 | |
| 			});
 | |
| 		}
 | |
| 		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);
 | |
| 			//console.log("gun set start");
 | |
| 			Gun.obj.map(gun.__.opt.peers, function(peer, url){
 | |
| 				request(url, nodes, function respond(err, reply, id){
 | |
| 					//console.log("gun set done", err, reply, id);
 | |
| 
 | |
| 					return;
 | |
| 					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: tab.headers});
 | |
| 			});
 | |
| 			Gun.obj.map(nodes, function(node, soul){
 | |
| 				gun.__.on(soul).emit(node, true); // should we emit difference between local and not?
 | |
| 			});
 | |
| 		}
 | |
| 		tab.set.defer = {};
 | |
| 		request.createServer(function(req, res){
 | |
| 			//console.log("client server received request", req);
 | |
| 			if(!req.body){ return }
 | |
| 			gun.union(req.body);
 | |
| 		});
 | |
| 		(function(){
 | |
| 			tab.store = {};
 | |
| 			var store = window.localStorage || {setItem: function(){}, removeItem: function(){}, getItem: function(){}};
 | |
| 			tab.store.set = function(key, val){ 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) }
 | |
| 		}());
 | |
| 		gun.__.opt.hooks.load = gun.__.opt.hooks.load || tab.load;
 | |
| 		gun.__.opt.hooks.set = gun.__.opt.hooks.set || tab.set;
 | |
| 		gun.__.opt.hooks.key = gun.__.opt.hooks.key || tab.key;
 | |
| 	});
 | |
| 	var request = (function(){
 | |
| 		function r(base, body, cb, opt){
 | |
| 			opt = opt || (base.length? {base: base} : base);
 | |
| 			opt.base = opt.base || base;
 | |
| 			opt.body = opt.body || body;
 | |
| 			if(!opt.base){ return }
 | |
| 			r.transport(opt, cb);
 | |
| 		}
 | |
| 		r.createServer = function(fn){ (r.createServer = fn).on = true }
 | |
| 		r.transport = function(opt, cb){
 | |
| 			if(r.ws(opt, cb)){ return }
 | |
| 			r.jsonp(opt, cb);
 | |
| 		}
 | |
| 		r.ws = function(opt, cb){
 | |
| 			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 }
 | |
| 				if(opt.url){ req.url = opt.url }
 | |
| 				r.ws.cbs[req.wsrid = 'WS' + (+ new Date()) + '.' + Math.floor((Math.random()*65535)+1)] = function(err,res){
 | |
| 					delete r.ws.cbs[req.wsrid];
 | |
| 					cb(err,res);
 | |
| 				}
 | |
| 				ws.send(JSON.stringify(req));
 | |
| 				return true;
 | |
| 			}
 | |
| 			if(ws === false){ return }
 | |
| 			ws = r.ws.peers[opt.base] = new WebSocket(opt.base.replace('http','ws'));
 | |
| 			ws.onopen = function(o){ r.ws(opt, cb) };
 | |
| 			ws.onclose = function(c){
 | |
| 				if(!c){ return }
 | |
| 				if(1006 === c.code){ // websockets cannot be used
 | |
| 					ws = r.ws.peers[opt.base] = false;
 | |
| 					r.transport(opt, cb);
 | |
| 					return;
 | |
| 				}
 | |
| 				ws = r.ws.peers[opt.base] = null; // this will make the next request try to reconnect
 | |
| 			};
 | |
| 			ws.onmessage = function(m){
 | |
| 				if(!m || !m.data){ return }
 | |
| 				var res;
 | |
| 				try{res = JSON.parse(m.data);
 | |
| 				}catch(e){ return }
 | |
| 				if(!res){ return }
 | |
| 				if(res.wsrid){ (r.ws.cbs[res.wsrid]||function(){})(null, res) }
 | |
| 				//console.log("We have a pushed message!", res);
 | |
| 				if(res.body){ r.createServer(res, function(){}) } // emit extra events.
 | |
| 			};
 | |
| 			ws.onerror = function(e){ console.log(e); };
 | |
| 			return true;
 | |
| 		}
 | |
| 		r.ws.peers = {};
 | |
| 		r.ws.cbs = {};
 | |
| 		r.jsonp = function(opt, cb){
 | |
| 			//console.log("jsonp send", opt);
 | |
| 			r.jsonp.ify(opt, function(url){
 | |
| 				//console.log(url);
 | |
| 				if(!url){ return }
 | |
| 				r.jsonp.send(url, function(reply){
 | |
| 					//console.log("jsonp reply", reply);
 | |
| 					cb(null, reply);
 | |
| 					r.jsonp.poll(opt, reply);
 | |
| 				}, opt.jsonp);
 | |
| 			});
 | |
| 		}
 | |
| 		r.jsonp.send = function(url, cb, id){
 | |
| 			var js = document.createElement('script');
 | |
| 			js.src = url;
 | |
| 			window[js.id = id] = function(res){
 | |
| 				cb(res);
 | |
| 				cb.id = js.id;
 | |
| 				js.parentNode.removeChild(js);
 | |
| 				window[cb.id] = null; // TODO! BUG: This needs to handle chunking!
 | |
| 				try{delete window[cb.id];
 | |
| 				}catch(e){}
 | |
| 			}
 | |
| 			js.async = true;
 | |
| 			document.getElementsByTagName('head')[0].appendChild(js);
 | |
| 			return js;
 | |
| 		}
 | |
| 		r.jsonp.poll = function(opt, res){
 | |
| 			if(!opt || !opt.base || !res || !res.headers || !res.headers.poll){ return }
 | |
| 			(r.jsonp.poll.s = r.jsonp.poll.s || {})[opt.base] = r.jsonp.poll.s[opt.base] || setTimeout(function(){ // TODO: Need to optimize for Chrome's 6 req limit?
 | |
| 				//console.log("polling again");
 | |
| 				var o = {base: opt.base, headers: {pull: 1}};
 | |
| 				r.each(opt.headers, function(v,i){ o.headers[i] = v })
 | |
| 				r.jsonp(o, function(err, reply){
 | |
| 					delete r.jsonp.poll.s[opt.base];
 | |
| 					//console.log(' ');
 | |
| 					while(reply.body && reply.body.length && reply.body.shift){ // we're assuming an array rather than chunk encoding. :(
 | |
| 						var res = reply.body.shift();
 | |
| 						//console.log("-- go go go", res);
 | |
| 						if(res && res.body){ r.createServer(res, function(){}) } // emit extra events.
 | |
| 					}
 | |
| 				});
 | |
| 			}, res.headers.poll);
 | |
| 		}
 | |
| 		r.jsonp.ify = function(opt, cb){
 | |
| 			var uri = encodeURIComponent, q = '?';
 | |
| 			if(opt.url && opt.url.pathname){ q = opt.url.pathname + q; }
 | |
| 			q = opt.base + q;
 | |
| 			//console.log("what up doc?", opt);
 | |
| 			r.each((opt.url||{}).query, function(v, i){ q += uri(i) + '=' + uri(v) + '&' });
 | |
| 			if(opt.headers){ q += uri('`') + '=' + uri(JSON.stringify(opt.headers)) + '&' }
 | |
| 			if(r.jsonp.max < q.length){ return cb() }
 | |
| 			q += uri('jsonp') + '=' + uri(opt.jsonp = 'P'+Math.floor((Math.random()*65535)+1));
 | |
| 			if(opt.body){
 | |
| 				q += '&';
 | |
| 				var w = opt.body, wls = function(w,l,s){
 | |
| 					return uri('%') + '=' + uri(w+'-'+(l||w)+'/'+(s||w))  + '&' + uri('$') + '=';
 | |
| 				}
 | |
| 				if(typeof w != 'string'){
 | |
| 					w = JSON.stringify(w);
 | |
| 					q += uri('^') + '=' + uri('json') + '&';
 | |
| 				}
 | |
| 				w = uri(w);
 | |
| 				var i = 0, l = w.length
 | |
| 				, s = r.jsonp.max - (q.length + wls(l.toString()).length);
 | |
| 				if(s < 0){ return cb() }
 | |
| 				while(w){
 | |
| 					cb(q + wls(i, (i = i + s), l) + w.slice(0, i));
 | |
| 					w = w.slice(i);
 | |
| 				}
 | |
| 			} else {
 | |
| 				cb(q);
 | |
| 			}
 | |
| 		}
 | |
| 		r.jsonp.max = 2000;
 | |
| 		r.each = function(obj, cb){
 | |
| 			if(!obj || !cb){ return }
 | |
| 			for(var i in obj){
 | |
| 				if(obj.hasOwnProperty(i)){
 | |
| 					cb(obj[i], i);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return r;
 | |
| 	}());
 | |
| }({}));
 | 
