load testing!!!

This commit is contained in:
Mark Nadal 2017-03-11 07:09:14 -08:00
parent 209bf2a214
commit 33a0cf5629
14 changed files with 387 additions and 474 deletions

8
gun.js
View File

@ -1117,10 +1117,10 @@
});
setTimeout(function(){
var c = new Chain(), u;
c.get('app').map().map(x => x.bat? {baz: x.bat} : u).get('baz').val(function(data, key, env){
console.log("envelope", env);
});
//var c = new Chain(), u;
//c.get('app').map().map(x => x.bat? {baz: x.bat} : u).get('baz').val(function(data, key, env){
// console.log("envelope", env);
//});
},1000);

3
gun.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ function HAM(machineState, incomingState, currentState, incomingValue, currentVa
return {converge: true, incoming: true};
}
}
return {err: "you have not properly handled recursion through your data or filtered it as JSON"};
return {err: "Invalid CRDT Data: "+ incomingValue +" to "+ currentValue +" at "+ incomingState +" to "+ currentState +"!"};
}
if(typeof JSON === 'undefined'){
throw new Error(

View File

@ -5,24 +5,48 @@ var root, noop = function(){}, u;
if(typeof window !== 'undefined'){ root = window }
var store = root.localStorage || {setItem: noop, removeItem: noop, getItem: noop};
var check = {}, dirty = {}, async = {}, count = 0, max = 10000, wait;
Gun.on('put', function(at){ var err, id, opt, root = at.gun._.root;
this.to.next(at);
(opt = {}).prefix = (at.opt || opt).prefix || at.gun.back('opt.prefix') || 'gun/';
Gun.graph.is(at.put, function(node, soul, map){
var keys = Gun.obj.ify(store.getItem(opt.prefix + soul+'_')||{});
map(function(val, key){
keys[key] = 1;
var state = Gun.state.is(node, key);
// #soul.field=val>state
try{store.setItem(opt.prefix + soul+key, JSON.stringify([val,state]));
var graph = root._.graph;
Gun.obj.map(at.put, function(node, soul){
async[soul] = graph[soul] || node;
});
count += 1;
check[at['#']] = root;
function save(){
clearTimeout(wait);
var ack = check;
var all = async;
count = 0;
wait = false;
check = {};
async = {};
Gun.obj.map(all, function(node, soul){
// Since localStorage only has 5MB, it is better that we keep only
// the data that the user is currently interested in.
node = graph[soul] || all[soul];
try{store.setItem(opt.prefix + soul, JSON.stringify(node));
}catch(e){ err = e || "localStorage failure" }
});
try{store.setItem(opt.prefix + soul+'_', JSON.stringify(keys));
}catch(e){ err = e || "localStorage failure" }
});
if(Gun.obj.empty(at.gun.back('opt.peers'))){
Gun.on.ack(at, {err: err, ok: 0}); // only ack if there are no peers.
if(!Gun.obj.empty(at.gun.back('opt.peers'))){ return } // only ack if there are no peers.
Gun.obj.map(ack, function(root, id){
root.on('in', {
'@': id,
err: err,
ok: 0 // localStorage isn't reliable, so make its `ok` code be a low number.
});
});
}
if(count >= max){ // goal is to do 10K inserts/second.
return save();
}
if(wait){ return }
clearTimeout(wait);
wait = setTimeout(save, 1000);
});
Gun.on('get', function(at){
this.to.next(at);
@ -30,18 +54,17 @@ Gun.on('get', function(at){
//setTimeout(function(){
(opt = at.opt || {}).prefix = opt.prefix || at.gun.back('opt.prefix') || 'gun/';
if(!lex || !(soul = lex[Gun._.soul])){ return }
//if(0 >= at.cap){ return }
var field = lex['.'];
if(field){
if(data = Gun.obj.ify(store.getItem(opt.prefix + soul+field)||null)||u){
data = Gun.state.ify(u, field, data[1], data[0], soul);
}
} else {
Gun.obj.map(Gun.obj.ify(store.getItem(opt.prefix + soul+'_')), function(v,field){
v = Gun.obj.ify(store.getItem(opt.prefix + soul+field)||{});
data = Gun.state.ify(data, field, v[1], v[0], soul);
});
data = Gun.obj.ify(store.getItem(opt.prefix + soul) || null) || async[soul] || u;
if(data && field){
data = Gun.state.ify(u, field, Gun.state.is(data, field), data[field], soul);
}
gun.back(-1).on('in', {'@': at['#'], put: Gun.graph.node(data)});
if(!data && !Gun.obj.empty(gun.back('opt.peers'))){ // if data not found, don't ack if there are peers.
return; // Hmm, what if we have peers but we are disconnected?
}
gun.on('in', {'@': at['#'], put: Gun.graph.node(data), how: 'lS'});
//},11);
});

View File

@ -38,7 +38,7 @@ function output(at){
if(!at.gun._){ return }
(at.gun._).on('out', {
get: {'#': rel, '.': get},
'#': Gun.on.ask(Gun.HAM.synth, at.gun),
'#': root._.ask(Gun.HAM.synth, at.gun),
gun: at.gun
});
return;
@ -61,7 +61,7 @@ function output(at){
if(!at.gun._){ return }
(at.gun._).on('out', {
get: {'#': cat.soul, '.': get},
'#': Gun.on.ask(Gun.HAM.synth, at.gun),
'#': root._.ask(Gun.HAM.synth, at.gun),
gun: at.gun
});
return;
@ -86,7 +86,7 @@ function output(at){
});
}
if(cat.ack){
if(!obj_has(cat, 'put')){
if(!obj_has(cat, 'put')){ // u !== cat.put instead?
return;
}
}
@ -94,7 +94,8 @@ function output(at){
if(cat.soul){
cat.on('out', {
get: {'#': cat.soul},
'#': Gun.on.ask(Gun.HAM.synth, cat.gun),
'#': root._.ask(Gun.HAM.synth, cat.gun),
gun: cat.gun
});
return;
}
@ -114,7 +115,7 @@ function output(at){
function input(at){
at = at._ || at;
var ev = this, cat = this.as, gun = at.gun, coat = gun._, change = at.put, back = cat.back._ || empty, rel, tmp;
if(0 > cat.ack && at.via && !Gun.val.rel.is(change)){ // for better behavior?
if(0 > cat.ack && !Gun.val.rel.is(change)){ // for better behavior?
cat.ack = 1;
}
if(cat.get && at.get !== cat.get){
@ -138,6 +139,7 @@ function input(at){
return;
}
if(cat.soul){
if(cat.root._.now){ at = obj_to(at, {put: change = coat.put}) } // TODO: Ugly hack for uncached synchronous maps.
ev.to.next(at);
echo(cat, at, ev);
obj_map(change, map, {at: at, cat: cat});
@ -255,7 +257,7 @@ function ask(cat, soul){
tmp.ack = tmp.ack || -1;
tmp.on('out', {
get: {'#': soul},
'#': Gun.on.ask(Gun.HAM.synth, tmp.gun),
'#': cat.root._.ask(Gun.HAM.synth, tmp.gun),
gun: tmp.gun
});
return;
@ -263,7 +265,7 @@ function ask(cat, soul){
obj_map(cat.next, function(gun, key){
(gun._).on('out', {
get: {'#': soul, '.': key},
'#': Gun.on.ask(Gun.HAM.synth, tmp.gun),
'#': cat.root._.ask(Gun.HAM.synth, tmp.gun),
gun: gun
});
});

169
src/experiment.js Normal file
View File

@ -0,0 +1,169 @@
return;
var Gun = require('./root');
var onto = require('./onto');
function Chain(back){
var at = this._ = {back: back, on: onto, $: this, next: {}};
at.root = back? back.root : at;
at.on('in', input, at);
at.on('out', output, at);
}
var chain = Chain.prototype;
chain.back = function(arg){ var tmp;
if(tmp = this._.back){
return tmp.$;
}
}
chain.next = function(arg){
var at = this._, cat;
if(cat = at.next[arg]){
return cat.$;
}
cat = (new Chain(at)._);
at.next[arg] = cat;
cat.key = arg;
return cat.$;
}
chain.get = function(arg){
if(typeof arg == 'string'){
var at = this._, cat;
if(cat = at.next[arg]){
return cat.$;
}
cat = (this.next(arg)._);
if(at.get || at === at.root){
cat.get = arg;
}
return cat.$;
} else {
var at = this._;
var out = {'#': Gun.text.random(), get: {}, cap: 1};
var to = at.root.on(out['#'], get, {next: arg})
at.on('in', get, to);
at.on('out', out);
}
return this;
}
function get(env){
var as = this.as;
if(as.next){
as.next(env, this);
}
}
chain.map = function(cb){
var at = this._;
var chain = new Chain(at);
var cat = chain._;
var u;
at.on('in', function(env){ var tmp;
if(!env){ return }
var cat = this.as;
var to = this.to;
if(tmp = env.put){
to.next(env);
Gun.obj.map(tmp, function(data, key){
if('_' == key){ return }
if(cb){
data = cb(data, key);
if(u === data){ return }
}
cat.on('in', Gun.obj.to(env, {put: data}));
});
}
}, cat);
return chain;
}
function input(env){ var tmp;
if(!env){ return }
var cat = this.as;
var to = this.to;
if(tmp = env.put){
if(tmp && tmp['#'] && (tmp = Gun.val.rel.is(tmp))){
//input.call(this, Gun.obj.to(env, {put: cat.root.put[tmp]}));
return;
}
cat.put = tmp;
to.next(env);
var next = cat.next;
Gun.obj.map(tmp, function(data, key){
if(!(key = next[key])){ return }
key.on('in', Gun.obj.to(env, {put: data}))
});
}
}
function output(env){ var tmp;
var u;
if(!env){ return }
var cat = this.as;
var to = this;
if(!cat.back){
env.test = true;
env.gun = cat.root.$;
Gun.on('out', env);
return;
}
if(tmp = env.get){
if(cat.get){
env = Gun.obj.to(env, {get: {'#': cat.get, '.': tmp}});
} else
if(cat.key){
env = Gun.obj.to(env, {get: Gun.obj.put({}, cat.key, tmp)});
} else {
env = Gun.obj.to(env, {get: {'*': tmp}})
}
}
cat.back.on('out', env);
}
chain.val = function(cb, opt){
var at = this._;
if(cb){
if(opt){
} else {
if(at.val){
cb(at.put, at.get, at);
}
}
this.get(function(env, ev){
cb(env.put, env.get, env);
});
}
}
var graph = {
app: {_:{'#':'app'},
foo: {_:{'#':'foo'},
bar: {'#': 'asdf'},
rab: {'#': 'fdsa'}
}/*,
oof: {_:{'#':'oof'},
bar: {bat: "really"},
rab: {bat: "nice!"}
}*/
},
asdf: {_:{'#': 'asdf'}, baz: "hello world!"},
fdsa: {_:{'#': 'fdsa'}, baz: "world hello!"}
}
Gun.on('out', function(env){
if(!env.test){ return }
setTimeout(function(){
console.log("reply", env.get);
env.gun._.on('in', {'@': env['#'],
put: Gun.graph.node(graph[env.get['#']])
});
return;
env.gun._.on('in', {put: graph, '@': env['#']});
},100);
});
setTimeout(function(){
//var c = new Chain(), u;
//c.get('app').map().map(x => x.bat? {baz: x.bat} : u).get('baz').val(function(data, key, env){
// console.log("envelope", env);
//});
},1000);

View File

@ -13,9 +13,9 @@ Gun.chain.get = function(key, cb, as){
var gun = this, at = gun._;
as = cb || {};
as.use = key;
as.out = as.out || {};
as.out = as.out || {cap: 1};
as.out.get = as.out.get || {};
(at.root._).now = true;
'_' != at.get && ((at.root._).now = true); // ugly hack for now.
at.on('in', use, as);
at.on('out', as.out);
(at.root._).now = false;

View File

@ -35,7 +35,7 @@ module.exports = Gun;
if(!val_is(cv) && u !== cv){ return true } // Undefined is okay since a value might not exist on both nodes. // it is true that this is an invalid HAM comparison.
var HAM = Gun.HAM(machine, is, cs, iv, cv);
if(HAM.err){
console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", HAM.err); // this error should never happen.
console.log(".!HYPOTHETICAL AMNESIA MACHINE ERR!.", field, HAM.err); // this error should never happen.
return;
}
if(HAM.state || HAM.historical || HAM.current){ // TODO: BUG! Not implemented.
@ -103,13 +103,11 @@ module.exports = Gun;
}
Gun.HAM.synth = function(at, ev, as){ var gun = this.as || as;
var cat = gun._, root = cat.root._, put = {}, tmp;
//if(cat.ack){
// cat.ack = 1;
//}
if(!at.put){
//if(obj_has(cat, 'put')){ return }
if(cat.put !== u){ return }
cat.on('in', {
//root.ack(at['@'], {
get: cat.get,
put: cat.put = u,
gun: gun,
@ -122,6 +120,9 @@ module.exports = Gun;
put[soul] = Gun.HAM.delta(graph[soul], node, {graph: graph}); // TODO: PERF! SEE IF WE CAN OPTIMIZE THIS BY MERGING UNION INTO DELTA!
graph[soul] = Gun.HAM.union(graph[soul], node) || graph[soul];
}, root);
if(at.gun !== root.gun){
put = at.put;
}
// TODO: PERF! Have options to determine if this data should even be in memory on this peer!
obj_map(put, function(node, soul){
var root = this, next = root.next || (root.next = {}), gun = next[soul] || (next[soul] = root.gun.get(soul)), coat = (gun._);

View File

@ -1,7 +0,0 @@
var Gun = require('./core');
Gun.chain.init = function(){ // TODO: DEPRECATE?
(this._.opt = this._.opt || {}).init = true;
return this.back(-1).put(Gun.node.ify({}, this._.get), null, this._.get);
}

View File

@ -24,7 +24,7 @@ Gun.chain.map = function(cb, opt, t){
}
function map(at){
if(!at.put || Gun.val.is(at.put)){ return }
if(this.as.val){ this.off() }
if(this.as.val){ this.off() } // TODO: Ugly hack!
obj_map(at.put, each, {cat: this.as, gun: at.gun});
this.to.next(at);
}

View File

@ -54,15 +54,15 @@ function ok(at, ev){ var opt = this;
Gun.chain.val = function(cb, opt){
var gun = this, at = gun._, data = at.put;
if(0 < at.ack && u !== data && cb){
cb.call(gun, data, at.get);
if(0 < at.ack && u !== data){
(cb || noop).call(gun, data, at.get);
return gun;
}
if(cb){
(opt = opt || {}).ok = cb;
opt.cat = at;
gun.get(val, {as: opt});
opt.async = at.stun? 1 : true;
opt.async = true; //opt.async = at.stun? 1 : true;
} else {
Gun.log.once("valonce", "Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it.");
var chain = gun.chain();
@ -87,7 +87,8 @@ function val(at, ev, to){
data = tmp.put;
}
if(ev.wait){ clearTimeout(ev.wait) }
if(!to && (!(0 < coat.ack) || ((true === opt.async) && 0 !== opt.wait))){
//if(!to && (!(0 < coat.ack) || ((true === opt.async) && 0 !== opt.wait))){
if(!opt.async){
ev.wait = setTimeout(function(){
val.call({as:opt}, at, ev, ev.wait || 1)
}, opt.wait || 99);
@ -128,5 +129,5 @@ Gun.chain.off = function(){
}
var obj = Gun.obj, obj_has = obj.has, obj_del = obj.del, obj_to = obj.to;
var rel = Gun.val.rel;
var empty = {}, u;
var empty = {}, noop = function(){}, u;

View File

@ -1,7 +1,6 @@
var Gun = require('./core');
// Check for stone-age browsers.
if (typeof JSON === 'undefined') {
throw new Error(
'Gun depends on JSON. Please load it first:\n' +
@ -9,382 +8,97 @@ if (typeof JSON === 'undefined') {
);
}
function Client (url, options, wscOptions ) {
if (!(this instanceof Client)) {
return new Client(url, options, wscOptions);
var WebSocket;
if(typeof window !== 'undefined'){
WebSocket = window.WebSocket || window.webkitWebSocket || window.mozWebSocket;
} else {
return;
}
var message, count = 0, noop = function(){}, wait;
Gun.on('out', function(at){
this.to.next(at);
var cat = at.gun._.root._, wsp = cat.wsp || (cat.wsp = {});
if(at.wsp && 1 === wsp.count){ return } // if the message came FROM the only peer we are connected to, don't echo it back.
message = JSON.stringify(at);
//if(++count){ console.log("msg OUT:", count, Gun.obj.ify(message)) }
if(cat.udrain){
cat.udrain.push(message);
return;
}
this.url = Client.formatURL(url);
this.socket = null;
this.queue = [];
this.sid = Gun.text.random(10);
this.on = Gun.on;
this.options = options || {};
this.options.wsc = wscOptions;
this.resetBackoff();
}
Client.prototype = {
constructor: Client,
drainQueue: function () {
var queue = this.queue;
var client = this;
// Reset the queue.
this.queue = [];
// Send each message.
queue.forEach(function (msg) {
client.send(msg);
});
return queue.length;
},
connect: function () {
var client = this;
var socket = new Client.WebSocket(this.url, this.options.wsc.protocols, this.options.wsc );
this.socket = socket;
// Forward messages into the emitter.
socket.addEventListener('message', function (msg) {
client.on('message', msg);
});
// Reconnect on close events.
socket.addEventListener('close', function () {
client.scheduleReconnect();
});
// Send the messages in the queue.
this.ready(function () {
client.drainQueue();
});
return socket;
},
resetBackoff: function () {
var backoff = this.options;
this.backoff = {
time: backoff.time || 100,
max: backoff.max || 2000,
factor: backoff.factor || 2
};
return this.backoff;
},
nextBackoff: function () {
var backoff = this.backoff;
var next = backoff.time * backoff.factor;
var max = backoff.max;
if (next > max) {
next = max;
}
return (backoff.time = next);
},
// Try to efficiently reconnect.
scheduleReconnect: function () {
var client = this;
var time = this.backoff.time;
this.nextBackoff();
setTimeout(function () {
client.connect();
client.ready(function () {
client.resetBackoff();
});
}, time);
},
isClosed: function () {
var socket = this.socket;
if (!socket) {
return true;
}
var state = socket.readyState;
if (state === socket.CLOSING || state === socket.CLOSED) {
return true;
}
return false;
},
ready: function (callback) {
var socket = this.socket;
var state = socket.readyState;
if (state === socket.OPEN) {
callback();
return;
}
if (state === socket.CONNECTING) {
socket.addEventListener('open', callback);
}
},
send: function (msg) {
if (this.isClosed()) {
this.queue.push(msg);
// Will send once connected.
this.connect();
return false;
}
var socket = this.socket;
// Make sure the socket is open.
this.ready(function () {
socket.send(msg);
});
return true;
}
};
if (typeof window !== 'undefined') {
Client.WebSocket = window.WebSocket ||
window.webkitWebSocket ||
window.mozWebSocket ||
null;
}
Client.isSupported = !!Client.WebSocket;
if(!Client.isSupported){ return } // TODO: For now, don't do anything in browsers/servers that don't work. Later, use JSONP fallback and merge with server code?
// Ensure the protocol is correct.
Client.formatURL = function (url) {
return url.replace(/^http/, 'ws');
};
// Send a message to a group of peers.
Client.broadcast = function (urls, msg) {
var pool = Client.pool;
msg.headers = msg.headers || {};
Gun.obj.map(urls, function (options, addr) {
var url = Client.formatURL(addr);
var peer = pool[url];
var envelope = {
headers: Gun.obj.to(msg.headers, {
'gun-sid': peer.sid
}),
body: msg.body
};
var serialized = Gun.text.ify(envelope);
peer.send(serialized);
});
};
// A map of URLs to client instances.
Client.pool = {};
// Close all WebSockets when the window closes.
if (typeof window !== 'undefined') {
window.addEventListener('unload', function () {
Gun.obj.map(Client.pool, function (client) {
if (client.isClosed()) {
return;
}
client.socket.close();
});
});
}
// Define client instances as gun needs them.
// Sockets will not be opened until absolutely necessary.
Gun.on('opt', function (ctx) {
this.to.next(ctx);
var gun = ctx.gun;
var peers = gun.back('opt.peers') || {};
Gun.obj.map(peers, function (options, addr) {
var url = Client.formatURL(addr);
// Ignore clients we've seen before.
if (Client.pool.hasOwnProperty(url)) {
return;
}
var client = new Client(url, options.backoff, gun.back('opt.wsc') || {protocols:null});
// Add it to the pool.
Client.pool[url] = client;
// Listen to incoming messages.
client.on('message', function (msg) {
var data;
try {
data = Gun.obj.ify(msg.data);
} catch (err) {
// Invalid message, discard it.
return;
}
if (!data || !data.body) {
return;
}
gun.on('in', data.body);
});
});
cat.udrain = [];
clearTimeout(wait);
wait = setTimeout(function(){
if(!cat.udrain){ return }
var tmp = cat.udrain;
cat.udrain = null;
message = JSON.stringify(tmp);
Gun.obj.map(cat.opt.peers, send, cat);
},1);
wsp.count = 0;
Gun.obj.map(cat.opt.peers, send, cat);
});
function request (peers, ctx) {
if (Client.isSupported) {
Client.broadcast(peers, ctx);
function send(peer){
var msg = message, cat = this;
var wire = peer.wire || open(peer, cat);
if(cat.wsp){ cat.wsp.count++ }
if(!wire){ return }
if(wire.readyState === wire.OPEN){
wire.send(msg);
return;
}
(peer.queue = peer.queue || []).push(msg);
}
// Broadcast the messages.
Gun.on('out', function (ctx) {
this.to.next(ctx);
var gun = ctx.gun;
var peers = gun.back('opt.peers') || {};
var headers = gun.back('opt.headers') || {};
// Validate.
if (Gun.obj.empty(peers)) {
function receive(msg, peer, cat){
if(!cat || !msg){ return }
try{msg = JSON.parse(msg.data || msg);
}catch(e){}
if(msg instanceof Array){
var i = 0, m;
while(m = msg[i++]){
receive(m, peer, cat);
}
return;
}
//if(++count){ console.log("msg in:", count, msg.body || msg) }
if(cat.wsp && 1 === cat.wsp.count){ (msg.body || msg).wsp = noop } // If there is only 1 client, just use noop since it doesn't matter.
cat.gun.on('in', msg.body || msg);
}
request(peers, {body: ctx, headers: headers});
});
request.jsonp = function (opt, cb) {
request.jsonp.ify(opt, function (url) {
if (!url) {
return;
}
request.jsonp.send(url, function (err, reply) {
cb(err, reply);
request.jsonp.poll(opt, reply);
}, opt.jsonp);
});
};
request.jsonp.send = function (url, cb, id) {
var js = document.createElement('script');
js.src = url;
js.onerror = function () {
(window[js.id] || function () {})(null, {
err: 'JSONP failed!'
});
function open(peer, as){
if(!peer || !peer.url){ return }
var url = peer.url.replace('http', 'ws');
var wire = peer.wire = new WebSocket(url);
wire.onclose = function(){
reconnect(peer, as);
};
window[js.id = id] = function (res, err) {
cb(err, res);
cb.id = js.id;
js.parentNode.removeChild(js);
delete window[cb.id];
wire.onerror = function(error){
reconnect(peer, as);
if(!error){ return }
if(error.code === 'ECONNREFUSED'){
//reconnect(peer, as);
}
};
js.async = true;
document.getElementsByTagName('head')[0].appendChild(js);
return js;
};
request.jsonp.poll = function (opt, res) {
if (!opt || !opt.base || !res || !res.headers || !res.headers.poll) {
return;
}
var polls = request.jsonp.poll.s = request.jsonp.poll.s || {};
polls[opt.base] = polls[opt.base] || setTimeout(function () {
var msg = {
base: opt.base,
headers: { pull: 1 }
};
request.each(opt.headers, function (header, name) {
msg.headers[name] = header;
wire.onopen = function(){
var queue = peer.queue;
peer.queue = [];
Gun.obj.map(queue, function(msg){
message = msg;
send.call(as, peer);
});
}
wire.onmessage = function(msg){
receive(msg, peer, as);
};
return wire;
}
request.jsonp(msg, function (err, reply) {
delete polls[opt.base];
var body = reply.body || [];
while (body.length && body.shift) {
var res = reply.body.shift();
if (res && res.body) {
request.createServer.ing(res, function () {
request(opt.base, null, null, res);
});
}
}
});
}, res.headers.poll);
};
request.jsonp.ify = function (opt, cb) {
var uri = encodeURIComponent, query = '?';
if (opt.url && opt.url.pathname) {
query = opt.url.pathname + query;
}
query = opt.base + query;
request.each((opt.url || {}).query, function (value, key) {
query += (uri(key) + '=' + uri(value) + '&');
});
if (opt.headers) {
query += uri('`') + '=' + uri(
JSON.stringify(opt.headers)
) + '&';
}
if (request.jsonp.max < query.length) {
return cb();
}
var random = Math.floor(Math.random() * (0xffff + 1));
query += (uri('jsonp') + '=' + uri(opt.jsonp = 'P' + random));
if (opt.body) {
query += '&';
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);
query += uri('^') + '=' + uri('json') + '&';
}
w = uri(w);
var i = 0, l = w.length
, s = request.jsonp.max - (query.length + wls(l.toString()).length);
if (s < 0){
return cb();
}
while (w) {
cb(query + wls(i, (i += s), l) + w.slice(0, i));
w = w.slice(i);
}
} else {
cb(query);
}
};
request.jsonp.max = 2000;
request.each = function (obj, cb, as) {
if (!obj || !cb) {
return;
}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cb.call(as, obj[key], key);
}
}
};
module.exports = Client;
function reconnect(peer, as){
clearTimeout(peer.defer);
peer.defer = setTimeout(function(){
open(peer, as);
}, 2 * 1000);
}

View File

@ -4,7 +4,7 @@ Gun.chain.put = function(data, cb, as){
// #soul.field=value>state
// ~who#where.where=what>when@was
// TODO: BUG! Put probably cannot handle plural chains!
var gun = this, root = (gun._).root, tmp;
var gun = this, at = (gun._), root = at.root, tmp;
as = as || {};
as.data = data;
as.gun = as.gun || gun;
@ -13,9 +13,12 @@ Gun.chain.put = function(data, cb, as){
} else {
as.ack = cb;
}
if(at.soul){
as.soul = at.soul;
}
if(as.soul || root === gun){
if(!obj_is(as.data)){
(opt.any||noop).call(opt.as || gun, as.out = {err: Gun.log("No field to put", (typeof as.data), '"' + as.data + '" on!')});
(as.ack||noop).call(as, as.out = {err: Gun.log("Data saved to the root level of the graph must be a node (an object), not a", (typeof as.data), 'of "' + as.data + '"!')});
if(as.res){ as.res() }
return gun;
}
@ -27,12 +30,17 @@ Gun.chain.put = function(data, cb, as){
if(Gun.is(data)){
data.get(function(at,ev){ev.off();
var s = Gun.node.soul(at.put);
if(!s){Gun.log("Can only save a node, not a property.");return}
if(!s){Gun.log("The reference you are saving is a", typeof at.put, '"'+ as.put +'", not a node (object)!');return}
gun.put(Gun.val.rel.ify(s), cb, as);
});
return gun;
}
as.ref = as.ref || (root === (tmp = (gun._).back))? gun : tmp;
as.ref = as.ref || (root === (tmp = at.back))? gun : tmp;
if(as.ref._.soul && Gun.val.is(as.data) && at.get){
as.data = obj_put({}, at.get, as.data);
as.ref.put(as.data, as.soul, as);
return gun;
}
as.ref.get('_').get(any, {as: as});
if(!as.out){
// TODO: Perf idea! Make a global lock, that blocks everything while it is on, but if it is on the lock it does the expensive lookup to see if it is a dependent write or not and if not then it proceeds full speed. Meh? For write heavy async apps that would be terrible.
@ -48,7 +56,7 @@ function ify(as){
env.soul = as.soul;
as.graph = Gun.graph.ify(as.data, env, as);
if(env.err){
(as.ack||noop).call(opt.as || as.gun, as.out = {err: Gun.log(env.err)});
(as.ack||noop).call(as, as.out = {err: Gun.log(env.err)});
if(as.res){ as.res() }
return;
}
@ -59,8 +67,9 @@ function batch(){ var as = this;
if(!as.graph || obj_map(as.stun, no)){ return }
(as.res||iife)(function(){
(as.ref._).on('out', {
cap: 3,
gun: as.ref, put: as.out = as.env.graph, opt: as.opt,
'#': Gun.on.ask(function(ack){ this.off(); // One response is good enough for us currently. Later we may want to adjust this.
'#': as.gun.back(-1)._.ask(function(ack){ this.off(); // One response is good enough for us currently. Later we may want to adjust this.
if(!as.ack){ return }
as.ack(ack, this);
}, as.opt)
@ -75,7 +84,7 @@ function map(v,f,n, at){ var as = this;
var path = at.path, ref = as.ref, opt = as.opt;
var i = 0, l = path.length;
for(i; i < l; i++){
ref = ref.get(path[i]); // TODO: API change! We won't need 'path: true' anymore.
ref = ref.get(path[i]);
}
if(as.not || Gun.node.soul(at.obj)){
at.soul(Gun.node.soul(at.obj) || ((as.opt||{}).uuid || as.gun.back('opt.uuid') || Gun.text.random)());
@ -106,7 +115,7 @@ function any(at, ev){
var cat = (at.gun._.back._), data = cat.put, opt = as.opt||{}, root, tmp;
ev.off();
if(as.ref !== as.gun){
tmp = (as.gun._).get;
tmp = (as.gun._).get || cat.get;
if(!tmp){ // TODO: Handle
console.log("Please report this as an issue! Put.no.get"); // TODO: BUG!??
return;
@ -129,10 +138,10 @@ function any(at, ev){
}
if(!as.not && !(as.soul = Gun.node.soul(data))){
if(as.path && obj_is(as.data)){ // Apparently necessary
as.soul = (opt.uuid || as.gun.back('opt.uuid') || Gun.text.random)();
as.soul = (opt.uuid || cat.root._.opt.uuid || Gun.text.random)();
} else {
//as.data = obj_put({}, as.gun._.get, as.data);
as.soul = at.soul;
as.soul = at.soul || cat.soul || (opt.uuid || cat.root._.opt.uuid || Gun.text.random)();
}
}
as.ref.put(as.data, as.soul, as);

View File

@ -37,27 +37,43 @@ Gun._ = { // some reserved key words, these are not the only ones.
at.root = at.root || at.gun;
at.graph = at.graph || {};
at.dup = at.dup || new Gun.dup;
at.ask = Gun.on.ask;
at.ack = Gun.on.ack;
var gun = at.gun.opt(at.opt);
if(!at.once){
at.on('in', input, at);
at.on('out', output, at);
at.on('in', root, at);
at.on('out', root, at);
}
at.once = 1;
return gun;
}
function output(at){
//console.log("add to.next(at)!"); // TODO: BUG!!!!
var cat = this.as, gun = cat.gun, tmp;
// TODO: BUG! Outgoing `get` to read from in memory!!!
if(at.get && get(at, cat)){ return }
cat.on('in', obj_to(at, {gun: cat.gun})); // TODO: PERF! input now goes to output so it would be nice to reduce the circularity here for perf purposes.
if(at['#']){
function root(at){
//console.log("add to.next(at)"); // TODO: BUG!!!
var ev = this, cat = ev.as, coat;
if(!at.gun){ at.gun = cat.gun }
if(!at['#'] && at['@']){
at['#'] = Gun.text.random(); // TODO: Use what is used other places instead.
// TODO: BUG! For multi-instances, the "ack" system is globally shared, but it shouldn't be.
if(cat.ack(at['@'], at)){ return } // TODO: Consider not returning here, maybe, where this would let the "handshake" on sync occur for Holy Grail?
cat.dup.track(at['#']);
Gun.on('out', obj_to(at, {gun: cat.gun}));
return;
}
if(!at.gun){
at = obj_to(at, {gun: gun});
if(at['#'] && cat.dup.check(at['#'])){ return }
cat.dup.track(at['#']);
if(cat.ack(at['@'], at)){ return }
//cat.ack(at['@'], at);
coat = obj_to(at, {gun: cat.gun});
if(at.get){
if(!get(at, cat)){
Gun.on('get', coat);
}
}
Gun.on('out', at); // TODO: BUG! PERF? WARNING!!! A in-memory `put` triggers an out with an existing ID which reflows into IN which at the end also goes Gun OUT, and then this scope/function resumes and it triggers OUT again!
if(at.put){
Gun.HAM.synth(at, ev, cat.gun); // TODO: Clean up, just make it part of on('put')!
Gun.on('put', coat);
}
Gun.on('out', coat);
}
function get(at, cat){
var soul = at.get[_soul], node = cat.graph[soul], field = at.get[_field], tmp;
@ -68,50 +84,36 @@ Gun._ = { // some reserved key words, these are not the only ones.
tmp = Gun.obj.put(Gun.node.soul.ify({}, soul), field, node[field]);
node = Gun.state.ify(tmp, field, Gun.state.is(node, field));
}
as.on('in', {
put: node, // TODO: BUG! Clone node!
get: as.soul,
//if(at.gun === cat.gun){
node = Gun.graph.node(node); // TODO: BUG! Clone node?
//} else {
// cat = (at.gun._);
//}
tmp = as.ack;
cat.on('in', {
'@': at['#'],
how: 'mem',
put: node,
gun: as.gun
});
if(0 < as.ack){
if(0 < tmp){
return true;
}
}
function input(at){
//console.log("add to.next(at)"); // TODO: BUG!!!
var ev = this, cat = ev.as;
if(!at.gun){ at.gun = cat.gun }
if(!at['#'] && at['@']){
at['#'] = Gun.text.random(); // TODO: Use what is used other places instead.
if(Gun.on.ack(at['@'], at)){ return } // TODO: Consider not returning here, maybe, where this would let the "handshake" on sync occur for Holy Grail?
cat.dup.track(at['#']);
cat.on('out', at);
return;
}
if(at['#'] && cat.dup.check(at['#'])){ return }
cat.dup.track(at['#']);
if(Gun.on.ack(at['@'], at)){ return }
if(at.put){
Gun.HAM.synth(at, ev, cat.gun); // TODO: Clean up, just make it part of on('put')!
Gun.on('put', at);
}
if(at.get){ Gun.on('get', at) }
Gun.on('out', at);
}
}());
;(function(){
var ask = Gun.on.ask = function(cb, as){
Gun.on.ask = function(cb, as){
if(!this.on){ return }
var id = Gun.text.random();
if(cb){ ask.on(id, cb, as) }
if(cb){ this.on(id, cb, as) }
return id;
}
ask.on = Gun.on;
Gun.on.ack = function(at, reply){
if(!at || !reply || !ask.on){ return }
if(!at || !reply || !this.on){ return }
var id = at['#'] || at;
if(!ask.tag || !ask.tag[id]){ return }
ask.on(id, reply);
if(!this.tag || !this.tag[id]){ return }
this.on(id, reply);
return true;
}
}());