Begin 1 Years worth of Merges... (#1116)

* tmp for hn issue

* log top to stats

* test for guntest peer

* try big messages

* parse time?

* what bin/node is 11ms?

* be normal for hnoon

* tolerate 0.5s

* try 3s for hnoon?

* stop empty gets

* tmp for guntest

* back to normal

* check hash time

* back to normal in hear

* screen / upload / play / pause

* merge latest npm release into manhattan

* merge master 0.2020.421 into manhattan manually

* WIP

* manually merge from master

* gatling

* Update upload.html

* work in progress...

* yson panic chat basic

* after `.put(` walk

* restructure acks

* messy but 2 units passing!

* put recursive once on map

* basics

* have NTS use DAM + fix other utils

* Rewrote nts

* Allow passing test cli args.

Before, no CLI args would be passed when running `npm test`. Keeping the `mocha` at the end of the test script allows passing CLI args to Mocha.

* put back scan & once tweak

* PANIC user paste OK

* manhattan sea

* stub out nts for now

* AXE tweak

* tweak for quick first prod testing

* tweak for first in-prod testing

* tweak

* tweak

* sketchy in-prod debug attempt

* caught it? maybe? now restore

* Create download-log.html

* stub out yson test prod?

* ugh, gotta see what is going on

* move dl

* gonna stop doing commit messages for in-prod test/tweaks/debugging

* a

* p

* squelch

* console stats

* stats

* stop travis

* restore yson

* ahhh no file access without sudo

* mem

* no stub

* fix axe

* bump

* back to in-prod testing, isolate/stub out code

* stub all out for 17K ? CPU ? test

* stub dup gc

* ugh main stub

* does this stop url format blocking?

* re-add dup

* no top :(

* will this work?

* get ack stats?

* a map chain may ask for data not a root soul chain

* move proper logic into .get(

* how 2019 compat?

* a couple more!

* more tests passing! :D :)

* even more! SO EXCITING :D

* Am I alive?

* wow I can't believe it works like this

* THANK YOU @rogowski !!!!!!

* Create trace.html

Adding tracing to debuging.

* @rogowski is a super star :) :) :)

* Update trace.js

Change `Gun.logs` to `Gun.traces` and `Gun._log` to `Gun._trace`.

* Update trace.html

Change `Gun.logs` to `Gun.traces` and `Gun._log` to `Gun._trace`.
Overload get,put,on,map

* @rogowski approved of these trace changes :)

* Update trace.html

More decoupled.

* Update trace.js

More decoupled

* 2 steps backwards, 1 step forward?

* back where we ( @rogowski ) started :P

* YAYAYAYAYAYAYAY past where we started at!

* safer to have it here

* slight tweak? Let's see how long it lasts.

* merge checks we left out during consolidation

* ugly common.js for @rogowski

* slightly better

* amazing map discovery + don't clear on not found if data exists

* onto next test...

* all caught up!!! Now update tests from graphify to statedisk

* Update common.js

Tests updated from graphify to statedisk.

* easy to debug & fix thanks to @rogowski 's test upgrades & trace!

* hmm, IDK if we should support this anymore?

* support once chaining?

* check if listener order is earlier than write

* in-process message passing needs to clean itself of flags for now

* ack to chains that can't be reached

* call sub chains on clear/empty WIP

* call sub chains clear/empty OK!

* into unlink. Clean/refactor later.

* oh that was nice

* self check not needed?

* test was poorly constructed?

* refactor unlink to cleaner logic

* Will you blame me for this? Special case, maybe later move to cleaner place?

* use stun's run id instead.

* cleaner unlink logic

* better map, link, and unlink logic.

* unstub relay

* refactor unlink

* invert

* if prev value would have caused unlink, do not unlink again.

* w000h00! Best unlink so far.

* woops, fix unlinking nested

* unsubscribe nested maps (working, tho possible perf regression? check)

* put check soul

* add default uuid

* improved browser peer retry logic, let devices sleep, etc.

* Chaining API unit tests passing!

* merge new panic tests into here to test

* add panic utils

* fix long streaming timeout/expiry issue, update examples

* yield generating test data

* yeah, adapter errors (like out of storage) should not affect sync/networking logic, that was a bad experiment

* git glitch?

* some mid debugging fixes but maybe scary changes, hopefully safe to revert here except dub

* SEA unit tests passing!!! Needed quite a few fixes on async write queue stuff.

* optionally make auth async

* revise/correct set

* Fix reverse boundary check

* Add extra tests, catch bad guy, obliterate bug.

* chat app with emoji examples

* handle empty string keyed objects

* starting lex support

* tweak for lex

* woops! lexical alphabetical oopsies. That was bad.

* upload either way

* debug

* start

* fix

* fix

* clean + feature

* update dependencies in package.json (#1086)

* rad lex once map once

* axe polyfill for now

* oops log

* oops maybe without this it crashed the peer

* what on earth happened to my browser/OS? "unplug & plug it back in" restart seemed to fix it.

* oh, don't memory leak req/res asks. :/ duh!

* no accidental #soul.""

* ugh, still have to sort :(, really should polyfill weakmap then

* oops, pluck needs new object to go into

* oops, make sure soul is passed

* updating deprecated functions

* begin AXE. Next: load balance!

* Update sea.js

* keys are dangerous!

* AXE round robin load balance

* better ash hash checking

* lS reuse in-mem reply chunking

* state machine!!!

* RAD needs to pass cache misses.

* updating deprecated functions (#1088)

* update dependencies in package.json

* updating deprecated functions

* remove where.gundb.io

* Bring SEA.certify into manhattan branch (#1092)

Co-authored-by: Radu Cioienaru <radu@projectmanager.com>

* fix rad, make get() hookable

* rad browser tests seem to be passing!

* reverse user random side, add err, update styles, + more

* fix pack/max, update dom

* paste!

* of course it'll dedup cause it just called track on hear, fix

* 📦 Adding the hub feature to this branch & improvements. (#1102)

* 📦 Adding the hub feature to this branch.

* 🗑 Removed the container for speed improvement !

* 📝 I added some comments to the code.

Co-authored-by: Hector <fairfairytotor@gmail.com>
Co-authored-by: Hector <pro.hector.kub@gmail.com>

* Update axe.js

* 🦅 Wrap everything in a try & catch for error handling…  (#1105)

* 🦅 Wrap everything in a try & catch for error handling & speed improvement.

* 📦 Finally here : opt.file for the hub feature !

* 📦 Finally here : opt.file for the hub feature !

And also : fixed indentation 😋

Co-authored-by: noctisatrae <pro.hector.kub@gmail.com>

* probs better this way, safer

* moved test/axe tests to test/panic/axe.

* New test: axe load balance.

* axe test: webrtc data balance(fix paths and file renamed).

* test axe: renaming webrtc file.

* axe test: separating webrtc test for data_balance.

* axe test: test only with the relay(without webrtc).

* Update sea.js

Same as https://github.com/amark/gun/pull/1062

* Update gun.js

var tmp

* Update upload.js

* merge, update stun

* SEA.certify wire logic + unit tests (#1110)

* SEA.certify wire logic + unit tests

* picking white hair

* ack err

* axe tests using puppeteer.

* change stun system

* ~20lines

* put use parent soul link if need

* handle errors

* finally seems fixed

* cb not to

* relay

* nasty bug! Don't crash, tho need to find what causes it

* undo local changes/notes to self

* deprecation warnings

* "old" data to test against

* oops, forgot I played with ascii

* debug

* in-prod check: sites

* in-prod isolate

* gotta find this, by stubbing out

* where?

* will this work?

* clearly not, lol what's the point then? maybe like this

* and again

* must we?

* USE THIS MANHATTAN VERSION

* clean

* better panic hints

Co-authored-by: Robin Bron <finwo@pm.me>
Co-authored-by: Pavel Diatchenko <diatche@users.noreply.github.com>
Co-authored-by: rogowski <163828+rogowski@users.noreply.github.com>
Co-authored-by: I001962 <i001962@gmail.com>
Co-authored-by: Adriano Rogowski <rogowski.adriano@gmail.com>
Co-authored-by: Radu <cetatuie@gmail.com>
Co-authored-by: Radu Cioienaru <radu@projectmanager.com>
Co-authored-by: Hector <46224745+noctisatrae@users.noreply.github.com>
Co-authored-by: Hector <fairfairytotor@gmail.com>
Co-authored-by: Hector <pro.hector.kub@gmail.com>
Co-authored-by: Martti Malmi <sirius@iki.fi>
Co-authored-by: mimiza <dev@mimiza.com>
This commit is contained in:
Mark Nadal 2021-08-21 21:19:29 -07:00 committed by GitHub
parent e7afd231eb
commit 087704ec6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 5823 additions and 19252 deletions

View File

@ -2,6 +2,7 @@ language: node_js
branches:
except:
- debug
- manhattan
node_js:
- 10
cache:

View File

@ -2,6 +2,8 @@
## 0.2020.x
`>0.2020.520` may break in-process `gun1` `gun2` message passing. Check `test/common.js` "Check multi instance message passing" for a hint and/or complain on community chat.
- No breaking changes to core API.
- Storage adapter `put` event breaking change (temporary?), RAD is official now and storage adapters should be RAD plugins instead of GUN adapters.
- GUN soul format changed from being a random UUID to being a more predictable graph path (of where initially created) to support even better offline behavior. This means `null`ing & replacing an object will not create a new but re-merge.

View File

@ -1 +1 @@
web: node examples/http.js
web: node --inspect examples/http.js

View File

@ -8,7 +8,7 @@
"description": "Javascript, Offline-First Javascript Graph Database Server Peer",
"env": {
"NPM_CONFIG_PRODUCTION": {
"description": "If you don't want to serve the Gun landing page, set to \"true\".",
"description": "If you do not want default features, set to \"true\".",
"value": "false"
},
"PEERS": {

314
axe.js
View File

@ -25,298 +25,46 @@
;USE(function(module){
var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
var AXE = USE('./root'), Gun = (AXE.window||'').Gun || USE('./gun', 1);
(Gun.AXE = AXE).GUN = AXE.Gun = Gun;
var ST = 0;
Gun.on('opt', function(at){
start(at);
this.to.next(at); // make sure to call the "next" middleware adapter.
});
if(!Gun.window){ try{ USE('./lib/axe', 1) }catch(e){} }
Gun.on('opt', function(at){ start(at) ; this.to.next(at) }); // make sure to call the "next" middleware adapter.
function start(at){
if(at.axe){ return }
var opt = at.opt, peers = opt.peers;
function start(root){
if(root.axe){ return }
var opt = root.opt, peers = opt.peers;
if(false === opt.axe){ return }
if((typeof process !== "undefined") && 'false' === ''+(process.env||{}).AXE){ return }
var axe = at.axe = {}, tmp;
// 1. If any remembered peers or from last cache or extension
// 2. Fallback to use hard coded peers from dApp
// 3. Or any offered peers.
//if(Gun.obj.empty(p)){
// Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){
// p[url] = {url: url, axe: {}};
// });
//}
// Our current hypothesis is that it is most optimal
// to take peers in a common network, and align
// them in a line, where you only have left and right
// peers, so messages propagate left and right in
// a linear manner with reduced overlap, and
// with one common superpeer (with ready failovers)
// in case the p2p linear latency is high.
// Or there could be plenty of other better options.
if((typeof process !== "undefined") && 'false' === ''+(process.env||'').AXE){ return }
if(!Gun.window){ return }
var axe = root.axe = {}, tmp, id;
tmp = peers[id = 'http://localhost:8765/gun'] = peers[id] || {};
tmp.id = tmp.url = id;
tmp.retry = tmp.retry || 0; // BUG: Check 0?
console.log("AXE enabled: Trying to find network via (1) local peer (2) last used peers (3) hard coded peers.");
var last = JSON.parse((localStorage||'')[(opt.file||'')+'axe/']||null) || {};
Object.keys(last.peers||'').forEach(function(key){
tmp = peers[id = key] = peers[id] || {};
tmp.id = tmp.url = id;
});
tmp = peers[id = 'https://gun-manhattan.herokuapp.com/gun'] = peers[id] || {};
tmp.id = tmp.url = id;
/*
AXE should have a couple of threshold items...
let's pretend there is a variable max peers connected
mob = 10000
if we get more peers than that...
we should start sending those peers a remote command
that they should connect to this or that other peer
and then once they (or before they do?) drop them from us.
sake of the test... gonna set that peer number to 1.
The mob threshold might be determined by other factors,
like how much RAM or CPU stress we have.
*/
opt.mob = opt.mob || 9876 || Infinity;
var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
console.log("AXE enabled.");
function verify(dht, msg) {
var S = (+new Date);
var puts = Object.keys(msg.put);
var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
var subs = dht(soul);
if (!subs) { return; }
var tmp = [];
Gun.obj.map(subs.split(','), function(pid) {
if (pid in peers) {
tmp.push(pid);
mesh.say(msg, peers[pid]);
}
});
/// Only connected peers in the tmp array.
if (opt.super) {
dht(soul, tmp.join(','));
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); // DAM!
mesh.way = function(msg){
if(root.$ === msg.$ || (msg._||'').via){
mesh.say(msg, opt.peers);
return;
}
console.STAT && console.STAT(S, +new Date - S, 'axe verify');
}
function route(get){ var tmp;
if(!get){ return }
if('string' != typeof (tmp = get['#'])){ return }
return tmp;
}
// TODO: AXE NEEDS TO BE CHECKED FOR NEW CODE SYSTEM!!!!!!!!!!
var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
at.opt.dht = Rad();
at.on('in', input);
function input(msg){
var to = this.to, peer = (msg._||'').via; // warning! mesh.leap could be buggy!
var dht = opt.dht;
var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
var get = msg.get, hash, tmp;
//if(get && opt.super && peer){
if(get && opt.super && peer && (tmp = route(get))){
hash = tmp; //Gun.obj.hash(get); // USE RAD INSTEAD!
(routes[hash] || (routes[hash] = {}))[peer.id] = peer;
(peer.routes || (peer.routes = {}))[hash] = routes[hash];
/*if(soul = get['#']){ // SWITCH BACK TO USING DHT!
if(key = get['.']){
} else {
}
if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);}
var pids = joindht(dht, soul, peer.id);
if (pids) {
var dht = {};
dht[soul] = pids;
mesh.say({dht:dht}, opt.peers[peer.id]);
}
}*/
var at = (msg.$||'')._;
if(!at){ mesh.say(msg, opt.peers); return }
if(msg.get){
if(at.axe){ return } // don't ask for it again!
at.axe = {};
}
if((tmp = msg['@']) && (tmp = at.dup.s[tmp])){
tmp.ack = (tmp.ack || 0) + 1; // count remote ACKs to GET.
}
to.next(msg);
if (opt.rtc && msg.dht) {
Gun.obj.map(msg.dht, function(pids, soul) {
dht(soul, pids);
Gun.obj.map(pids.split(','), function(pid) {
/// TODO: here we can put an algorithm of who must connect?
if (!pid || pid in opt.peers || pid === opt.pid || opt.announce[pid]) { return; }
opt.announce[pid] = true; /// To try only one connection to the same peer.
opt.announce(pid);
});
});
}
};
//try{console.log(req.connection.remoteAddress)}catch(e){};
mesh.hear['opt'] = function(msg, peer){
if(msg.ok){ return opt.log(msg) }
var tmp = msg.opt;
if(!tmp){ return }
tmp = tmp.peers;
if(!tmp || !Gun.text.is(tmp)){ return }
if(axe.up[tmp] || 6 <= Object.keys(axe.up).length){ return }
var o = tmp; //{peers: tmp};
at.$.opt(o);
o = peers[tmp];
if(!o){ return }
o.retry = 9;
mesh.wire(o);
if(peer){ mesh.say({dam: 'opt', ok: 1, '@': msg['#']}, peer) }
mesh.say(msg, opt.peers);
}
setInterval(function(tmp){
if(!(tmp = at.stats && at.stats.stay)){ return }
(tmp.axe = tmp.axe || {}).up = Object.keys(axe.up||{});
},1000 * 60)
setTimeout(function(tmp){
if(!(tmp = at.stats && at.stats.stay)){ return }
Gun.obj.map((tmp.axe||{}).up, function(url){ mesh.hear.opt({opt: {peers: url}}) })
},1000);
if(at.opt.super){
var rotate = 0;
mesh.way = function(msg) {
if (msg.rtc) {
if (msg.rtc.to) {
/// Send announce to one peer only if the msg have 'to' attr
var peer = (peers) ? peers[msg.rtc.to] : null;
if (peer) { mesh.say(msg, peer); }
return;
}
}
if(msg.get){ mesh.say(msg, axe.up) } // always send gets up!
if(msg.get && (tmp = route(msg.get))){
var hash = tmp; //Gun.obj.hash(msg.get);
var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
var peers = routes[hash];
function chat(peers, old){ // what about optimizing for directed peers?
if(!peers){ return chat(opt.peers) }
var S = (+new Date); // STATS!
var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
var meta = (msg._||yes);
clearTimeout(meta.lack);
var id, peer, c = 1; // opt. ?redundancy?
while((id = ids[meta.turn || 0]) && c--){ // TODO: This hits peers in order, not necessarily best for load balancing. And what about optimizing for directed peers?
peer = peers[id];
meta.turn = (meta.turn || 0) + 1;
if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
}
console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe chat');
//console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
if(0 < c){
if(peers === opt.peers){ return } // prevent infinite lack loop.
return meta.turn = 0, chat(opt.peers, peers)
}
var hash = msg['##'], ack = meta.ack || at.dup.s[msg['#']];
meta.lack = setTimeout(function(){
if(ack && hash && hash === msg['##']){ return }
if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
//console.log(msg['#'], "CONTINUE:", ack, hash, msg['##']);
chat(peers, old); // keep asking for data if there is mismatching hashes.
}, 25);
}
return chat(peers);
}
// TODO: PUTs need to only go to subs!
if(msg.put){
mesh.say(msg, axe.up); // always send gets up! Hope that mesh.say below dedups via DAM's check.
var S = (+new Date); // STATS!
var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
var peers = {};
Gun.obj.map(msg.put, function(node, soul){
var hash = soul; //Gun.obj.hash({'#': soul});
var to = routes[hash];
if(!to){ return }
Gun.obj.to(to, peers);
});
console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'axe put');
mesh.say(msg, peers);
return;
}
mesh.say(msg, opt.peers); return; // TODO: DISABLE THIS!!! USE DHT!
if (!msg.put) { mesh.say(msg); return; }
//console.log('AXE HOOK!! ', msg);
verify(opt.dht, msg);
};
} else {
mesh.route = function(msg) {
if (msg.rtc) {
}
if (!msg.put) { mesh.say(msg); return; }
verify(opt.dht, msg);
/// Always send to superpeers?
Gun.obj.map(peers, function(peer) {
if (peer.url) {
mesh.say(msg, peer);
}
});
};
}
axe.up = {};
at.on('hi', function(peer){
this.to.next(peer);
if(!peer.url){ return }
axe.up[peer.id] = peer;
});
at.on('bye', function(peer){ this.to.next(peer);
if(peer.url){ delete axe.up[peer.id] }
var S = +new Date;
Gun.obj.map(peer.routes, function(route, hash){
delete route[peer.id];
if(Gun.obj.empty(route)){
delete axe.routes[hash];
}
});
console.STAT && console.STAT(S, +new Date - S, 'axe bye');
});
// handle rebalancing a mob of peers:
at.on('hi', function(peer){
this.to.next(peer);
if(peer.url){ return } // I am assuming that if we are wanting to make an outbound connection to them, that we don't ever want to drop them unless our actual config settings change.
var count = Object.keys(opt.peers).length;
if(opt.mob >= count){ return } // TODO: Make dynamic based on RAM/CPU also. Or possibly even weird stuff like opt.mob / axe.up length?
var peers = Object.keys(axe.up);
if(!peers.length){ return }
mesh.say({dam: 'mob', mob: count, peers: peers}, peer);
//setTimeout(function(){ mesh.bye(peer) }, 9); // something with better perf? // UNCOMMENT WHEN WE ACTIVATE THIS FEATURE
});
at.on('bye', function(peer){
this.to.next(peer);
});
at.on('hi', function(peer){
this.to.next(peer);
// this code handles disconnecting from self & duplicates
setTimeout(function(){ // must wait
if(peer.pid !== opt.pid){
// this extra logic checks for duplicate connections between 2 peers.
if(!Gun.obj.map(axe.up, function(p){
if(peer.pid === p.pid && peer !== p){
return yes = true;
}
})){ return }
}
mesh.say({dam: '-'}, peer);
delete at.dup.s[peer.last];
}, Math.random() * 100);
});
mesh.hear['-'] = function(msg, peer){
mesh.bye(peer);
peer.url = '';
}
}
function joindht(dht, soul, pids) {
if (!pids || !soul || !dht) { return; }
var subs = dht(soul);
var tmp = subs ? subs.split(',') : [];
Gun.obj.map(pids.split(','), function(pid) {
if (pid && tmp.indexOf(pid) === -1) { tmp.push(pid); }
});
tmp = tmp.join(',');
dht(soul, tmp);
return tmp;
}
var empty = {}, yes = true, u;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<ul id='list'>
</ul>
<ul id='list'></ul>
<form id='form'>
<input id='who' placeholder='name'>
<input id='input' placeholder='say'>
<input id='what' placeholder='say'>
<input type='submit' value='send'>
</form>
<script src="https://cdn.jsdelivr.net/npm/emojione@4.0.0/lib/js/emojione.min.js"></script>
<script src="../../../gun/gun.js"></script>
<script src="https://cdn.jsdelivr.net/npm/emojione@4.0.0/lib/js/emojione.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/emojione@4.0.0/extras/css/emojione.min.css"/>
<script>
var gun = Gun(location.origin + '/gun');
var chat = gun.get("chat/" + location.hash.slice(1));
form.onsubmit = function(eve){
eve.preventDefault();
chat.set(who.value +': ' + input.value);
input.value = "";
}
chat.map().once(function(msg){ render(msg) });
function render(msg, li, beep){
gun = Gun(location.origin + '/gun'), chat = gun.get("chat/" + location.hash.slice(1));
form.onsubmit = (eve) => { chat.set(who.value+': '+what.value), eve.preventDefault(what.value = "") }
chat.map().once((data) => { render(data) });
function render(msg, li, beep){ // render emoji, scroll to bottom, and notify sound!
(li = document.createElement("li")).innerText = emojione.shortnameToUnicode(msg);
list.appendChild(li);
window.scroll(0, list.offsetHeight);
list.appendChild(li), window.scroll(0, list.offsetHeight);
(beep = new SpeechSynthesisUtterance()).text = "new";
beep.rate = 10; beep.pitch = 2;
window.speechSynthesis.speak(beep);
beep.rate = 10, beep.pitch = 2, window.speechSynthesis.speak(beep);
}
</script>
</body>
</html>
</script>

View File

@ -1,22 +1,9 @@
<!DOCTYPE html>
<style>html, body, textarea { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
<textarea placeholder="paste here!"></textarea>
<script src="../jquery.js"></script>
<script src="../../../gun/gun.js"></script>
<script>
var gun = Gun(location.origin + '/gun');
var to, paste = gun.get('test').get('paste').on(function(data){
$('textarea').val(data);
})
$('textarea').on('input change blur keyup mouseup touchend', function(){
clearTimeout(to); // debounce
to = setTimeout(function(){
paste.put($('textarea').val());
}, 100);
})
<textarea id="paste" placeholder="paste here!"></textarea>
<script src="../../../gun/gun.js"></script><script>
gun = GUN(location.origin + '/gun');
copy = gun.get('test').get('paste');
paste.oninput = () => { copy.put(paste.value) };
copy.on((data) => { paste.value = data });
</script>

148
examples/basic/poll.html Normal file
View File

@ -0,0 +1,148 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="../jquery.js"></script>
<script src="../../../gun/gun.js"></script>
<script src="../../../gun/sea.js"></script>
<!-- script src="../../../gun/axe.js"></script -->
<script> // main init!
var app = {
view: $, // replace with not jquery!
data: GUN('http://localhost:8765/gun'), // peer-to-peer database!
};
app.user = app.data.user().recall({sessionStorage: true});
</script>
</head>
<body>
<div id="login" class="center pad">
<style>
#login input {
max-width: 6em;
}
</style>
<form id="sign" onsubmit="app.login(event)">
<input id="alias" placeholder="username" class="jot rim">
<input id="pass" type="password" placeholder="passphrase" class="jot rim">
<input id="in" type="submit" value="sign in" class="green whitet act gap sap rim">
<input id="up" type="button" value="sign up" onclick="app.register()" class="act gap sap rim">
</form>
<script>
app.login = function(eve){
if(app.error(eve)){ return }
app.data.user().auth(
app.view('#alias').val(),
app.view('#pass').val(),
app.error
);
};
app.register = function(eve){
app.data.user().create(
app.view('#alias').val(),
app.view('#pass').val(),
app.login
);
};
app.data.on('auth', function(eve){
app.view('#sign').hide(); // hide login form upon logging in.
});
</script>
</div>
<div id="poll" class="pad">
<style>
#poll {
display: flex;
flex-wrap: wrap;
}
#poll div {
margin: 1%;
width: 100%;
}
</style>
<script>
(window.onhashchange = async function(){
app.poll = app.data.get(location.hash.slice(1));
app.poll.map().on(function(data, id){
app.render(id = 'p'+String.hash(id), '.q', '#poll', data).css({order: data.how}).data('as',{$:this});
console.log("poll?", id, data);
});
})();
app.render = function(id, model, onto, data){
var ui = $(
$('#'+id).get(0) ||
$('.model').find(model).clone(true).attr('id', id).appendTo(onto)
);
$.each(data, function(field, val){
if($.isPlainObject(val)){ return }
ui.find("[name='" + field + "']").val(val).text(val);
});
return ui;
}
</script>
<div class="model">
<div class="q">
<span name="what"></span>
</div>
</div>
</div>
<div id="make" class="pad">
<style>
#make #add {
border-radius: 100%;
width: 2em;
height: 2em;
line-height: 0em;
padding: 0;
margin: 0;
text-align: center;
}
</style>
<button id="add" onclick="app.add()" class="green whitet act">+</button>
<span class="hint">add new title, text, question...</span>
<script>
app.add = async function(){
if(app.error(app.user)){ return }
var tmp = await (app.poll = app.poll || app.data.get(location.hash.slice(1)));
if(!tmp){ app.poll = app.user.get('poll').set({}) }
app.poll.set({how: tmp = Object.keys(tmp||'').length || 1, what: "Question " + tmp });
if(!location.hash){ location.hash = (await app.poll)._['#'] }
}
</script>
</div>
<span id="error">
<span id="err"></span>
<script>
app.error = function(eve){
app.view('#err').text('').hide();
if(!eve){ return }
if(eve.preventDefault){
eve.preventDefault();
return;
}
if(eve._ && !eve.is){ eve = {err: "Not signed in!"} }
if(!eve.err){ return }
app.view('#err').text(eve.err).show();
return true;
}
</script>
</span>
<style>
#error { position: fixed; top: 0; width: 100%; text-align: center; background: white; }
</style>
<link rel="stylesheet" href="../style.css"/>
<style>
@import url('https://fonts.googleapis.com/css?family=Oxygen');
html, body { font-family: "Oxygen", sans-serif; }
</style>
</body>
</html>

View File

@ -60,7 +60,7 @@ $('#add').on('submit', function(event){
if(!(event.what = what.value)){ return err.innerText = "No description!" }
if(!(event.where = where.value)){ return err.innerText = "No location!" }
var day = gun.get(name+now(event.when));
day.get(id.value || Gun.text.random(9)).put(event);
day.get(id.value || String.random(9)).put(event);
what.value = where.value = id.value = err.innerText = '';
go.value = 'add';
schedule(event.when);

View File

@ -6,29 +6,30 @@
<img style="max-width: 100%;">
</div>
<center>
<p>Drag & drop videos, songs, or images! <input type="file" multiple></p>
<p>Drag & drop videos, songs, or images! <input id="upload" type="file" multiple></p>
</center>
<script src="../jquery.js"></script>
<script src="../../../gun/lib/yson.js"></script>
<script src="../../../gun/gun.js"></script>
<script src="../../../gun/lib/dom.js"></script>
<script src="../../../gun/lib/upload.js"></script>
<script>
var gun = Gun(location.origin + '/gun');
gun = GUN(location.origin + '/gun');
$('html').upload(function resize(eve, up){
$('html, #upload').upload(function resize(eve, up){
if(up){ return up.shrink(eve, resize, 1024) }
var b64 = (eve.base64 || ((eve.event || eve).target || eve).result || eve);
gun.get('test').get(eve.id).put(b64);
var b64 = (eve.base64 || ((eve.event || eve).target || eve).result || eve); // which one? try all!
gun.get('test').get((eve.id+(new Date).getUTCSeconds()) % 60).put(b64); // limit uploads to 1 of 60 slots.
});
gun.get('test').map().on(function(data){
if(!data){ return }
gun.get('test').map().once(function(data){
if("string" != typeof data){ return }
var type = data.split(';')[0], ui;
if(type.indexOf('image') + 1){ ui = $("img").get(0) }
if(type.indexOf('video') + 1){ ui = $('video').get(0) }
if(type.indexOf('audio') + 1){ ui = $('audio').get(0) }
if(!ui){ return }
$(ui).clone().prependTo('center').get(0).src = data;
})
});
</script>

View File

@ -166,11 +166,11 @@
function submit(e) {
e.preventDefault();
var msg = { when: Gun.time.is() };
var msg = { when: Gun.state() };
msg.who = $('.chat__name-input').val();
if (!msg.who) {
msg.who = 'user' + Gun.text.random(3);
msg.who = 'user' + String.random(3);
$('.chat__name-input').val(msg.who);
}
@ -181,7 +181,7 @@
$('.chat__message-input').val('').focus();
}
chat.map().val(function (msg, id) {
chat.map().once(function (msg, id) {
if (!msg) { return }
var messageList = $('.chat__message-list');
var last = sort(msg.when, messageList.children('li').last());

View File

@ -22,11 +22,23 @@
[contenteditable]:focus {
outline: none;
}
.meta-on, div:hover, ul:hover, ol:hover, li:hover, p:hover, span:hover, form:hover, button:hover, input:hover, textarea:hover, img:hover {
outline: 1px solid;
animation: meta-on 3s infinite;
transition: none !important;
} @keyframes meta-on {
0% {outline-color: magenta;}
33% {outline-color: cyan;}
66% {outline-color: yellow;}
100% {outline-color: magenta;}
}
</style>
<div class="hold full hue2">
<div id="page" class="max focus gap" style="padding-top: 9%;"></div>
<div id="page" class="max focus gap" style="margin-top: 9%;"></div>
</div>
<script src="../../gun/gun.js"></script>
<script src="../../gun/lib/monotype.js"></script>
<script src="../../gun/lib/meta.js"></script>
@ -37,27 +49,46 @@
<!-- script async src="https://edide.io/music.lib"></script -->
<script>
var gun = Gun(['https://guntest.herokuapp.com/gun', 'http://localhost:8765/gun']);
var gun = Gun();
var page = {};
//var gun = Gun(['https://guntest.herokuapp.com/gun', 'http://localhost:8765/gun']);
;(window.onhashchange = function(){
var file = (location.hash||'').slice(1);
var S = +new Date;
$('#page').empty().attr('contenteditable', 'false');
gun.get('test/gun/docs/'+file).get('what').map().on(function render(data, i){
if(window.LOCK){ return }
gun.get('test/gun/docs/'+file).get('what').map().on(function render(data, i, msg, eve){
var tmp = page[i] || '';
var last = Gun.state.is(gun._.root.graph[msg.put['#']], i);
if(last < tmp.last){ return }
//});
//if(window.LOCK){ return }
var p = $('#page').children().get(i);
if(!p){
$('#page').append('<p>');
setTimeout(function(){ render(data, i) },0);
setTimeout(function(){ render(data, i, msg, eve) },0);
return;
}
var DBG = {s: +new Date};
var r = monotype(p);
DBG.mono = +new Date;
var safe = $.normalize(data);
p.outerHTML = safe;
DBG.norm = +new Date;
p.outerHTML = data;
DBG.html = +new Date;
r.restore();
DBG.rest = +new Date;
//console.log("mono:", DBG.mono - DBG.s, "norm:", DBG.norm - DBG.mono, 'html:', DBG.html - DBG.norm, 'rest:', DBG.rest - DBG.html, ':::', msg, eve);
});
})();
window.requestAnimationFrame = window.requestAnimationFrame || setTimeout;
window.requestAnimationFrame(function frame(){
window.requestAnimationFrame(frame, 16);
}, 16);
document.execCommand('defaultParagraphSeparator', false, 'p');
meta.edit({
name: "Edit",
@ -91,17 +122,28 @@ meta.edit({
});
}).on('keyup.tmp', '[contenteditable]', function(eve){
//$('#debug').val(doc.html());
var p = $(window.getSelection().anchorNode).closest('p');
var p = $(window.getSelection().anchorNode).closest('p'), tmp;
(tmp = page[p.index()] || (page[p.index()] = {})).last = (+new Date) + 99;
clearTimeout(tmp.to); tmp.to = setTimeout(function(){
var DBG = {s: +new Date};
var r = monotype(p);
DBG.m = +new Date;
var html = p.html() || '';
DBG.g = +new Date;
if(!html && !p.prev().length && !p.next().length && !$('#page').html()){
edit.init();
}
DBG.i = +new Date;
var safe = $.normalize(html);
DBG.n = +new Date;
p.html(safe);
DBG.h = +new Date;
r.restore();
DBG.r = +new Date;
edit.save(p);
});
DBG.p = +new Date;
//console.log("save:", DBG.p - DBG.r, "rest:", DBG.r - DBG.h, "html:", DBG.h - DBG.n, "norm:", DBG.n - DBG.i, 'init:', DBG.i - DBG.g, 'grab:', DBG.g - DBG.m, 'mono:', DBG.m - DBG.s);
},50)});
},
up: function(){
console.log("UP");
@ -120,6 +162,7 @@ meta.edit({
var i = p.index();// = Array.prototype.indexOf.call(parent.children, child);
var file = (location.hash||'').slice(1);
var data = (p.get(0)||{}).outerHTML||'';
//data = $.normalize(data); // GOOD TO DO SECURITY ON SENDING SIDE TOO!!!
window.LOCK = true;
gun.get('test/gun/docs/'+file).get('what').get(i).put(data);
window.LOCK = false;
@ -144,26 +187,25 @@ meta.edit({
;(function(){
meta.edit({name: "Layout", combo: ['L']});
meta.edit({name: "Fill", combo: ['L','F'],
meta.edit({name: "Design", combo: ['D']});
meta.edit({name: "Fill", combo: ['D','F'], // TODO!
use: function(eve){},
on: function(eve){
var on = meta.tap();
meta.ask('Color name, code, or URL?', function(color){
on.css('background', color);
});
}, true);
},
up: function(eve){}
});
meta.edit({name: "Add", combo: ['L','A']});
meta.edit({name: "Row", combo: ['L','A', 'R'],
meta.edit({name: "Add", combo: ['D','A']});
meta.edit({name: "Row", combo: ['D','A', 'R'],
on: function(eve){
meta.tap().append('<div style="min-height: 9em; padding: 2%;">');
}
});
meta.edit({name: "Columns", combo: ['L','A','C'],
meta.edit({name: "Columns", combo: ['D','A','C'],
on: function(eve){
var on = meta.tap().addClass('center'), tmp, c;
var html = '<div class="unit col" style="min-height: 9em; padding: 2%;"></div>';
@ -174,14 +216,72 @@ meta.edit({
})
}
});
meta.edit({name: "Text", combo: ['L','A','T'],
meta.edit({name: "Text", combo: ['D','A','T'],
on: function(eve){
var tag = $('<p>text</p>');
meta.tap().append(tag);
tag.focus();
}
});
meta.edit({name: "Delete", combo: ['D','A','D'],
on: function(eve){
meta.tap().remove();
}
});
meta.edit({name: "Turn", combo: ['D','T']});
meta.edit({name: "Size", combo: ['D','S']});
meta.edit({name: "X", combo: ['D','S','X'],
on: function(eve){
var on = this.a = meta.tap().addClass('meta-on'), was = on.width();
$(document).on('mousemove.tmp', function(eve){
var be = was + ((eve.pageX||0) - was);
on.css({'max-width': be, width: '100%'});
});
meta.ask('Width in px, %, or other unit?', function(w){
if(!w){ return }
on.css({'max-width': w, width: '100%'});
}, true);
}, up: function(){
$(document).off('mousemove.tmp');
this.a.removeClass('meta-on');
}
});
meta.edit({name: "Y", combo: ['D','S','Y'],
//on: function(eve){ console.log('on Y') },
on: function(eve){ console.log('use Y')
var on = this.a = meta.tap().addClass('meta-on'), was = on.height();
$(document).on('mousemove.tmp', function(eve){
var be = was + ((eve.pageY||0) - was);
on.css({'min-height': be});
})
}, up: function(){ console.log('up Y')
$(document).off('mousemove.tmp');
this.a.removeClass('meta-on');
}
});
}());
;(function(){
var logic = {};
meta.edit({name: "Logic", combo: ['L']});
meta.edit({name: "Symbol", combo: ['L','S'],
on: function(eve){
console.log(1);
}
});
meta.edit({name: "Action", combo: ['L','A'],
on: function(eve){
console.log(2);
}
});
meta.edit({name: "Data", combo: ['L','D'],
on: function(eve){
console.log(3);
}
});
}());
;(function(){
@ -232,9 +332,47 @@ meta.edit({
});
$(document).on('keydown', function(eve){
music.play(String.fromCharCode(eve.which));
if(eve.which === music.which){ return }
music.play(String.fromCharCode(music.which = eve.which));
});
}());
;(function(){
/*
Edit
Bold
Italic
Link
?
Left
Middle
Right
Justify
?
Small
Normal
Header
Title
Design
Add
Row
Column
Text
Delete
Turn
Grab
Size
X
Y
Fill
Logic
Symbol
Action
Data
*/
/*
*/
}());
</script>
</body>

View File

@ -90,7 +90,7 @@
</div>
<div class="focus center row leak">
<!-- just like in real life, looking pretty attracts attention, so show off and look glamorous! -->
<img class="unit blink" src="file:///Users/mark/Downloads/supercatdog.png" style="min-width: 10em; width: 80%;">
<img class="unit blink" src="file:///Users/mark/Pictures/supercatdog.png" style="min-width: 10em; width: 80%;">
</div>
<script>location.hash = ''</script>
<script src="https://cdn.jsdelivr.net/npm/gun/lib/fun.js"></script>
@ -114,7 +114,7 @@
</div>
</div>
<div class="focus center row leak">
<img class="unit blink" src="file:///Users/mark/Downloads/supercatdog.png" style="transform: scaleX(-1); filter: invert(1); min-width: 10em; width: 80%;">
<img class="unit blink" src="file:///Users/mark/Pictures/supercatdog.png" style="transform: scaleX(-1); filter: invert(1); min-width: 10em; width: 80%;">
</div>
</div>

331
examples/game/win.html Normal file
View File

@ -0,0 +1,331 @@
<!DOCTYPE html>
<html>
<head>
<!-- always start with these two lines to set a clean baseline for different devices -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="../style.css">
<!-- link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/gun/examples/style.css" -->
<script src="https://cdn.jsdelivr.net/npm/gun/examples/jquery.js"></script>
<title>Win</title>
</head>
<body class="black whitet">
<style>
/*
Choose white text on a black background so you can add color in.
Pick your favorite font and choose a font size.
*/
@import url('https://fonts.googleapis.com/css?family=Montserrat');
html, body {
font-family: "Montserrat", sans-serif;
}
button, input {
padding: 1em;
background: transparent;
border: 1px solid white;
border-radius: 1.5em;
color: white;
margin: 0.5em;
margin-bottom: 0;
cursor: pointer;
}
button:hover, input:hover {
background: white;
color: black;
transform: scale(1.1);
}
.air { padding-top: 9%; }
.yak button { font-size: 80%; }
.wag {
-webkit-animation: wag 3s infinite;
animation: wag 3s infinite;
} @keyframes wag {
0% {transform: rotate(0deg);}
50% {transform: rotate(-1deg);}
100% {transform: rotate(0deg);}
}
</style>
</style>
<!-- for educational sites, consider starting with a nice full screen welcome message -->
<div class="home hold full huef center air">
<div class="focus row">
<p><i>how to</i></p>
<p class="shout wag">Win at Life!</p>
<p><i>success, fame, power.</i></p>
<p><i>sex, ethics, & integrity.</i></p>
<!-- just like in real life, say who you are and give a concise reason why you add value to someone's life and then make a call to action, if they want to learn more they can always scroll to learn more -->
<div>
<!-- a class="unit hold" href="#fullscreen"><button>WATCH TRAILER</button></a -->
<a class="unit yak gap" href="#breathe"><button>PLAY GAME</button></a>
</div>
</div>
<div class="focus center row leak">
<!-- just like in real life, looking pretty attracts attention, so show off and look glamorous! -->
<img class="unit blink" src="" style="min-width: 10em; width: 80%;">
</div>
<script>location.hash = ''</script>
<script src="https://cdn.jsdelivr.net/npm/gun/lib/fun.js"></script>
</div>
<div id="breathe" class="hold full green">
<style>
</style>
<div class="story pad">
<p class="loud crack">Step 1: Breathe</p>
<a class="unit yak" href="#water"><button>Yupe</button></a>
</div>
<script>
</script>
</div>
<div id="water" class="hold full blue">
<style>
</style>
<div class="story pad">
<p class="loud crack">Step 2: Drink Water</p>
<p></p>
<a class="unit yak" href="#eat"><button>Next</button></a>
</div>
<script>
</script>
</div>
<div id="eat" class="hold full red">
<style>
</style>
<div class="story pad">
<p class="loud crack">Step 3: Eat Once a Day</p>
<p>If you do not want to be eaten, do not eat things that would not want to be eaten.</p>
<a class="unit yak" href="#babies"><button>Got It</button></a>
</div>
<script>
</script>
</div>
<div id="babies" class="hold full red">
<style>
</style>
<div class="story pad">
<p class="loud crack">Step 4: Babymaking* 😉</p>
<p>Find a willing player.</p>
<p><small> * This does not always make babies.</small></p>
<a class="unit yak" href="#make"><button>How?</button></a>
</div>
<script>
</script>
</div>
<div id="make" class="hold full hue">
<style>
</style>
<div class="story pad">
<p class="loud crack">Step 5: Make Dance</p>
<p>Moving your body is how you express your thoughts.</p>
<p>What you do with your body is what others will come to know you for. So do well.</p>
<p>You can make art, songs, or stories; You can make science, tools, or discoveries.</p>
<p>Who are you?</p>
<a class="unit yak" href="#science"><button>I am a Scientist!</button></a>
<a class="unit yak" href="#art"><button>I am an Artist!</button></a>
</div>
<script>
</script>
</div>
<div id="science" class="hold full hue">
<div class="story pad">
<p class="loud crack">Science: Knowing Games</p>
<p>If you must win one game, it should be the game of making games.</p>
<p>If you can make any game, then you will know how to win any game.</p>
<a class="unit yak" href="#games"><button>Games?</button></a>
</div>
<script>
</script>
</div>
<div id="games" class="hold full hue">
<div class="story pad">
<p>Games have goals and play.</p>
<p>Play is a safe space to try new dances.</p>
<p>Goals try to get players to do a type of dance.</p>
<a class="unit yak" href="#swim"><button>Start</button></a>
</div>
<script>
</script>
</div>
<div id="swim" class="hold full blue">
<style>
@import url('https://fonts.googleapis.com/css?family=Audiowide');
#hud {
opacity: 0.4;
font-family: 'Audiowide', cursive;
z-index: 999999999999;
transition: all 3s;
}
#hud .life {
position: fixed;
left: 50%;
bottom: 0px;
padding: 0.25em 1em 0.1em;
border-radius: 0.5em 0.5em 0 0;
transform: translateX(-50%);
background: black;
text-shadow: 0em -0.125em 0.75em white;
}
#hud .score {
position: fixed;
left: 50%;
top: 0px;
padding: 0.1em 1em 0.25em;
border-radius: 0 0 0.5em 0.5em;
transform: translateX(-50%);
background: black;
text-shadow: 0em 0.1em 0.75em white;
}
#hud .down {
bottom: -2em !important;
}
#hud .up {
top: -2em !important;
}
</style>
<div class="story pad">
<p>The simplest goal is to not "die" in the game.</p>
<p>Oh look, you've fallen into water and cannot breathe.</p>
<p>If you do not push the button to swim to the top, you'll lose the game.</p>
<a class="unit yak"><button>Swim</button></a>
</div>
<div id="hud">
<div class="score shade up">
SCORE: <span id="hudscore">0</span>%
</div>
<div class="life shade down">
LIFE: <span id="hudlife">100</span>%
</div>
</div>
<script>
;(function(){
var go, life = $('#hudlife').data();
$(window).on('hashchange', function(){
if(location.hash != '#swim'){ return }
$('#hudlife').text($('#hudlife').data().is = 100);
$('#hud .life').removeClass('down');
go = setInterval(function(){
if(0 >= life.is){
location.hash = 'die';
clearInterval(go);
go = false;
return;
}
$('#hudlife').text(life.is -= 1);
}, 100);
})
$('#swim a').on('click', function(){
$('#hudlife').text((life.is += 5) < 100? life.is : 100);
if(100 <= life.is){
location.hash = 'won';
clearInterval(go);
go = false;
return;
}
});
}());
</script>
</div>
<div id="won" class="hold full hue">
<div class="story pad">
<p>You won your first game! 🎉</p>
<p>See? I told you breathing is important.</p>
<p>You also learned the most basic dance: rapid poking.</p>
<p>The goal of the next game is to make the game you just won.</p>
<a class="unit yak" href="#won" style="z-index: 999999;"><button>Make</button></a>
</div>
<script src="https://cdn.jsdelivr.net/gh/amark/gun/lib/meta.js"></script>
<script>
meta.edit({name: "Add", combo: ['A']});
meta.edit({
name: "Timer",
combo: ['A','T'],
on: function(){
console.log("up");
this.to = this.to || setInterval(this.on, 100);
$("html, body").stop().animate({ scrollTop: $(window).scrollTop()-100 }, 100);
p.css({top: --p.y +'%'});
},
use: function(){},
up: function(){ clearTimeout(this.to); this.to = 0 }
});
meta.edit({
name: "Delete",
combo: ['A','D'],
on: function(){
$(meta.tap.on).remove();
},
use: function(){},
up: function(){}
});
meta.edit({
name: "Button",
combo: ['A','B'],
on: function(){
$(meta.tap.on).append("<a class='unit yak'><button>Button</button></a>")
},
use: function(){},
up: function(){}
});
meta.edit({
name: "Edit",
combo: ['E'],
on: function(){
$('body').attr('contenteditable', 'true' == $('body').attr('contenteditable')? false : true);
},
use: function(){},
up: function(){ }
});
</script>
<script>
window.requestAnimationFrame(function frame(){
return;
window.requestAnimationFrame(frame);
if(location.hash != '#won'){ return }
var p = $('#won a').offset();
var bx = p.left, by = p.top, mx = meta.tap.x, my = meta.tap.y;
bx = mx - bx; by = my - by;
var d = Math.sqrt(bx*bx + by*by);
console.log(bx, by, mx, my, d);
if(d > 250){ return }
$('#won a').css({position: 'fixed', left: bx + (Math.random()*100), top: by + (Math.random()*100)});
})
</script>
</div>
<div id="rules" class="hold full white blackt">
<style>
</style>
<div class="story pad">
<p class="loud crack">Bend these Rules if it is more Moral to do so</p>
<p></p>
<a class="unit yak" href="#water"><button>Next</button></a>
</div>
<script>
</script>
</div>
<div id="die" class="hold full red">
<style>
</style>
<div class="story pad">
<p class="loud crack">GAME OVER</p>
<p></p>
<a class="unit yak" href="#make"><button>Start Over</button></a>
</div>
<script>
</script>
</div>
</body>
</html>

View File

@ -20,7 +20,8 @@
}
var gun = Gun({web: config.server.listen(config.port), peers: config.peers});
console.log('Relay peer started on port ' + config.port + ' with /gun');
module.exports = gun;
}());
}());

View File

@ -1,57 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<title>GUN — the database for freedom fighters</title>
<meta charset="utf-8">
<meta name="description" content="GUN is a distributed, offline-first, realtime graph database engine with built-in encryption.">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta property="og:title" content="GUN — the database for freedom fighters">
<meta property="og:description" content="GUN is a distributed, offline-first, realtime graph database engine with built-in encryption.">
<meta property="og:type" content="website">
<meta property="og:image" content="iris/img/gun-og-image.png">
<meta name="twitter:card" content="summary"></meta>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" sizes="180x180" href="./iris/img/apple-touch-icon.png">
<link rel="icon" href="iris/img/gun-48x48.png">
<link rel="manifest" href="./iris/site.webmanifest">
<link rel="mask-icon" href="./iris/img/safari-pinned-tab.svg" color="#74d5f1">
<link rel="shortcut icon" href="iris/img/gun-48x48.png">
<meta name="msapplication-TileColor" content="#74d5f1">
<meta name="msapplication-config" content="./iris/browserconfig.xml">
<meta name="theme-color" content="#74d5f1">
<link rel="stylesheet" type="text/css" href="./iris/css/cropper.min.css">
<link rel="stylesheet" href="./iris/css/dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="./iris/css/light.css" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: light)">
<!-- The main stylesheet -->
<link rel="stylesheet" type="text/css" href="./iris/css/style.css">
</head>
<body>
<script src="./iris/js/lib/webtorrent.min.js"></script>
<script src="./iris/js/lib/jquery.js"></script>
<script src="./iris/js/lib/cropper.min.js"></script>
<script src="./iris/js/lib/pica.min.js"></script>
<script src="./iris/js/lib/underscore-min.js"></script>
<script src="./iris/js/lib/gun.js"></script>
<script src="./iris/js/lib/open.js"></script>
<script src="./iris/js/lib/sea.js"></script>
<script src="./iris/js/lib/nts.js"></script>
<script src="./iris/js/lib/radix.js"></script>
<script src="./iris/js/lib/radisk.js"></script>
<script src="./iris/js/lib/store.js"></script>
<script src="./iris/js/lib/rindexed.js"></script>
<script src="./iris/js/lib/iris.min.js"></script>
<script src="./iris/js/lib/emoji-button.js"></script>
<script src="./iris/js/lib/Autolinker.min.js"></script>
<script src="./iris/js/lib/qrcode.min.js"></script>
<script src="./iris/js/lib/qr.zxing.js"></script>
<script type="module" src="./Main.js"></script>
</body>
</html>
<p>This is the examples folder.
<p>The most basic example is <a href="/basic/paste.html">./basic/paste.html</a>!</p>
<p>Home page temporarily disabled.</p>

View File

@ -178,21 +178,21 @@
});
$('#share').addClass("hide");
} else {
document.cookie = 'gps=' + (gps.track = (document.cookie.match(/gps\=(.*?)(\&|$|\;)/i)||[])[1] || Gun.text.random(5)); // trick with cookies!
document.cookie = 'gps=' + (gps.track = (document.cookie.match(/gps\=(.*?)(\&|$|\;)/i)||[])[1] || String.random(5)); // trick with cookies!
gps.ref = gun.get('gps/' + gps.track);
gps.opt.track = function(pos){
pos = pos.latlng;
if(gps.follow
|| Gun.time.is() - gps.when < 1000
|| Gun.state() - gps.when < 1000
|| gps.last && gps.last.lat == pos.lat && gps.last.lng == pos.lng){
return; // throttle!
}
gps.when = Gun.time.is();
gps.when = Gun.state();
gps.ref.put(gps.last = pos);
//$('#debug').value = JSON.stringify(gps.last);
}
gps.where = gps.where || Where(gps.opt);
$('#follow').text(("where.gunDB.io/" || (location.origin + location.pathname)) + '#' + gps.track);
$('#follow').text((location.origin + location.pathname) + '#' + gps.track);
$('#share').removeClass("hide");
$('#share').on('click', function(){
$('#link').toggleClass("hide");

View File

@ -29,7 +29,7 @@
.tall { height: 5em; }
</style>
<div class="center"><span class="shout" id="peers">0</span> peers <span class="shout" id="time">0</span> min <span class="shout" id="nodes">0</span> nodes <span class="shout" id="hours">0</span> hours <span class="shout" id="block">0</span> block</div>
<div class="center"><span class="shout" id="peers">0</span> peers <span class="shout" id="time">0</span> min <span class="shout" id="nodes">0</span> nodes <span class="shout" id="hours">0</span> hours <span class="shout" id="block">0</span> block <span class="shout" id="stack">0</span> stack</div>
<input id="url" class="center input crack" placeholder="enter peer stats source url">
@ -44,19 +44,29 @@
<script src="./jquery.js"></script>
<script src="./smoothie.js" charset="utf-8"></script>
<script>
var up, br = 0, bt = 0;
var up, br = 0, bt = 0, tmp;
var fetchData = async function(){
// fetch the data from server
var S = +new Date;
var data = await (await fetch(url.value||(location.origin+'/gun/stats.radata'), {method: 'GET',mode: 'cors'})).json();
$('#block').text(((br += (+new Date - S)/1000) / ++bt).toFixed(1));
data.over = (data.over/1000) || 15;
$('#peers').text(data.peers.count);
$('#stack').text((data.cpu||'').stack);
$('#peers').text(data.peers.count);
$('#time').text((data.peers.time / 1000 / 60).toFixed(0));
$('#nodes').text(data.node.count);
$('#hours').text((data.up.time / 60 / 60).toFixed(1));
if(data.up.time === up){ console.log("up same as before") } up = data.up.time;
;(async function(){ try{
Stats('peers#').line.append(+new Date, data.peers.count);
return;
Stats('cpu%');
tmp = await (await fetch(new Request(location.origin+'/gun/stats.top.radata'), {method: 'GET',mode: 'cors'})).text();
tmp = parseFloat((tmp.split('\n').filter(l => l.indexOf('node')+1)[0]||'').split(/\s+/).slice(-4)[0]||'0');
Stats('cpu%').line.append(+new Date, tmp);
}catch(e){console.log(e)}}());
Stats('memory').line.append(+new Date, data.memory.heapTotal / 1024 / 1024);
try{ Stats('dam # in/s').line.append(+new Date, Math.round(data.dam.in.count / data.over)); }catch(e){}
try{ Stats('dam in MB/s').line.append(+new Date, data.dam.in.done / 1024 / 1024 / data.over); }catch(e){}
@ -64,6 +74,7 @@
try{ Stats('dam out MB/s').line.append(+new Date, data.dam.out.done / 1024 / 1024 / data.over); }catch(e){}
console.log('data',data);
//fetch keys in all, these may be dynamically changing
//for each key, check if we already have created a time series, if not, create it and add it
// to the chart corredsponding to the unit of measure
@ -76,9 +87,10 @@
// append data [timestamp], [data]
chart.line.append(arr[i][0], arr[i][1]);
}
})
});
}
setInterval(fetchData, 15 * 1000);
//setInterval(fetchData, 15 * 1000);
setInterval(fetchData, 5000);
fetchData();
function Stats(key, chart){

View File

@ -4,6 +4,7 @@ html, body {
position: relative;
line-height: 1.5;
font-size: 18pt;
f-ont-size: max(18pt, 2?vw);
}
div, ul, ol, li, p, span, form, button, input, textarea, img {
@ -14,6 +15,7 @@ div, ul, ol, li, p, span, form, button, input, textarea, img {
-webkit-transition: all 0.3s;
transition: all 0.3s;
box-sizing: border-box;
font: inherit;
}
a, button, input, textarea {
@ -21,16 +23,31 @@ a, button, input, textarea {
border: inherit;
color: inherit;
text-decoration: inherit;
outline: none;
}
a:focus, button:focus, input[type=button]:focus, input[type=submit]:focus {
animation: pulse 2s infinite;
}
input, textarea {
input:not([type=button]):not([type=submit]), textarea {
width: 100%;
}
::placeholder, .hint {
color: inherit;
opacity: 0.3;
}
ul, li {
list-style: none;
}
p {
padding: 0;
}
p + p {
padding-top: 0;
}
[contenteditable=true]:empty:before {
content: attr(placeholder);
}
@ -85,8 +102,11 @@ ul, li {
vertical-align: bottom;
}
.rim { margin: 2%; }
.gap { padding: 3%; }
.rim { margin: 1%; }
.gap {
padding: 3%;
padding: clamp(0.5em, 3%, 1.5em);
}
.stack { line-height: 0; }
.crack { margin-bottom: 1%; }
.sit { margin-bottom: 0; }
@ -103,16 +123,12 @@ ul, li {
float: none;
clear: both;
}
.unit, .symbol {
display: inline-block;
vertical-align: inherit;
}
.leak { overflow: visible; }
.hold { overflow: hidden; }
.act {
display: block;
/*display: block;*/
font-weight: normal;
text-decoration: none;
-webkit-transition: all 0.3s;
@ -120,6 +136,10 @@ ul, li {
cursor: pointer;
}
.unit, .symbol {
display: inline-block;
vertical-align: inherit;
}
.sap { border-radius: 0.1em; }
.jot { border-bottom: 1px dashed #95B2CA; }
@ -131,7 +151,6 @@ ul, li {
font-size: 6.5vmax;
}
.red { background: #ea3224; }
.green { background: #33cc33; }
.blue { background: #4D79D8; }
@ -324,4 +343,4 @@ ul, li {
padding: 0;
position: absolute;
width: 1px;
}
}

View File

@ -127,7 +127,7 @@
$(document).on('keyup', '.thought__input', function () {
var input = $(this), id = input.attr('id');
if (!id) {
input.attr('id', id = Gun.text.random());
input.attr('id', id = String.random());
}
typing = id;
clearTimeout(throttle);

2691
gun.js

File diff suppressed because it is too large Load Diff

113
lib/axe.js Normal file
View File

@ -0,0 +1,113 @@
// I don't quite know where this should go yet, so putting it here
// what will probably wind up happening is that minimal AXE logic added to end of gun.js
// and then rest of AXE logic (here) will be moved back to gun/axe.js
// but for now... I gotta rush this out!
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'), u;
Gun.on('opt', function(at){ start(at) ; this.to.next(at) }); // make sure to call the "next" middleware adapter.
function start(root){
if(root.axe){ return }
var opt = root.opt, peers = opt.peers;
if(false === opt.axe){ return }
if((typeof process !== "undefined") && 'false' === ''+(process.env||'').AXE){ return }
console.log("AXE relay enabled!");
var axe = root.axe = {}, tmp, id;
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); // DAM!
var dup = root.dup;
mesh.way = function(msg){
if(!msg){ return }
if(msg.get){ return GET(msg) }
if(msg.put){ return } // relaying handled by HAM aggregation, no message forwarding!
fall(msg);
}
function GET(msg){
if(!msg){ return }
var via = (msg._||'').via, soul, has, tmp, ref;
if(!via || !via.id){ return fall(msg) }
var sub = (via.sub || (via.sub = new Object.Map));
if('string' == typeof (soul = msg.get['#'])){ ref = root.$.get(soul) }
if('string' == typeof (tmp = msg.get['.'])){ has = tmp } else { has = '' }
ref && (sub.get(soul) || (sub.set(soul, tmp = new Object.Map) && tmp)).set(has, 1); // {soul: {'':1, has: 1}}
if(!(ref = (ref||'')._)){ return fall(msg) }
ref.asked = +new Date;
(ref.route || (ref.route = new Object.Map)).set(via.id, via); // this approach is not gonna scale how I want it to, but try for now.
GET.turn(msg, ref.route, 0);
}
GET.turn = function(msg, route, turn){
var tmp = msg['#'], tag = dup.s[tmp], next;
if(!tmp){ return }
// Ideas: Save a random seed that sorts the route, store it and the index. // Or indexing on lowest latency is probably better.
clearTimeout(tag.lack);
if(tag.ack && (tmp = tag['##']) && msg['##'] === tmp){ return } // hashes match, stop asking other peers!
next = (Object.maps(route||opt.peers)).slice(turn = turn || 0);
if(!next.length){
if(!route){ return } // asked all peers, stop asking!
GET.turn(msg, u, 0); // asked all subs, now now ask any peers. (not always the best idea, but stays )
return;
}
setTimeout.each(next, function(id){
var peer = opt.peers[id]; turn++;
if(!peer || !peer.wire){ route && route.delete(id); return } // bye!
if(mesh.say(msg, peer) === false){ return } // was self
if(0 == (turn % 3)){ return 1 }
}, function(){
tag['##'] = msg['##']; // should probably set this in a more clever manner, do live `in` checks ++ --, etc. but being lazy for now. // TODO: Yes, see `in` TODO, currently this might match against only in-mem cause no other peers reply, which is "fine", but could cause a false positive.
tag.lack = setTimeout(function(){ GET.turn(msg, route, turn) }, 25);
}, 3);
}
function fall(msg){ mesh.say(msg, opt.peers) }
root.on('in', function(msg){ var tmp;
if((tmp = msg['@']) && (tmp = dup.s[tmp])){
tmp.ack = (tmp.ack || 0) + 1; // count remote ACKs to GET. // TODO: If mismatch, should trigger next asks.
}
this.to.next(msg);
});
root.on('create', function(){
var Q = {};
root.on('put', function(msg){
var eve = this, at = eve.as, put = msg.put, soul = put['#'], has = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
eve.to.next(msg);
if(msg['@']){ return } // acks send existing data, not updates, so no need to resend to others.
if(!soul || !has){ return }
var ref = root.$.get(soul)._, route = (ref||'').route;
if(!route){ return }
if(Q[soul+has]){ return; } (Q[soul+has] = setTimeout(function(){ delete Q[soul+has]; // TODO: add debounce here!? hmm, scope would need sub. // Q is a quick hack!
setTimeout.each(Object.maps(route), function(id){ var peer, tmp;
if(!(peer = route.get(id))){ return }
if(!peer.wire){ route.delete(id); return } // bye!
var sub = (peer.sub || (peer.sub = new Object.Map)).get(soul);
if(!sub){ return }
if(!sub.get(has) && !sub.get('')){ return }
var put = peer.put || (peer.put = {});
var node = root.graph[soul], tmp;
if(node && u !== (tmp = node[has])){
state = state_is(node, has);
val = tmp;
}
put[soul] = state_ify(put[soul], has, state, val, soul);
if(peer.to){ return }
peer.to = setTimeout(function(){ flush(peer) }, opt.gap);
});
}, 9));
});
});
function flush(peer){
var msg = {put: peer.put};
peer.put = peer.to = null;
mesh.say(msg, peer);
}
var state_ify = Gun.state.ify, state_is = Gun.state.is;
}
;(function(){
var from = Array.from;
Object.maps = function(o){
if(from && o instanceof Map){ return from(o.keys()) }
if(o instanceof Object.Map){ o = o.s }
return Object.keys(o);
}
if(from){ return Object.Map = Map }
(Object.Map = function(){ this.s = {} }).prototype = {set:function(k,v){this.s[k]=v;return this},get:function(k){return this.s[k]},delete:function(k){delete this.s[k]}};
}());

View File

@ -1,55 +0,0 @@
;(function(){ // jQuery shim
if(window.$){ return }
(($ = window.$ = function(q, tag){
if(!(this instanceof $)){ return new $(q, tag) }
this.tags = (q && q.tags) || (('string' != typeof q)?
(q?[q]:[]) : (tag||document).querySelectorAll(q));
return this;
}).fn = $.prototype).each = function(cb){ return $.each(this.tags, cb), this }
$.each = function(o, cb){ Object.keys(o).forEach(function(k){ cb(k, o[k]) }) }
$.fn.get = function(i, l, u){ return l = this.tags, (i === u)? l : l[i] }
$.fn.on = function(eve, cb){ return this.each(function(i, tag){ tag.addEventListener(eve, cb) })}
$.fn.is = function(q, b){ return this.each(function(i, tag){ b = b || tag.matches(q) }), b }
$.fn.css = function(obj){ return this.each(function(i, tag){ $.each(obj, function(k,v){ tag.style[k] = v }) })}
$.fn.text = function(text, key, f, u){
text = (text === u)? '' : (f = 1) && text;
key = key || 'textContent';
this.each(function(i, tag){
if(f){ tag[key] = text }
else { text += (tag[key]||'') }
});
return f? this : text;
}
$.fn.html = function(html){ return this.text(html, 'innerHTML') }
$.fn.find = function(q){
var I = $(), l = I.tags;
this.each(function(i, tag){
$(q, tag).each(function(i, tag){
if(0 > l.indexOf(tag)){ l.push(tag) }
});
});
return I;
}
$.fn.add = function(html, div){
(div = document.createElement('div')).innerHTML = html;
this.tags = [].slice.call(this.tags).concat([].slice.call(div.childNodes));
return this;
}
$.fn.append = function(html, op, f){ return this.each(function(i, tag){
(('<' === html[0])? $().add(html) : $(html)).each(function(i, node){
(f? node : tag)[op || 'appendChild'](f? tag : node);
})
})}
$.fn.appendTo = function(html){ return this.append(html, 0, 1) }
$.fn.parents = function(q, c){
var I = $(), l = I.tags, p = 'parentElement';
this.each(function(i, tag){
if(c){ (c = {})[p] = tag ; tag = c }
while(tag){ if((tag = tag[p]) && $(tag).is(q)){
l.push(tag); if(c){ return }
}}
});
return I;
}
$.fn.closest = function(q, c){ return this.parents(q, 1) }
}());

74
lib/dom.js Normal file
View File

@ -0,0 +1,74 @@
;(function(){ // jQuery shim
// u = undefined, n = null, b = boolean = true/false, n = number, t = text, l = list = array, o = object, cb = callback = function, q = query CSS, k = key, eve = event.
if(window.$){ return }
(($ = window.$ = function(q, tag, I, u){
if(q instanceof $){ return q }
if(!((I = this) instanceof $)){ return new $(q, tag) }
if('string' != typeof q){ return I.tags = (q = q||[]).tags || (u === q.length)? [q] : q, I }
if('<' === q[0]){ return I.add(q) }
return q.split(",").forEach(function(q){ I.add((tag||document).querySelectorAll(q)) }), I;
}).fn = $.prototype).each = function(cb){ return $.each(this.tags, cb), this }
$.each = function(o, cb){ Object.keys(o).forEach(function(k){ cb(k, o[k]) }) }
$.isPlainObject = function(o){
return (o? (o instanceof Object && o.constructor === Object)
|| 'Object' === Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1]
: false);
}
$.fn.add = function(add, tmp, u){ if(!add){ return this }
if('<' === (tmp = add)[0]){ (add = document.createElement('div')).innerHTML = tmp }
add = ('string' == typeof add)? $(add).tags : (u == add.length)? add : [].slice.call(add);
return this.tags = [].slice.call(this.tags||[]).concat(add), this;
}
$.fn.get = function(i, l, u){ return l = this.tags, (i === u)? l : l[i] }
$.fn.is = function(q, b){ return this.each(function(i, tag){ b = b || tag.matches(q) }), b }
$.fn.css = function(o){ return this.each(function(i, tag){ $.each(o, function(k,v){ tag.style[k] = v }) })}
$.fn.on = function(t, cb){ return this.each(function(i, tag){
t.split(" ").forEach(function(t){ tag.addEventListener(t, cb) });
})}
$.fn.val = function(t, k, f, u){
t = (t === u)? '' : (f = 1) && t;
k = k || 'value';
return this.each(function(i, tag){
if(f){ tag[k] = t }
else { t += (tag[k]||'') }
}), f? this : t;
}
$.fn.text = function(t){ return this.val(t, 'textContent') }
$.fn.html = function(html){ return this.val(html, 'innerHTML') }
$.fn.find = function(q, I, l){
I = $(), l = I.tags;
return this.each(function(i, tag){
$(q, tag).each(function(i, tag){
if(0 > l.indexOf(tag)){ l.push(tag) }
});
}), I;
}
$.fn.place = function(where, on, f, op, I){ return (I = this).each(function(i, tag){ $(on).each(function(i, node){
(f? tag : node)[op||'insertAdjacentElement'](({
'-1':'beforebegin', '-0.1': 'afterbegin', '0.1':'beforeend', '1': 'afterend'
})[where], (f? node : tag));
})})}
$.fn.append = function(html){ return $(html).place(0.1, this), this }
$.fn.appendTo = function(html){ return this.place(0.1, $(html)) }
function rev(o, I){ (I = $()).tags = [].slice.call(o.tags).reverse(); return I };
$.fn.prependTo = function(html){ return rev(this).place(-0.1, $(html)), this }
$.fn.prepend = function(html){ return rev($(html)).place(-0.1, this), this }
$.fn.parents = function(q, c, I, l, p){
I = $(), l = I.tags, p = 'parentElement';
this.each(function(i, tag){
if(c){ (c = {})[p] = tag ; tag = c }
while(tag){ if((tag = tag[p]) && $(tag).is(q)){
l.push(tag); if(c){ return }
}}
});
return I;
}
$.fn.closest = function(q, c){ return this.parents(q, 1) }
$.fn.clone = function(b, I, l){
I = $(), l = I.tags;
this.each(function(i, tag){
l.push(tag.cloneNode(true))
});
return I;
}
}());

View File

@ -16,6 +16,7 @@
function check(){
var used = util().rss / 1024 / 1024;
var hused = heap().used_heap_size / 1024 / 1024;
var tmp; if(tmp = console.STAT){ tmp.memax = parseFloat(ev.max.toFixed(1)); tmp.memused = parseFloat(used.toFixed(1)); tmp.memhused = parseFloat(hused.toFixed(1)); }
if(hused < ev.max && used < ev.max){ return }
//if(used < ev.max){ return }
console.STAT && console.STAT('evict memory:', hused.toFixed(), used.toFixed(), ev.max.toFixed());
@ -25,11 +26,10 @@
var S = +new Date;
var souls = Object.keys(root.graph||empty);
var toss = Math.ceil(souls.length * 0.01);
//var S = +new Date;
Gun.list.map(souls, function(soul){
if(--toss < 0){ return }
setTimeout.each(souls, function(soul){
if(--toss < 0){ return 1 }
root.$.get(soul).off();
});
},0,99);
root.dup.drop(1000 * 9); // clean up message tracker
console.STAT && console.STAT(S, +new Date - S, 'evict');
}

View File

@ -5,61 +5,56 @@ const gun = Gun();
let chokidar;
try { chokidar = require('chokidar') } catch {
console.log('Type "npm i chokidar" if you want to use the hub feature !')
}
try { chokidar = require('chokidar') } catch (error) {
} // Must install chokidar to use this feature.
function watch(what, timeout) {
timeout = timeout || 2000
function watch(what, opt) {
opt = opt || { }
// Set up the file watcher !
const watcher = chokidar.watch(what, {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true
});
let modifiedPath = (opt.file || "");
const log = console.log.bind(console);
// Handle events !
watcher
.on('add', async function(path) {
log(`File ${path} has been added`);
let container = gun.get(path).put({
file: path,
content: fs.readFileSync(path, 'utf8')
})
gun.get('hub').set(container).then(console.log('Done!'));
})
.on('change', async function(path) {
let watcher;
try {
// Set up the file watcher.
watcher = chokidar.watch(what, {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true
});
log(`File ${path} has been changed`);
let container = gun.get(path).put({
file: path,
content: fs.readFileSync(path, 'utf8')
}).then(console.log('Done!'))
const log = console.log.bind(console);
})
.on('unlink', async function (path) {
log(`File ${path} has been removed`);
let container = gun.get(path).put({
file: null,
content: null,
// Handle events !
watcher
.on('add', async function(path) {
log(`File ${path} has been added`);
gun.get('hub').get(modifiedPath + '/' + path).put(fs.readFileSync(path, 'utf-8'))
})
})
.on('addDir', path => log(`Directory ${path} has been added`))
.on('unlinkDir', path => log(`Directory ${path} has been removed`))
.on('error', error => log(`Watcher error: ${error}`))
.on('ready', () => log('Initial scan complete. Ready for changes'))
.on('change', async function(path) {
log(`File ${path} has been changed`);
gun.get('hub').get(modifiedPath + '/' + path).put(fs.readFileSync(path, 'utf-8'))
})
.on('unlink', async function (path) {
log(`File ${path} has been removed`);
gun.get('hub').get(modifiedPath + '/' + path).put(null)
})
.on('addDir', path => log(`Directory ${path} has been added`))
.on('unlinkDir', path => log(`Directory ${path} has been removed`))
.on('error', error => log(`Watcher error: ${error}`))
.on('ready', () => log('Initial scan complete. Ready for changes'))
} catch (err) {
console.log('If you want to use the hub feature, you must install `chokidar` by typing `npm i chokidar` in your terminal.')
}
}
module.exports = { watch : watch, }
gun.get('hub').on(data => {
console.log(data);
})
module.exports = { watch : watch }

View File

@ -32,7 +32,7 @@ Gun.chain.list = function(cb, opt){
list.last({name: "Mark Nadal", type: "human", age: 23}).val(function(val){
//console.log("oh yes?", val, '\n', this.__.graph);
});
list.last({name: "Amber Cazzell", type: "human", age: 23}).val(function(val){
list.last({name: "Timber Nadal", type: "cat", age: 3}).val(function(val){
//console.log("oh yes?", val, '\n', this.__.graph);
});
list.list().last({name: "Hobbes", type: "kitten", age: 4}).val(function(val){

View File

@ -25,8 +25,9 @@
return m.flip(false)
} // cancel and close when no action and "meta key" held down (e.g. ctrl+c)
if(!eve.fake && key === k.last){ return }; k.last = key; // jussi: polyfilling eve.repeat?
if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length && !$(eve.target).closest('#meta').length){
if(meta.flip.is() && !withMeta(eve)) eve.preventDefault()
if(!eve.fake && $(eve.target).closest('input, textarea, [contenteditable=true]').length/* && !$(eve.target).closest('#meta').get().length*/){
return;
//if(meta.flip.is() && !withMeta(eve)) eve.preventDefault()
}
m.check('on', key, k.at || (k.at = m.edit));
if(k.meta[key]){ m.flip() }
@ -103,7 +104,7 @@
if(opt){ m.flip(true) }
$ul.append($('<li>').html('&larr;').on('click', back));
}
m.ask = function(help, cb){
m.ask = function(help, cb, opt){
var $ul = $('#meta .meta-menu ul').empty();
var $put = $('<input>').attr('id', 'meta-ask').attr('placeholder', help);
var $form = $('<form>').append($put).on('submit', function(eve){
@ -112,6 +113,9 @@
$li.remove();
k.wipe();
});
if(opt){
$form.on('keyup', function(eve){ cb($put.val()) })
}
var $li = $('<li>').append($form);
$ul.append($li);
m.flip(true);
@ -235,6 +239,8 @@
background: 'rgba(0,0,0,0.5)'
},
'#meta a': {color: 'black'},
'#meta:hover': {opacity: 1},
'#meta:hover .meta-menu': {display: 'block'},
'#meta .meta-menu ul:before': {
content: "' '",
display: 'block',
@ -264,7 +270,7 @@
})(USE, './metaUI');
;USE(function(module){
var m = meta, k = m.key;
$(window).on('focus', k.wipe.bind(null, false)); // .on('blur', k.wipe.bind(null, false))
//$(window).on('focus', k.wipe.bind(null, false)); // .on('blur', k.wipe.bind(null, false))
$(document).on('mousedown mousemove mouseup', function(eve){
m.tap.eve = eve;
m.tap.x = eve.pageX||0;

View File

@ -8,7 +8,7 @@
var has = (Radisk.has || (Radisk.has = {}))[opt.file];
if(has){ return has }
opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
opt.max = opt.max || (opt.memory? (opt.memory * 999 * 999) : 300000000) * 0.3;
opt.until = opt.until || opt.wait || 250;
opt.batch = opt.batch || (10 * 1000);
opt.chunk = opt.chunk || (1024 * 1024 * 1); // 1MB
@ -17,12 +17,11 @@
opt.jsonify = true;
function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') } // TODO: Hash this also, but allow migration!
function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
var timediate = (typeof setImmediate === "undefined")? setTimeout : setImmediate;
var puff = setTimeout.puff || timediate;
var map = Gun.obj.map;
var obj_empty = Gun.obj.empty;
var timediate = (''+u === typeof setImmediate)? setTimeout : setImmediate;
var puff = setTimeout.turn || timediate, u;
var map = Radix.object;
var ST = 0;
if(!opt.store){
@ -38,6 +37,9 @@
//opt.log("WARNING: `store.list` interface might be needed!");
}
if(''+u != typeof require){ require('./yson') }
var parse = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } }
var json = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } }
/*
Any and all storage adapters should...
1. Because writing to disk takes time, we should batch data to disk. This improves performance, and reduces potential disk corruption.
@ -60,6 +62,7 @@
s.file = file || (file = opt.code.from);
DBG && (DBG = DBG[file] = DBG[file] || {});
DBG && (DBG.sf = DBG.sf || +new Date);
//console.only.i && console.log('found', file);
if(tmp = r.disk[file]){ s.mix(u, tmp); return }
r.parse(file, s.mix, u, DBG);
}
@ -84,6 +87,7 @@
cb = null;
}
DBG && (DBG.st = DBG.st || +new Date);
//console.only.i && console.log('mix', disk.Q);
if(disk.Q){ cb && disk.Q.push(cb); return } disk.Q = (cb? [cb] : []);
disk.to = setTimeout(s.write, opt.until);
}
@ -95,6 +99,7 @@
delete disk.Q;
delete r.disk[file];
delete disk.tags;
//console.only.i && console.log('write', file, disk, 'was saving:', key, data);
r.write(file, disk, s.ack, u, DBG);
}
s.ack = function(err, ok){
@ -107,7 +112,8 @@
if((tmp = r.disk[f]) && (tmp = tmp.tags) && tmp[tag]){ continue }
ack = tag[f];
delete tag[f];
if(!obj_empty(tag)){ continue }
var ne; for(var k in tag){ if(tag.hasOwnProperty(k)){ ne = true; break } } // is not empty?
if(ne){ continue } //if(!obj_empty(tag)){ continue }
delete r.tags[tag];
ack && ack(err, ok);
}
@ -128,6 +134,7 @@
cb || (cb = function(err, ok){ // test delete!
if(!err){ return }
});
//console.only.i && console.log('save', key);
r.find(key, s.find);
}
r.disk = {};
@ -153,12 +160,15 @@
r.disk[file = rad.file || f.file || file] = rad;
var S = +new Date;
DBG && (DBG.wd = S);
//console.only.i && console.log('add', file);
r.find.add(file, function add(err){
DBG && (DBG.wa = +new Date);
if(err){ cb(err); return }
//console.only.i && console.log('disk', file, text);
opt.store.put(ename(file), text, function safe(err, ok){
DBG && (DBG.wp = +new Date);
console.STAT && console.STAT(S, ST = +new Date - S, "wrote disk", JSON.stringify(file), ++RWC, 'total all writes.');
//console.only.i && console.log('done', err, ok || 1, cb);
cb(err, ok || 1);
if(!rad.Q){ delete r.disk[file] } // VERY IMPORTANT! Clean up memory, but not if there is already queued writes on it!
});
@ -206,13 +216,14 @@
}
f.each = function(val, key, k, pre){
if(u !== val){ f.count++ }
if(opt.pack <= (val||'').length){ return cb("Data too big!"), true }
if(opt.max <= (val||'').length){ return cb("Data too big!"), true }
var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n';
if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){
return f.split();
}
f.text += enc;
}
//console.only.i && console.log('writing');
if(opt.jsonify){ r.write.jsonify(f, rad, cb, o, DBG); return } // temporary testing idea
if(!Radix.map(rad, f.each, true)){ f.write() }
}
@ -286,9 +297,10 @@
DBG && (DBG.rl = +new Date);
if(!o.next){ o.more = 0 }
if(o.next){
if(!o.reverse && (key < o.next && 0 != o.next.indexOf(key)) || (u !== o.end && (o.end || '\uffff') < o.next)){ o.more = 0 }
if(o.reverse && (key > o.next && 0 != key.indexOf(o.next)) || (u !== o.start && (o.start || '') > o.next)){ o.more = 0 }
if(!o.reverse && ((key < o.next && 0 != o.next.indexOf(key)) || (u !== o.end && (o.end || '\uffff') < o.next))){ o.more = 0 }
if(o.reverse && ((key > o.next && 0 != key.indexOf(o.next)) || ((u !== o.start && (o.start || '') > o.next && file <= o.start)))){ o.more = 0 }
}
//console.log(5, process.memoryUsage().heapUsed);
if(!o.more){ cb(g.err, data, o); return }
if(data){ cb(g.err, data, o) }
if(o.parsed >= o.limit){ return }
@ -301,6 +313,7 @@
},0);
}
g.check = function(err, disk, info){
//console.log(4, process.memoryUsage().heapUsed);
g.get(err, disk, info);
if(!disk || disk.check){ return } disk.check = 1;
var S = +new Date;
@ -309,7 +322,7 @@
// assume in memory for now, since both write/read already call r.find which will init it.
r.find(key, function(file){
if((file || (file = opt.code.from)) === info.file){ return }
var id = Gun.text.random(3);
var id = (''+Math.random()).slice(-3);
puff(function(){
r.save(key, val, function ack(err, ok){
if(err){ r.save(key, val, ack); return } // ad infinitum???
@ -321,7 +334,7 @@
});
console.STAT && console.STAT(S, +new Date - S, "rad check");
}
r.find(key, g.find);
r.find(key || (o.reverse? (o.end||'') : (o.start||'')), g.find);
}
function rev(a,b){ return b }
var revo = {reverse: true};
@ -345,40 +358,54 @@
p.read = function(err, data){ var tmp;
DBG && (DBG.rpg = +new Date);
console.STAT && console.STAT(S, +new Date - S, 'read disk', JSON.stringify(file), ++RPC, 'total all parses.');
delete Q[file];
if((p.err = err) || (p.not = !data)){ p.map(q, p.ack); return }
//console.log(2, process.memoryUsage().heapUsed);
if((p.err = err) || (p.not = !data)){
delete Q[file];
p.map(q, p.ack);
return;
}
if('string' !== typeof data){
try{
if(opt.pack <= data.length){
if(opt.max <= data.length){
p.err = "Chunk too big!";
} else {
data = data.toString(); // If it crashes, it crashes here. How!?? We check size first!
}
}catch(e){ p.err = e }
if(p.err){ p.map(q, p.ack); return }
if(p.err){
delete Q[file];
p.map(q, p.ack);
return;
}
}
info.parsed = data.length;
DBG && (DBG.rpl = info.parsed);
DBG && (DBG.rpa = q.length);
S = +new Date;
if(opt.jsonify || '{' === data[0]){
try{
var json = JSON.parse(data); // TODO: this caused a out-of-memory crash!
p.disk.$ = json;
if(!(opt.jsonify || '{' === data[0])){
p.radec(err, data);
return;
}
parse(data, function(err, tree){
//console.log(3, process.memoryUsage().heapUsed);
if(!err){
delete Q[file];
p.disk.$ = tree;
console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'rad parsed JSON');
DBG && (DBG.rpd = +new Date);
p.map(q, p.ack); // hmmm, v8 profiler can't see into this cause of try/catch?
return;
}catch(e){ tmp = e }
}
if('{' === data[0]){
delete Q[file];
p.err = tmp || "JSON error!";
p.map(q, p.ack);
return;
}
}
p.radec(err, data);
p.radec(err, data);
});
}
p.map = function(){
p.map = function(){ // switch to setTimeout.each now?
if(!q || !q.length){ return }
//var i = 0, l = q.length, ack;
var S = +new Date;
@ -398,6 +425,7 @@
cb(u, p.disk, info);
}
p.radec = function(err, data){
delete Q[file];
S = +new Date;
var tmp = p.split(data), pre = [], i, k, v;
if(!tmp || 0 !== tmp[1]){
@ -493,7 +521,7 @@
dir = dir || rad;
dir.file = f;
tmp = Q; Q = null;
Gun.list.map(tmp, function(arg){
map(tmp, function(arg){
r.find(arg[0], arg[1]);
});
}
@ -515,10 +543,10 @@
while(i != -1){ t += s; i = d.indexOf(s, i+1) }
return t + '"' + d + s;
} else
if(d && d['#'] && (tmp = Gun.val.link.is(d))){
if(d && d['#'] && 1 == Object.keys(d).length){
return t + '#' + tmp + t;
} else
if(Gun.num.is(d)){
if('number' == typeof d){
return t + '+' + (d||0) + t;
} else
if(null === d){
@ -545,7 +573,7 @@
return d;
} else
if('#' === p){
return Gun.val.link.ify(d);
return {'#':d};
} else
if('+' === p){
if(0 === d.length){

View File

@ -15,7 +15,7 @@
k += key[++i];
}
if(!at){
if(!map(t, function(r, s){
if(!each(t, function(r, s){
var ii = 0, kk = '';
if((s||'').length){ while(s[ii] == key[ii]){
kk += s[ii++];
@ -33,6 +33,7 @@
('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val);
//(__[_] = function $(){ $.sort = Object.keys(__).sort(); return $ }());
t[kk] = __;
if(Radix.debug && 'undefined' === ''+kk){ console.log(0, kk); debugger }
delete t[s];
//(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }());
return true;
@ -40,6 +41,7 @@
})){
if(u === val){ return; }
(t[k] || (t[k] = {}))[''] = val;
if(Radix.debug && 'undefined' === ''+k){ console.log(1, k); debugger }
//(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }());
}
if(u === val){
@ -64,6 +66,7 @@
var t = ('function' == typeof radix)? radix.$ || {} : radix;
//!opt && console.log("WHAT IS T?", JSON.stringify(t).length);
if(!t){ return }
if('string' == typeof t){ if(Radix.debug){ throw ['BUG:', radix, cb, opt, pre] } return; }
var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort, rev; // ONLY 17% of ops are pre-sorted!
//var keys = Object.keys(t).sort();
opt = (true === opt)? {branch: true} : (opt || {});
@ -102,17 +105,17 @@
}
};
Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) }
if(typeof window !== "undefined"){
var Gun = window.Gun;
window.Radix = Radix;
} else {
var Gun = require('../gun');
try{ module.exports = Radix }catch(e){}
}
var map = Gun.obj.map, no = {}, u;
var each = Radix.object = function(o, f, r){
for(var k in o){
if(!o.hasOwnProperty(k)){ continue }
if((r = f(o[k], k)) !== u){ return r }
}
}, no = {}, u;
var _ = String.fromCharCode(24);
}());

View File

@ -14,17 +14,22 @@ function Store(opt){
// TODO!!! ADD ZLIB INFLATE / DEFLATE COMPRESSION!
store.put = function(file, data, cb){
puts[file] = data;
var random = Math.random().toString(36).slice(-3);
puts[file] = {id: random, data: data};
var tmp = opt.file+'-'+file+'-'+random+'.tmp';
fs.writeFile(tmp, data, function(err, ok){
delete puts[file];
if(err){ return cb(err) }
move(tmp, opt.file+'/'+file, cb);
if(err){
if(random === (puts[file]||'').id){ delete puts[file] }
return cb(err);
}
move(tmp, opt.file+'/'+file, function(err, ok){
if(random === (puts[file]||'').id){ delete puts[file] }
cb(err, ok || !err);
});
});
};
store.get = function(file, cb){ var tmp; // this took 3s+?
if(tmp = puts[file]){ cb(u, tmp); return }
if(tmp = puts[file]){ cb(u, tmp.data); return }
fs.readFile(opt.file+'/'+file, function(err, data){
if(err){
if('ENOENT' === (err.code||'').toUpperCase()){

View File

@ -1,25 +1,35 @@
;(function(){
/* // from @jabis
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
*/
function Store(opt){
opt = opt || {};
opt.file = String(opt.file || 'radata');
var db = null, u;
var store = Store[opt.file], db = null, u;
try{opt.indexedDB = opt.indexedDB || indexedDB}catch(e){}
if(store){
console.log("Warning: reusing same IndexedDB store and options as 1st.");
return Store[opt.file];
}
store = Store[opt.file] = function(){};
try{opt.indexedDB = opt.indexedDB || Store.indexedDB || indexedDB}catch(e){}
try{if(!opt.indexedDB || 'file:' == location.protocol){
var store = {}, s = {};
store.put = function(f, d, cb){ s[f] = d; cb(null, 1) };
store.get = function(f, cb){ cb(null, s[f] || u) };
var s = store.d || (store.d = {});
store.put = function(f, d, cb){ s[f] = d; setTimeout(function(){ cb(null, 1) },250) };
store.get = function(f, cb){ setTimeout(function(){ cb(null, s[f] || u) },5) };
console.log('Warning: No indexedDB exists to persist data to!');
return store;
}}catch(e){}
var store = function Store(){};
if(Store[opt.file]){
console.log("Warning: reusing same IndexedDB store and options as 1st.");
return Store[opt.file];
}
Store[opt.file] = store;
store.start = function(){
var o = indexedDB.open(opt.file, 1);
@ -53,6 +63,7 @@
if(typeof window !== "undefined"){
(Store.window = window).RindexedDB = Store;
Store.indexedDB = window.indexedDB; // safari bug
} else {
try{ module.exports = Store }catch(e){}
}

View File

@ -68,9 +68,10 @@ function Store(opt){
//console.log("RS3 GET ---->", file);
s3.getObject(params, function got(err, ack){
if(err && 'NoSuchKey' === err.code){ err = u }
//console.log("RS3 GOT <----", err, file, cbs.length, ((ack||{}).Body||'').toString().slice(0,20));
//console.log("RS3 GOT <----", err, file, cbs.length, ((ack||{}).Body||'').length);//.toString().slice(0,20));
delete c.g[file];//Gun.obj.del(c.g, file);
var data, data = (ack||'').Body;
console.log(1, process.memoryUsage().heapUsed);
var i = 0, cba; while(cba = cbs[i++]){ cba && cba(err, data) }//Gun.obj.map(cbs, cbe);
});
};

View File

@ -1,4 +1,5 @@
;(function(){
require('./yson');
var Gun = require('../gun'), u;
Gun.serve = require('./serve');
//process.env.GUN_ENV = process.env.GUN_ENV || 'debug';
@ -9,7 +10,7 @@
root.opt.log = root.opt.log || Gun.log;
this.to.next(root);
})
require('../nts');
//require('../nts');
require('./store');
require('./rfs');
require('./rs3');
@ -17,8 +18,8 @@
try{require('../sea');}catch(e){}
try{require('../axe');}catch(e){}
//require('./file');
require('./evict');
require('./multicast');
//require('./evict');
//require('./multicast');
require('./stats');
module.exports = Gun;
}());

View File

@ -6,6 +6,7 @@ Gun.on('opt', function(root){
if(typeof process === 'undefined'){ return }
if(typeof require === 'undefined'){ return }
if(false === root.opt.stats){ return }
var file = root.opt.file || 'radata';
var noop = function(){};
var os = require('os') || {};
var fs = require('fs') || {};
@ -19,31 +20,36 @@ Gun.on('opt', function(root){
os.freemem = os.freemem || noop;
os.loadavg = os.loadavg || noop;
os.cpus = os.cpus || noop;
var S = +new Date, W;
var obj_ify = function(o){try{o = JSON.parse(o)}catch(e){o={}};return o;}
setTimeout(function(){
root.stats = Gun.obj.ify((fs.existsSync(__dirname+'/../stats.'+root.opt.file) && fs.readFileSync(__dirname+'/../stats.'+root.opt.file).toString())) || {};
root.stats = obj_ify((fs.existsSync(__dirname+'/../stats.'+(root.opt.file||file)) && fs.readFileSync(__dirname+'/../stats.'+(root.opt.file||file)).toString())) || {};
root.stats.up = root.stats.up || {};
root.stats.up.start = root.stats.up.start || +(new Date);
root.stats.up.count = (root.stats.up.count || 0) + 1;
root.stats.stay = root.stats.stay || {};
root.stats.gap = {};
root.stats.over = +new Date;
},1);
setInterval(function(){
if(!root.stats){ root.stats = {} }
var S = +new Date;
if(W){ return }
var stats = root.stats, tmp;
stats.over = S - (stats.over||S);
stats.over = -(S - (S = +new Date));
(stats.up||{}).time = process.uptime();
stats.memory = process.memoryUsage() || {};
stats.memory.totalmem = os.totalmem();
stats.memory.freemem = os.freemem();
stats.cpu = process.cpuUsage() || {};
stats.cpu.loadavg = os.loadavg();
stats.cpu.stack = (((setTimeout||'').turn||'').s||'').length;
stats.peers = {};
stats.peers.count = Object.keys(root.opt.peers||{}).length;
stats.peers.count = console.STAT.peers || Object.keys(root.opt.peers||{}).length; // TODO: .keys( is slow
stats.node = {};
stats.node.count = Object.keys(root.graph||{}).length;
stats.node.count = Object.keys(root.graph||{}).length; // TODO: .keys( is slow
stats.all = all;
stats.sites = console.STAT.sites;
all = {}; // will this cause missing stats?
var dam = root.opt.mesh;
if(dam){
stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d}, 'out': {count: dam.say.c, done: dam.say.d}};
@ -55,15 +61,17 @@ Gun.on('opt', function(root){
stats.rad = rad;
root.opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // reset
}
console.STAT && console.STAT(S, +new Date - S, 'stats');
S = +new Date;
fs.writeFile(__dirname+'/../stats.'+root.opt.file, JSON.stringify(stats, null, 2), function(err){ console.STAT && console.STAT(S, +new Date - S, 'stats stash') });
stats.over = S;
stats.gap = {};
}, 1000 * 15);
Object.keys = Object.keys || function(o){ return Gun.obj.map(o, function(v,k,t){t(k)}) }
JSON.stringifyAsync(stats, function(err, raw){ if(err){ return } W = true;
fs.writeFile(__dirname+'/../stats.'+(root.opt.file||file), raw, function(err){ W = false; err && console.log(console.STAT.err = err); console.STAT && console.STAT(S, +new Date - S, 'stats stash') });
});
//exec("top -b -n 1", function(err, out){ out && fs.writeFile(__dirname+'/../stats.top.'+(root.opt.file||file), out, noop) }); // was it really seriously actually this?
//}, 1000 * 15);
}, 1000 * 5);
});
var exec = require("child_process").exec, noop = function(){};
require('./yson');
var log = Gun.log, all = {}, max = 1000;
Gun.log = console.STAT = function(a,b,c,d){

View File

@ -4,7 +4,7 @@ Gun.on('create', function(root){
if(Gun.TESTING){ root.opt.file = 'radatatest' }
this.to.next(root);
var opt = root.opt, empty = {}, u;
if(false === opt.radisk){ return }
if(false === opt.rad || false === opt.radisk){ return }
var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
var Radix = Radisk.Radix;
var dare = Radisk(opt), esc = String.fromCharCode(27);
@ -15,18 +15,22 @@ Gun.on('create', function(root){
if((msg._||'').rad){ return } // don't save what just came from a read.
var id = msg['#'], put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], tmp;
var DBG = (msg._||'').DBG; DBG && (DBG.sp = DBG.sp || +new Date);
var lot = (msg._||'').lot||''; count[id] = (count[id] || 0) + 1;
//var lot = (msg._||'').lot||''; count[id] = (count[id] || 0) + 1;
var S = (msg._||'').RPS || ((msg._||'').RPS = +new Date);
dare(soul+esc+key, {':': val, '>': state}, dare.one[id] || function(err, ok){
//console.log("PUT ------->>>", soul,key, val, state);
//dare(soul+esc+key, {':': val, '>': state}, dare.one[id] || function(err, ok){
dare(soul+esc+key, {':': val, '>': state}, function(err, ok){
//console.log("<<<------- PAT", soul,key, val, state, 'in', +new Date - S);
DBG && (DBG.spd = DBG.spd || +new Date);
console.STAT && console.STAT(S, +new Date - S, 'put');
if(!err && count[id] !== lot.s){ console.log(err = "Disk count not same as ram count."); console.STAT && console.STAT(+new Date, lot.s - count[id], 'put ack != count') } delete count[id];
//if(!err && count[id] !== lot.s){ console.log(err = "Disk count not same as ram count."); console.STAT && console.STAT(+new Date, lot.s - count[id], 'put ack != count') } delete count[id];
if(err){ root.on('in', {'@': id, err: err, DBG: DBG}); return }
root.on('in', {'@': id, ok: ok, DBG: DBG});
}, id, DBG && (DBG.r = DBG.r || {}));
//}, id, DBG && (DBG.r = DBG.r || {}));
}, false && id, DBG && (DBG.r = DBG.r || {}));
DBG && (DBG.sps = DBG.sps || +new Date);
});
var count = {}, obj_empty = Gun.obj.empty;
var count = {}, obj_empty = Object.empty;
root.on('get', function(msg){
this.to.next(msg);
@ -56,7 +60,7 @@ Gun.on('create', function(root){
if((tmp = get['%']) || o.limit){
o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1;
}
if(has['-'] || (soul||{})['-'] || get['-']){ o.reverse = true; }
if(has['-'] || (soul||{})['-'] || get['-']){ o.reverse = true }
if((tmp = (root.next||'')[soul]) && tmp.put){
if(o.atom){
tmp = (tmp.next||'')[o.atom] ;
@ -67,7 +71,9 @@ Gun.on('create', function(root){
var now = Gun.state();
var S = (+new Date), C = 0, SPT = 0; // STATS!
DBG && (DBG.sgm = S);
//var GID = String.random(3); console.log("GET ------->>>", GID, key, o, '?', get);
dare(key||'', function(err, data, info){
//console.log("<<<------- GOT", GID, +new Date - S, err, data);
DBG && (DBG.sgr = +new Date);
DBG && (DBG.sgi = info);
try{opt.store.stats.get.time[statg % 50] = (+new Date) - S; ++statg;
@ -136,7 +142,7 @@ Gun.on('create', function(root){
(graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul);
}
});
var val_is = Gun.val.is
opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS!
var val_is = Gun.valid;
(opt.store||{}).stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS!
var statg = 0, statp = 0; // STATS!
});

View File

@ -4,12 +4,13 @@
opt = $.isPlainObject(opt)? opt : {input: opt};
el.on('drop', function(e){
e.preventDefault();
upload.drop(((e.originalEvent||{}).dataTransfer||{}).files||[], 0);
upload.drop(((e.originalEvent||e).dataTransfer||{}).files||[], 0);
}).on('dragover', function(e){
e.preventDefault();
});
$(opt.input||el).on('change', function(e){
upload.drop((e.target||this||{}).files, 0);
if(!(e = (e.target||this||{}).files)){ return }
upload.drop(e, 0);
});
upload.drop = function(files,i){
if(opt.max && (files[i].fileSize > opt.max || files[i].size > opt.max)){
@ -17,7 +18,7 @@
if(files[++i]){ upload.drop(files,i) }
return false;
}
reader = new FileReader();
var reader = new FileReader();
reader.onload = function(e){
cb({file: files[i], event: e, id: i}, upload);
if(files[++i]){ upload.drop(files,i) }

451
lib/utils.js Normal file
View File

@ -0,0 +1,451 @@
;(function(){
var u;
if(''+u == typeof Gun){ return }
var DEP = function(n){ console.log("Warning! Deprecated internal utility will break in next version:", n) }
// Generic javascript utilities.
var Type = Gun;
//Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }}
Type.fn = Type.fn || {is: function(fn){ DEP('fn'); return (!!fn && 'function' == typeof fn) }}
Type.bi = Type.bi || {is: function(b){ DEP('bi');return (b instanceof Boolean || typeof b == 'boolean') }}
Type.num = Type.num || {is: function(n){ DEP('num'); return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }}
Type.text = Type.text || {is: function(t){ DEP('text'); return (typeof t == 'string') }}
Type.text.ify = Type.text.ify || function(t){ DEP('text.ify');
if(Type.text.is(t)){ return t }
if(typeof JSON !== "undefined"){ return JSON.stringify(t) }
return (t && t.toString)? t.toString() : t;
}
Type.text.random = Type.text.random || function(l, c){ DEP('text.random');
var s = '';
l = l || 24; // you are not going to make a 0 length random number, so no need to check type
c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz';
while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- }
return s;
}
Type.text.match = Type.text.match || function(t, o){ var tmp, u; DEP('text.match');
if('string' !== typeof t){ return false }
if('string' == typeof o){ o = {'=': o} }
o = o || {};
tmp = (o['='] || o['*'] || o['>'] || o['<']);
if(t === tmp){ return true }
if(u !== o['=']){ return false }
tmp = (o['*'] || o['>'] || o['<']);
if(t.slice(0, (tmp||'').length) === tmp){ return true }
if(u !== o['*']){ return false }
if(u !== o['>'] && u !== o['<']){
return (t >= o['>'] && t <= o['<'])? true : false;
}
if(u !== o['>'] && t >= o['>']){ return true }
if(u !== o['<'] && t <= o['<']){ return true }
return false;
}
Type.text.hash = Type.text.hash || function(s, c){ // via SO
DEP('text.hash');
if(typeof s !== 'string'){ return }
c = c || 0;
if(!s.length){ return c }
for(var i=0,l=s.length,n; i<l; ++i){
n = s.charCodeAt(i);
c = ((c<<5)-c)+n;
c |= 0;
}
return c;
}
Type.list = Type.list || {is: function(l){ DEP('list'); return (l instanceof Array) }}
Type.list.slit = Type.list.slit || Array.prototype.slice;
Type.list.sort = Type.list.sort || function(k){ // creates a new sort function based off some key
DEP('list.sort');
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 }
}
}
Type.list.map = Type.list.map || function(l, c, _){ DEP('list.map'); return obj_map(l, c, _) }
Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation
Type.obj = Type.boj || {is: function(o){ DEP('obj'); return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }}
Type.obj.put = Type.obj.put || function(o, k, v){ DEP('obj.put'); return (o||{})[k] = v, o }
Type.obj.has = Type.obj.has || function(o, k){ DEP('obj.has'); return o && Object.prototype.hasOwnProperty.call(o, k) }
Type.obj.del = Type.obj.del || function(o, k){ DEP('obj.del');
if(!o){ return }
o[k] = null;
delete o[k];
return o;
}
Type.obj.as = Type.obj.as || function(o, k, v, u){ DEP('obj.as'); return o[k] = o[k] || (u === v? {} : v) }
Type.obj.ify = Type.obj.ify || function(o){ DEP('obj.ify');
if(obj_is(o)){ return o }
try{o = JSON.parse(o);
}catch(e){o={}};
return o;
}
;(function(){ var u;
function map(v,k){
if(obj_has(this,k) && u !== this[k]){ return }
this[k] = v;
}
Type.obj.to = Type.obj.to || function(from, to){ DEP('obj.to');
to = to || {};
obj_map(from, map, to);
return to;
}
}());
Type.obj.copy = Type.obj.copy || function(o){ DEP('obj.copy'); // 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!
}
;(function(){
function empty(v,i){ var n = this.n, u;
if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return }
if(u !== i){ return true }
}
Type.obj.empty = Type.obj.empty || function(o, n){ DEP('obj.empty');
if(!o){ return true }
return obj_map(o,empty,{n:n})? false : true;
}
}());
;(function(){
function t(k,v){
if(2 === arguments.length){
t.r = t.r || {};
t.r[k] = v;
return;
} t.r = t.r || [];
t.r.push(k);
};
var keys = Object.keys, map, u;
Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) }
Type.obj.map = map = Type.obj.map || function(l, c, _){ DEP('obj.map');
var u, i = 0, x, r, ll, lle, f = 'function' == typeof c;
t.r = u;
if(keys && obj_is(l)){
ll = keys(l); lle = true;
}
_ = _ || {};
if(list_is(l) || ll){
x = (ll || l).length;
for(;i < x; i++){
var ii = (i + Type.list.index);
if(f){
r = lle? c.call(_, l[ll[i]], ll[i], t) : c.call(_, l[i], ii, t);
if(r !== u){ return r }
} else {
//if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing!
if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now
}
}
} else {
for(i in l){
if(f){
if(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 } // use this for now
}
}
}
return f? t.r : Type.list.index? 0 : -1;
}
}());
Type.time = Type.time || {};
Type.time.is = Type.time.is || function(t){ DEP('time'); return t? t instanceof Date : (+new Date().getTime()) }
var fn_is = Type.fn.is;
var list_is = Type.list.is;
var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map;
var Val = {};
Val.is = function(v){ DEP('val.is'); // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first.
if(v === u){ return false }
if(v === null){ return true } // "deletes", nulling out keys.
if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face.
if(text_is(v) // by "text" we mean strings.
|| bi_is(v) // by "binary" we mean boolean.
|| num_is(v)){ // by "number" we mean integers or decimals.
return true; // simple values are valid.
}
return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types.
}
Val.link = Val.rel = {_: '#'};
;(function(){
Val.link.is = function(v){ DEP('val.link.is'); // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'}
if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object.
var o = {};
obj_map(v, map, o);
if(o.id){ // a valid id was found.
return o.id; // yay! Return it.
}
}
return false; // the value was not a valid soul relation.
}
function map(s, k){ var o = this; // map over the object...
if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid.
if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value.
o.id = s; // we found the soul!
} else {
return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid.
}
}
}());
Val.link.ify = function(t){ DEP('val.link.ify'); return obj_put({}, rel_, t) } // convert a soul into a relation and return it.
Type.obj.has._ = '.';
var rel_ = Val.link._, u;
var bi_is = Type.bi.is;
var num_is = Type.num.is;
var text_is = Type.text.is;
var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map;
Type.val = Type.val || Val;
var Node = {_: '_'};
Node.soul = function(n, o){ DEP('node.soul'); return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it.
Node.soul.ify = function(n, o){ DEP('node.soul.ify'); // put a soul on an object.
o = (typeof o === 'string')? {soul: o} : o || {};
n = n || {}; // make sure it exists.
n._ = n._ || {}; // make sure meta exists.
n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it.
return n;
}
Node.soul._ = Val.link._;
;(function(){
Node.is = function(n, cb, as){ DEP('node.is'); var s; // checks to see if an object is a valid node.
if(!obj_is(n)){ return false } // must be an object.
if(s = Node.soul(n)){ // must have a soul on it.
return !obj_map(n, map, {as:as,cb:cb,s:s,n:n});
}
return false; // nope! This was not a valid node.
}
function map(v, k){ // we invert this because the way we check for this is via a negation.
if(k === Node._){ return } // skip over the metadata.
if(!Val.is(v)){ return true } // it is true that this is an invalid node.
if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value.
}
}());
;(function(){
Node.ify = function(obj, o, as){ DEP('node.ify'); // returns a node from a shallow object.
if(!o){ o = {} }
else if(typeof o === 'string'){ o = {soul: o} }
else if('function' == typeof o){ o = {map: o} }
if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) }
if(o.node = Node.soul.ify(o.node || {}, o)){
obj_map(obj, map, {o:o,as:as});
}
return o.node; // This will only be a valid node if the object wasn't already deep!
}
function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value.
if(o.map){
tmp = o.map.call(this.as, v, ''+k, o.node);
if(u === tmp){
obj_del(o.node, k);
} else
if(o.node){ o.node[k] = tmp }
return;
}
if(Val.is(v)){
o.node[k] = v;
}
}
}());
var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map;
var text = Type.text, text_random = text.random;
var soul_ = Node.soul._;
var u;
Type.node = Type.node || Node;
var State = Type.state;
State.lex = function(){ DEP('state.lex'); return State().toString(36).replace('.','') }
State.to = function(from, k, to){ DEP('state.to');
var val = (from||{})[k];
if(obj_is(val)){
val = obj_copy(val);
}
return State.ify(to, k, State.is(from, k), val, Node.soul(from));
}
;(function(){
State.map = function(cb, s, as){ DEP('state.map'); var u; // for use with Node.ify
var o = obj_is(o = cb || s)? o : null;
cb = fn_is(cb = cb || s)? cb : null;
if(o && !cb){
s = num_is(s)? s : State();
o[N_] = o[N_] || {};
obj_map(o, map, {o:o,s:s});
return o;
}
as = as || obj_is(s)? s : u;
s = num_is(s)? s : State();
return function(v, k, o, opt){
if(!cb){
map.call({o: o, s: s}, v,k);
return v;
}
cb.call(as || this || {}, v, k, o, opt);
if(obj_has(o,k) && u === o[k]){ return }
map.call({o: o, s: s}, v,k);
}
}
function map(v,k){
if(N_ === k){ return }
State.ify(this.o, k, this.s) ;
}
}());
var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy;
var num = Type.num, num_is = num.is;
var fn = Type.fn, fn_is = fn.is;
var N_ = Node._, u;
var Graph = {};
;(function(){
Graph.is = function(g, cb, fn, as){ DEP('graph.is'); // checks to see if an object is a valid graph.
if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object.
return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object.
}
function map(n, s){ // we invert this because the way'? we check for this is via a negation.
if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph.
if(!this.cb){ return }
nf.n = n; nf.as = this.as; // sequential race conditions aren't races.
this.cb.call(nf.as, n, s, nf);
}
function nf(fn){ // optional callback for each node.
if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value.
}
}());
;(function(){
Graph.ify = function(obj, env, as){ DEP('graph.ify');
var at = {path: [], obj: obj};
if(!env){
env = {};
} else
if(typeof env === 'string'){
env = {soul: env};
} else
if('function' == typeof env){
env.map = env;
}
if(typeof as === 'string'){
env.soul = env.soul || as;
as = u;
}
if(env.soul){
at.link = Val.link.ify(env.soul);
}
env.shell = (as||{}).shell;
env.graph = env.graph || {};
env.seen = env.seen || [];
env.as = env.as || as;
node(env, at);
env.root = at.node;
return env.graph;
}
function node(env, at){ var tmp;
if(tmp = seen(env, at)){ return tmp }
at.env = env;
at.soul = soul;
if(Node.ify(at.obj, map, at)){
at.link = at.link || Val.link.ify(Node.soul(at.node));
if(at.obj !== env.shell){
env.graph[Val.link.is(at.link)] = at.node;
}
}
return at;
}
function map(v,k,n){
var at = this, env = at.env, is, tmp;
if(Node._ === k && obj_has(v,Val.link._)){
return n._; // TODO: Bug?
}
if(!(is = valid(v,k,n, at,env))){ return }
if(!k){
at.node = at.node || n || {};
if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ?
at.node._ = obj_copy(v._);
}
at.node = Node.soul.ify(at.node, Val.link.is(at.link));
at.link = at.link || Val.link.ify(Node.soul(at.node));
}
if(tmp = env.map){
tmp.call(env.as || {}, v,k,n, at);
if(obj_has(n,k)){
v = n[k];
if(u === v){
obj_del(n, k);
return;
}
if(!(is = valid(v,k,n, at,env))){ return }
}
}
if(!k){ return at.node }
if(true === is){
return v;
}
tmp = node(env, {obj: v, path: at.path.concat(k)});
if(!tmp.node){ return }
return tmp.link; //{'#': Node.soul(tmp.node)};
}
function soul(id){ var at = this;
var prev = Val.link.is(at.link), graph = at.env.graph;
at.link = at.link || Val.link.ify(id);
at.link[Val.link._] = id;
if(at.node && at.node[Node._]){
at.node[Node._][Val.link._] = id;
}
if(obj_has(graph, prev)){
graph[id] = graph[prev];
obj_del(graph, prev);
}
}
function valid(v,k,n, at,env){ var tmp;
if(Val.is(v)){ return true }
if(obj_is(v)){ return 1 }
if(tmp = env.invalid){
v = tmp.call(env.as || {}, v,k,n);
return valid(v,k,n, at,env);
}
env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!";
if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." }
}
function seen(env, at){
var arr = env.seen, i = arr.length, has;
while(i--){ has = arr[i];
if(at.obj === has.obj){ return has }
}
arr.push(at);
}
}());
Graph.node = function(node){ DEP('graph.node');
var soul = Node.soul(node);
if(!soul){ return }
return obj_put({}, soul, node);
}
;(function(){
Graph.to = function(graph, root, opt){ DEP('graph.to');
if(!graph){ return }
var obj = {};
opt = opt || {seen: {}};
obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt});
return obj;
}
function map(v,k){ var tmp, obj;
if(Node._ === k){
if(obj_empty(v, Val.link._)){
return;
}
this.obj[k] = obj_copy(v);
return;
}
if(!(tmp = Val.link.is(v))){
this.obj[k] = v;
return;
}
if(obj = this.opt.seen[tmp]){
this.obj[k] = obj;
return;
}
this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt);
}
}());
var fn_is = Type.fn.is;
var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy;
var u;
Type.graph = Type.graph || Graph;
}());

View File

@ -67,9 +67,9 @@ Gun.on('opt', function(root){
if(!ws.server){ this.to.next(root); return } // ugh, bug fix for @jamierez & unstoppable ryan.
}
ws.web = ws.web || new opt.WebSocket.Server(ws); // we still need a WS server.
ws.web.on('connection', function(wire){ var peer;
wire.upgradeReq = wire.upgradeReq || {};
wire.url = url.parse(wire.upgradeReq.url||'', true);
ws.web.on('connection', function(wire, req){ var peer;
wire.headers = wire.headers || (req||'').headers || '';
console.STAT && ((console.STAT.sites || (console.STAT.sites = {}))[wire.headers.origin] = 1);
opt.mesh.hi(peer = {wire: wire});
wire.on('message', function(msg){
opt.mesh.hear(msg.data || msg, peer);

View File

@ -2,16 +2,17 @@
// JSON: JavaScript Object Notation
// YSON: Yielding javaScript Object Notation
var yson = {}, u, sI = setTimeout.turn || (typeof setImmediate != ''+u && setImmediate) || setTimeout;
yson.parseAsync = function(text, done, revive, M){
var ctx = {i: 0, text: text, done: done, o: {}, l: text.length, up: []};
ctx.at = ctx.o;
if('string' != typeof text){ try{ done(u,JSON.parse(text)) }catch(e){ done(e) } return }
var ctx = {i: 0, text: text, done: done, l: text.length, up: []};
//M = 1024 * 1024 * 100;
//M = M || 1024 * 64;
M = M || 1024 * 32;
parse();
function parse(){
//var S = +new Date;
var s = ctx.text, o = ctx.o;
var s = ctx.text;
var i = ctx.i, l = ctx.l, j = 0;
var w = ctx.w, b, tmp;
while(j++ < M){
@ -29,15 +30,16 @@ yson.parseAsync = function(text, done, revive, M){
tmp = ctx.s;
if(ctx.a){
tmp = s.slice(ctx.sl, i);
if(b){ tmp = JSON.parse('"'+tmp+'"') }
if(b || (1+tmp.indexOf('\\u'))){ tmp = JSON.parse('"'+tmp+'"') } // unicode :( handling
if(ctx.at instanceof Array){
ctx.at.push(ctx.s = tmp);
} else {
ctx.at[ctx.s] = ctx.s = tmp;
if(!ctx.at){ ctx.end = j = M; tmp = u }
(ctx.at||{})[ctx.s] = ctx.s = tmp;
}
} else {
ctx.s = s.slice(ctx.sl, i);
if(b){ ctx.s = JSON.parse('"'+ctx.s+'"'); }
if(b || (1+ctx.s.indexOf('\\u'))){ ctx.s = JSON.parse('"'+ctx.s+'"'); } // unicode :( handling
}
ctx.a = b = u;
}
@ -71,11 +73,11 @@ yson.parseAsync = function(text, done, revive, M){
}
break;
case '{':
ctx.up.push(ctx.at);
ctx.up.push(ctx.at||(ctx.at = {}));
if(ctx.at instanceof Array){
ctx.at.push(ctx.at = {});
} else
if(tmp = ctx.s){
if(u !== (tmp = ctx.s)){
ctx.at[tmp] = ctx.at = {};
}
ctx.a = u;
@ -87,7 +89,8 @@ yson.parseAsync = function(text, done, revive, M){
if(ctx.at instanceof Array){
ctx.at.push(tmp);
} else {
ctx.at[ctx.s] = tmp;
if(!ctx.at){ ctx.end = j = M; tmp = u }
(ctx.at||{})[ctx.s] = tmp;
}
}
}
@ -96,11 +99,15 @@ yson.parseAsync = function(text, done, revive, M){
ctx.at = ctx.up.pop();
break;
case '[':
if(tmp = ctx.s){
if(u !== (tmp = ctx.s)){
ctx.up.push(ctx.at);
ctx.at[tmp] = ctx.at = [];
} else
if(!ctx.at){
ctx.up.push(ctx.at = []);
}
ctx.a = true;
ctx.ai = i;
break;
case ']':
if(ctx.a){
@ -120,13 +127,17 @@ yson.parseAsync = function(text, done, revive, M){
}
}
}
ctx.s = u;
ctx.i = i;
ctx.w = w;
//console.log("!!!!!!!!", +new Date - S, ctx.i, ctx.l);
if(ctx.end){
ctx.done(u, ctx.o);
tmp = ctx.at;
if(u === tmp){
try{ tmp = JSON.parse(text)
}catch(e){ return ctx.done(e) }
}
ctx.done(u, tmp);
} else {
//setTimeout.turn(parse);
sI(parse);
}
}
@ -148,6 +159,85 @@ function value(s){
}
}
module.exports = yson;
yson.stringifyAsync = function(data, done, replacer, space, ctx){
//try{done(u, JSON.stringify(data, replacer, space))}catch(e){done(e)}return;
ctx = ctx || {};
ctx.text = ctx.text || "";
ctx.up = [ctx.at = {d: data}];
ctx.done = done;
ctx.i = 0;
var j = 0;
ify();
function ify(){
var at = ctx.at, data = at.d, add = '', tmp;
if(at.i && (at.i - at.j) > 0){ add += ',' }
if(u !== (tmp = at.k)){ add += JSON.stringify(tmp) + ':' } //'"'+tmp+'":' } // only if backslash
switch(typeof data){
case 'boolean':
add += ''+data;
break;
case 'string':
add += JSON.stringify(data); //ctx.text += '"'+data+'"';//JSON.stringify(data); // only if backslash
break;
case 'number':
add += data;
break;
case 'object':
if(!data){
add += 'null';
break;
}
if(data instanceof Array){
add += '[';
at = {i: -1, as: data, up: at, j: 0};
at.l = data.length;
ctx.up.push(ctx.at = at);
break;
}
if('function' != typeof (data||'').toJSON){
add += '{';
at = {i: -1, ok: Object.keys(data).sort(), as: data, up: at, j: 0};
at.l = at.ok.length;
ctx.up.push(ctx.at = at);
break;
}
if(tmp = data.toJSON()){
add += tmp;
break;
}
// let this & below pass into default case...
case 'function':
if(at.as instanceof Array){
add += 'null';
break;
}
default: // handle wrongly added leading `,` if previous item not JSON-able.
add = '';
at.j++;
}
ctx.text += add;
while(1+at.i >= at.l){
ctx.text += (at.ok? '}' : ']');
at = ctx.at = at.up;
}
if(++at.i < at.l){
if(tmp = at.ok){
at.d = at.as[at.k = tmp[at.i]];
} else {
at.d = at.as[at.i];
}
if(++j < 9){ return ify() } else { j = 0 }
sI(ify);
return;
}
ctx.done(u, ctx.text);
}
}
if(typeof window != ''+u){ window.YSON = yson }
try{ if(typeof module != ''+u){ module.exports = yson } }catch(e){}
if(typeof JSON != ''+u){
JSON.parseAsync = yson.parseAsync;
JSON.stringifyAsync = yson.stringifyAsync;
}
}());
}());

124
nts.js
View File

@ -1,49 +1,81 @@
;(function(){
// NOTE: While the algorithm is P2P,
// the current implementation is one sided,
// only browsers self-modify, servers do not.
// Need to fix this! Since WebRTC is now working.
var env;
if(typeof global !== "undefined"){ env = global }
if(typeof window !== "undefined"){ var Gun = (env = window).Gun }
else {
if(typeof require !== "undefined"){ var Gun = require('./gun') }
}
var Gun = (typeof window !== "undefined")? window.Gun : require('./gun');
var dam = 'nts';
var smooth = 2;
Gun.on('opt', function(ctx){
this.to.next(ctx);
if(ctx.once){ return }
ctx.on('in', function(at){
if(!at.nts && !at.NTS){
return this.to.next(at);
}
if(at['@']){
(ask[at['@']]||noop)(at);
return;
}
if(env.window){
return this.to.next(at);
}
this.to.next({'@': at['#'], nts: Gun.time.is()});
});
var ask = {}, noop = function(){};
if(!env.window){ return }
Gun.on('create', function(root){ // switch to DAM, deprecated old
return ; // stub out for now. TODO: IMPORTANT! re-add back in later.
var opt = root.opt, mesh = opt.mesh;
if(!mesh) return;
Gun.state.drift = Gun.state.drift || 0;
setTimeout(function ping(){
var NTS = {}, ack = Gun.text.random(), msg = {'#': ack, nts: true};
NTS.start = Gun.state();
ask[ack] = function(at){
NTS.end = Gun.state();
Gun.obj.del(ask, ack);
NTS.latency = (NTS.end - NTS.start)/2;
if(!at.nts && !at.NTS){ return }
NTS.calc = NTS.latency + (at.NTS || at.nts);
Gun.state.drift -= (NTS.end - NTS.calc)/2;
setTimeout(ping, 1000);
}
ctx.on('out', msg);
}, 1);
});
// test by opening up examples/game/nts.html on devices that aren't NTP synced.
}());
// Track connections
var connections = [];
root.on('hi', function(peer) {
this.to.next(peer);
connections.push({peer, latency: 0, offset: 0, next: 0});
});
root.on('bye', function(peer) {
this.to.next(peer);
var found = connections.find(connection => connection.peer.id == peer.id);
if (!found) return;
connections.splice(connections.indexOf(found), 1);
});
function response(msg, connection) {
var now = Date.now(); // Lack of drift intentional, provides more accurate RTT
connection.latency = (now - msg.nts[0]) / 2;
connection.offset = (msg.nts[1] + connection.latency) - (now + Gun.state.drift);
console.log(connection.offset);
Gun.state.drift += connection.offset / (connections.length + smooth);
console.log(`Update time by local: ${connection.offset} / ${connections.length + smooth}`);
}
// Handle echo & setting based on known connection latency as well
mesh.hear[dam] = function(msg, peer) {
console.log('MSG', msg);
var now = Date.now() + Gun.state.drift;
var connection = connections.find(connection => connection.peer.id == peer.id);
if (!connection) return;
if (msg.nts.length >= 2) return response(msg, connection);
mesh.say({dam, '@': msg['#'], nts: msg.nts.concat(now)}, peer);
connection.offset = msg.nts[0] + connection.latency - now;
Gun.state.drift += connection.offset / (connections.length + smooth);
console.log(`Update time by remote: ${connection.offset} / ${connections.length + smooth}`);
};
// Handle ping transmission
setTimeout(function trigger() {
console.log('TRIGGER');
if (!connections.length) return setTimeout(trigger, 100);
var now = Date.now(); // Lack of drift intentional, provides more accurate RTT & NTP reference
// Send pings
connections.forEach(function(connection) {
if (connection.next > now) return;
mesh.say({
dam,
'#': String.random(3),
nts: [now],
});
});
// Plan next round of pings
connections.forEach(function(connection) {
if (connection.next > now) return;
// https://discord.com/channels/612645357850984470/612645357850984473/755334349699809300
var delay = Math.min(2e4, Math.max(250, 150000 / Math.abs((connection.offset)||1)));
connection.next = now + delay;
});
// Plan next trigger round
// May overshoot by runtime of this function
var nextRound = Infinity;
connections.forEach(function(connection) {
nextRound = Math.min(nextRound, connection.next);
});
setTimeout(trigger, nextRound - now);
console.log(`Next sync round in ${(nextRound - now) / 1000} seconds`);
}, 1);
});
}());

View File

@ -1,6 +1,6 @@
{
"name": "gun",
"version": "0.2020.1232",
"version": "0.2020.1233",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"types": "index.d.ts",
"main": "index.js",
@ -9,12 +9,11 @@
"android": "browser.android.js",
"scripts": {
"start": "node --prof examples/http.js",
"debug": "node --prof-process --preprocess -j isolate*.log > v8.json && rm isolate*.log && echo 'drag & drop ./v8.json into https://mapbox.github.io/flamebearer/'",
"debug": "node --prof-process --preprocess -j isolate*.log > v8data.json && rm isolate*.log && echo 'drag & drop ./v8data.json into https://mapbox.github.io/flamebearer/'",
"https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start",
"prepublishOnly": "npm run unbuild",
"test": "mocha",
"test": "echo 'Did you run PANIC holy-grail, 1~X, on-recover, etc.?' && mocha",
"testsea": "mocha test/sea/sea.js",
"testaxe": "mocha test/axe/holy-grail.js",
"e2e": "mocha e2e/distributed.js",
"docker": "hooks/build",
"minify": "uglifyjs gun.js -o gun.min.js -c -m",
@ -55,36 +54,21 @@
"engines": {
"node": ">=0.8.4"
},
"tsd": {
"directory": "types"
},
"dependencies": {
"ws": "^7.2.1"
},
"optionalDependencies": {
"@peculiar/webcrypto": "^1.1.1",
"buffer": "^5.4.3",
"emailjs": "^2.2.0",
"text-encoding": "^0.7.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"text-encoding": "^0.7.0",
"utf-8-validate": "^5.0.2"
},
"tsd": {
"directory": "types"
"emailjs": "^2.2.0"
},
"devDependencies": {
"@types/ip": "^1.1.0",
"@types/mocha": "^7.0.1",
"@types/node": "^13.7.0",
"@types/uglify-js": "^3.0.4",
"@types/ws": "^7.2.1",
"aws-sdk": "^2.528.0",
"ip": "^1.1.5",
"iris-messenger": "https://github.com/irislib/iris-messenger",
"mocha": "^6.2.0",
"panic-manager": "^1.2.0",
"panic-server": "^1.1.1",
"tsd": "^0.17.0",
"uglify-js": "^3.6.0"
}
}

619
sea.js
View File

@ -37,7 +37,7 @@
&& location.host.indexOf('localhost') < 0
&& ! /^127\.\d+\.\d+\.\d+$/.test(location.hostname)
&& location.protocol.indexOf('file:') < 0){
console.warn('WebCrypto used by GUN SEA implementation does not work without HTTPS. Will automatically redirect.')
console.warn('HTTPS needed for WebCrypto in SEA, redirecting...');
location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS!
}
} }catch(e){}
@ -165,6 +165,17 @@
const api = {Buffer: Buffer}
var o = {};
// ideally we can move away from JSON entirely? unlikely due to compatibility issues... oh well.
JSON.parseAsync = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } }
JSON.stringifyAsync = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } }
api.parse = function(t,r){ return new Promise(function(res, rej){
JSON.parseAsync(t,function(err, raw){ err? rej(err) : res(raw) },r);
})}
api.stringify = function(v,r,s){ return new Promise(function(res, rej){
JSON.stringifyAsync(v,function(err, raw){ err? rej(err) : res(raw) },r,s);
})}
if(SEA.window){
api.crypto = window.crypto || window.msCrypto
api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
@ -199,7 +210,7 @@
;USE(function(module){
var SEA = USE('./root');
var Buffer = USE('./buffer');
var shim = USE('./shim');
var s = {};
s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64};
s.ecdsa = {
@ -230,10 +241,10 @@
};
s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) }
s.parse = function p(t){ try {
s.parse = async function p(t){ try {
var yes = (typeof t == 'string');
if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) }
return yes ? JSON.parse(t) : t;
return yes ? await shim.parse(t) : t;
} catch (e) {}
return t;
}
@ -245,7 +256,7 @@
;USE(function(module){
var shim = USE('./shim');
module.exports = async function(d, o){
var t = (typeof d == 'string')? d : JSON.stringify(d);
var t = (typeof d == 'string')? d : await shim.stringify(d);
var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t));
return shim.Buffer.from(hash);
}
@ -274,7 +285,7 @@
cb = salt;
salt = u;
}
data = (typeof data == 'string')? data : JSON.stringify(data);
data = (typeof data == 'string')? data : await shim.stringify(data);
if('sha' === (opt.name||'').toLowerCase().slice(0,3)){
var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64')
if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
@ -387,15 +398,16 @@
SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
opt = opt || {};
if(!(pair||opt).priv){
if(!SEA.I){ throw 'No signing key.' }
pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why});
}
if(u === data){ throw '`undefined` not allowed.' }
var json = S.parse(data);
var json = await S.parse(data);
var check = opt.check = opt.check || json;
if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m))
&& u !== await SEA.verify(check, pair)){ // don't sign if we already signed it.
var r = S.parse(check);
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
var r = await S.parse(check);
if(!opt.raw){ r = 'SEA' + await shim.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
}
@ -406,7 +418,7 @@
var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign'])
.then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(!opt.raw){ r = 'SEA' + await shim.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -429,9 +441,9 @@
var u;
SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
var json = S.parse(data);
var json = await S.parse(data);
if(false === pair){ // don't verify!
var raw = S.parse(json.m);
var raw = await S.parse(json.m);
if(cb){ try{ cb(raw) }catch(e){console.log(e)} }
return raw;
}
@ -450,7 +462,7 @@
return await SEA.opt.fall_verify(data, pair, cb, opt);
}
}
var r = check? S.parse(json.m) : u;
var r = check? await S.parse(json.m) : u;
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -478,20 +490,22 @@
if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
var tmp = data||'';
data = SEA.opt.unpack(data) || data;
var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility.
var json = await S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(await S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility.
var buf; var sig; var check; try{
buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
sig = new Uint8Array(buf)
check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash))
if(!check){ throw "Signature did not match." }
}catch(e){
}catch(e){ try{
buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA!
sig = new Uint8Array(buf)
check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash))
}catch(e){
if(!check){ throw "Signature did not match." }
}
}
var r = check? S.parse(json.m) : u;
var r = check? await S.parse(json.m) : u;
O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>'];
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -529,10 +543,11 @@
var key = (pair||opt).epriv || pair;
if(u === data){ throw '`undefined` not allowed.' }
if(!key){
if(!SEA.I){ throw 'No encryption key.' }
pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why});
key = pair.epriv || pair;
}
var msg = (typeof data == 'string')? data : JSON.stringify(data);
var msg = (typeof data == 'string')? data : await shim.stringify(data);
var rand = {s: shim.random(9), iv: shim.random(15)}; // consider making this 9 and 15 or 18 or 12 to reduce == padding.
var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
@ -542,7 +557,7 @@
iv: rand.iv.toString(opt.encode || 'base64'),
s: rand.s.toString(opt.encode || 'base64')
}
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(!opt.raw){ r = 'SEA' + await shim.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -567,10 +582,11 @@
opt = opt || {};
var key = (pair||opt).epriv || pair;
if(!key){
if(!SEA.I){ throw 'No decryption key.' }
pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
key = pair.epriv || pair;
}
var json = S.parse(data);
var json = await S.parse(data);
var buf, bufiv, bufct; try{
buf = shim.Buffer.from(json.s, opt.encode || 'base64');
bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
@ -585,7 +601,7 @@
return await SEA.decrypt(data, pair, cb, opt);
}
}
var r = S.parse(new shim.TextDecoder('utf8').decode(ct));
var r = await S.parse(new shim.TextDecoder('utf8').decode(ct));
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
} catch(e) {
@ -607,6 +623,7 @@
SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try {
opt = opt || {};
if(!pair || !pair.epriv || !pair.epub){
if(!SEA.I){ throw 'No secret mix.' }
pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why});
}
var pub = key.epub || key;
@ -654,7 +671,6 @@
;USE(function(module){
var SEA = USE('./root');
// This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph
SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try {
/*
@ -665,14 +681,12 @@
"cb": A callback function after all things are done.
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.blacklist is set, SEA will look for blacklist before syncing.
*/
console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.')
certificants = (() => {
var data = []
if (certificants) {
if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') !== -1) return '*'
if (typeof certificants === 'string') {
return certificants
}
@ -686,7 +700,6 @@
}
if (typeof certificants === 'object' && certificants.pub) return certificants.pub
return data.length > 0 ? data : null
}
return null
@ -780,27 +793,21 @@
// But all other behavior needs to be equally easy, like opinionated ways of
// Adding friends (trusted public keys), sending private messages, etc.
// Cheers! Tell me what you think.
var Gun = (SEA.window||{}).Gun || USE((typeof MODULE == "undefined"?'.':'')+'./gun', 1);
Gun.SEA = SEA;
SEA.GUN = SEA.Gun = Gun;
((SEA.window||{}).GUN||{}).SEA = SEA;
module.exports = SEA
// -------------- END SEA MODULES --------------------
// -- BEGIN SEA+GUN MODULES: BUNDLED BY DEFAULT UNTIL OTHERS USE SEA ON OWN -------
})(USE, './sea');
;USE(function(module){
var Gun = USE('./sea').Gun;
Gun.chain.then = function(cb, opt){
var gun = this, p = (new Promise(function(res, rej){
gun.once(res, opt);
}));
return cb? p.then(cb) : p;
var SEA = USE('./sea'), Gun, u;
if(SEA.window){
Gun = SEA.window.GUN || {chain:{}};
} else {
Gun = USE((typeof MODULE == "undefined"?'.':'')+'./gun', 1);
}
})(USE, './then');
;USE(function(module){
var SEA = USE('./sea');
var Gun = SEA.Gun;
var then = USE('./then');
SEA.GUN = Gun;
function User(root){
this._ = {$: this};
@ -812,44 +819,62 @@
// only one user can be logged in at a time, per gun instance.
Gun.chain.user = function(pub){
var gun = this, root = gun.back(-1), user;
if(pub){ return root.get('~'+pub) }
if(pub){
pub = SEA.opt.pub((pub._||'')['#']) || pub;
return root.get('~'+pub);
}
if(user = root.back('user')){ return user }
var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
var root = (root._), at = root, uuid = at.opt.uuid || lex;
(at = (user = at.user = gun.chain(new User))._).opt = {};
at.opt.uuid = function(cb){
var id = uuid(), pub = root.user;
if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
id = id + '~' + pub + '/';
id = '~' + pub + '/' + id;
if(cb && cb.call){ cb(null, id) }
return id;
}
return user;
}
function lex(){ return Gun.state().toString(36).replace('.','') }
Gun.User = User;
User.GUN = Gun;
User.SEA = Gun.SEA = SEA;
module.exports = User;
})(USE, './user');
;USE(function(module){
// TODO: This needs to be split into all separate functions.
// Not just everything thrown into 'create'.
var u, Gun = (''+u != typeof window)? (window.Gun||{chain:{}}) : USE((''+u === typeof MODULE?'.':'')+'./gun', 1);
Gun.chain.then = function(cb, opt){
var gun = this, p = (new Promise(function(res, rej){
gun.once(res, opt);
}));
return cb? p.then(cb) : p;
}
})(USE, './then');
var SEA = USE('./sea');
var User = USE('./user');
var authsettings = USE('./settings');
var Gun = SEA.Gun;
var noop = function(){};
;USE(function(module){
var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
// Well first we have to actually create a user. That is what this function does.
User.prototype.create = function(...args){
const pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null;
const alias = pair && (pair.pub || pair.epub) ? pair.pub : typeof args[0] === 'string' ? args[0] : null;
const pass = pair && (pair.pub || pair.epub) ? pair : alias && typeof args[1] === 'string' ? args[1] : null;
const cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair
const opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb
var pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null;
var alias = pair && (pair.pub || pair.epub) ? pair.pub : typeof args[0] === 'string' ? args[0] : null;
var pass = pair && (pair.pub || pair.epub) ? pair : alias && typeof args[1] === 'string' ? args[1] : null;
var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair
var opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb
var gun = this, cat = (gun._), root = gun.back(-1);
cb = cb || noop;
opt = opt || {};
if(false !== opt.check){
var err;
if(!alias){ err = "No user." }
if((pass||'').length < 8){ err = "Password too short!" }
if(err){
cb({err: Gun.log(err)});
return gun;
}
}
if(cat.ing){
(cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true});
return gun;
@ -866,7 +891,7 @@
gun.leave();
return;
}
act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
act.salt = String.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
}
act.b = function(proof){
@ -898,24 +923,49 @@
}
act.g = function(auth){ var tmp;
act.data.auth = act.data.auth || auth;
root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID.
root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp)), act.h); // next up, we want to associate the alias with the public key. So we add it to the alias list.
root.get(tmp = '~'+act.pair.pub).put(act.data).on(act.h); // awesome, now we can actually save the user with their public key as their ID.
var link = {}; link[tmp] = {'#': tmp}; root.get('~@'+alias).put(link).get(tmp).on(act.i); // next up, we want to associate the alias with the public key. So we add it to the alias list.
}
act.h = function(){
act.h = function(data, key, msg, eve){
eve.off(); act.h.ok = 1; act.i();
}
act.i = function(data, key, msg, eve){
if(eve){ act.i.ok = 1; eve.off() }
if(!act.h.ok || !act.i.ok){ return }
cat.ing = false;
(cb || noop)({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
if(!cb) {pair ? gun.auth(pair) : gun.auth(alias, pass)} // if no callback is passed, auto-login after signing up.
cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
if(noop === cb){ pair? gun.auth(pair) : gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
}
root.get('~@'+alias).once(act.a);
return gun;
}
User.prototype.leave = function(opt, cb){
var gun = this, user = (gun.back(-1)._).user;
if(user){
delete user.is;
delete user._.is;
delete user._.sea;
}
if(SEA.window){
try{var sS = {};
sS = window.sessionStorage;
delete sS.recall;
delete sS.pair;
}catch(e){};
}
return gun;
}
})(USE, './create');
;USE(function(module){
var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
// now that we have created a user, we want to authenticate them!
User.prototype.auth = function(...args){
const pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null;
const alias = !pair && typeof args[0] === 'string' ? args[0] : null;
const pass = alias && typeof args[1] === 'string' ? args[1] : null;
const cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair
const opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb
User.prototype.auth = function(...args){ // TODO: this PR with arguments need to be cleaned up / refactored.
var pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null;
var alias = !pair && typeof args[0] === 'string' ? args[0] : null;
var pass = alias && typeof args[1] === 'string' ? args[1] : null;
var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair
var opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb
var gun = this, cat = (gun._), root = gun.back(-1);
@ -929,8 +979,7 @@
act.a = function(data){
if(!data){ return act.b() }
if(!data.pub){
var tmp = [];
Gun.node.is(data, function(v){ tmp.push(v) })
var tmp = []; Object.keys(data).forEach(function(k){ if('_'==k){ return } tmp.push(data[k]) })
return act.b(tmp);
}
if(act.name){ return act.f(data) }
@ -946,7 +995,7 @@
}
act.c = function(auth){
if(u === auth){ return act.b() }
if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy
if('string' == typeof auth){ return act.c(obj_ify(auth)) } // in case of legacy
SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
}
act.d = function(proof){
@ -963,12 +1012,12 @@
act.half = half;
act.f(act.data);
}
act.f = function(data){
if(!data || !data.pub){ return act.b() }
var tmp = act.half || {};
act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv});
act.f = function(pair){
var half = act.half || {}, data = act.data || {};
act.g(act.lol = {pub: pair.pub || data.pub, epub: pair.epub || data.epub, priv: pair.priv || half.priv, epriv: pair.epriv || half.epriv});
}
act.g = function(pair){
if(!pair || !pair.pub || !pair.epub){ return act.b() }
act.pair = pair;
var user = (root._).user, at = (user._);
var tmp = at.tag;
@ -979,7 +1028,7 @@
user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair};
at.sea = act.pair;
cat.ing = false;
try{if(pass && !Gun.obj.has(Gun.obj.ify(cat.root.graph['~'+pair.pub].auth), ':')){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle!
try{if(pass && u == (obj_ify(cat.root.graph['~'+pair.pub].auth)||'')[':']){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle!
opt.change? act.z() : (cb || noop)(at);
if(SEA.window && ((gun.back('user')._).opt||opt).remember){
// TODO: this needs to be modular.
@ -990,7 +1039,9 @@
}catch(e){}
}
try{
if(root._.tag.auth){ // auth handle might not be registered yet
(root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do.
} else { setTimeout(function(){ (root._).on('auth', at) },1) } // if not, hackily add a timeout.
//at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
}catch(e){
Gun.log("Your 'auth' callback crashed with:", e);
@ -998,7 +1049,7 @@
}
act.z = function(){
// password update so encrypt private key using new pwd + salt
act.salt = Gun.text.random(64); // pseudo-random
act.salt = String.random(64); // pseudo-random
SEA.work(opt.change, act.salt, act.y);
}
act.y = function(proof){
@ -1010,8 +1061,8 @@
act.w = function(auth){
if(opt.shuffle){ // delete in future!
console.log('migrate core account from UTF8 & shuffle');
var tmp = Gun.obj.to(act.data);
Gun.obj.del(tmp, '_');
var tmp = {}; Object.keys(act.data).forEach(function(k){ tmp[k] = act.data[k] });
delete tmp._;
tmp.auth = auth;
root.get('~'+act.pair.pub).put(tmp);
} // end delete
@ -1042,46 +1093,16 @@
}
return gun;
}
User.prototype.pair = function(){
console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!");
var user = this;
if(!user.is){ return false }
return user._.sea;
}
User.prototype.leave = function(opt, cb){
var gun = this, user = (gun.back(-1)._).user;
if(user){
delete user.is;
delete user._.is;
delete user._.sea;
}
if(SEA.window){
try{var sS = {};
sS = window.sessionStorage;
delete sS.recall;
delete sS.pair;
}catch(e){};
}
return gun;
}
// If authenticated user wants to delete his/her account, let's support it!
User.prototype.delete = async function(alias, pass, cb){
console.log("user.delete() IS DEPRECATED AND WILL BE MOVED TO A MODULE!!!");
var gun = this, root = gun.back(-1), user = gun.back('user');
try {
user.auth(alias, pass, function(ack){
var pub = (user.is||{}).pub;
// Delete user data
user.map().once(function(){ this.put(null) });
// Wipe user data from memory
user.leave();
(cb || noop)({ok: 0});
});
} catch (e) {
Gun.log('User.delete failed! Error:', e);
}
return gun;
function obj_ify(o){
if('string' != typeof o){ return o }
try{o = JSON.parse(o);
}catch(e){o={}};
return o;
}
})(USE, './auth');
;USE(function(module){
var User = USE('./user'), SEA = User.SEA, Gun = User.GUN;
User.prototype.recall = function(opt, cb){
var gun = this, root = gun.back(-1), tmp;
opt = opt || {};
@ -1106,6 +1127,36 @@
*/
return gun;
}
})(USE, './recall');
;USE(function(module){
var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
User.prototype.pair = function(){
var user = this, proxy; // undeprecated, hiding with proxies.
try{ proxy = new Proxy({DANGER:'\u2620'}, {get: function(t,p,r){
if(!user.is || !(user._||'').sea){ return }
return user._.sea[p];
}})}catch(e){}
return proxy;
}
// If authenticated user wants to delete his/her account, let's support it!
User.prototype.delete = async function(alias, pass, cb){
console.log("user.delete() IS DEPRECATED AND WILL BE MOVED TO A MODULE!!!");
var gun = this, root = gun.back(-1), user = gun.back('user');
try {
user.auth(alias, pass, function(ack){
var pub = (user.is||{}).pub;
// Delete user data
user.map().once(function(){ this.put(null) });
// Wipe user data from memory
user.leave();
(cb || noop)({ok: 0});
});
} catch (e) {
Gun.log('User.delete failed! Error:', e);
}
return gun;
}
User.prototype.alive = async function(){
console.log("user.alive() IS DEPRECATED!!!");
const gunRoot = this.back(-1)
@ -1120,6 +1171,7 @@
}
}
User.prototype.trust = async function(user){
console.log("`.trust` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
// TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too.
//gun.get('alice').get('age').trust(bob);
if (Gun.is(user)) {
@ -1212,26 +1264,24 @@
}
*/
module.exports = User
})(USE, './create');
})(USE, './share');
;USE(function(module){
var SEA = USE('./sea')
var S = USE('./settings')
var Gun = SEA.Gun;
var SEA = USE('./sea'), S = USE('./settings'), noop = function() {}, u;
var Gun = (''+u != typeof window)? (window.Gun||{on:noop}) : USE((''+u === typeof MODULE?'.':'')+'./gun', 1);
// After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
// We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
Gun.on('opt', function(at){
if(!at.sea){ // only add SEA once per instance, on the "at" context.
at.sea = {own: {}};
//at.on('in', security, at); // now listen to all input data, acting as a firewall.
//at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
at.on('put', check, at);
at.on('put', check, at); // SEA now runs its firewall on HAM diffs, not all i/o.
}
this.to.next(at); // make sure to call the "next" middleware adapter.
});
// Alright, this next adapter gets run at the per node level in the graph database.
// correction: 2020 it gets run on each key/value pair in a node upon a HAM diff.
// This will let us verify that every property on a node has a value signed by a public key we trust.
// If the signature does not match, the data is just `undefined` so it doesn't get passed on.
// If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature).
@ -1244,56 +1294,26 @@
// Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
// This means we should ONLY trust our "friends" (our key ring) public keys, not any ones.
// I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile!
function each(msg){ // TODO: Warning: Need to switch to `gun.on('node')`! Do not use `Gun.on('node'` in your apps!
// NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
// WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
var to = this.to, vertex = (msg.$._).put, c = 0, d;
Gun.node.is(msg.put, function(val, key, node){
// only process if SEA formatted?
var tmp = Gun.obj.ify(val) || noop;
if(u !== tmp[':']){
node[key] = SEA.opt.unpack(tmp);
return;
}
if(!SEA.opt.check(val)){ return }
c++; // for each property on the node
SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value.
if(d && !c && (c = -1)){ to.next(msg) }
});
});
if((d = true) && !c){ to.next(msg) }
}
// signature handles data output, it is a proxy to the security function.
function signature(msg){
if((msg._||noop).user){
return this.to.next(msg);
}
var ctx = this.as;
(msg._||(msg._=function(){})).user = ctx.user;
security.call(this, msg);
}
var u;
function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB?
var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
if(!soul || !key){ return }
if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){
SEA.verify(SEA.opt.pack(put), false, function(data){ // this is synchronous if false
SEA.opt.pack(put, function(raw){
SEA.verify(raw, false, function(data){ // this is synchronous if false
put['='] = SEA.opt.unpack(data);
eve.to.next(msg);
});
})})
return
}
var no = function(why){ at.on('in', {'@': id, err: why}) };
var no = function(why){ at.on('in', {'@': id, err: msg.err = why}) }; // exploit internal relay stun for now, maybe violates spec, but testing for now. // Note: this may be only the sharded message, not original batch.
//var no = function(why){ msg.ack(why) };
(msg._||'').DBG && ((msg._||'').DBG.c = +new Date);
if(0 <= soul.indexOf('<?')){ // special case for "do not sync data X old"
if(0 <= soul.indexOf('<?')){ // special case for "do not sync data X old" forget
// 'a~pub.key/b<?9'
tmp = parseFloat(soul.split('<?')[1]||'');
if(tmp && (state < (Gun.state() - (tmp * 1000)))){ // sec to ms
(tmp = msg._) && (tmp = tmp.lot) && (tmp.more--); // THIS IS BAD CODE! It assumes GUN internals do something that will probably change in future, but hacking in now.
(tmp = msg._) && (tmp.stun) && (tmp.stun--); // THIS IS BAD CODE! It assumes GUN internals do something that will probably change in future, but hacking in now.
return; // omit!
}
}
@ -1330,8 +1350,8 @@
if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
no("Alias not same!"); // that way nobody can tamper with the list of public keys.
};
check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp // Example: {_:#~asdf, hello:'world'~fdsa}}
const raw = S.parse(val) || {}
check.pub = async function(eve, msg, val, key, soul, at, no, user, pub){ var tmp // Example: {_:#~asdf, hello:'world'~fdsa}}
const raw = await S.parse(val) || {}
const verify = (certificate, certificant, cb) => {
if (certificate.m && certificate.s && certificant && pub)
// now verify certificate
@ -1371,52 +1391,65 @@
return no("Account not same!")
}
if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.out || {}).opt || {}).cert))){
SEA.sign(SEA.opt.pack(msg.put), (user._).sea, function(data){
if (u === data) return no(SEA.err || 'Signature fail.')
msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s}
msg.put['='] = tmp
// if writing to own graph, just allow it
if (pub === user.is.pub) {
if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1
msg.put[':'] = JSON.stringify(msg.put[':'])
return eve.to.next(msg)
}
// if writing to other's graph, check if cert exists then try to inject cert into put, also inject self pub so that everyone can verify the put
if (pub !== user.is.pub && ((msg._.out || {}).opt || {}).cert) {
const cert = S.parse(msg._.out.opt.cert)
// even if cert exists, we must verify it
if (cert && cert.m && cert.s)
verify(cert, user.is.pub, _ => {
msg.put[':']['+'] = cert // '+' is a certificate
msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts
msg.put[':'] = JSON.stringify(msg.put[':'])
return eve.to.next(msg)
if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.msg || {}).opt || {}).cert))){
SEA.opt.pack(msg.put, packed => {
SEA.sign(packed, (user._).sea, async function(data) {
if (u === data) return no(SEA.err || 'Signature fail.')
msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s}
msg.put['='] = tmp
// if writing to own graph, just allow it
if (pub === user.is.pub) {
if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1
JSON.stringifyAsync(msg.put[':'], function(err,s){
if(err){ return no(err || "Stringify error.") }
msg.put[':'] = s;
return eve.to.next(msg);
})
}
}, {raw: 1})
return
}
// if writing to other's graph, check if cert exists then try to inject cert into put, also inject self pub so that everyone can verify the put
if (pub !== user.is.pub && ((msg._.msg || {}).opt || {}).cert) {
const cert = await S.parse(msg._.msg.opt.cert)
// even if cert exists, we must verify it
if (cert && cert.m && cert.s)
verify(cert, user.is.pub, _ => {
msg.put[':']['+'] = cert // '+' is a certificate
msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts
JSON.stringifyAsync(msg.put[':'], function(err,s){
if(err){ return no(err || "Stringify error.") }
msg.put[':'] = s;
return eve.to.next(msg);
})
return
})
}
}, {raw: 1})
})
return;
}
SEA.verify(SEA.opt.pack(msg.put), raw['*'] || pub, function(data){ var tmp;
data = SEA.opt.unpack(data);
if (u === data) return no("Unverified data.") // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1
// check if cert ('+') and putter's pub ('*') exist
if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*'])
// now verify certificate
verify(raw['+'], raw['*'], _ => {
SEA.opt.pack(msg.put, packed => {
SEA.verify(packed, raw['*'] || pub, function(data){ var tmp;
data = SEA.opt.unpack(data);
if (u === data) return no("Unverified data.") // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1
// check if cert ('+') and putter's pub ('*') exist
if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*'])
// now verify certificate
verify(raw['+'], raw['*'], _ => {
msg.put['='] = data;
return eve.to.next(msg);
})
else {
msg.put['='] = data;
return eve.to.next(msg);
})
else {
msg.put['='] = data;
return eve.to.next(msg);
}
});
}
});
})
return
};
check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub;
if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") }
@ -1427,162 +1460,9 @@
}).on.on('secure', msg);
return;
}
var link_is = Gun.val.link.is, state_ify = Gun.state.ify;
// okay! The security function handles all the heavy lifting.
// It needs to deal read and write of input and output of system data, account/public key data, and regular data.
// This is broken down into some pretty clear edge cases, let's go over them:
function security(msg){
var at = this.as, sea = at.sea, to = this.to;
if(at.opt.faith && (msg._||noop).faith){ // you probably shouldn't have faith in this!
this.to.next(msg); // why do we allow skipping security? I'm very scared about it actually.
return; // but so that way storage adapters that already verified something can get performance boost. This was a community requested feature. If anybody finds an exploit with it, please report immediately. It should only be exploitable if you have XSS control anyways, which if you do, you can bypass security regardless of this.
}
if(msg.get){
// if there is a request to read data from us, then...
var soul = msg.get['#'];
if(soul){ // for now, only allow direct IDs to be read.
if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
return to.next(msg); // yes.
} else
if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias?
return to.next(msg); // yes.
} else { // Allow reading everything?
return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on.
}
}
}
if(msg.put){
/*
NOTICE: THIS IS OLD AND GETTING DEPRECATED.
ANY SECURITY CHANGES SHOULD HAPPEN ABOVE FIRST
THEN PORTED TO HERE.
*/
// potentially parallel async operations!!!
var check = {}, each = {}, u;
each.node = function(node, soul){
if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them.
Gun.obj.map(node, each.way, {soul: soul, node: node});
};
each.way = function(val, key){
var soul = this.soul, node = this.node, tmp;
if('_' === key){ return } // ignore meta data
if('~@' === soul){ // special case for shared system data, the list of aliases.
each.alias(val, key, node, soul); return;
}
if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
each.pubs(val, key, node, soul); return;
}
if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
each.pub(val, key, node, soul, tmp, (msg._||noop).user); return;
}
each.any(val, key, node, soul, (msg._||noop).user); return;
return each.end({err: "No other data allowed!"});
};
each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist
if('~@'+key === Gun.val.link.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
each.end({err: "Mismatching alias."}); // if it isn't, reject.
};
each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist
if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
};
each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
if('pub' === key){
if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
return each.end({err: "Account must match!"});
}
check['user'+soul+key] = 1;
if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel;
if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
if(rel = Gun.val.link.is(val)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
check['user'+soul+key] = 0;
each.end({ok: 1});
}, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
return;
}
SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp;
data = SEA.opt.unpack(data, key, node);
if(u === data){ // make sure the signature matches the account it claims to be on.
return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
}
if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['user'+soul+key] = 0;
each.end({ok: 1});
});
};
each.any = function(val, key, node, soul, user){ var tmp, pub;
if(!(pub = SEA.opt.pub(soul))){
if(at.opt.secure){
each.end({err: "Soul is missing public key at '" + key + "'."});
return;
}
// TODO: Ask community if should auto-sign non user-graph data.
check['any'+soul+key] = 1;
at.on('secure', function(msg){ this.off();
check['any'+soul+key] = 0;
if(at.opt.secure){ msg = null }
each.end(msg || {err: "Data cannot be modified."});
}).on.on('secure', msg);
//each.end({err: "Data cannot be modified."});
return;
}
if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
/*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}*/
check['any'+soul+key] = 1;
SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
check['any'+soul+key] = 0;
each.end({ok: 1});
}, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
return;
}
check['any'+soul+key] = 1;
SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel;
data = SEA.opt.unpack(data, key, node);
if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['any'+soul+key] = 0;
each.end({ok: 1});
});
}
each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
if(each.err){ return }
if((each.err = ctx.err) || ctx.no){
console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI
return;
}
if(!each.end.ed){ return }
if(Gun.obj.map(check, function(no){
if(no){ return true }
})){ return }
(msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out.
to.next(msg);
};
Gun.obj.map(msg.put, each.node);
each.end({end: each.end.ed = true});
return; // need to manually call next after async.
}
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
}
var valid = Gun.valid, link_is = function(d,l){ return 'string' == typeof (l = valid(d)) && l }, state_ify = (Gun.state||'').ify;
var pubcut = /[^\w_-]/; // anything not alphanumeric or _ -
SEA.opt.pub = function(s){
if(!s){ return }
@ -1594,13 +1474,17 @@
s = s.slice(0,2).join('.');
return s;
}
SEA.opt.prep = function(d,k, n,s){ // prep for signing
return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)};
SEA.opt.stringy = function(t){
// TODO: encrypt etc. need to check string primitive. Make as breaking change.
}
SEA.opt.pack = function(d,k, n,s){ // pack for verifying
if(SEA.opt.check(d)){ return d }
var meta = (Gun.obj.ify((d && d[':'])||d)||''), sig = meta['~'];
return sig? {m: {'#':s||d['#'],'.':k||d['.'],':':meta[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig} : d;
SEA.opt.pack = function(d,cb,k, n,s){ var tmp, f; // pack for verifying
if(SEA.opt.check(d)){ return cb(d) }
if(d && d['#'] && d['.'] && d['>']){ tmp = d[':']; f = 1 }
JSON.parseAsync(f? tmp : d, function(err, meta){
var sig = ((u !== (meta||'')[':']) && (meta||'')['~']); // or just ~ check?
if(!sig){ cb(d); return }
cb({m: {'#':s||d['#'],'.':k||d['.'],':':(meta||'')[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig});
});
}
var O = SEA.opt;
SEA.opt.unpack = function(d, k, n){ var tmp;
@ -1610,7 +1494,7 @@
if(!k || !n){ return }
if(d === n[k]){ return d }
if(!SEA.opt.check(n[k])){ return d }
var soul = Gun.node.soul(n) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
var soul = (n && n._ && n._['#']) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
return d[2];
}
@ -1619,10 +1503,7 @@
}
}
SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
var noop = function(){}, u;
var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
var rel_is = Gun.val.rel.is;
var obj_ify = Gun.obj.ify;
// TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
})(USE, './index');

File diff suppressed because it is too large Load Diff

13
test/download-log.html Normal file
View File

@ -0,0 +1,13 @@
<html>
<body>
Paste the log URL into here:
<input id="peer">
Then right click save-as here:
<a id="url">right click not normal click</a>
<script>
peer.onkeyup = function(){
url.href = url.download = this.value;
}
</script>
</body>
</html>

View File

@ -16,6 +16,7 @@
<script>mocha.setup({ui:'bdd',globals:[]});</script>
<script src="./expect.js"></script>
<script></script>
<script src="../lib/yson.js"></script>
<script src="../gun.js"></script>
<script src="../sea.js"></script>
@ -26,6 +27,7 @@
<script src="./rad/rad.js"></script>
<script src="./sea/sea.js"></script>
<script src="./common.js"></script>
<script>
if(location.search){
@ -33,7 +35,7 @@
console.log('async?', Gun.debug);
}
var run = mocha.run(function(a,b,c){
//document.body.prepend("MARK! REMEMBER TO REMOVE RETURN!");return;
document.body.prepend("TODO: localStorage gun/gap ???");return;
var yes = confirm("REFRESH BROWSER FOR ASYNC TESTS?");
if(yes){
if(location.search){

View File

@ -11,8 +11,8 @@ describe('All', function(){
'emails/aquiva@gmail.com': 'asdf',
'emails/mark@gunDB.io': 'asdf',
'user/marknadal': 'asdf',
'emails/amber@cazzell.com': 'fdsa',
'user/ambernadal': 'fdsa',
'emails/timber@cazzell.com': 'fdsa',
'user/timbernadal': 'fdsa',
'user/forrest': 'abcd',
'emails/banana@gmail.com': 'qwert',
'user/marknadal/messages/asdf': 'rti',
@ -29,7 +29,7 @@ describe('All', function(){
//console.log(r);
expect(r).to.be.eql({
'user/marknadal': { '#': 'asdf' },
'user/ambernadal': { '#': 'fdsa' },
'user/timbernadal': { '#': 'fdsa' },
'user/forrest': { '#': 'abcd' },
'user/marknadal/messages/asdf': { '#': 'rti' },
'user/marknadal/messages/fobar': { '#': 'yuoi' },
@ -45,7 +45,7 @@ describe('All', function(){
//console.log('upto', r);
expect(r).to.be.eql({
'user/marknadal': { '#': 'asdf' },
'user/ambernadal': { '#': 'fdsa' },
'user/timbernadal': { '#': 'fdsa' },
'user/forrest': { '#': 'abcd' }
});
});
@ -61,7 +61,7 @@ describe('All', function(){
it('map', function(done) { return done();
var users = gun.put({
a: {name: "Mark Nadal"},
b: {name: "Amber Nadal"},
b: {name: "timber Nadal"},
c: {name: "Charlie Chapman"},
d: {name: "Johnny Depp"},
e: {name: "Santa Clause"}

View File

@ -33,9 +33,9 @@
age: 23,
type: "human"
}).back.set({
name: "Amber Nadal",
age: 23,
type: "human"
name: "Timber Nadal",
age: 3,
type: "cat"
}).back.set({
name: "Hobbes",
age: 4,

View File

@ -72,7 +72,7 @@ describe("Put ACK", function(){
}
}
console.log(port, " connect to ", peers);
var gun = Gun({file: env.i+'data', peers: peers, web: server});
var gun = Gun({file: env.i+'data', peers: peers, web: server, axe: false}); // not working with axe currently!
server.listen(port, function(){
test.done();
});
@ -119,8 +119,8 @@ describe("Put ACK", function(){
ref.hear = ref.hear || [];
var hear = ref._.root.opt.mesh.hear;
ref._.root.opt.mesh.hear = function(raw, peer){
var msg = Gun.obj.ify(raw);
console.log('hear:', msg);
var msg = JSON.parse(raw);
hear(raw, peer);
ref.hear.push(msg);
}
@ -129,7 +129,7 @@ describe("Put ACK", function(){
var yes = say(raw, peer);
if(yes === false){ return }
console.log("say:", msg, yes);
(ref.say || (ref.say = [])).push(Gun.obj.ify(msg));
(ref.say || (ref.say = [])).push(JSON.parse(msg));
}
}
}, {acks: config.servers});
@ -154,7 +154,7 @@ describe("Put ACK", function(){
ref.hear = ref.hear || [];
var hear = ref._.root.opt.mesh.hear;
ref._.root.opt.mesh.hear = function(raw, peer){
var msg = Gun.obj.ify(raw);
var msg = JSON.parse(raw);
console.log('hear:', msg);
hear(raw, peer);
ref.hear.push(msg);

View File

@ -0,0 +1,326 @@
/**
* AXE test data balance
* What we want here: (1) Superpeer and (n) peers
* - The peers receives only the requested data.
* - If the Superpeer crash, must recreate all subscriptions and update the peers.
* - If some peer crash or go offline, when connected again must receive the changes made by others while out.
*/
var config = {
IP: require('ip').address(),
port: 8765,
servers: 2,
browsers: 2,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../../gun.js',
'/gun/axe.js': __dirname + '/../../../axe.js',
'/jquery.js': __dirname + '/../../../examples/jquery.js'
}
}
var panic = require('panic-server');
panic.server().on('request', function(req, res){
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port);
var clients = panic.clients;
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){
return {
type: 'node',
port: config.port + (i + 1)
}
}),
panic: 'http://' + config.IP + ':' + config.port
});
var servers = clients.filter('Node.js');
var server = servers.pluck(1);
var server2 = servers.excluding(server).pluck(1);
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var bob = browsers.excluding(alice).pluck(1);
var again = {};
describe("Data sync AXE Test!", function(){
console.time('TOTAL TEST TIME');
this.timeout(5 * 60 * 1000);
// this.timeout(10 * 60 * 1000);
it("Servers have joined!", function(){
return servers.atLeast(config.servers);
});
it("GUN started!", function(){
return server.run(function(test){
var env = test.props;
test.async();
try{ require('fs').unlinkSync(env.i+'dataaxe') }catch(e){}
try{ require('fs').unlinkSync((env.i+1)+'dataaxe') }catch(e){}
var port = env.config.port + env.i;
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
var Gun = require('gun');
require('gun/axe');
var gun = Gun({
file: env.i+'dataaxe',
web: server
});
server.listen(port, function(){
test.done();
});
}, {i: 1, config: config});
});
it(config.browsers +" browser(s) have joined!", function(){
require('../util/open').web(config.browsers, "http://"+ config.IP +":"+ config.port);
// console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
localStorage.clear(); console.log('Clear localStorage!!!');
var env = test.props;
var gun = window.gun = Gun({peers:['http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'], wait: 1000});
window.ref = gun.get('holy').get('grail');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Wait for Alice and Bob...", function(done){
setTimeout(done, 1000);
});
it("Alice Write: Hi Bob!", function(){
return alice.run(function(test){
console.log("I AM ALICE");
test.async();
ref.once(function() { // TODO: Need `.once` first for subscription. If Alice do a `.put` before a `.once`, Alice will get old data from localStorage if Bob update
ref.put('Hi Bob!', function(ack) {
console.log(ack);
setTimeout(test.done, 10000);
});
});
});
});
it("Bob receive ONCE from Alice: Hi Bob!", function(){
return bob.run(function(test){
console.log("I AM BOB");
test.async();
ref.once(function(data){
if('Hi Bob!' === data){
console.log('[OK] Bob receive the question: ', data);
return test.done();
} else {
var err = '[FAIL] Bob MUST receive: Hi Bob! but receive: ' + data + ' Storage: ' + localStorage.getItem('gun/');
console.log(err);
return test.fail(err);
}
})
})
});
it("Bob Write response: Hi Alice!", function(){
return bob.run(function(test){
test.async();
ref.put('Hi Alice!', function(ack) {
console.log('[OK] Bob Write response: Hi Alice!', JSON.stringify(ack));
setTimeout(test.done, 2000);
});
});
});
it("Alice Read response from Bob: Hi Alice!", function(){
return alice.run(function(test){
test.async();
ref.once(function(data){
if('Hi Alice!' === data){
console.log('[OK] Alice receive the response: ', data);
return test.done();
} else {
//TODO: aqui em duvida.. está pegando do localStorage, mas Bob alterou o dado.
var err = '[FAIL] Alice receive wrong response: "' + data + '" and must be "Hi Alice!"';
console.log(err);
return test.fail(err);
}
})
})
});
it("Bob Write in some data, Alice not subscribed", function(){
return bob.run(function(test){
test.async();
gun.get('bob').get('mine').put('Alice dont want this data!', function() {
setTimeout(test.done, 2000);
});
});
});
it("Alice not subscribed. Must NOT receive data from Bob", function(){
return alice.run(function(test){
test.async();
/// This must be empty, because alice don't make a subscription to this node.
var bobdata = JSON.parse(localStorage.getItem('gun/')).bob;
if (bobdata) {
var err = '[FAIL] Alice receive not subscribed data: ' + JSON.stringify(bobdata);
console.log(err);
return test.fail(err);
} else {
console.log('[OK] Alice Read must NOT receive data from Bob: ', bobdata);
return test.done();
}
})
});
it("Alice subscription Bob data with ONCE, MUST receive", function(){
return alice.run(function(test){
test.async();
gun.get('bob').once(function(data){
if(data){
console.log('[OK] Alice receive the value: ', JSON.stringify(data));
return test.done();
} else {
var err = '[FAIL] Alice receive the value: ' + data;
console.log(err);
return test.fail(err);
}
})
})
});
it("Bob Write in some data. Now Alice is subscribed.", function(){
return bob.run(function(test){
test.async();
gun.get('bob').get('mine').put('Alice WANT this data now!', function() {
setTimeout(test.done, 2000);
});
});
});
it("Alice must receive updates from Bob node", function(){
return alice.run(function(test){
test.async();
if (gun._.graph.bob && gun._.graph.bob.mine === 'Alice WANT this data now!') {
console.log('[OK] GRAPH: ', JSON.stringify(gun._.graph.bob));
test.done();
} else {
var err = '[FAIL] GRAPH: ' + JSON.stringify(gun._.graph.bob);
console.log(err);
test.fail(err);
}
})
});
it("Server has crashed!", function(){
return server.run(function(test){
console.log(3);
// var env = test.props;
// try{ require('fs').unlinkSync(env.i+'data'); }catch(e){}
process.exit(0);
}, {i: 1, config: config})
});
it("Wait...", function(done){
console.log(4);
setTimeout(done, 2000);
});
it("Alice update the data (superpeer crashed yet).", function(){
return alice.run(function(test){
var env = test.props;
if(window.WebSocket){
var err;
try{ new WebSocket('http://'+ env.config.IP + ':' + (env.config.port + 2) + '/gun') }catch(e){ err = e }
if(!err){
test.fail("Server did not crash.");
}
}
test.async()
ref.put("Superpeer? Where are you?", function() {
setTimeout(test.done, 100);
});
}, {config: config});
});
it("Bob can't see what Alice change because Superpeer is out.", function(){
return bob.run(function(test){
test.async();
ref.once(function(data){
if('Superpeer? Where are you?' !== data){
console.log('[OK] Bob have old data: ', data);
return test.done();
} else {
var err = '[FAIL] Bob MUST not receive: "Superpeer? Where are you?", but receive: ' + data;
console.log(err);
return test.fail(err);
}
})
})
});
it("Superpeer come started again!", function(){
return server2.run(function(test){
var env = test.props;
test.async();
// try{ require('fs').unlinkSync(env.i+'dataaxe') }catch(e){}
// try{ require('fs').unlinkSync((env.i+1)+'dataaxe') }catch(e){}
var port = env.config.port + env.i;
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
var Gun = require('gun');
require('gun/axe');
var gun = Gun({
file: env.i+'dataaxe',
web: server
});
server.listen(port, function(){
test.done();
});
}, {i: 1, config: config});
});
it("Wait sync...", function(done){
console.log(4);
setTimeout(done, 5000);
});
it("Bob now receive what Alice change because Superpeer is on.", function(){
return bob.run(function(test){
test.async();
ref.once(function(data){
if('Superpeer? Where are you?' === data){
console.log('[OK] Bob have old data: ', data);
return test.done();
} else {
var err = '[FAIL] Bob MUST not receive: "Superpeer? Where are you?", but receive: ' + data;
console.log(err);
return test.fail(err);
}
})
})
});
it("All finished!", function(done){
console.log("Done! Cleaning things up...");
setTimeout(function(){
done();
},1000);
});
after("Everything shut down.", function(){
require('../util/open').cleanup() || browsers.run(function(){
setTimeout(location.reload, 15 * 1000);
});
return servers.run(function(){
process.exit();
});
console.timeEnd('TOTAL TEST TIME');
});
});

View File

@ -1,13 +1,9 @@
/**
* AXE test 1
* AXE test data balance webrtc
* What we want here: (1) Superpeer and (n) peers
* - The peers receives only the requested data.
* - If the Superpeer crash, after restart, must recreate all subscriptions and update the peers.
* - If some peer crash or go offline, must receive the changes via RTC.
*
* Tip: to run this `npm run testaxe`
* Tip 2: if you clone the gun repo, you need to create a link do gun package. Do `npm install && cd node_modules && ln -s ../ gun`
* Tip 3: If you not in localhost, run the browsers in anonymous mode because of domain security policies. https://superuser.com/questions/565409/how-to-stop-an-automatic-redirect-from-http-to-https-in-chrome
*/
var config = {
IP: require('ip').address(),
@ -16,11 +12,10 @@ var config = {
browsers: 3,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/gun/axe.js': __dirname + '/../../axe.js',
'/gun/lib/radix.js': __dirname + '/../../lib/radix.js',
'/gun/lib/webrtc.js': __dirname + '/../../lib/webrtc.js',
'/jquery.js': __dirname + '/../../examples/jquery.js'
'/gun.js': __dirname + '/../../../gun.js',
'/gun/axe.js': __dirname + '/../../../axe.js',
'/gun/lib/webrtc.js': __dirname + '/../../../lib/webrtc.js',
'/jquery.js': __dirname + '/../../../examples/jquery.js'
}
}
var panic = require('panic-server');
@ -80,7 +75,8 @@ describe("The Holy Grail AXE Test!", function(){
});
it(config.browsers +" browser(s) have joined!", function(){
require('./util/open').web(config.browsers, "http://"+ config.IP +":"+ config.port);
require('../util/open').web(config.browsers, "http://"+ config.IP +":"+ config.port);
// console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
@ -413,7 +409,9 @@ describe("The Holy Grail AXE Test!", function(){
},1000);
});
after("Everything shut down.", function(){
require('./util/open').cleanup();
require('../util/open').cleanup() || browsers.run(function(){
setTimeout(location.reload, 15 * 1000);
});
return servers.run(function(){
process.exit();
});

View File

@ -0,0 +1,172 @@
/**
* AXE test loadbalance
*
* Bob, Carl, Dave, Ed, subscribed to Zebra(relay).
*
* Test case 3) Bob Carl Dave Ed browser peers, all subscribed to Zebra(Relay). Alice joins, gets Zebra. Relay should only load balance GET to 3 other peers, as acks will have matching hashes and therefore stop propagating. (if acks are inconsistent, it will keep propagating, but we're not testing that here). The tricky thing is you'll have to hijack requests to make sure the 4th peer doesn't get the GET.
*/
var config = { IP: require('ip').address(), port: 8765, servers: 1, browsers:4, i:0,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../../gun.js',
'/gun/axe.js': __dirname + '/../../../axe.js',
'/jquery.js': __dirname + '/../../../examples/jquery.js'
}
};
var panic = require('panic-server');
panic.server().on('request', function(req, res){ config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);}).listen(config.port);
var clients = panic.clients;
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){ return { type: 'node', port: config.port + (i + 1) } }),
panic : 'http://' + config.IP + ':' + config.port
});
var servers = clients.filter('Node.js');
var server = servers.pluck(1);
var browsers = clients.excluding(servers);
describe("AXE Test: LOADBALANCE", function(){
this.timeout(5 * 60 * 1000);
it("Servers have joined!", function(){ return servers.atLeast(config.servers); });
it("GUN started!", function(){
return server.run(function(test){
var env = test.props;
test.async();
try{ require('fs').unlinkSync(env.i+'dataaxe') }catch(e){}
try{ require('fs').unlinkSync((env.i+1)+'dataaxe') }catch(e){}
var port = env.config.port + env.i;
var server = require('http').createServer(function(req, res){ res.end("I am "+ env.i +"!"); });
var Gun = require('gun');
// require('gun/axe');
var gun = global.gun = Gun({ file: env.i+'dataaxe', web: server, pid:'Relay_pid' });
console.log(' [ RELAY PID ] '+gun._.opt.pid);
Gun.on('create', function(root){
this.to.next(root);
root.on('in', function(msg){
console.log('[ GET RELAY ]* PID:'+gun._.opt.pid+' RELAY MESSAGE: ', (msg));
this.to.next(msg);
});
root.on('out', function(msg){
console.log('[ OUT RELAY ]* ', msg);
this.to.next(msg);
});
});
server.listen(port, function(){ test.done(); });
gun.get('ref_soul').put({ 'hi':'value_'+String.random(3) });
}, {i: 1, config: config});
});
it(config.browsers +" browser(s) have joined!", function(){
require('../util/open').web(config.browsers, "http://"+ config.IP +":"+ config.port);
// console.log(" PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i=0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
localStorage.clear(); //console.log('Clear localStorage!!!');
window.uuid = function(l){ return new Date(Gun.state()).toISOString() + '/' + String.random(l||3) };
var env = test.props;
var opt = {
peers:['http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'],
// pid:'Peer_'+('0'+(env.config.i+1)).slice(-2)+'_',
uuid
};
Gun.on('create', function(root){
this.to.next(root);
root.on('in', function(msg){
this.to.next(msg);
if (msg.get && msg.get['#'] && msg.get['#'] !== 'balance') {
++gun.total_gets; /// increment each peer total `in` events
var hash = (msg['#'] ? '_'+msg['#'] : '') + (msg['><'] ? '_O ' : '');
gun.get('balance').set(gun._.opt.pid+' '+(msg['#'] ? '_msg_id_'+msg['#'] : ''));
}
});
});
var gun = window.gun = Gun(opt);
gun.total_gets=0;
}, {i: i += 1, config: config}));
++config.i;
});
return Promise.all(tests);
});
it("Peers subscribe", function(){
var tests = [], i=0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
test.async();
function done(v,k) {
// console.log('!!!!!!! Peer subscribed pid:' + gun._.opt.pid + ' msg:' + JSON.stringify({ k, v }));
test.done();
}
gun.get('ref_soul').once(done);
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Check balance!", function(){
return server.run(function(test){
function onlyUnique(value, index, self) { return self.indexOf(value) === index; }
console.log('RELAY PID:' + gun._.opt.pid);
test.async();
gun.get('balance').once(function(v,k) {
var tmp = [], i=0, participants=[], keys = Object.keys(v['_']).sort();
for (i=0;i<keys.length;++i) { participants.push(v[ keys[ i ] ]); }
participants.sort();
var pid, pids = Object.keys(v).sort();
var pids_sorted = Object.keys(v['_']['>']).map(soul => v[soul].split(' ')[0]).filter(onlyUnique).sort();
var msgs_id = Object.keys(v['_']['>']).map(soul => v[soul].split(' ')[1]).filter(onlyUnique);
var msgs_sorted_bytime = Object.keys(v['_']['>']).sort();
var table={}, msg_id, peer_id;
for (i=0;i<msgs_sorted_bytime.length;++i) {
tmp =v[msgs_sorted_bytime[i]].split(' ');
peer_id = tmp[0];
msg_id = tmp[1];
if (!table[msg_id]) { table[msg_id]=[]; }
table[msg_id].push(peer_id);
if (table[msg_id].length > 4) { console.log('Ouch!!!!', table); test.fail('Msg ('+msg_id+') with more then 4 requests.'); return; }
}
for (i=0;i<pids_sorted.length;++i) {
if (typeof pids_sorted[i] !== 'string') { continue; }
pid = pids_sorted[i];
}
for (i=0;i<pids.length;++i) {
var p1=pids[i-1], p2=pids[i];
if (!v[p1] || !v[p2]) { continue; }
if ('string' !== typeof v[p1]) { v[p1]='relay'; }
if ('string' !== typeof v[p2]) { v[p2]='relay'; }
}
// console.log('TABLE: ', table);/// NOTE: this data have each peer_id who participant of a message delivery.
setTimeout(test.done, 1000);
});
});
});
it("All finished!", function(done){
// browsers.each(function(client, id){ client.run(function() { console.log('TOTAL gets PID:'+gun._.opt.pid+': ', gun.total_gets); }); });
console.log("Done! Cleaning things up...");
setTimeout(done, 2000);
});
after("Everything shut down.", function(){
require('../util/open').cleanup() || browsers.run(function(){
setTimeout(function(){
location.reload();
}, 15 * 1000);
});
return servers.run(function(){ process.exit(); });
});
});

202
test/panic/chat.js Normal file
View File

@ -0,0 +1,202 @@
var config = {
IP: require('ip').address(),
port: 8765,
servers: 1,
browsers: 2, //3,
each: 100000,
size: 1,
wait: 1,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/jquery.js': __dirname + '/../../examples/jquery.js'
}
}
/*
Assume we have 3 peers in a star topology,
..B..
./.\.
A...C
And they share a chat room with 10K messages.
A -> GET chat -> B (cache miss) -> C
C hosts the data and streams it back
C -> PUT chat -> B (relay) -> A got.
Using the WebRTC module, C <-> A directly, no need for a relay!
But we're wanting to test the performance of the whole network.
*/
var panic; try{ panic = require('panic-server') } catch(e){ console.log("PANIC not installed! `npm install panic-server panic-manager panic-client`") }
panic.server().on('request', function(req, res){ // Static server
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port); // Start panic server.
// In order to tell the clients what to do,
// We need a way to reference all of them.
var clients = panic.clients;
// Some of the clients may be NodeJS servers on different machines.
// PANIC manager is a nifty tool that lets us remotely spawn them.
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){ // Create a bunch of servers.
return {
type: 'node',
port: config.port + (i + 1) // They'll need unique ports to start their servers on, if we run the test on 1 machine.
}
}),
panic: 'http://' + config.IP + ':' + config.port // Auto-connect to our panic server.
});
// Now lets divide our clients into "servers" and "browsers".
var servers = clients.filter('Node.js');
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var carl = browsers.excluding(alice).pluck(1);
describe("Load test "+ config.browsers +" browser(s) across "+ config.servers +" server(s)!", function(){
// We'll have to manually launch the browsers,
// So lets up the timeout so we have time to do that.
this.timeout(50 * 60 * 1000);
it("Servers have joined!", function(){
// Alright, lets wait until enough gun server peers are connected.
return servers.atLeast(config.servers);
});
it("GUN has spawned!", function(){
// Once they are, we need to actually spin up the gun server.
var tests = [], i = 0;
servers.each(function(client){
// for each server peer, tell it to run this code:
tests.push(client.run(function(test){
// NOTE: Despite the fact this LOOKS like we're in a closure...
// it is not! This code is actually getting run
// in a DIFFERENT machine or process!
var env = test.props;
// As a result, we have to manually pass it scope.
test.async();
// Clean up from previous test.
try{ require('fs').unlinkSync(env.i+'data.json') }catch(e){}
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
// Launch the server and start gun!
var Gun; try{ Gun = require('gun') }catch(e){ console.log("GUN not found! You need to link GUN to PANIC. Nesting the `gun` repo inside a `node_modules` parent folder often fixes this.") }
// Attach the server to gun.
var gun = Gun({file: env.i+'data', web: server, axe: false, localStorage: false, radisk: false});
server.listen(env.config.port + env.i, function(){
// This server peer is now done with the test!
// It has successfully launched.
test.done();
});
}, {i: i += 1, config: config}));
});
// NOW, this is very important:
// Do not proceed to the next test until
// every single server (in different machines/processes)
// have ALL successfully launched.
return Promise.all(tests);
});
it(config.browsers +" browser(s) have joined!", function(){
require('./util/open').web(config.browsers, "http://"+ config.IP +":"+ config.port); //console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
try{ localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radata') }catch(e){}
var env = test.props;
//var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun');
var gun = Gun({localStorage: false, radisk: false, peers: 'http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'});
window.gun = gun;
window.ref = gun.get('chat');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Carl Create Chats", function(){
return carl.run(function(test){
console.log("I AM CARL");
$('body').append("<div>CPU turns stacked: <u></u> <button onclick='this.innerText = Math.random();'>Can you click me?</button><input id='msg' style='width:100%;'><b></b></div>");
test.async();
var rand = String.random || Gun.text.random;
var i = test.props.each, chat = {}, S = Gun.state();
var tmp = "generating " + i + " records..."; console.log(tmp); $('b').text(tmp);
var big = rand(test.props.size || 1); //1000 * 10);
function gen(){
var j = 99;
$('b').text(i + ' left to generate...');
var data = rand(100);
while(--j && i){ --i;
Gun.state.ify(chat, i/*+'-'+rand(9)*/, S, rand(100) + data + big, 'chat');
}
if(i === 0){
gun._.graph.chat = chat;
test.done();
$('b').text('');
return;
}
setTimeout.turn(gen);
}
gen();
//window.chat = chat;
//console.log(JSON.stringify(chat,null,2));
setInterval(function(){ $('u').text(setTimeout.turn.s.length) },1000);
}, config);
});
it("Alice Asks for Chat", function(){
return alice.run(function(test){
console.log("I AM ALICE");
test.async();
var i = 0, t = test.props.each, tmp;
$('body').append("<div><i></i> / "+t+", seconds to first reply: <span></span>, CPU turns stacked: <u></u> <button onclick='this.innerText = Math.random();'>Can you click me?</button><input id='msg' style='width:100%;'><b></b></div>");
var $msg = $('#msg'), $i = $('i');
var V, I, S = +new Date, SS = S, tmp;
ref.map().once(function(v,k){
S && console.log('first:', $('span').text(tmp = (+new Date - S)/1000) && tmp) || (S = null);
if(!v){ no_data }
V = v;
I = ++i;
//console.log(i, "chat:",k,v);
if(i === t){
console.log(tmp = "seconds from start to end: " + (tmp = ((+new Date - SS)/1000)) + ", roughly " + (t/tmp).toFixed(2) + "ops/sec.");
$('b').text(tmp);
setTimeout(function(){ test.done() },100);
}
});
window.requestAnimationFrame = window.requestAnimationFrame || setTimeout;
window.requestAnimationFrame(function frame(){
window.requestAnimationFrame(frame, 16);
$msg.val(V);
$i.text(I);
}, 16);
setInterval(function(){ $('u').text(setTimeout.turn.s.length) },1000);
}, config);
});
after("Everything shut down.", function(){
// which is to shut down all the browsers.
require('./util/open').cleanup() || browsers.run(function(){
setTimeout(function(){
location.reload();
}, 15 * 1000);
});
// And shut down all the servers.
return servers.run(function(){
process.exit();
});
});
})

View File

@ -10,7 +10,8 @@ var config = {
}
}
var panic = require('panic-server');
var panic; try{ panic = require('panic-server') } catch(e){ console.log("PANIC not installed! `npm install panic-server panic-manager panic-client`") }
panic.server().on('request', function(req, res){
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port);
@ -56,12 +57,12 @@ describe("The Holy Grail Test!", function(){
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
var Gun = require('gun');
var Gun; try{ Gun = require('gun') }catch(e){ console.log("GUN not found! You need to link GUN to PANIC. Nesting the `gun` repo inside a `node_modules` parent folder often fixes this.") }
var gun = Gun({file: env.i+'data', web: server});
server.listen(port, function(){
test.done();
});
}, {i: 1, config: config});
}, {i: 1, config: config});
});
it(config.browsers +" browser(s) have joined!", function(){
@ -73,10 +74,8 @@ describe("The Holy Grail Test!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
window.ENV = test.props;
localStorage.clear();
var env = test.props;
var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun');
window.ref = gun.get('holy').get('grail');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
@ -85,6 +84,9 @@ describe("The Holy Grail Test!", function(){
it("Write initial value", function(){
return alice.run(function(test){
console.log("I AM ALICE");
var env = window.ENV;
var gun = Gun({peers: ['http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'], file: 'alicedata'});
window.ref = gun.get('holy').get('grail');
ref.put("value");
setTimeout(test.async(), 2000);
});
@ -93,6 +95,9 @@ describe("The Holy Grail Test!", function(){
it("Read initial value", function(){
return bob.run(function(test){
console.log("I AM BOB");
var env = window.ENV;
var gun = Gun({peers: ['http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'], file: 'bobdata'});
window.ref = gun.get('holy').get('grail');
test.async();
ref.on(function(data){
if("value" === data){
@ -192,36 +197,45 @@ describe("The Holy Grail Test!", function(){
}, {i: 2, config: config});
});
it("Browsers re-initialized gun!", function(){
it("Browsers ready!", function(){
var tests = [], i = 0;
new panic.ClientList([again.alice, again.bob]).each(function(client, id){
tests.push(client.run(function(test){
window.ENV = test.props;
// NOTE: WE DO NOT CLEAR localStorage!
console.log(localStorage['gun/holy']);
var env = test.props;
var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 2) + '/gun');
window.ref = gun.get('holy').get('grail');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Alice re-initialized gun!", function(){
return again.alice.run(function(test){
var env = window.ENV;
var gun = Gun({peers: ['http://'+ env.config.IP + ':' + (env.config.port + 2) + '/gun'], file: 'alicedata'});
window.ref = gun.get('holy').get('grail');
});
});
it("Bob re-initialized gun!", function(){
return again.bob.run(function(test){
var env = window.ENV;
var gun = Gun({peers: ['http://'+ env.config.IP + ':' + (env.config.port + 2) + '/gun'], file: 'bobdata'});
window.ref = gun.get('holy').get('grail');
});
});
it("Alice conflict.", function(){
return again.alice.run(function(test){
test.async();
var c = 0;
ref.on(function(data){
console.log("======", data);
window.stay = data;
if(!c){
c++;
if("Alice" == data){
setTimeout(function(){
window.ALICE = true;
test.done();
}, 2000);
}
return;
if("Bob" == data){
setTimeout(function(){
if(window.ALICE){ return }
window.ALICE = true;
test.done();
}, 1000);
}
});
});
@ -230,22 +244,18 @@ describe("The Holy Grail Test!", function(){
it("Bob converged.", function(){
return again.bob.run(function(test){
test.async();
var c = 0;
ref.on(function(data){
console.log("======", data);
//return;
window.stay = data;
if("Bob" != data){
test.fail("wrong local value!");
return;
}
setTimeout(function(){
if(c){ return }
c++;
if("Bob" === data){
test.done();
}
}, 2000);
if(window.BOB){ return }
window.BOB = true;
test.done();
}, 1000);
});
});
});

176
test/panic/large-nodes.js Normal file
View File

@ -0,0 +1,176 @@
var config = {
IP: require('ip').address(),
port: 8765,
servers: 1,
browsers: 2,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/jquery.js': __dirname + '/../../examples/jquery.js'
}
}
var panic = require('panic-server');
panic.server().on('request', function(req, res){
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port);
var clients = panic.clients;
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){
return {
type: 'node',
port: config.port + (i + 1)
}
}),
panic: 'http://' + config.IP + ':' + config.port
});
var servers = clients.filter('Node.js');
var bob = servers.pluck(1);
//var carl = servers.excluding(bob).pluck(1);
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var dave = browsers.excluding(alice).pluck(1);
describe("Put ACK", function(){
//this.timeout(5 * 60 * 1000);
this.timeout(10 * 60 * 1000);
it("Servers have joined!", function(){
return servers.atLeast(config.servers);
});
it("GUN started!", function(){
var tests = [], i = 0;
servers.each(function(client){
tests.push(client.run(function(test){
var env = test.props;
test.async();
try{ require('fs').unlinkSync(env.i+'data') }catch(e){}
try{ require('gun/lib/fsrm')(env.i+'data') }catch(e){}
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
var port = env.config.port + env.i;
var Gun = require('gun');
var peers = [], i = env.config.servers;
while(i--){
var tmp = (env.config.port + (i + 1));
if(port != tmp){ // ignore ourselves
peers.push('http://'+ env.config.IP + ':' + tmp + '/gun');
}
}
console.log(port, " connect to ", peers);
var gun = Gun({file: env.i+'data', peers: peers, web: server, chunk: 1024 * 10});
server.listen(port, function(){
test.done();
});
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it(config.browsers +" browser(s) have joined!", function(){
console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
try{ localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radata') }catch(e){}
var env = test.props;
var gun = Gun('http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun');
window.ref = gun.get('a');
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Put", function(){
return alice.run(function(test){
console.log("I AM ALICE");
test.async();
var i = test.props.each || 25000;
var put = {};
while(--i){
put[Gun.text.random(9)] = i;
}
var S = +new Date;
ref.put(put, function(ack){
console.log("acks:", +new Date - S, ack);
test.done();
});
}, {acks: config.servers});
});
return;
it("Get", function(){
/*
Here is the recursive rule for GET, keep replying while hashes mismatch.
1. Receive a GET message.
2. If it has a hash, and if you have a thing matching the GET, then see if the hashes are the same, if they are then don't ACK, don't relay, end.
3. If you would have the thing but do not, then ACK that YOU have nothing.
4. If you have a thing matching the GET or an ACK for the GET's message, add the hash to the GET message, and ACK with the thing or ideally the remaining difference.
5. Pick ?3? OTHER peers preferably by priority that they have got the thing, send them the GET, plus all "up" peers.
6. If no ACKs you are done, end.
7. If you get ACKs back to the GET with things and different hashes, optionally merge into the thing you have GOT and update the hash.
8. Go to 4.
*/
return dave.run(function(test){
console.log("I AM DAVE");
test.async();
var c = 0, to;
ref.hear = ref.hear || [];
var hear = ref._.root.opt.mesh.hear;
ref._.root.opt.mesh.hear = function(raw, peer){
var msg = Gun.obj.ify(raw);
console.log('hear:', msg);
hear(raw, peer);
ref.hear.push(msg);
if(msg.put){ ++c }
}
ref.get(function(ack){
if(!ack.put || ack.put.hello !== 'world'){ return }
if(c > 1){ too_many_acks }
clearTimeout(to);
to = setTimeout(test.done, 1000);
});
}, {acks: config.servers});
});
it("DAM", function(){
return alice.run(function(test){
test.async();
if(ref.say){ said_too_much }
if(ref.hear.length > 1){ heard_to_much }
test.done()
}, {acks: config.servers});
});
it("All finished!", function(done){
console.log("Done! Cleaning things up...");
setTimeout(function(){
done();
},1000);
});
after("Everything shut down.", function(){
browsers.run(function(){
//location.reload();
//setTimeout(function(){
//}, 15 * 1000);
});
return servers.run(function(){
process.exit();
});
});
});

170
test/panic/latency.js Normal file
View File

@ -0,0 +1,170 @@
// this has Alice read data, measuring its latency, while other browsers are flooding relay with updates.
var config = {
IP: require('ip').address(),
port: 8765,
servers: 1,
browsers: 2,
each: 10000,
burst: 10,
wait: 1,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/jquery.js': __dirname + '/../../examples/jquery.js',
'/sea.js': __dirname + '/../../sea.js',
'/yson.js': __dirname + '/../../lib/yson.js'
},
dir: __dirname
}
var panic = require('panic-server');
panic.server().on('request', function(req, res){ // Static server
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port); // Start panic server.
// In order to tell the clients what to do,
// We need a way to reference all of them.
var clients = panic.clients;
// Some of the clients may be NodeJS servers on different machines.
// PANIC manager is a nifty tool that lets us remotely spawn them.
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){ // Create a bunch of servers.
return {
type: 'node',
port: config.port + (i + 1) // They'll need unique ports to start their servers on, if we run the test on 1 machine.
}
}),
panic: 'http://' + config.IP + ':' + config.port // Auto-connect to our panic server.
});
// Now lets divide our clients into "servers" and "browsers".
var servers = clients.filter('Node.js');
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var others = browsers.excluding(alice);
describe("Test vanishing property "+ config.browsers +" browser(s) across "+ config.servers +" server(s)!", function(){
// We'll have to manually launch the browsers,
// So lets up the timeout so we have time to do that.
this.timeout(5 * 60 * 1000);
it("Servers have joined!", function(){
// Alright, lets wait until enough gun server peers are connected.
return servers.atLeast(config.servers);
});
it("GUN has spawned!", function(){
// Once they are, we need to actually spin up the gun server.
var tests = [], i = 0;
servers.each(function(client){
// for each server peer, tell it to run this code:
tests.push(client.run(function(test){
// NOTE: Despite the fact this LOOKS like we're in a closure...
// it is not! This code is actually getting run
// in a DIFFERENT machine or process!
var env = test.props;
// As a result, we have to manually pass it scope.
test.async();
// Clean up from previous test.
try{ require('fs').unlinkSync(env.i+'data.json') }catch(e){}
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
// Launch the server and start gun!
var Gun = require(env.config.dir+'/../../');
// Attach the server to gun.
//var gun = Gun({file: env.i+'data', web: server});
var gun = Gun({file: env.i+'data', web: server, rad: false, localStorage: false});
server.listen(env.config.port + env.i, function(){
// This server peer is now done with the test!
// It has successfully launched.
test.done();
});
//setInterval(function(){ console.log("CPU turns stacked:", setTimeout.turn.s.length) },1000);
}, {i: i += 1, config: config}));
});
// NOW, this is very important:
// Do not proceed to the next test until
// every single server (in different machines/processes)
// have ALL successfully launched.
return Promise.all(tests);
});
it(config.browsers +" browser(s) have joined!", function(){
console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
try{ localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radata') }catch(e){}
var env = test.props;
var gun = Gun({retry: 2, peers: 'http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'});
window.gun = gun;
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Start flooding", function(){
var tests = [], i = 0;
others.each(function(client, id){
tests.push(client.run(function(test){
console.log("I SHALL FLOOD");
test.async();
var config = test.props.config;
gun.get('test').get('latency').put("hello world");
var go = setInterval(function(){
var burst = config.burst;
while(--burst){
console.log(burst);
gun.get(String.random(Math.random()*100)).get(String.random(Math.random()*10)).put(String.random(Math.random()*1000))
}
},config.wait);
setTimeout(function(){
test.done();
setTimeout(function(){ clearInterval(go) }, 2000);
}, 1000 * 10);
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Alice reads during flood", function(){
return alice.run(function(test){
console.log("I AM ALICE", gun.back('opt.pid'));
$('body').css('background', 'red');
test.async();
var S = +new Date;
gun.get('test').get('latency').on(function(data){
var latency = +new Date - S;
console.log(latency, data);
if(!data){ return }
//test.done();
});
}, config);
});
after("Everything shut down.", function(){
// which is to shut down all the browsers.
browsers.run(function(){
setTimeout(function(){
return;
location.reload();
}, 15 * 1000);
});
// And shut down all the servers.
return servers.run(function(){
process.exit();
});
});
})

View File

@ -27,7 +27,8 @@ var config = {
// Each device/browser in the distributed system we are testing connects to it.
// It then coordinates these clients to cause chaos in the distributed system.
// Cool huh?
var panic = require('panic-server');
var panic; try{ panic = require('panic-server') } catch(e){ console.log("PANIC not installed! `npm install panic-server panic-manager panic-client`") }
panic.server().on('request', function(req, res){ // Static server
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port); // Start panic server.
@ -86,7 +87,7 @@ describe("Load test "+ config.browsers +" browser(s) across "+ config.servers +"
res.end("I am "+ env.i +"!");
});
// Launch the server and start gun!
var Gun = require('gun');
var Gun; try{ Gun = require('gun') }catch(e){ console.log("GUN not found! You need to link GUN to PANIC. Nesting the `gun` repo inside a `node_modules` parent folder often fixes this.") }
// Attach the server to gun.
var gun = Gun({file: env.i+'data', web: server, localStorage: false});
server.listen(env.config.port + env.i, function(){

195
test/panic/user-paste.js Normal file
View File

@ -0,0 +1,195 @@
// test SEA end to end.
var config = {
IP: require('ip').address(),
port: 8765,
servers: 1,
browsers: 2,
each: 10000,
wait: 1,
route: {
'/': __dirname + '/index.html',
'/gun.js': __dirname + '/../../gun.js',
'/jquery.js': __dirname + '/../../examples/jquery.js',
'/sea.js': __dirname + '/../../sea.js',
'/yson.js': __dirname + '/../../lib/yson.js'
},
dir: __dirname
}
var panic = require('panic-server');
panic.server().on('request', function(req, res){ // Static server
config.route[req.url] && require('fs').createReadStream(config.route[req.url]).pipe(res);
}).listen(config.port); // Start panic server.
// In order to tell the clients what to do,
// We need a way to reference all of them.
var clients = panic.clients;
// Some of the clients may be NodeJS servers on different machines.
// PANIC manager is a nifty tool that lets us remotely spawn them.
var manager = require('panic-manager')();
manager.start({
clients: Array(config.servers).fill().map(function(u, i){ // Create a bunch of servers.
return {
type: 'node',
port: config.port + (i + 1) // They'll need unique ports to start their servers on, if we run the test on 1 machine.
}
}),
panic: 'http://' + config.IP + ':' + config.port // Auto-connect to our panic server.
});
// Now lets divide our clients into "servers" and "browsers".
var servers = clients.filter('Node.js');
var browsers = clients.excluding(servers);
var alice = browsers.pluck(1);
var bob = browsers.excluding(alice).pluck(1);
describe("Test vanishing property "+ config.browsers +" browser(s) across "+ config.servers +" server(s)!", function(){
// We'll have to manually launch the browsers,
// So lets up the timeout so we have time to do that.
this.timeout(5 * 60 * 1000);
it("Servers have joined!", function(){
// Alright, lets wait until enough gun server peers are connected.
return servers.atLeast(config.servers);
});
it("GUN has spawned!", function(){
// Once they are, we need to actually spin up the gun server.
var tests = [], i = 0;
servers.each(function(client){
// for each server peer, tell it to run this code:
tests.push(client.run(function(test){
// NOTE: Despite the fact this LOOKS like we're in a closure...
// it is not! This code is actually getting run
// in a DIFFERENT machine or process!
var env = test.props;
// As a result, we have to manually pass it scope.
test.async();
// Clean up from previous test.
try{ require('fs').unlinkSync(env.i+'data.json') }catch(e){}
var server = require('http').createServer(function(req, res){
res.end("I am "+ env.i +"!");
});
// Launch the server and start gun!
var Gun = require(env.config.dir+'/../../');
// Attach the server to gun.
//var gun = Gun({file: env.i+'data', web: server});
console.log("UNDO THIS!!!!!!!!");var gun = Gun({file: env.i+'data', web: server, rad: false, localStorage: false});
server.listen(env.config.port + env.i, function(){
// This server peer is now done with the test!
// It has successfully launched.
test.done();
});
//setInterval(function(){ console.log("CPU turns stacked:", setTimeout.turn.s.length) },1000);
}, {i: i += 1, config: config}));
});
// NOW, this is very important:
// Do not proceed to the next test until
// every single server (in different machines/processes)
// have ALL successfully launched.
return Promise.all(tests);
});
it(config.browsers +" browser(s) have joined!", function(){
console.log("PLEASE OPEN http://"+ config.IP +":"+ config.port +" IN "+ config.browsers +" BROWSER(S)!");
return browsers.atLeast(config.browsers);
});
it("Browsers load SEA!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
test.async();
//console.log("load?");
function load(src, cb){
var script = document.createElement('script');
script.onload = cb; script.src = src;
document.head.appendChild(script);
}
load('sea.js', function(){
load('yson.js', function(){
test.done();
});
});
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Browsers initialized gun!", function(){
var tests = [], i = 0;
browsers.each(function(client, id){
tests.push(client.run(function(test){
try{ localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radata') }catch(e){}
var env = test.props;
var gun = Gun({peers: 'http://'+ env.config.IP + ':' + (env.config.port + 1) + '/gun'});
window.gun = gun;
}, {i: i += 1, config: config}));
});
return Promise.all(tests);
});
it("Alice creates user", function(){
return alice.run(function(test){
console.log("I AM ALICE");
test.async();
gun.user().create('alice','password', function(){ gun.user().auth('alice','password', function(ack){
if(ack.err){ _bad_login_ }
test.done();
})});
}, config);
});
it("Bob logs into user", function(){
return bob.run(function(test){
console.log("I AM BOB");
test.async();
console.only.i=1;console.log("====================");
gun.user().auth('alice','password', function(ack){
if(ack.err){ _bad_login_ }
test.done();
});
}, config);
});
it("Alice pastes", function(){
return alice.run(function(test){
console.log("paste");
test.async();
gun.user().get('paste').put('hello world!').on(function(data){
if(this.stop){ return } this.stop = 1; // hack for now!
if('hello world!' != data){ _bad_data_ }
setTimeout(function(){ test.done() }, 1000);
})
}, config);
});
it("Bob reads", function(){
return bob.run(function(test){
console.log("I AM BOB");
test.async();
gun.user().once(function(data){
console.log("data!", data);
if('hello world!' != data.paste){ _bad_data2_ }
test.done();
});
}, config);
});
after("Everything shut down.", function(){
// which is to shut down all the browsers.
browsers.run(function(){
setTimeout(function(){
return;
location.reload();
}, 15 * 1000);
});
// And shut down all the servers.
return servers.run(function(){
process.exit();
});
});
})

8
test/rad/2020and2021.js Normal file
View File

@ -0,0 +1,8 @@
var Gun = require('../../index');
var gun = Gun({file: __dirname+'/old2020json'});
gun.get('test').once(function(data, key){
console.log(key, data);
if(!data){ throw "not compatible!" }
});

1
test/rad/old2020json/! Normal file
View File

@ -0,0 +1 @@
{"~":{"@testing123\u001b~6LHYiwdkGMqW45nTBKAjUNO_OyxIauOltwNKF-9mJCQ.yqQrGr1yC-N0VBahwYnbnt9D9Y05OoaQbcPneDzM-3A":{"":{":":{"#":"~6LHYiwdkGMqW45nTBKAjUNO_OyxIauOltwNKF-9mJCQ.yqQrGr1yC-N0VBahwYnbnt9D9Y05OoaQbcPneDzM-3A"},">":1620286970976}},"6LHYiwdkGMqW45nTBKAjUNO_OyxIauOltwNKF-9mJCQ.yqQrGr1yC-N0VBahwYnbnt9D9Y05OoaQbcPneDzM-3A":{"\u001b":{"pub":{"":{":":"6LHYiwdkGMqW45nTBKAjUNO_OyxIauOltwNKF-9mJCQ.yqQrGr1yC-N0VBahwYnbnt9D9Y05OoaQbcPneDzM-3A",">":1620286970971}},"epub":{"":{":":"{\":\":\"qIKEeDg23hc0ylkyTsPeXcsBwWwiR4spJZ4wbD2bcek.UN75LS5RWe3IStRPuDKXDt-ia2XCDLQbP9oVhYezF04\",\"~\":\"9NuEZuTzsBv8intGrpFb4rISVigiVMV9kxF9bAs3XGK/1A/E154XUJaFmergCIu6pINPao+3HAEYxfOV1HKqaA==\"}",">":1620286970971}},"a":{"lias":{"":{":":"{\":\":\"testing123\",\"~\":\"/UTDwurvc52XoZoNOoVaF6Uncc9VzRBomczHKidrnbMcJIIxbLJiezYHONGXe5RIWmVZoLWrGjqUYf/1DuRHJQ==\"}",">":1620286970971}},"uth":{"":{":":"{\":\":\"{\\\"ek\\\":{\\\"ct\\\":\\\"fmyGN0w0PFd/7K8+KjZW4id75XGpOn9MdbJIMbvJTHv2AdjEX3LHCMrykXdiAEZtwinJSMyE6qFE+q3AaelijOirYZoC1LicFWuoptFYqwKuiyEdchv1IoEglK4umLhFpb8LJ25oXXK2eTFJUrLWmqP4tE95IbdLyn5CLA==\\\",\\\"iv\\\":\\\"zK8ZPmlek+jlXHV1JSAG\\\",\\\"s\\\":\\\"jftXz+L2GhWh\\\"},\\\"s\\\":\\\"9P4PBBEXdj55gLTtHgkHis4ukesIEu5ULyogvnglH0pFpcPtKEWSgtb9jRCw42kt\\\"}\",\"~\":\"vQLL5kA1BXmingXaDyTD7s62ZQHzHbFz5u4OS6rWh0Y64xjYo7DiBGmXyUDkxX9UlB1PVdGAIapp6OWLwl3Gag==\"}",">":1620286970971}}},"said":{"":{":":"{\":\":{\"#\":\"~6LHYiwdkGMqW45nTBKAjUNO_OyxIauOltwNKF-9mJCQ.yqQrGr1yC-N0VBahwYnbnt9D9Y05OoaQbcPneDzM-3A/said\"},\"~\":\"Qm4q1ZLXnSeJ0KazwtWYuWqMMFzKWJIOuthAyVS5tpRnhV84VCjvtQBcbsivsh68oX+7fTa5038cAeiymwK8Xg==\"}",">":1620286972880}}},"/said\u001bkoc":{"l1":{"2":{"a7X2NnZMH":{"":{":":"{\":\":\"b\",\"~\":\"/296iLEq5Rhl0xnw0PVOZnWmFb//tJa3fKmFf9zeuUk2vXo5aCA61Fn5V91oAPLSaQAOB4cMYeInTRq/wusxng==\"}",">":1620286973647.001}},"w0fVwD9SF":{"":{":":"{\":\":\"c\",\"~\":\"CR6+M3CnW+BdD4gISJvCDGaYHPnBUgUFTcLV6wHRjbIWrsXg1orxvMPFB/I0djggfQyIHTd7WbwneEt4BD/P2g==\"}",">":1620286974432.001}}},"1ov2HGy3pP":{"":{":":"{\":\":\"a\",\"~\":\"rxavUWplLfAH7yRRz7WOTeiqA5cVB5Pe0MBsaK85R3N9AmL81afktynkCmvIgu0C+Vd4dS3v/OG64lIwv7Kxow==\"}",">":1620286972880}},"a2msNGEG5j":{"":{":":"{\":\":\"cool\",\"~\":\"hIlAjwklp07vhR6qd0aHFHd988Sdz6RVIwfiV5gwdK7/Cy0LWdIE8N+8tK1rnZdkVA7DKzRuPClk+P5EdPbGHQ==\"}",">":1620286983744}}},"m2":{"rgfrjl4ph4":{"":{":":"{\":\":\"sweet\",\"~\":\"z4xeMjvp00yjuMiesSh3V4NenXiYbItmgLZbrtPjwmCQhsJev2BZB2TGTLd/xBTOMNEea3pPnB6M4iiflk3QvA==\"}",">":1620288732544}},"shv4QO5daU":{"":{":":"{\":\":\"neat\",\"~\":\"AzzHhVdxtT0ZZVRFWEWUYBP7Tm6eYFBTc7xTRiz6xyMoSeJsHSFvC65gSu5yTRG0N8ykSBQDxkCxECWb2XIJ4w==\"}",">":1620288733891.001}},"td2ZtiM4kP":{"":{":":"{\":\":\"cool\",\"~\":\"Sh8Hr491IAcTPI4sv0/bZG7hBCfyHGG2+mEqFzXoAbOmaUZqmcYSqeXuuMRpnQtIJFwwbTc8eC97gOAwABvwyQ==\"}",">":1620288735014.001}}}}}},"#\u001buU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=":{"":{":":"hello world",">":1620599842832}},"test\u001bpaste":{"":{":":"hello world!!!",">":1629535024341}}}

1
test/rad/old2020json/%1C Normal file
View File

@ -0,0 +1 @@
{"!":{"":1}}

View File

@ -6,7 +6,7 @@ var Gun;
if(typeof window !== 'undefined'){ env = window }
root = env.window? env.window : global;
try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radatatest') }catch(e){}
//try{ indexedDB.deleteDatabase('radatatest') }catch(e){}
if(root.Gun){
root.Gun = root.Gun;
root.Gun.TESTING = true;
@ -15,7 +15,6 @@ var Gun;
try{ require('../../lib/fsrm')('radatatest') }catch(e){}
root.Gun = require('../../gun');
root.Gun.TESTING = true;
//require('../lib/file');
require('../../lib/store');
require('../../lib/rfs');
}
@ -27,15 +26,7 @@ var Gun;
;(function(){
Gun = root.Gun
if(Gun.window && !Gun.window.RindexedDB){ return }
var opt = {};
opt.file = 'radatatest';
var Radisk = (Gun.window && Gun.window.Radisk) || require('../../lib/radisk');
opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))(opt);
opt.chunk = 1000;
var Radix = Radisk.Radix;
var rad = Radisk(opt), esc = String.fromCharCode(27);
var Radix = (Gun.window && Gun.window.Radix) || require('../../lib/radix');
describe('RAD', function(){
@ -53,10 +44,10 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
rad('ab', {yes: 1});
rad('node/circle.bob', 'awesome');
expect(Gun.obj.copy(rad('asdf.'))).to.be.eql({pub: {'': 'yum'}});
expect(JSON.parse(JSON.stringify(rad('asdf.')))).to.be.eql({pub: {'': 'yum'}});
expect(rad('nv/foo.bar')).to.be(undefined);
expect(rad('ab')).to.eql({yes: 1});
expect(Gun.obj.copy(rad())).to.be.eql({"a":{"sdf.pub":{"":"yum"},"b":{"lah":{"":"cool"},"":{"yes":1}}},"node/circle.bob":{"":"awesome"}});
expect(JSON.parse(JSON.stringify(rad()))).to.be.eql({"a":{"sdf.pub":{"":"yum"},"b":{"lah":{"":"cool"},"":{"yes":1}}},"node/circle.bob":{"":"awesome"}});
});
it('radix write read', function(done){
@ -66,11 +57,11 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
all[v] = v;
radix(v, i)
});
expect(Gun.obj.empty(all)).to.not.be.ok();
expect(Object.empty(all)).to.not.be.ok();
Radix.map(radix, function(v,k){
delete all[k];
});
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
});
@ -81,11 +72,11 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
all[v] = v;
//rad(v, i)
});
expect(Gun.obj.empty(all)).to.not.be.ok();
expect(Object.empty(all)).to.not.be.ok();
Radix.map(radix, function(v,k){
delete all[k];
});
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
});
@ -98,12 +89,12 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
all[v] = v;
//rad(v, i)
});
expect(Gun.obj.empty(all)).to.not.be.ok();
expect(Object.empty(all)).to.not.be.ok();
Radix.map(radix, function(v,k, a,b){
//if(!all[k]){ throw "out of range!" }
delete all[k];
}, {start: start, end: end});
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
});
@ -116,12 +107,12 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
all[v] = v;
//rad(v, i)
});
expect(Gun.obj.empty(all)).to.not.be.ok();
expect(Object.empty(all)).to.not.be.ok();
Radix.map(radix, function(v,k, a,b){
//if(!all[k]){ throw "out of range!" }
delete all[k];
}, {start: start, end: end});
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
});
@ -140,9 +131,9 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
r('alice', 1);r('bob', 2);r('carl', 3);r('carlo',4);
r('dave', 5);r('zach',6);r('zachary',7);
var by = ['alice','bob','carl','carlo','dave','zach','zachary'];
Gun.obj.map(by, function(k,i){
/*Object.keys(by).forEach(function(i){ var k = by[i]; console.log(k, i);
r(k,i);
});
});*/
Radix.map(r, function(v,k, a,b){
expect(by.pop()).to.be(k);
tmp = v;
@ -157,6 +148,15 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
});
});
if(Gun.window && !Gun.window.RindexedDB){ return }
var opt = {};
opt.file = 'radatatest';
var Radisk = (Gun.window && Gun.window.Radisk) || require('../../lib/radisk');
opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))(opt);
opt.chunk = 1000;
var rad = Radisk(opt), esc = String.fromCharCode(27);
describe('Radisk', function(){
/*it('parse', function(done){
@ -169,6 +169,15 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
return;
});*/
it('deleting old RAD tests (may take long time)', function(done){
done(); // Mocha doesn't print test until after its done, so show this first.
});
it('deleted', function(done){
this.timeout(60 * 1000);
if(!Gun.window){ return done() }
//await new Promise(function(res){ indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ res() } } );
indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ done() }
});
it('write contacts', function(done){
var all = {}, to, start;
@ -178,30 +187,60 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
rad(v, i, function(err, ok){
expect(err).to.not.be.ok();
delete all[v];
if(!Gun.obj.empty(all)){ return }
if(!Object.empty(all)){ return }
done();
})
})
});
/*it('read contacts reverse', function(done){
it('read contacts reverse', function(done){
var opt = {};
opt.reverse = true;
opt.end = 'nothing';
opt.start = 'marcy';
opt.start = 'keeley';
var first, last;
var all = {}, start = opt.start.toLowerCase(), end = opt.end.toLowerCase();
names.forEach(function(v,i){
v = v.toLowerCase();
if(v < start){ return }
if(end < v){ return }
//console.log(v, i);
all[v] = v;
//rad(v, i)
});
rad('', function(err, data){
console.log("???", err, data);
return;
Radix.map(data, function(v,k){
console.log(k, v);
//delete all[find+k];
//console.log(k, v);
delete all[k];
});
//if(!Gun.obj.empty(all)){ return }
//done();
if(!Object.empty(all)){ return }
done();
}, opt);
});
it('read contacts range', function(done){
var opt = {};
opt.end = 'nothing';
opt.start = 'keeley';
var first, last;
var all = {}, start = opt.start.toLowerCase(), end = opt.end.toLowerCase();
names.forEach(function(v,i){
v = v.toLowerCase();
if(v < start){ return }
if(end < v){ return }
//console.log(v, i);
all[v] = v;
//rad(v, i)
});
rad('', function(err, data){
Radix.map(data, function(v,k){
//console.log(k, v);
delete all[k];
});
if(!Object.empty(all)){ return }
done();
}, opt);
});
console.log("UNDO THIS RETURN!!!");return;*/
it('read contacts start end', function(done){
var opt = {};
@ -219,7 +258,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
//console.log(find+k, v);
delete all[find+k];
});
if(!Gun.obj.empty(all)){ return }
if(!Object.empty(all)){ return }
if(!data){ return } // in case there is "more" that returned empty
done();
}, opt);
@ -237,7 +276,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
Radix.map(data, function(v,k){
delete all[find+k];
});
if(!Gun.obj.empty(all)){ return }
if(!Object.empty(all)){ return }
done();
});
});
@ -252,7 +291,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
Radix.map(data, function(v,k){
delete all[find+k];
});
if(!Gun.obj.empty(all)){ return }
if(!Object.empty(all)){ return }
done();
});
});
@ -270,7 +309,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
});
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.not.be.ok();
expect(Object.empty(all)).to.not.be.ok();
done();
},100);
}, {limit: 1});
@ -280,24 +319,40 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
var ntmp = names;
describe('RAD + GUN', function(){
this.timeout(1000 * 5);
var ochunk = 1000;
Gun.on('opt', function(root){
root.opt.localStorage = false;
Gun.window && console.log("RAD disabling localStorage during tests.");
this.to.next(root);
})
var gun = Gun({chunk: ochunk});
it('write same', function(done){
/*it('deleting old tests (may take long time)', function(done){
done(); // Mocha doesn't print test until after its done, so show this first.
}); it('deleted', function(done){
this.timeout(60 * 1000);
if(!Gun.window){ return done() }
indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ done() }
});*/
/*it('write same', function(done){
var all = {}, to, start, tmp;
var names = [], c = 285;
while(--c){ names.push('bob') }
names.forEach(function(v,i){
all[++i] = true;
tmp = v.toLowerCase();
//console.only.i=1;console.log("save", tmp, v, i);
gun.get('names').get(tmp).put({name: v, age: i}, function(ack){
//console.log("???", ack);
expect(ack.err).to.not.be.ok();
delete all[i];
if(!Gun.obj.empty(all)){ return }
if(!Object.empty(all)){ return }
done();
})
});
});
});*/
it('write contacts', function(done){
var all = {}, to, start, tmp;
@ -307,39 +362,123 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
gun.get('names').get(tmp).put({name: v, age: i}, function(ack){
expect(ack.err).to.not.be.ok();
delete all[i];
if(!Gun.obj.empty(all)){ return }
if(!Object.empty(all)){ return }
done();
})
})
});
it('read one', function(done){
var g = Gun({chunk: ochunk});
//gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
gun.get('names').get('stu').once(function(data, key){
g.get('names').get('stu').once(function(data, key){ // on this chunk setting, Stu should be split between 2 files.
if(done.c){ return } done.c = 1;
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
done();
});
});
it('small range', function(done){
var check = {};
gun.get('users').get('alice').put({cool: 'beans'});
gun.get('users').get('alexander').put({nice: 'beans'});
gun.get('users').get('bob').put({lol: 'beans'});
//console.log("=================");console.only.i=1;
gun.get('users').get({'.': {'*': 'a'}, '%': 1000 * 100}).map().on(function(d,k){
//console.log("small range:", k, d);
expect('a' === k[0]).to.be.ok();
check[k] = d;
if(check.alice && check.alexander){
if(done.c){ return } done.c = 1;
done();
}
});
});
/*it.only('small range once TEST', function(done){
var gun = Gun({file: 'yuio'});
var check = {};
gun.get('people').get('alice').put({cool: 'beans'});
gun.get('people').get('alexander').put({nice: 'beans'});
gun.get('people').get('bob').put({lol: 'beans'});
//setTimeout(function(){
console.only.i=1;
console.log("==================");
console.log("==================");
console.log("==================");
gun.get('people').get({'.': {'*': 'a'}, '%': 1000 * 100}).once().map().once(function(d,k){
console.log("***********", k,d);
expect('a' === k[0]).to.be.ok();
check[k] = d;
if(check.alice && check.alexander){
if(done.c){ return } done.c = 1;
done();
}
});
//},500);
});*/
it('small range once', function(done){
var check = {};
gun.get('people').get('alice').put({cool: 'beans'});
gun.get('people').get('alexander').put({nice: 'beans'});
gun.get('people').get('bob').put({lol: 'beans'});
gun.get('people').get({'.': {'*': 'a'}, '%': 1000 * 100}).once().map().once(function(d,k){
expect('a' === k[0]).to.be.ok();
check[k] = d;
if(check.alice && check.alexander){
if(done.c){ return } done.c = 1;
done();
}
});
});
it('small range twice', function(done){
var check = {};
var gun = Gun();
gun.get('peoplez').get('alice').put({cool: 'beans'});
gun.get('peoplez').get('alexander').put({nice: 'beans'});
gun.get('peoplez').get('bob').put({lol: 'beans'});
gun.get('peoplez').get({'.': {'*': 'a'}, '%': 1000 * 100}).once().map().once(function(d,k){
expect('a' === k[0]).to.be.ok();
check[k] = (check[k] || 0) + 1;
expect(check[k]).to.be(1);
if(check.alice && check.alexander){
if(next.c){ return } next.c = 1;
next();
}
});
function next(){
var neck = {};
gun.get('peoplez').get({'.': {'*': 'a'}, '%': 1000 * 100}).once().map().once(function(d,k){
expect('a' === k[0]).to.be.ok();
neck[k] = d;
if(neck.alice && neck.alexander){
if(done.c){ return } done.c = 1;
done();
}
});
}
});
it('read contacts', function(done){
var all = {}, find = 'm', to;
names.forEach(function(v){
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
//console.log("<<<<<<<<<");
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
expect('m' == key[0]).to.be.ok();
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
},100);
});
//console.log(">>>>>>>>>");
});
it('read contacts again', function(done){
@ -349,14 +488,15 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
if(v.indexOf(find) == 0){ all[v] = true }
});
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
//console.log("*******", key, data, this._.back.get);
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
},100);
},300);
});
});
@ -367,19 +507,26 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
v = v.toLowerCase();
if(v.indexOf(find) == 0){ all[v] = true }
});
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
gun.get('names').map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
delete all[key];
if(!Object.empty(all)){ return }
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
setTimeout(function(){
gun.get('names').get({'.': {'*': find}, '%': 1000 * 100}).once().map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
});
},500);
},100);
});
});
it.skip('read contacts smaller than cursor', function(done){ // TODO!!!
it('read contacts smaller than cursor', function(done){ // TODO!!!
var all = {}, cursor = 'm', to;
names.forEach(function(v){
v = v.toLowerCase();
@ -393,21 +540,24 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(Gun.obj.empty(all)).to.be.ok();
expect(Object.empty(all)).to.be.ok();
done();
},100);
});
});
it.skip('read contacts in descending order', function(done){ // TURN THIS BACK ON AFTER FIX IN-MEMORY ISSUE!
var to;
const filtered = names.filter(v => v.startsWith('M'));
gun.get('names').get({'.': { '*': 'm' }, '%': 1000 * 100, '-': 1}).map().once(function(data){
expect(filtered.pop()).to.be(data.name);
it.skip('read contacts in descending order', function(done){ // TODO!!!
var all = {}, to;
names.forEach(function(v){
all[v] = true;
});
gun.get('names').get({'.': {'-': 1}, '%': 1000 * 100, '-': 1}).once().map().once(function(data, key){
expect(data.name).to.be.ok();
expect(data.age).to.be.ok();
delete all[key];
clearTimeout(to);
to = setTimeout(function(){
expect(filtered.length).to.be(0);
expect(Object.empty(all)).to.be.ok();
done();
},100);
});

View File

@ -5,6 +5,9 @@ const http = require("http");
require("../../lib/promise");
let gunClient, server;
// MOVED TO SEA!!!!!!!
describe("SEA node client auth", () => {
it("should start server", done => {
server = http.createServer().listen(8765, done);
@ -22,7 +25,7 @@ describe("SEA node client auth", () => {
it("should create user", done => {
gunClient.user().create("gun", "password", res => {
console.log({ res });
//console.log({ res });
expect(res.err).to.equal(undefined);
done();
});
@ -46,10 +49,10 @@ describe("SEA node client auth", () => {
});
});
xit("should not stuck on null node", async () => {
it("should not stuck on null node", async () => {
const r1 = await gunClient
.user()
.once(console.log)
//.once(console.log)
.get("test")
.promPut({ z: 1 });

View File

@ -6,7 +6,7 @@ var Gun;
if(typeof window !== 'undefined'){ env = window }
root = env.window? env.window : global;
try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){}
try{ indexedDB.deleteDatabase('radatatest') }catch(e){}
//try{ indexedDB.deleteDatabase('radatatest') }catch(e){}
if(root.Gun){
root.Gun = root.Gun;
root.Gun.TESTING = true;
@ -15,7 +15,6 @@ var Gun;
try{ require('../../lib/fsrm')('radatatest') }catch(e){}
root.Gun = require('../../gun');
root.Gun.TESTING = true;
//require('../lib/file');
require('../../lib/store');
require('../../lib/rfs');
}
@ -27,6 +26,7 @@ var Gun;
}
}(this));
;(function(){
Gun = root.Gun
var SEA = Gun.SEA
@ -36,7 +36,18 @@ describe('SEA', function(){
var user;
var gun;
var pub;
var prep = async function(d,k, n,s){ return {'#':s,'.':k,':': await SEA.opt.parse(d),'>':Gun.state.is(n, k)} }; // shim for old - prep for signing.
var pack = function(d,cb,k, n,s){ return new Promise(function(res, rej){ SEA.opt.pack(d, function(r){ res(r) }, k,n,s) }) }; // make easier to upgrade test, cb to await
describe('Utility', function(){
it('deleting old SEA tests (may take long time)', function(done){
done(); // Mocha doesn't print test until after its done, so show this first.
});
it('deleted', function(done){
this.timeout(60 * 1000);
if(!Gun.window){ return done() }
indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ done() }
});
/*it('generates aeskey from jwk', function(done) { // DEPRECATED!!!
console.log("WARNING: THIS DOES NOT WORK IN BROWSER!!!! NEEDS FIX");
SEA.opt.aeskey('x','x').then(k => {
@ -72,13 +83,12 @@ describe('SEA', function(){
SEA.sign('asdf', alice, function(data){
SEA.verify(data, bob.pub, function(msg){
expect(msg).to.be(undefined);
SEA.verify(data+1, alice.pub, function(msg){
SEA.verify(data.slice(0,20)+data.slice(21), alice.pub, function(msg){
expect(msg).to.be(undefined);
SEA.encrypt('secret', alice, function(enc){
SEA.decrypt(enc, bob, function(dec){
expect(dec).to.be(undefined);
SEA.decrypt(enc+1, alice, function(dec){
SEA.decrypt(enc.slice(0,20)+enc.slice(21), alice, function(dec){
expect(dec).to.be(undefined);
done();
});});});});});});});});
@ -197,14 +207,14 @@ describe('SEA', function(){
expect(dec.priv).to.be(okey.priv);
expect(dec.epriv).to.be(okey.epriv);
var gun = Gun({super: true}), tmp = Gun.node.soul(old);
var gun = Gun({super: true}), tmp = old._['#'];
var graph = {};
graph[tmp] = old;
gun._.graph[tmp] = graph[tmp] = old;
var alias = await SEA.verify(old.alias, false);
expect(alias).to.be('bob');
alias = Gun.state.ify({}, tmp, 1, Gun.val.rel.ify(tmp), tmp = '~@'+alias);
alias = Gun.state.ify({}, tmp, 1, {'#': tmp}, tmp = '~@'+alias);
graph[tmp] = alias;
gun.on('test', {$: gun, put: graph});
gun._.graph[tmp] = alias;//gun.on('test', {$: gun, put: graph});
var use = gun.user();
use.auth('bob', 'test123', function(ack){
expect(ack.err).to.not.be.ok();
@ -218,14 +228,14 @@ describe('SEA', function(){
var old = JSON.parse(atob("eyJfIjp7IiMiOiJ+VThkS0dySFJhX01sMFZ1YlR5OUZBYTlQS1ZlYlh0eTFjS05zWWxnYjduNC5QeVd5cUVVb0ZpYVduUElOV0Nad0xBbzFobjN1MldPWTU3SzZHZnpsNjhVIiwiPiI6eyJwdWIiOjE1NDY5MDI1MDQ5NzksImFsaWFzIjoxNTQ2OTAyNTA0OTc5LCJlcHViIjoxNTQ2OTAyNTA0OTc5LCJhdXRoIjoxNTQ2OTAyNTA0OTc5fX0sInB1YiI6IlU4ZEtHckhSYV9NbDBWdWJUeTlGQWE5UEtWZWJYdHkxY0tOc1lsZ2I3bjQuUHlXeXFFVW9GaWFXblBJTldDWndMQW8xaG4zdTJXT1k1N0s2R2Z6bDY4VSIsImFsaWFzIjoiU0VBe1wibVwiOltcIn5VOGRLR3JIUmFfTWwwVnViVHk5RkFhOVBLVmViWHR5MWNLTnNZbGdiN240LlB5V3lxRVVvRmlhV25QSU5XQ1p3TEFvMWhuM3UyV09ZNTdLNkdmemw2OFVcIixcImFsaWFzXCIsXCJhbGljZVwiLDE1NDY5MDI1MDQ5NzldLFwic1wiOlwienpuaGtIZjhZdFpZM2lGd3FVd0lJUldMTjhZMmlHbmNkcnVTaStGNDNmU1BLYWpSZlI0VzhXVHM4bElSMDBndGJmTWJxS0NjQkpGN3VNSkdGRC9WV2c9PVwifSIsImVwdWIiOiJTRUF7XCJtXCI6W1wiflU4ZEtHckhSYV9NbDBWdWJUeTlGQWE5UEtWZWJYdHkxY0tOc1lsZ2I3bjQuUHlXeXFFVW9GaWFXblBJTldDWndMQW8xaG4zdTJXT1k1N0s2R2Z6bDY4VVwiLFwiZXB1YlwiLFwiRkRzM1VvNTNFZEp6eFNocEpDaVctRGZPQ3lUS0M2U3cxeS1PZVJxam5ZRS5xVGdyYTlFQk1maEpNdVlMVmNaejRZYklLRm85enNBMHpMcV82dEVPMHI0XCIsMTU0NjkwMjUwNDk3OV0sXCJzXCI6XCJPZzRVVjY4OTluSjE4dC9ybWVnV0lkdnNqN01KaEpFc29ranZYQmdteVVRUXVNVjFTdnh4cXJqOFoyV1o2Q25XSkZnTlVDbEVYYWxuMURjUFE3M1R6UT09XCJ9IiwiYXV0aCI6IlNFQXtcIm1cIjpbXCJ+VThkS0dySFJhX01sMFZ1YlR5OUZBYTlQS1ZlYlh0eTFjS05zWWxnYjduNC5QeVd5cUVVb0ZpYVduUElOV0Nad0xBbzFobjN1MldPWTU3SzZHZnpsNjhVXCIsXCJhdXRoXCIsXCJ7XFxcImVrXFxcIjpcXFwiU0VBe1xcXFxcXFwiY3RcXFxcXFxcIjpcXFxcXFxcIi94ZnNPdVNkQUtrNkJiR00zbUV6MnVlSjI3Y0tJNThYMEtUL1FsaExSZXpWcjRkNzVZb2M5QlZNRjkzejl4QXI4N080S2FDNjJUWGVoeERQN0FFa2V4N1paaEpYL2hsVm9kK1FIcVFaaUZMK2lVQzFvL2hpUEJGWElBZmtINGRrcklGOFdqcEVaU3NIVmRSOVRhY2ZzbTB3aHN5NGJXN1ZLSEUySGc9PVxcXFxcXFwiLFxcXFxcXFwiaXZcXFxcXFxcIjpcXFxcXFxcIjhWekduTStEc1lTUktIU3Z4cSszTGc9PVxcXFxcXFwiLFxcXFxcXFwic1xcXFxcXFwiOlxcXFxcXFwibVVSSlJ4TzUvdXM9XFxcXFxcXCJ9XFxcIixcXFwic1xcXCI6XFxcImE1SlA3VFpuVE9jYjEwMGJOejlscEU4dnpqcUE3TWl0NHcwN3pjQTdIOFV0bml1WnVHSmdpZnNNQlFNSGdRdE5cXFwifVwiLDE1NDY5MDI1MDQ5NzldLFwic1wiOlwiSGFzMytJaHFEZTYyN016cElXZVE1cVFrZ2NOMlk3WHRpNGw0TFU3T2JyaktxSlBnSllrVWE2bk9YdlRmQkFzV1BPVzVnemh4Q2RPVGNFQm5icWlpWXc9PVwifSJ9"));
var okey = {"pub":"U8dKGrHRa_Ml0VubTy9FAa9PKVebXty1cKNsYlgb7n4.PyWyqEUoFiaWnPINWCZwLAo1hn3u2WOY57K6Gfzl68U","epub":"FDs3Uo53EdJzxShpJCiW-DfOCyTKC6Sw1y-OeRqjnYE.qTgra9EBMfhJMuYLVcZz4YbIKFo9zsA0zLq_6tEO0r4","priv":"jMy7WfcldJ4esZEijAj4LTb99smtY_H0yKJLemJl2HI","epriv":"1DszMh-85pGTPLYtRunG-Q-xB78AE4k07PPkbedYYwk"}
var gun = Gun({super: true}), tmp = Gun.node.soul(old);
var gun = Gun({super: true}), tmp = old._['#'];//Gun.node.soul(old);
var graph = {};
graph[tmp] = old;
gun._.graph[tmp] = graph[tmp] = old;
var alias = SEA.opt.unpack(await SEA.verify(old.alias, false), 'alias', old);
expect(alias).to.be('alice');
alias = Gun.state.ify({}, tmp, 1, Gun.val.rel.ify(tmp), tmp = '~@'+alias);
graph[tmp] = alias;
gun.on('test', {$: gun, put: graph});
alias = Gun.state.ify({}, tmp, 1, {'#': tmp}, tmp = '~@'+alias);
gun._.graph[tmp] = graph[tmp] = alias;
//gun.on('test', {$: gun, put: graph});
var use = gun.user();
use.auth('alice', 'test123', function(ack){
expect(ack.err).to.not.be.ok();
@ -233,20 +243,20 @@ describe('SEA', function(){
});
}())})
it('JSON escape', function(done){
it('JSON escape', function(done){ (async function(){
var plain = "hello world";
var json = JSON.stringify({hello:'world'});
var n1 = Gun.state.ify({}, 'key', 1, plain, 'soul');
var n2 = Gun.state.ify({}, 'key', 1, json, 'soul');
var tmp = SEA.opt.prep(plain, 'key', n1, 'soul');
var tmp = await prep(plain, 'key', n1, 'soul');
expect(tmp[':']).to.be("hello world");
tmp = SEA.opt.prep(json, 'key', n2, 'soul');
tmp = await prep(json, 'key', n2, 'soul');
expect(tmp[':'].hello).to.be("world");
tmp = SEA.opt.unpack(tmp);
expect(tmp.hello).to.be("world");
done();
});
}())});
it('double sign', function(done){ (async function(){
var pair = await SEA.pair();
@ -256,15 +266,15 @@ describe('SEA', function(){
var json = JSON.stringify({hello:'world'});
var n1 = Gun.state.ify({}, 'key', 1, json, 'soul');
var sig = await SEA.sign(SEA.opt.prep(json, 'key', n1, 'soul'), pair, null, {raw:1 , check: SEA.opt.pack(json, 'key', n1, 'soul')});
var dup = await SEA.sign(SEA.opt.prep(sig, 'key', n1, 'soul'), pair, null, {raw:1 , check: SEA.opt.pack(sig, 'key', n1, 'soul')});
var sig = await SEA.sign(await prep(json, 'key', n1, 'soul'), pair, null, {raw:1 , check: await pack(json, 'key', n1, 'soul')});
var dup = await SEA.sign(await prep(sig, 'key', n1, 'soul'), pair, null, {raw:1 , check: await pack(sig, 'key', n1, 'soul')});
expect(dup).to.be.eql(sig);
var json = JSON.stringify({hello:'world'});
var n1 = Gun.state.ify({}, 'key', 1, json, 'soul');
var bob = await SEA.pair();
var sig = await SEA.sign(SEA.opt.prep(json, 'key', n1, 'soul'), bob, null, {raw:1 , check: SEA.opt.pack(json, 'key', n1, 'soul')});
var dup = await SEA.sign(SEA.opt.prep(sig, 'key', n1, 'soul'), pair, null, {raw:1 , check: SEA.opt.pack(sig, 'key', n1, 'soul')});
var sig = await SEA.sign(await prep(json, 'key', n1, 'soul'), bob, null, {raw:1 , check: await pack(json, 'key', n1, 'soul')});
var dup = await SEA.sign(await prep(sig, 'key', n1, 'soul'), pair, null, {raw:1 , check: await pack(sig, 'key', n1, 'soul')});
expect(dup).to.not.be.eql(sig);
var json = JSON.stringify({hello:'world'});
@ -286,8 +296,8 @@ describe('SEA', function(){
SEA.pair(function(p){
user.is = user._.sea = p;
gtmp = gid = 'test~'+p.pub;
g.get(gid).put({yo: 'hi'}, function(ack){
var data = SEA.opt.parse(g._.graph[gid].yo);
g.get(gid).put({yo: 'hi'}, async function(ack){
var data = await SEA.opt.parse(g._.graph[gid].yo);
expect(data[':']).to.be('hi');
expect(data['~']).to.be.ok();
g.get(gid).get('yo').once(function(r){
@ -306,7 +316,7 @@ describe('SEA', function(){
})
it('register users', function(done){
user.create('carl', 'test123', function(ack){
user.create('carl', 'testing123', function(ack){
pub = '~'+ack.pub;
expect(ack.err).to.not.be.ok();
done();
@ -314,7 +324,7 @@ describe('SEA', function(){
});
it('login users', function(done){
user.auth('carl', 'test123', function(ack){
user.auth('carl', 'testing123', function(ack){
expect(ack.err).to.not.be.ok();
done()
})
@ -326,7 +336,6 @@ describe('SEA', function(){
done();
});
})
it('read data', function(done){
user.get('a').get('b').once(function(data){
expect(data).to.be(0);
@ -368,7 +377,7 @@ describe('SEA', function(){
setTimeout(function(){
gun = Gun();
user = gun.user();
user.auth('carl', 'test123', function(ack){
user.auth('carl', 'testing123', function(ack){
expect(ack.err).to.not.be.ok();
done()
})
@ -405,7 +414,7 @@ describe('SEA', function(){
})
});
it('set user ref null override', function(done){
it('set user ref null override', function foo(done){
this.timeout(9000);
var gun = Gun();
//user.leave();
@ -413,23 +422,28 @@ describe('SEA', function(){
var msg = {what: 'hello world'};
user.create('xavier', 'password');
gun.on('auth', function(){
//console.log(1);
if(done.a){ return } done.a = 1;
var ref = user.get('who').get('all').set(msg);
var ref = user.get('who').get('all').set(msg, A);
var stub = user.get('stub').put({});
var tmp = ref._.dub || ref._.link;
setTimeout(function(){
user.get('who').put(stub);
setTimeout(function(){
user.get('who').get('all').get(tmp).put({boom: 'ah'});
setTimeout(function(){
function A(){
//console.log(2);
user.get('who').put(stub, B);
function B(){
//console.log(3);
var tmp = ref._.has || ref._.soul;
user.get('who').get('all').get(tmp).put({boom: 'ah'}, C);
function C(){
//console.log(4);
user.get('who').get('all').map().once(function(data){
//console.log(5);
expect(data).to.be.ok();
expect(data.what).to.not.be.ok();
done();
});
},9);
},9);
},9);
}
}
};
});
});
@ -439,11 +453,12 @@ describe('SEA', function(){
this.timeout(9000);
var gun = Gun();
var user = gun.user();
user.auth('xavier', 'password');
user.create('xavier2', 'password2');
gun.on('auth', function(){
user.get("testauthed").get("arumf").set({"this": "is", "an": {"obj2": "again2"}}, function(ack) {
var notsigned = [];
Gun.obj.map(gun._.graph, function(v,k) {
//Gun.obj.map(gun._.graph, function(v,k) {
Object.keys(gun._.graph).forEach(function(k,v){ v = gun._.graph[k];
if (k[0]==='~' || k.indexOf('~', 1)!==-1) { return; } /// ignore '~pubkey' and '~@alias'
notsigned.push(k);
});
@ -467,7 +482,6 @@ describe('SEA', function(){
if(done.c){ return } done.c = 1;
var g = gun._.graph;
var p = '~'+alice.pub+'/';
console.log(1);
//console.log(p, g);
expect(g[p+'z']).to.be.ok();
expect(g[p+'z/y']).to.be.ok();
@ -482,21 +496,34 @@ describe('SEA', function(){
it('user mix', function(done){
var gun = Gun();
gun.on('auth', function(){
gun.on('auth', async function(){
if(done.a){ return } done.a = 1;
var ref = gun.user().get('zasdf').put({a: 9});
var at = gun.user().get('zfdsa').get('y').get('x').get('c').put(ref);
at.get('foo').get('bar').put('yay');
ref.get('foo').get('ah').put(1, function(){setTimeout(function(){
var c = 0, go = function(){ check(++c) }
var ref = gun.user().get('zasdf').put({a: 9}, go);
//ref._.REF = 'ref!';
//console.only.i=1;console.log("=================");
var at = gun.user().get('zfdsa').get('y').get('x').get('c').put(ref, go);
//ref._.DAT = 'dat!';
at.get('foo').get('bar').put('yay', go);
ref.get('foo').get('ah').put(1, go);
function check(){
if(c !== 4){ return }
setTimeout(function(){
if(done.c){ return } done.c = 1;
var g = gun._.graph;
var p = '~'+alice.pub+'/';
//console.log(p, g);
console.log(2);
expect(Object.keys(g[p+'zasdf']).sort()).to.be.eql(['_', 'a', 'foo'].sort());
expect(Object.keys(g[p+'zasdf/foo']).sort()).to.be.eql(['_', 'bar', 'ah'].sort());
var p = '~'+alice.pub;
//console.log(g);
expect(Object.keys(g[p]).sort()).to.be.eql(['_', 'zasdf', 'zfdsa'].sort());
expect(Object.keys(g[p+'/zasdf']).sort()).to.be.eql(['_', 'a', 'foo'].sort());
expect(Object.keys(g[p+'/zasdf/foo']).sort()).to.be.eql(['_', 'bar', 'ah'].sort());
expect(Object.keys(g[p+'/zfdsa']).sort()).to.be.eql(['_', 'y'].sort());
expect(Object.keys(g[p+'/zfdsa/y']).sort()).to.be.eql(['_', 'x'].sort());
expect(Object.keys(g[p+'/zfdsa/y/x']).sort()).to.be.eql(['_', 'c'].sort());
expect(g[p+'/zfdsa'].y.indexOf('/zfdsa/y"') > 0).to.be.ok();
expect(g[p+'/zfdsa/y'].x.indexOf('/zfdsa/y/x"') > 0).to.be.ok();
expect(g[p+'/zfdsa/y/x'].c.indexOf('/zasdf"') > 0).to.be.ok();
done();
},200)});
},100)};
});
gun.user().auth(alice);
});
@ -513,8 +540,7 @@ describe('SEA', function(){
if(done.c){ return } done.c = 1;
var g = gun._.graph;
var p = '~'+alice.pub+'/';
console.log(3);
console.log(p, Object.keys(g[p+'pchat/their.pub/2020']||{}).sort());
//console.log(p, Object.keys(g[p+'pchat/their.pub/2020']||{}).sort());
expect(Object.keys(g[p+'pchat/their.pub/2020']).sort()).to.be.eql(['_', 'msg'].sort());
expect(g[p+'2020']).to.not.be.ok();
done();
@ -523,169 +549,198 @@ describe('SEA', function(){
gun.user().auth(alice);
});
});
it('Certify: Simple', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var dave = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, () => {
// Bob reads
gun.get("~" + alice.pub)
describe('CERTIFY', function () {
var gun = Gun()
var user = gun.user()
it('Certify: Simple', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var dave = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice)
user.leave()
user.auth(bob, () => {
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
user.leave()
// everyone reads
.get("qwerty")
.put(data, () => {
// Bob reads
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
user.auth(dave, () => {
// Dave reads
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
done()
user.leave()
// everyone reads
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
user.auth(dave, () => {
// Dave reads
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty").once(_data=>{
expect(_data).to.be(data)
user.leave()
done()
})
})
})
})
})
}, { opt: { cert } })
})
}())})
}, { opt: { cert } })
})
}())})
it('Certify: Attack', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("wrongway")
.get("asdf")
.get("qwerty")
.put(data, ack => {
expect(ack.err).to.be.ok()
done()
}, { opt: { cert } })
})
}())})
it('Certify: Attack', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice);
user.leave()
user.auth(bob, () => {
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("wrongway")
.get("asdf")
.get("qwerty")
.put(data, ack => {
expect(ack.err).to.be.ok()
user.leave()
done()
}, { opt: { cert } })
})
}())})
it('Certify: Public inbox', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify('*', [{"*": "test", "+": "*"}, {"*": "inbox", "+": "*"}], alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("inbox")
.get(user.is.pub)
.put(data, ack => {
expect(ack.err).to.not.be.ok()
done()
}, { opt: { cert } })
})
}())})
it('Certify: Expiry', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice, null, {
expiry: Gun.state() - 100, // expired 100 miliseconds ago
})
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, ack => {
expect(ack.err).to.be.ok()
done()
}, { opt: { cert } })
})
}())})
it('Certify: Path or Key must contain Certificant Pub', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private", "+": "*"}, alice)
user.auth(bob, () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get('wrongway')
.put(data, ack => {
expect(ack.err).to.be.ok()
gun.get("~" + alice.pub)
.get("private")
.get(bob.pub)
.get('today')
it('Certify: Public inbox', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify('*', [{"*": "test", "+": "*"}, {"*": "inbox", "+": "*"}], alice)
user.leave()
user.auth(bob, () => {
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("inbox")
.get(user.is.pub)
.put(data, ack => {
expect(ack.err).to.not.be.ok()
user.leave()
done()
}, { opt: { cert } })
})
}())});
it('Certify: Expiry', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice, null, {
expiry: Gun.state() - 100, // expired 100 miliseconds ago
})
user.leave()
user.auth(bob, () => {
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, ack => {
expect(ack.err).to.be.ok()
user.leave()
done()
}, { opt: { cert } })
})
}())})
it('Certify: Path or Key must contain Certificant Pub', function(done){(async function(){
var alice = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private", "+": "*"}, alice)
user.leave()
user.auth(bob, () => {
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("private")
.get('wrongway')
.put(data, ack => {
expect(ack.err).to.be.ok()
gun.get("~" + alice.pub)
.get("private")
.get(bob.pub)
.get('today')
.once(_data => {
expect(_data).to.be(data)
done()
})
}, { opt: { cert } })
}, { opt: { cert } })
})
}())})
it('Certify: Advanced - Blacklist', function(done){(async function(){
var alice = await SEA.pair()
var dave = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice, null, {
expiry: Gun.state() + 5000, // expires in 5 seconds
blacklist: 'blacklist' // path to blacklist in Alice's graph
})
// Alice points her blacklist to Dave's graph
user.auth(alice, async () => {
await user.get('blacklist').put({'#': '~'+dave.pub+'/blacklist'})
await user.leave()
// Dave logins, he adds Bob to his blacklist, which is connected to the certificate that Alice issued for Bob
user.auth(dave, async () => {
await user.get('blacklist').get(bob.pub).put(true)
await user.leave()
// Bob logins and tries to hack Alice
user.auth(bob, async () => {
var data = Gun.state.lex()
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, ack => {
expect(ack.err).to.be.ok()
done()
expect(ack.err).to.not.be.ok()
gun.get("~" + alice.pub)
.get("private")
.get(bob.pub)
.get('today')
.once(_data => {
expect(_data).to.be(data)
user.leave();
done()
})
}, { opt: { cert } })
}, { opt: { cert } })
})
}())})
it.skip('Certify: Advanced - Blacklist', function(done){(async function(){
var alice = await SEA.pair()
var dave = await SEA.pair()
var bob = await SEA.pair()
var cert = await SEA.certify(bob, {"*": "private"}, alice, null, {
expiry: Gun.state() + 5000, // expires in 5 seconds
blacklist: 'blacklist' // path to blacklist in Alice's graph
})
console.log(111111);
// Alice points her blacklist to Dave's graph
user.leave()
user.auth(alice, async () => {
console.log("meeeeoooooow");
var ref = gun.get('~'+dave.pub+'/blacklist');
await user.get('blacklist').put(ref);
user.leave()
console.log(2222222);
// Dave logins, he adds Bob to his blacklist, which is connected to the certificate that Alice issued for Bob
user.auth(dave, async () => {
await user.get('blacklist').get(bob.pub).put(true)
user.leave()
console.log(333333);
// Bob logins and tries to hack Alice
user.auth(bob, async () => {
console.log(4444444);
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, ack => {
console.log(555555);
expect(ack.err).to.be.ok()
user.leave()
done()
}, { opt: { cert } })
})
})
})
})
}())})
}())})
});
describe('node', function(){
var u;
if(''+u === typeof process){ return }
console.log("REMEMBER TO RUN mocha test/sea/nodeauth !!!!");
});
});
})

166
test/trace.html Normal file
View File

@ -0,0 +1,166 @@
<html>
<head>
<title>Gun Msg Trace</title>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0 0;
font-family: monospace;
padding: 10px 15px;
color: white;
}
body {
background: rgba(0,0,0,0.7);
}
#trace {
}
#editor {
width: 90%;
height: 90%;
background: gray;
}
#diagram {
/* width: 100%; */
/* height: 100%; */
margin: 0 auto;
/* background: #d2d2d2; */
/* position: absolute; */
top:0;
left:0;
}
/*#diagram svg {
width: 100%;
height: 100%;
}*/
#diagram svg text {
fill: #79ce7f;
f-ill: black;
f-ill: red;
}
#diagram svg text:hover {
fill: #20ff3b;
cursor: pointer;
}
#diagram svg .note rect, #diagram svg .note path {
fill: #666666;
}
#diagram svg .title rect,
#diagram svg .title path,
#diagram svg .actor rect,
#diagram svg .actor path {
fill: #ffffff;
position: fixed;
top: 1em;
}
#diagram svg .actor {
opacity: 0;
}
#diagram svg .actor text {
fill: #000000;
}
#diagram svg .actor line {
fill: #000000;
stroke-width: 5px;
}
#diagram svg line {
stroke: rgba(0,0,0,0.5);
/* stroke-width: 5px; */
}
.method {
font-size: 140%;
display: block;
background: black;
color: white;
padding: 0.3em;
top: 7em;
}
</style>
</head>
<body>
<div id="trace">
<h1>Gun Msg Trace</h1>
<h2></h2>
<div id="diagram"></div>
</div>
<script src="../gun.js"></script>
<script src="../sea.js"></script>
<script src="./trace.js"></script>
<script src="../examples/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/gh/bramp/js-sequence-diagrams/test/webfont-mock.js"></script>
<script src="https://cdn.jsdelivr.net/npm/snapsvg@0.5.1/dist/snap.svg.min.js"></script>
<script src="https://pagecdn.io/lib/underscore/1.11.0/underscore.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/bramp/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
<script>
;(function(){
// OVERLOAD GUN FUNCTIONS THAT WE WANT TO TRACE:
/// GUN protocolo
var _get = Gun.on._get; Gun.on._get = function(a,b,c,d,e){ Trace.log('GET', a); _get.call(this, a,b,c,d,e) } /// PROTOCOL
var _put = Gun.on.put; Gun.on.put = function(a,b,c,d,e){ Trace.log('PUT', a); _put.call(this, a,b,c,d,e) } /// PROTOCOL
/// GUN chain
// var get = Gun.chain.get; Gun.chain.get = function(a,b,c,d,e){ Trace.log('.get', a); return get.call(this, a,b,c,d,e) } /// chain
// var put = Gun.chain.put; Gun.chain.put = function(a,b,c,d,e){ Trace.log('.put', a); return put.call(this, a,b,c,d,e) } /// chain
// var map = Gun.chain.map; Gun.chain.map = function(a,b,c,d,e){ Trace.log('.map', a); return map.call(this, a,b,c,d,e) } /// chain
// var once = Gun.chain.once; Gun.chain.once = function(a,b,c,d,e){ Trace.log('.once', a); return once.call(this, a,b,c,d,e) } /// chain
// var on = Gun.chain.on; Gun.chain.on = function(a,b,c,d,e){ Trace.log('.on', a); return on.call(this, a,b,c,d,e) } /// chain
/// GUN events
var input = Gun.on.in; Gun.on.in = function(a,b,c,d,e){ Trace.log('in', a); input.call(this, a,b,c,d,e) } /// EVENT
var output = Gun.on.out; Gun.on.out = function(a,b,c,d,e){ Trace.log('out', a); output.call(this, a,b,c,d,e) } /// EVENT
// var only = console.only; console.only = function(a,b,c,d,e,f,g){ Trace.log('ONLY'); return only.apply(console,arguments) }
// var start = +new Date;
// setTimeout(function log(){
// if((+new Date - start) > 100){ return }
// setTimeout(log, 0);
// Trace.log("ASYNC");
// },0);
}());
;(function(){ // PASTE YOUR UNIT TEST INTO HERE TO TRACE IT!
// if (typeof localStorage!=='undefined') { localStorage.clear(); }
// var goff = Gun();
// Gun.statedisk = function(o,s,cb){ goff.get(s).put(o, cb, {turn: function(fn){fn()}}); };
var gun = Gun();
var bob = {age: 29, name: "Bob!"};
var cat = {name: "Fluffy", species: "kitty"};
var user = {bob: bob};
bob.pet = cat;
cat.slave = bob;
Trace.log('START');
// Gun.statedisk(user, 'nodecircle', function(){
console.only.i=1;console.log("=============", gun);
// gun.get('nodecircle').put(user, function(ack) { Trace.log('ACK', ack); });
// gun.get('nodecircle').on(function(ack) { Trace.log('END', ack); });
gun.get('nodecircle').once(function(ack) { Trace.log('END', ack); });
// gun.get('nodecircle').get('a').get('b').get('c').on(function(v,k){
// gun.get('nodecircle').get('bob').on(function(v,k){
// gun.get('nodecircle').get('bob').get('pet').get('slave').once(function(v,k){
// });
}());
// Gun.on('trace.end', function(msg){
setTimeout(function(){
console.log('_____TOTAL LOGS: ',Trace.traces.length, new Date());
var id, code = Trace.traces.join('\n');
$('h2:first').text(`Total of steps: ${Trace.traces.length}`);
$('title:first').text(`(${Trace.traces.length}) steps | Gun Msg Trace`);
var diagram = Diagram.parse(code);
diagram.drawSVG(id||'diagram', {theme: 'simple'});
setTimeout(function(){
$('.actor').each(function(){
var pos = $(this).offset();
$("<span class='method'>").text($(this).text()).css({position: 'fixed', left: pos.left}).appendTo('body');
});
});
}, 2000);
</script>
</body>
</html>

63
test/trace.js Normal file
View File

@ -0,0 +1,63 @@
// @rogowski CAME UP WITH THIS BRILLIANT GENIUS ABSOLUTELY AMAZING AWESOME IDEA!!!!
/// This is a simple tool to generate logs in a Sequence Diagram compatible format.
var Trace = function() {}
Trace.traces=[];
Trace.log = function(evname, msg) {
//if(!(console.only.i)){ return }
// clearTimeout(Trace.log.to);
// Trace.log.to = setTimeout(function(){
// Trace.on('trace.end', {code: Trace.traces.join('\n')});
// }, 1000);
if (!msg) {
//console.log('WARN, empty message: ',msg);
// msg=evname;
// evname = 'GUN';
}
if (!Trace.loglastev) {
Trace.loglastev=evname||'GET';
return;
};
// msg.lastev = Trace.loglastev;
Trace.log.i = Trace.log.i ? ++Trace.log.i : 1;
//console.log(`*(${Trace.log.i}) ${Trace.loglastev}->:%s, msg:`, evname, msg);
var __ = (!msg||!msg['#'] ? '' : ('#'+msg['#']).slice(0,4)+'');
var dam = (!msg||!msg.dam ? '' : ('dam:'+msg.dam));
var at = (!msg||!msg['@'] ? '' : ('@'+msg['@']).slice(0,4)+'');
var lS = (!msg||!msg.lS ? '' : ('lS:'+msg.lS));
var id = (!msg||!msg.id ? '' : ('id:'+msg.id));
var ram = (!msg||!msg.ram ? '' : ('ram:'+msg.ram));
var get = (!msg||!msg.get ? '' : ('get:'+Trace.clean(msg.get)).slice(0,15)+'');
var put = !(typeof msg === 'object' && ('put' in msg)) ? '' : ('put:'+Trace.clean(msg&&msg.put?msg.put:'')).slice(0,30)+'...';
// Trace.loglastevdt Trace.loglastev;
// Trace.loglastevdt = +new Date();
var tm = +new Date();
Trace.loglastevdt = +new Date();
// if (dam && Trace.loglastev==='UNIVERSE') { evname='GET'; }
var keys = Array.isArray(msg) ? keys.sort().join(',')
: typeof msg==='object' ? Object.keys(msg).sort().join(',')
: 'this';
if (dam) {
if (msg['#']) {
evname='GUN';
} else {
Trace.loglastev='GUN';
}
Trace.traces.push(`${Trace.loglastev}->${evname}: (${Trace.log.i}) {${keys}} ${get} ${put} ${__} ${dam} ${ram} ${id} ${at} ${lS}`);
} else {
//Trace.traces.push(`${Trace.loglastev}->${evname}: (${Trace.log.i}) {${keys.slice(0,15)}} ${get} ${put} ${__} ${dam} ${ram} ${id} ${at} ${lS}`);
// Trace.traces.push(Trace.loglastev+'->'+evname+': '+(Trace.log.i+') '+(console.only.i||''))+' '+__+' '+at);
Trace.traces.push(Trace.loglastev+'->'+evname+': '+ Trace.log.i+') '+__+' '+at);
// Trace.traces.push(`${Trace.loglastev}->${evname}: (${Trace.log.i}) {${!keys?'this':keys}}`);
}
Trace.loglastev = evname;
Trace.loglastevdt = tm;
};
Trace.clean = function(txt) { return JSON.stringify(typeof txt==='undefined' ? 'undef' : txt||null).replace(/"|\{|\}+/g,'').slice(0,20).trim(); };