Master into Deploys (#1391)

* Thank you Murage Martin @murageyun for donating!!!

* Fix opt.s3.fakes3 parsing issue (#1318)

* Fix opt.s3.fakes3 parsing issue

* Fix second typo within if block

* Support variable number of auth retry attempts through opt.retries (#1325)

Maintain default to 9 to ensure backwards compatibility

* Thanks Jason Stallings @octalmage !!!

* Remove unused imports (#1337)

* Update README.md

* yay format change

* encode objects

* WS ws.path fix  (#1343)

* Update wire.js

* Update wire.js

* Update wire.js

* add one click deploy to readme (#1342)

* update src/index (#1254)

* update src/index

* update

* src/index fix

* added src/core

* is ??? this a MVP of book & rad ???? thanks to @rogowski

* book & rad APIs stabilizing

* RAD & Book promoted! + buggy example: test/rad/book.html

* bump path

* cleaned up Book results & sorting & caching

* sea blobs! (#1353)

* sea blobs!

* and null origins

* null fix

* null check is last

* add a way to select stats file from url (#1351)

* react-native detection, and load needed shims (#1349)

* react-native detection

* added lib mobile

* changed back to gun. for another solution

* have unbuild function wrap to prevent scope leaks & allow RETURN hehehe so I can reject @bmatusiak 's lS change O:) O:) I love you you're a hero!

later with @bmatusiak check sea.then for '../gun.js' vs '../' vs ...
note: src/index -> core.js
TODO: something about WebRTC candidates hitting ack decrement limits?

* quick-fix (#1355)

* Fix SEA certificate verification, allow multiple pubs (#1358)

* Create SECURITY.md (#1364)

* ... works (#1357)

* Loading fix (#1356)

* does this load better

* check window.Gun too in rfs

* update SECURITY.md file and change the versions to 0.2020.x (#1365)

* webrtc accept getUserMedia streams as peer

* Check atom exists in graph when deciding to read from disk (#1371)

* fix: ERROR: Radisk needs `store.put` interface (#1374)

* Update STUN servers (#1381)

Commented out sipgate.net STUN server.
Added Cloudflare STUN server.

* universal notification system

---------

Co-authored-by: ritchia1 <andrew.ritchie@estimateone.com>
Co-authored-by: Anton <dev@atjn.dk>
Co-authored-by: Bradley Matusiak <bmatusiak@gmail.com>
Co-authored-by: Jay Byoun <jay8061@pm.me>
Co-authored-by: mimiza <dev@mimiza.com>
Co-authored-by: Simardeep Singh <1003simar@gmail.com>
Co-authored-by: Malcolm Blaney <mblaney@gmail.com>
Co-authored-by: Andreas Heissenberger <andreas@heissenberger.at>
Co-authored-by: carlin978 <120719190+carlin978@users.noreply.github.com>
This commit is contained in:
Mark Nadal 2024-11-23 17:52:28 -08:00 committed by GitHub
parent 4c01db9a67
commit b1b408971d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 1259 additions and 606 deletions

View File

@ -1,6 +1,6 @@
<p id="readme"><a href="https://gun.eco/"><img width="40%" src="https://cldup.com/TEy9yGh45l.svg"/></a><img width="50%" align="right" vspace="25" src="https://gun.eco/see/demo.gif"/></p>
[![](https://data.jsdelivr.com/v1/package/gh/amark/gun/badge?style=rounded)](https://data.jsdelivr.com/v1/package/gh/amark/gun/stats)
[![](https://data.jsdelivr.com/v1/package/npm/gun/badge)](https://www.jsdelivr.com/package/npm/gun)
![Build](https://github.com/amark/gun/actions/workflows/ci.yml/badge.svg)
[![Gitter](https://img.shields.io/gitter/room/amark/gun.js.svg)](http://chat.gun.eco)
@ -42,7 +42,7 @@ GUN is *super easy* to get started with:
> **Note:** If you don't have [node](http://nodejs.org/) or [npm](https://www.npmjs.com/), read [this](https://github.com/amark/gun/blob/master/examples/install.sh) first.
> If the `npm` command line didn't work, you may need to `mkdir node_modules` first or use `sudo`.
- An online demo of the examples are available here: http://gunjs.herokuapp.com/
- An online demo of the examples are available here: http://try.axe.eco/
- Or write a quick app: ([try now in a playground](https://jsbin.com/kadobamevo/edit?js,console))
```html
<script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
@ -156,7 +156,8 @@ Thanks to:
<a href="https://github.com/JacobMillner">Jacob Millner</a>,
<a href="https://github.com/b-lack">Gerrit Balindt</a>,
<a href="https://github.com/gabriellemon">Gabriel Lemon</a>,
<a href="https://github.com/murageyun">Murage Martin</a>
<a href="https://github.com/murageyun">Murage Martin</a>,
<a href="https://github.com/octalmage">Jason Stallings</a>
</p>
- Join others in sponsoring code: https://www.patreon.com/gunDB !
@ -260,6 +261,11 @@ You can now safely `CTRL+A+D` to escape without stopping the peer. To stop every
Environment variables may need to be set like `export HTTPS_CERT=~/cert.pem HTTPS_KEY=~/key.pem PORT=443`. You can also look at a sample [nginx](https://gun.eco/docs/nginx) config. For production deployments, you probably will want to use something like `pm2` or better to keep the peer alive after machine reboots.
### [Dome](https://www.trydome.io/)
[Deploy GUN in one-click](https://app.trydome.io/signup?package=gun) with [Dome](https://trydome.io) and receive a free trial:
[![Deploy to Dome](https://trydome.io/button.svg)](https://app.trydome.io/signup?package=gun)
### [Heroku](https://www.heroku.com/)
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/amark/gun)

47
SECURITY.md Normal file
View File

@ -0,0 +1,47 @@
# Security Policy
## Introduction
Security is our top priority. We are committed to ensuring that our project is as secure as possible for everyone who uses it. This document outlines our security policy and procedures for dealing with security issues.
## Supported Versions
We provide security updates for the following versions of our project:
| Version | Supported |
| ------- | ------------------ |
| 0.2020.x| :white_check_mark: |
| < 0.2020| :x: |
## Reporting a Vulnerability
If you discover a vulnerability, we would like to know about it so we can take steps to address it as quickly as possible.
### Report Format
When reporting vulnerabilities, please include the following details:
- Description of the vulnerability
- Steps to reproduce the issue
- Potential impact if left unaddressed
- Suggested mitigation or resolution if any
### Response Time
We aim to confirm the receipt of your vulnerability report within 48 hours. Depending on the severity and complexity of the issue, we strive to investigate the issue and provide an initial response within a week.
### Disclosure Policy
If the vulnerability is confirmed, we will work on a fix and plan a release. We ask that you do not publicly disclose the issue until it has been addressed by us.
## Security Practices
We follow industry-standard security practices, including regular audits of the services and features we provide, to maintain the trust of our users.
## Security Updates
We will communicate any security updates through our standard communication channels, including our project's release notes and official website.
## Conclusion
We greatly value the work of security researchers and believe that responsible disclosure of vulnerabilities is a valuable contribution to the security of the Internet. We encourage users to contribute to the security of our project by reporting any security-related issues to us.

View File

@ -1 +1,4 @@
module.exports = require('./gun.js')
// if(!(typeof navigator == "undefined") && navigator.product == "ReactNative"){
// require("./lib/mobile.js");
// }
module.exports = require('./gun.js');

38
examples/basic/meet.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<center>must press play or unmute on new videos to accept meeting</center>
<center id="videos">
<video id="me" width="100%" controls autoplay playsinline muted></video>
</center>
<center>Stream <select id="select"><option id="from">from</option></select></center>
<script src="../jquery.js"></script>
<script src="../../../gun/gun.js"></script>
<script src="../../../gun/sea.js"></script>
<script src="../../../gun/lib/webrtc.js"></script>
<script>;(async function(){
streams = {}, gun = Gun(location.origin + '/gun'); //gun = GUN();
mesh = gun.back('opt.mesh');
(await (me.stream = navigator.mediaDevices).enumerateDevices()).forEach((device,i) => {
if('videoinput' !== device.kind){ return }
var opt = $(from).clone().prependTo('select').get(0);
$(opt).text(opt.id = device.label || 'Camera '+i);
opt.value = device.deviceId;
});
$('select').on('change', async eve => { $(from).text('Off'); // update label
if('Off' == select.value){ return me.srcObject.getTracks()[0].stop() }
mesh.hi(me.srcObject = await me.stream.getUserMedia({ audio: true,
video: (select.value && {deviceId: {exact: select.value}}) || {facingMode: "environment"}
}));
});
gun.on('rtc', async function(eve){ var ui, src;
console.log("?RTC?", eve.peer && eve.peer.connectionState, eve);
if(!(src = eve.streams)){ return }
ui = $('#v'+(src=src[0]).id).get(0) || $(me).clone().attr('id', 'v'+src.id).prependTo('#videos').get(0); // reuse or create video element
ui.srcObject = src;
});
}());</script>

View File

@ -44,6 +44,7 @@
<script src="./jquery.js"></script>
<script src="./smoothie.js" charset="utf-8"></script>
<script>
if(window.location.search){url.value = window.location.search.split("?")[1]}
var up, br = 0, bt = 0, tmp;
var fetchData = async function(){
// fetch the data from server

253
gun.js
View File

@ -136,12 +136,228 @@
};
})(USE, './onto');
;USE(function(module){
// TODO: BUG! Unbuild will make these globals... CHANGE unbuild to wrap files in a function.
// Book is a replacement for JS objects, maps, dictionaries.
var sT = setTimeout, B = sT.Book || (sT.Book = function(text){
var b = function book(word, is){
var has = b.all[word], p;
if(is === undefined){ return (has && has.is) || b.get(has || word) }
if(has){
if(p = has.page){
p.size += size(is) - size(has.is);
p.text = '';
}
has.text = '';
has.is = is;
return b;
}
//b.all[word] = {is: word}; return b;
return b.set(word, is);
};
// TODO: if from text, preserve the separator symbol.
b.list = [{from: text, size: (text||'').length, substring: sub, toString: to, book: b, get: b, read: list}];
b.page = page;
b.set = set;
b.get = get;
b.all = {};
return b;
}), PAGE = 2**12;
function page(word){
var b = this, l = b.list, i = spot(word, l, b.parse), p = l[i];
if('string' == typeof p){ l[i] = p = {size: -1, first: b.parse? b.parse(p) : p, substring: sub, toString: to, book: b, get: b, read: list} } // TODO: test, how do we arrive at this condition again?
//p.i = i;
return p;
// TODO: BUG! What if we get the page, it turns out to be too big & split, we must then RE get the page!
}
function get(word){
if(!word){ return }
if(undefined !== word.is){ return word.is } // JS falsey values!
var b = this, has = b.all[word];
if(has){ return has.is }
// get does an exact match, so we would have found it already, unless parseless page:
var page = b.page(word), l, has, a, i;
if(!page || !page.from){ return } // no parseless data
return got(word, page);
}
function got(word, page){
var b = page.book, l, has, a, i;
if(l = from(page)){ has = l[got.i = i = spot(word, l, B.decode)]; } // TODO: POTENTIAL BUG! This assumes that each word on a page uses the same serializer/formatter/structure. // TOOD: BUG!!! Not actually, but if we want to do non-exact radix-like closest-word lookups on a page, we need to check limbo & potentially sort first.
// parseless may return -1 from actual value, so we may need to test both. // TODO: Double check? I think this is correct.
if(has && word == has.word){ return (b.all[word] = has).is }
if('string' != typeof has){ has = l[got.i = i+=1] }
if(has && word == has.word){ return (b.all[word] = has).is }
a = slot(has) // Escape!
if(word != B.decode(a[0])){
has = l[got.i = i+=1]; // edge case bug?
a = slot(has); // edge case bug?
if(word != B.decode(a[0])){ return }
}
has = l[i] = b.all[word] = {word: ''+word, is: B.decode(a[1]), page: page, substring: subt, toString: tot}; // TODO: convert to a JS value!!! Maybe index! TODO: BUG word needs a page!!!! TODO: Check for other types!!!
return has.is;
}
function spot(word, sorted, parse){ parse = parse || spot.no || (spot.no = function(t){ return t }); // TODO: BUG???? Why is there substring()||0 ? // TODO: PERF!!! .toString() is +33% faster, can we combine it with the export?
var L = sorted, min = 0, page, found, l = (word=''+word).length, max = L.length, i = max/2;
while(((word < (page = (parse(L[i=i>>0])||'').substring())) || ((parse(L[i+1])||'').substring() <= word)) && i != min){ // L[i] <= word < L[i+1]
i += (page <= word)? (max - (min = i))/2 : -((max = i) - min)/2;
}
return i;
}
function from(a, t, l){
if('string' != typeof a.from){ return a.from }
//(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])); // slot
(l = a.from = slot(t = t||a.from||''));
return l;
}
function list(each){ each = each || function(x){return x}
var i = 0, l = sort(this), w, r = [], p = this.book.parse || function(){};
//while(w = l[i++]){ r.push(each(slot(w)[1], p(w)||w, this)) }
while(w = l[i++]){ r.push(each(this.get(w = w.word||p(w)||w), w, this)) } // TODO: BUG! PERF?
return r;
}
function set(word, is){
// TODO: Perf on random write is decent, but short keys or seq seems significantly slower.
var b = this, has = b.all[word];
if(has){ return b(word, is) } // updates to in-memory items will always match exactly.
var page = b.page(word=''+word), tmp; // before we assume this is an insert tho, we need to check
if(page && page.from){ // if it could be an update to an existing word from parseless.
b.get(word);
if(b.all[word]){ return b(word, is) }
}
// MUST be an insert:
has = b.all[word] = {word: word, is: is, page: page, substring: subt, toString: tot};
page.first = (page.first < word)? page.first : word;
if(!page.limbo){ (page.limbo = []) }
page.limbo.push(has);
b(word, is);
page.size += size(word) + size(is);
if((b.PAGE || PAGE) < page.size){ split(page, b) }
return b;
}
function split(p, b){ // TODO: use closest hash instead of half.
//console.time();
//var S = performance.now();
var L = sort(p), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp;
//console.timeEnd();
var next = {first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, f = next.from = [];
while(tmp = L[i++]){
f.push(tmp);
next.size += (tmp.is||'').length||1;
tmp.page = next;
}
p.from = p.from.slice(0, j);
p.size -= next.size;
b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. // TODO: BUG! spot may need parse too?
//console.timeEnd();
if(b.split){ b.split(next, p) }
//console.log(S = (performance.now() - S), 'split');
//console.BIG = console.BIG > S? console.BIG : S;
}
function slot(t){ return heal((t=t||'').substring(1, t.length-1).split(t[0]), t[0]) } B.slot = slot; // TODO: check first=last & pass `s`.
function heal(l, s){ var i, e;
if(0 > (i = l.indexOf(''))){ return l } // ~700M ops/sec on 4KB of Math.random()s, even faster if escape does exist.
if('' == l[0] && 1 == l.length){ return [] } // annoying edge cases! how much does this slow us down?
//if((c=i+2+parseInt(l[i+1])) != c){ return [] } // maybe still faster than below?
if((e=i+2+parseInt((e=l[i+1]).substring(0, e.indexOf('"'))||e)) != e){ return [] } // NaN check in JS is weird.
l[i] = l.slice(i, e).join(s||'|'); // rejoin the escaped value
return l.slice(0,i+1).concat(heal(l.slice(e), s)); // merge left with checked right.
}
function size(t){ return (t||'').length||1 } // bits/numbers less size? Bug or feature?
function subt(i,j){ return this.word }
//function tot(){ return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'" }
function tot(){ var tmp = {};
//if((tmp = this.page) && tmp.saving){ delete tmp.book.all[this.word]; } // TODO: BUG! Book can't know about RAD, this was from RAD, so this MIGHT be correct but we need to refactor. Make sure to add tests that will re-trigger this.
return this.text = this.text || ":"+B.encode(this.word)+":"+B.encode(this.is)+":";
tmp[this.word] = this.is;
return this.text = this.text || B.encode(tmp,'|',':').slice(1,-1);
//return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'";
}
function sub(i,j){ return (this.first||this.word||B.decode((from(this)||'')[0]||'')).substring(i,j) }
function to(){ return this.text = this.text || text(this) }
function text(p){ // PERF: read->[*] : text->"*" no edit waste 1 time perf.
if(p.limbo){ sort(p) } // TODO: BUG? Empty page meaning? undef, '', '||'?
return ('string' == typeof p.from)? p.from : '|'+(p.from||[]).join('|')+'|';
}
function sort(p, l){
var f = p.from = ('string' == typeof p.from)? slot(p.from) : p.from||[];
if(!(l = l || p.limbo)){ return f }
return mix(p).sort(function(a,b){
return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1;
});
}
function mix(p, l){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push(
l = l || p.limbo || []; p.limbo = null;
var j = 0, i, f = p.from;
while(i = l[j++]){
if(got(i.word, p)){
f[got.i] = i; // TODO: Trick: allow for a GUN'S HAM CRDT hook here.
} else {
f.push(i);
}
}
return f;
}
B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32);
switch(typeof d){
case 'string': // text
var i = d.indexOf(s), c = 0;
while(i != -1){ c++; i = d.indexOf(s, i+1) }
return (c?s+c:'')+ '"' + d;
case 'number': return (d < 0)? ''+d : '+'+d;
case 'boolean': return d? '+' : '-';
case 'object': if(!d){ return ' ' } // TODO: BUG!!! Nested objects don't slot correctly
var l = Object.keys(d).sort(), i = 0, t = s, k, v;
while(k = l[i++]){ t += u+B.encode(k,s,u)+u+B.encode(d[k],s,u)+u+s }
return t;
}
}
B.decode = function(t, s){ s = s || "|";
if('string' != typeof t){ return }
switch(t){ case ' ': return null; case '-': return false; case '+': return true; }
switch(t[0]){
case '-': case '+': return parseFloat(t);
case '"': return t.slice(1);
}
return t.slice(t.indexOf('"')+1);
}
B.hash = function(s, c){ // via SO
if(typeof s !== 'string'){ return }
c = c || 0; // CPU schedule hashing by
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;
}
function record(key, val){ return key+B.encode(val)+"%"+key.length }
function decord(t){
var o = {}, i = t.lastIndexOf("%"), c = parseFloat(t.slice(i+1));
o[t.slice(0,c)] = B.decode(t.slice(c,i));
return o;
}
try{module.exports=B}catch(e){}
})(USE, './book');
;USE(function(module){
// 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.
module.exports = function (v) {
module.exports = function(v){
// "deletes", nulling out keys.
return v === null ||
"string" === typeof v ||
@ -501,7 +717,7 @@
tmp = keys.length;
console.STAT && console.STAT(S, -(S - (S = +new Date)), 'got copied some');
DBG && (DBG.ga = +new Date);
root.on('in', {'@': to, '#': id, put: put, '%': (tmp? (id = text_rand(9)) : u), $: root.$, _: faith, DBG: DBG, FOO: 1});
root.on('in', {'@': to, '#': id, put: put, '%': (tmp? (id = text_rand(9)) : u), $: root.$, _: faith, DBG: DBG});
console.STAT && console.STAT(S, +new Date - S, 'got in');
if(!tmp){ return }
setTimeout.turn(go);
@ -1167,10 +1383,29 @@
USE('./put');
USE('./get');
module.exports = Gun;
})(USE, './core');
;USE(function(module){
var Gun = USE('./root');
USE('./shim');
USE('./onto');
USE('./book');
USE('./valid');
USE('./state');
USE('./dup');
USE('./ask');
USE('./core');
USE('./on');
USE('./map');
USE('./set');
USE('./mesh');
USE('./websocket');
USE('./localStorage');
module.exports = Gun;
})(USE, './index');
;USE(function(module){
var Gun = USE('./index');
var Gun = USE('./root');
Gun.chain.on = function(tag, arg, eas, as){ // don't rewrite!
var gun = this, cat = gun._, root = cat.root, act, off, id, tmp;
if(typeof tag === 'string'){
@ -1306,7 +1541,7 @@
})(USE, './on');
;USE(function(module){
var Gun = USE('./index'), next = Gun.chain.get.next;
var Gun = USE('./root'), next = Gun.chain.get.next;
Gun.chain.get.next = function(gun, lex){ var tmp;
if(!Object.plain(lex)){ return (next||noop)(gun, lex) }
if(tmp = ((tmp = lex['#'])||'')['='] || tmp){ return gun.get(tmp) }
@ -1351,7 +1586,7 @@
})(USE, './map');
;USE(function(module){
var Gun = USE('./index');
var Gun = USE('./root');
Gun.chain.set = function(item, cb, opt){
var gun = this, root = gun.back(-1), soul, tmp;
cb = cb || function(){};
@ -1451,10 +1686,10 @@
if((tmp = msg['><']) && 'string' == typeof tmp){ tmp.slice(0,99).split(',').forEach(function(k){ this[k] = 1 }, (msg._).yo = {}) } // Peers already sent to, do not resend.
// DAM ^
if(tmp = msg.dam){
(dup_track(id)||{}).via = peer;
if(tmp = mesh.hear[tmp]){
tmp(msg, peer, root);
}
dup_track(id);
return;
}
if(tmp = msg.ok){ msg._.near = tmp['/'] }
@ -1728,7 +1963,7 @@
})(USE, './mesh');
;USE(function(module){
var Gun = USE('./index');
var Gun = USE('./root');
Gun.Mesh = USE('./mesh');
// TODO: resync upon reconnect online/offline
@ -1747,10 +1982,10 @@
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root);
var wire = mesh.wire || opt.wire;
var wired = mesh.wire || opt.wire;
mesh.wire = opt.wire = open;
function open(peer){ try{
if(!peer || !peer.url){ return wire && wire(peer) }
if(!peer || !peer.url){ return wired && wired(peer) }
var url = peer.url.replace(/^http/, 'ws');
var wire = peer.wire = new opt.WebSocket(url);
wire.onclose = function(){

View File

@ -240,7 +240,36 @@ function start(root){
});
}());
}
;(function(){ // THIS IS THE UNIVERSAL NOTIFICATION MODULE
var to = {}, key = {}, email = require('./email');
if(email.err){ return }
mesh.hear['tag'] = function(msg, peer, who){
if(who = key[msg.key]){ who.rate = Math.max(msg.rate||1000*60*15, 1000*60); return }
if(!msg.src || !msg.email){ return }
if(+new Date < peer.emailed + 1000*60*2){ mesh.say({dam:'tag',err:'too fast'},peer); return } // peer can only send notifications > 2min
var src; try{ src = new URL(msg.src = msg.src.split(/\s/)[0]); } catch(e){ return } // throws if invalid URL.
(who = (to[msg.email] = to[msg.email] || {go:{}})).go[''+src] = 1; // we're keeping in-memory for now, maybe will "stay" to disk in future.
peer.emailed = +new Date;
if(who.batch){ return }
key[who.key = Math.random().toString(36).slice(2)] = who;
who.batch = setTimeout(function(){
email.send({
from: process.env.EMAIL,
to: msg.email,
subject: "Notification:",
text: 'Someone or a bot tagged you at: (⚠️ only click link if you recognize & trust it ⚠️)\n'+
'[use #'+who.key+' to unsubscribe please mute this thread by tapping the top most "⋮" button and clicking mute]\n\n' +
Object.keys(who.go).join('\n'), // TODO: NEEDS TO BE CPU SCHEDULED
headers: {'message-id': '<123456789.8765@example.com>'} // hardcode id so all batches also group into the same email thread to reduce clutter.
}, function(err, r){
who.batch = null; who.go = {};
err && console.log("email TAG:", err);
});
}, who.rate || (1000*60*60*24)); // default to 1 day
};
}());
};
;(function(){
var from = Array.from;

View File

@ -1,187 +1 @@
;(function(){ // Book
console.log("Warning: Book is in alpha!");
var sT = setTimeout, B = sT.Book || (sT.Book = function(text){
var b = function book(word, is){
var has = b.all[word], p;
if(is === undefined){ return (has && has.is) || b.get(has || word) }
if(has){
if(p = has.page){
p.size += size(is) - size(has.is);
p.text = '';
}
has.text = '';
has.is = is;
return b;
}
//b.all[word] = {is: word}; return b;
return b.set(word, is);
};
// TODO: if from text, preserve the separator symbol.
b.list = [{from: text, size: (text||'').length, substring: sub, toString: to, book: b, get: b}];
b.page = page;
b.set = set;
b.get = get;
b.all = {};
return b;
}), PAGE = 2**12;
function page(word){
var b = this, l = b.list, i = spot(B.encode(word), l), p = l[i];
if('string' == typeof p){ l[i] = p = {size: -1, first: p, substring: sub, toString: to, book: b, get: b} } // TODO: test, how do we arrive at this condition again?
p.i = i;
return p;
// TODO: BUG! What if we get the page, it turns out to be too big & split, we must then RE get the page!
}
function get(word){
if(!word){ return }
if(undefined !== word.is){ return word.is } // JS falsey values!
var b = this, has = b.all[word];
if(has){ return has.is }
// get does an exact match, so we would have found it already, unless parseless page:
var page = b.page(word), l, has, a, i;
if(!page || !page.from){ return } // no parseless data
if(l = from(page)){ subt.f=1; has = l[i = spot(B.encode(word), l)]; subt.f=0; } // because parseless data is encoded we need to make sure the word is encoded also, but because parseless data incrementally parses we need to set a flag on subt shim to indicate which parseless or not state we're in.
// parseless may return -1 from actual value, so we may need to test both.
if(has && word == has.word){ return (b.all[word] = has).is }
if('string' != typeof has){ has = l[i+=1] }
if(has && word == has.word){ return (b.all[word] = has).is }
a = slot(has) // Escape!
if(word != a[0]){
has = l[i+=1]; // edge case bug?
a = slot(has) // edge case bug?
if(word != a[0]){ return }
}
has = l[i] = b.all[word] = {word: word, is: a[1], page: page, substring: subt, toString: tot}; // TODO: convert to a JS value!!! Maybe index! TODO: BUG word needs a page!!!! TODO: Check for other types!!!
return has.is;
}
function spot(word, sorted){
var L = sorted, min = 0, page, found, l = word.length, max = L.length, i = max/2;
while((word < (page = (L[i=i>>0]||'').substring()) || ((L[i+1]||'').substring()||0) <= word) && i != min){ // L[i] <= word < L[i+1]
i += (page < word)? (max - (min = i))/2 : -((max = i) - min)/2;
}
return i;
}
function from(a, t, l){
if('string' != typeof a.from){ return a.from }
(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])).toString = join; // slot
return l;
}
function set(word, is){
var b = this, has = b.all[word];
if(has){ return b(word, is) } // updates to in-memory items will always match exactly.
var page = b.page(word), tmp; // before we assume this is an insert tho, we need to check
if(page && page.from){ // if it could be an update to an existing word from parseless.
b.get(word);
if(b.all[word]){ return b(word, is) }
}
// MUST be an insert:
has = b.all[word] = {word: word, is: is, page: page, substring: subt, toString: tot};
page.first = (page.first < (tmp = B.encode(word)))? page.first : tmp;
if(!page.list){ (page.list = []).toString = join }
page.list.push(has);
page.sort = 1;
b(word, is);
page.size += size(word) + size(is);
if((b.PAGE || PAGE) < page.size){ split(page, b) }
return b;
}
function split(p, b){
//console.time();
var L = p.list = p.list.sort(), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp;
//console.timeEnd();
var next = {list: [], first: B.encode(half.substring()), size: 0, substring: sub, toString: to, book: b, get: b}, nl = next.list;
nl.toString = join;
//console.time();
while(tmp = L[i++]){
nl.push(tmp);
next.size += (tmp.is||'').length||1;
tmp.page = next;
}
//console.timeEnd();
//console.time();
p.list = p.list.slice(0, j);
p.size -= next.size;
p.sort = 0;
b.list.splice(spot(next.first, b.list)+1, 0, next);
//console.timeEnd();
if(b.split){ b.split(next, p) }
}
function slot(t){ return (t=t||'').substring(1, t.length-1).split(t[0]) } B.slot = slot;
function size(t){ return (t||'').length||1 } // bits/numbers less size? Bug or feature?
function subt(i,j){ return subt.f? B.encode(this.word) : this.word }
//function tot(){ return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'" }
function tot(){ var tmp;
//if((tmp = this.page) && tmp.saving){ delete tmp.book.all[this.word]; } // TODO: BUG! Book can't know about RAD, this was from RAD, so this MIGHT be correct but we need to refactor. Make sure to add tests that will re-trigger this.
return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'";
}
function sub(i,j){ return (this.first||this.word||(from(this)||'')[0]||'').substring(i,j) }
function to(){ return this.text = this.text || text(this) }
function join(){ return this.join('|') }
function text(p){
if(!p.list){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' }
if(!p.from){ return '|'+((p.list && (p.list = p.list.sort()).join('|'))||'')+'|' }
return '|'+from(p).concat(p.list).sort().join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY.
}
B.encode = function(d, s){ s = s || "|";
switch(typeof d){
case 'string': // text
var i = d.indexOf(s), t = '';
while(i != -1){ t += s; i = d.indexOf(s, i+1) }
return t + '"' + d;
case 'number': return (d < 0)? ''+d : '+'+d;
case 'boolean': return d? '+' : '-';
case 'object': return d? "{TODO}" : ' ';
}
}
// deprecate this B.encode below:
B.encode2 = function(d, _){ _ = _ || "'";
if(typeof d == 'string'){
var i = d.indexOf(_), t = "";
while(i != -1){ t += _; i = d.indexOf(_, i+1) }
return t + _+d+_;
}
}
B.decode = function(t, s){ s = s || "|";
if('string' != typeof t){ return }
switch(t){ case ' ': return null; case '-': return false; case '+': return true; }
switch(t[0]){
case '-': case '+': return parseFloat(t);
case '"': return t.slice(1);
}
return B.decode(B.parse(t, s));
}
B.parse = function(l, s){ var i, c = 0, s = (s||'|');
if(0 !== (i = l.indexOf(s))){ return l }
while(s == l[++c + i]){ console.log('Count escape prefix:', c, i); }; // get count of escape prefix
return B.parse(l.slice(i+c), s);
}
function heal(l, s){ var i, c = 0;
if(0 > (i = l.indexOf(''))){ return l }
while('' == l[++c + i]){}; // get count of escape prefix
return heal(l.slice(0,i).concat(l.slice(i+c, i+c+c+1).join(s||'|'), l.slice(i+c+c+1)));
}
B.hash = function(s, c){ // via SO
if(typeof s !== 'string'){ return }
c = c || 0; // CPU schedule hashing by
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;
}
try{module.exports=B}catch(e){}
}());
console.log("Officially moved to gun.js core, use gun/src/book.js on own.");

View File

@ -590,17 +590,15 @@
}
}());
if(typeof window !== "undefined"){
var Gun = window.Gun;
var Radix = window.Radix;
window.Radisk = Radisk;
} else {
var Gun = require('../gun');
var Radix = require('./radix');
//var Radix = require('./radix2'); Radisk = require('./radisk2');
try{ module.exports = Radisk }catch(e){}
}
var Gun = (typeof window !== "undefined" && window.Gun)? window.Gun : require('../gun');
var Radix = (typeof window !== "undefined" && window.Radix)? window.Radix : require('./radix');
Radisk.Radix = Radix;
((name, exports) => {
try { module.exports = exports } catch (e) { }
if (typeof window !== "undefined") {
window[name] = window[name]||exports;
}
})("Radisk", Radisk);
}());

View File

@ -1,233 +0,0 @@
;(function(){ // RAD
console.log("Warning: Experimental rewrite of RAD to use Book. It is not API compatible with RAD yet and is very alpha.");
var sT = setTimeout, Book = sT.Book, RAD = sT.RAD || (sT.RAD = function(opt){
opt = opt || {};
opt.file = String(opt.file || 'radata');
var log = opt.log || nope;
var has = (sT.RAD.has || (sT.RAD.has = {}))[opt.file];
if (has) { return has }
var r = function rad(word, is, reply){
if(!b){ start(word, is, reply); return r }
if(is === undefined || 'function' == typeof is){ // THIS IS A READ:
var page = b.page(word);
if(page.from){
return is(null, page);
}
read(word, is, page); // get from disk
return
}
//console.log("OFF");return;
// ON WRITE:
// batch until read from disk is done (and if a write was going, do that first)
if(!valid(word, is, reply)){ return }
b(word, is);
write(word, reply);
return r;
}, /** @param b the book */ b;
async function read(word, reply, page){
var p = page || b.page(word);
reply = reply.call ? reply : () => { };
get(p, function(err, disk){
if(err){ log("ERR! in read() get() cb", err); reply(err); return }
if(!disk){
// do something to show whatever we have in-memory is going to be the disk.
}
if(p === disk){
} else
if(disk){
disk = Book(p.from = disk);
disk.PAGE = Infinity; // THIS BOOK IS ONLY TEMPORARY!
(p.list||[]).forEach(function(has){
disk(has.word, has.is);
});
// TODO: BUG! What happens if the merge causes too large of a page and it splits... WRITE TESTS TO HANDLE.
disk = disk.list[0];
disk.first = p.first = (disk.first < p.first? disk.first : p.first);
/*disk.book = disk.get = b;
disk.saving = p.saving;
disk.i = p.i;
p = b.list[p.i] = disk;*/
p.from = disk.from;
p.list = disk.list;
p.size = (disk.size < p.size? p.size : disk.size);
p.text = disk.text;
} else {
//p.from = disk || p.from; // TODO: NEED TO MERGE! AND HANDLE ERR!
}
//p.from = disk || p.from;
reply(null, p, b);
})
}
async function write(word, reply){
//log('write() word', word);
var p = b.page(word), tmp;
if(tmp = p.saving){ reply && tmp.push(reply); return } p.saving = [reply];
var S = +new Date; log(" writing", p.substring(), 'since last', S - p.saved, RAD.c, 'records', env.count++, 'mid-swap.');
read(p, function(err, disk){
if(err){ log("ERR! in write() get() cb ", err); return }
//log(' get() - p.saving ', (p.saving || []).length);
if(p.from && disk){
//log(" get() merge: p.from ", p.toString().slice(0, 40), " disk.length", disk?.length || 0);
}
// CODE CUT OUT AND MOVED TO READ
// var save = '' + p; if(!p.from){ p.from = save }
// p.list = p.text = p.from = 0;
// p.first = p.first.word || p.first;
tmp = p.saving; p.saving = [];
put(p, p.from = '' + p, function(err, ok){
env.count--; p.saved = +new Date; log(" ...wrote %d bytes in %dms", ('' + p).length, (p.saved = +new Date) - S);
if(!p.saving.length){ p.saving = 0; reply?.call && reply(err, ok); return; } p.saving = 0; // what?
// log({ tmp });
write(word, reply);
});
}, p);
}
function put(file, data, cb){
file.first && (file = Book.slot(file.first)[0]);
put[file = fname(file)] = { data: data };
RAD.put(file, data, function(err, ok){
delete put[file];
cb && cb(err, ok);
});
};
function get(file, cb) {
var tmp;
if(!file){ return } // TODO: HANDLE ERROR!!
if(file.from){ cb(null, file.from); return } // IS THIS LINE SAFE? ADD TESTS!
file.first && (file = Book.slot(file.first)[0]);
if (tmp = put[file = fname(file)]) { cb(u, tmp.data); return }
if (tmp = get[file]) { tmp.push(cb); return } get[file] = [cb];
RAD.get(file, function (err, data) {
tmp = get[file]; delete get[file];
var i = -1, f; while (f = tmp[++i]) { f(err, data) } // CPU SCHEDULE?
});
};
function start(word, is, reply) {
if (b) { r(word, is, reply); return }
get(' ', function (err, d) {
if (err) { log('ERR! in start() get()', err); reply && reply(err); return }
if (b) { r(word, is, reply); return }
//wrap(b = r.book = Book(d));
(b = r.book = Book()).list = Book.slot(d);
watch(b).list[0] = "'!'";
r(word, is, reply);
})
}
function watch(b) { // SPLIT LOGIC!
var split = b.split;
b.list.toString = function () {
console.time();
var i = -1, t = '', p; while (p = this[++i]) {
t += "|" + p.substring();
}
t += "|";
console.timeEnd();
return t;
}
b.split = function (next, page) {
//log("SPLIT!!!!", b.list.length);
put(' ', '' + b.list, function (err, ok) {
if (err) { console.log("ERR!"); return }
// ??
});
}
return b;
}
function ename(t) { return encodeURIComponent(t).replace(/\*/g, '%2A').slice(0, 250) }
function fname(p) { return opt.file + '/' + ename(p.substring()) }
function valid(word, is, reply){
if(is !== is){ reply(word +" cannot be NaN!"); return }
return true;
}
return r;
}), MAX = 1000/* 300000000 */;
try { module.exports = RAD } catch (e) { }
// junk below that needs to be cleaned up and corrected for the actual correct RAD API.
var env = {}, nope = function () { }, nah = function () { return nope }, u;
env.require = (typeof require !== '' + u && require) || nope;
env.process = (typeof process != '' + u && process) || { memoryUsage: nah };
env.os = env.require('os') || { totalmem: nope, freemem: nope };
env.v8 = env.require('v8') || { getHeapStatistics: nah };
env.fs = env.require('fs') || { writeFile: nope, readFile: nope };
env.max = env.v8.getHeapStatistics().total_available_size / (2 ** 12);
env.count = env.last = 0;
return;
//if(err && 'ENOENT' === (err.code||'').toUpperCase()){ err = null }
setInterval(function () {
var stats = { memory: {} };
stats.memory.total = env.os.totalmem() / 1024 / 1024; // in MB
stats.memory.free = env.os.freemem() / 1024 / 1024; // in MB
stats.memory.hused = env.v8.getHeapStatistics().used_heap_size / 1024 / 1024; // in MB
stats.memory.used = env.process.memoryUsage().rss / 1024 / 1024; // in MB
console.log(stats.memory);
}, 9);
}());
; (function () { // temporary fs storage plugin, needs to be refactored to use the actual RAD plugin interface.
var fs;
try { fs = require('fs') } catch (e) { };
if (!fs) { return }
var sT = setTimeout, RAD = sT.RAD;
RAD.put = function (file, data, cb) {
fs.writeFile(file, data, cb);
}
RAD.get = function (file, cb) {
fs.readFile(file, function (err, data) {
if (err && 'ENOENT' === (err.code || '').toUpperCase()) { return cb() }
cb(err, data.toString());
});
}
}());
;(function () { // temporary fs storage plugin, needs to be refactored to use the actual RAD plugin interface.
var lS;
try { lS = localStorage } catch (e) { };
if (!lS) { return }
var sT = setTimeout, RAD = sT.RAD;
RAD.put = function(file, data, cb){
//setTimeout(function(){
lS[file] = data;
cb(null, 1);
//},9);
}
RAD.get = function(file, cb){
cb(null, lS[file]);
}
}());
;(function(){
return;
var get;
try { get = fetch } catch (e) { };
if (!get) { return }
var sT = setTimeout, RAD = sT.RAD;
RAD.put = function(file, data, cb){ cb(401) }
RAD.get = async function(file, cb){
var t = (await (await fetch('http://localhost:8765/gun/'+file)).text());
if('404' == t){ cb(); return }
cb(null, t);
}
}());

View File

@ -108,11 +108,12 @@
} catch (e) { console.error(e); }
};
if(typeof window !== "undefined"){
window.Radix = Radix;
} else {
try{ module.exports = Radix }catch(e){}
}
(function(name, exports){
if(typeof window !== "undefined"){
window[name] = window[name]||exports;
}
try{ module.exports = exports }catch(e){}
})("Radix",Radix);
var each = Radix.object = function(o, f, r){
for(var k in o){
if(!o.hasOwnProperty(k)){ continue }

View File

@ -78,12 +78,12 @@ function Store(opt){
return store;
}
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
var Gun = (typeof window !== "undefined" && window.Gun) ? window.Gun : require('../gun');
Gun.on('create', function(root){
this.to.next(root);
var opt = root.opt;
if(opt.rfs === false){ return }
opt.store = opt.store || (!Gun.window && Store(opt));
opt.store = opt.store || ((!Gun.window || opt.rfs === true) && Store(opt));
});
module.exports = Store;

View File

@ -1,6 +1,5 @@
var Gun = require('../gun');
var Radisk = require('./radisk');
var fs = require('fs');
var Radix = Radisk.Radix;
var u, AWS;
@ -24,8 +23,9 @@ Gun.on('create', function(root){
opts.accessKeyId = opts.key = opts.key || opts.accessKeyId || process.env.AWS_ACCESS_KEY_ID;
opts.secretAccessKey = opts.secret = opts.secret || opts.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY;
if(opt.fakes3 = opt.fakes3 || process.env.fakes3){
opts.endpoint = opt.fakes3;
// opts.fakes3 should be the domain name of the S3-compatible service
if(opts.fakes3 = opts.fakes3 || process.env.fakes3){
opts.endpoint = opts.fakes3;
opts.sslEnabled = false;
opts.bucket = opts.bucket.replace('.','p');
}

View File

@ -66,7 +66,7 @@ Gun.on('create', function(root){
if((tmp = (root.next||'')[soul]) && tmp.put){
if(o.atom){
tmp = (tmp.next||'')[o.atom] ;
if(tmp && tmp.rad){ return }
if(tmp && tmp.root && tmp.root.graph && tmp.root.graph[soul] && tmp.root.graph[soul][o.atom]){ return }
} else
if(tmp && tmp.rad){ return }
}

View File

@ -110,6 +110,7 @@ var undent = function(code, n){
if(rcode != code){
console.log("unbuild:","update",file);
}
code = ";(function(){\n"+code+"\n}());";
write(file, code);
recurse();
}());

View File

@ -1,10 +1,10 @@
;(function(){
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
Gun.on('opt', function(root){
var GUN = (typeof window !== "undefined")? window.Gun : require('../gun');
GUN.on('opt', function(root){
this.to.next(root);
var opt = root.opt;
if(root.once){ return }
if(!Gun.Mesh){ return }
if(!GUN.Mesh){ return }
if(false === opt.RTCPeerConnection){ return }
var env;
@ -21,7 +21,8 @@
opt.RTCIceCandidate = rtcic;
opt.rtc = opt.rtc || {'iceServers': [
{urls: 'stun:stun.l.google.com:19302'},
{urls: "stun:stun.sipgate.net:3478"}/*,
{urls: 'stun:stun.cloudflare.com:3478'}/*,
{urls: "stun:stun.sipgate.net:3478"},
{urls: "stun:stun.stunprotocol.org"},
{urls: "stun:stun.sipgate.net:10000"},
{urls: "stun:217.10.68.152:10000"},
@ -33,44 +34,69 @@
opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2};
opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
opt.rtc.max = opt.rtc.max || 55; // is this a magic number? // For Future WebRTC notes: Chrome 500 max limit, however 256 likely - FF "none", webtorrent does 55 per torrent.
opt.rtc.room = opt.rtc.room || Gun.window && (location.hash.slice(1) || location.pathname.slice(1));
opt.rtc.room = opt.rtc.room || GUN.window && (location.hash.slice(1) || location.pathname.slice(1));
opt.announce = function(to){
opt.rtc.start = +new Date; // handle room logic:
root.$.get('/RTC/'+opt.rtc.room+'<?99').get('+').put(opt.pid, function(ack){
if(!ack.ok || !ack.ok.rtc){ return }
open(ack);
plan(ack);
}, {acks: opt.rtc.max}).on(function(last,key, msg){
if(last === opt.pid || opt.rtc.start > msg.put['>']){ return }
open({'#': ''+msg['#'], ok: {rtc: {id: last}}});
plan({'#': ''+msg['#'], ok: {rtc: {id: last}}});
});
};
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root);
var mesh = opt.mesh = opt.mesh || GUN.Mesh(root), wired = mesh.wire;
mesh.hear['rtc'] = plan;
mesh.wire = function(media){ try{ wired && wired(media);
if(!(media instanceof MediaStream)){ return }
(open.media = open.media||{})[media.id] = media;
for(var p in opt.peers){ p = opt.peers[p]||'';
p.addTrack && media.getTracks().forEach(track => {
p.addTrack(track, media);
});
p.createOffer && p.createOffer(function(offer){
p.setLocalDescription(offer);
mesh.say({'#': root.ask(plan), dam: 'rtc', ok: {rtc: {offer: offer, id: opt.pid}}}, p);
}, function(){}, opt.rtc.sdp);
}
} catch(e){console.log(e)} }
root.on('create', function(at){
this.to.next(at);
setTimeout(opt.announce, 1);
});
function open(msg){
if(this && this.off){ this.off() } // Ignore this, because of ask / ack.
function plan(msg){
if(!msg.ok){ return }
var rtc = msg.ok.rtc, peer, tmp;
if(!rtc || !rtc.id || rtc.id === opt.pid){ return }
//console.log("webrtc:", JSON.stringify(msg));
peer = open(msg, rtc);
if(tmp = rtc.candidate){
return peer.addIceCandidate(new opt.RTCIceCandidate(tmp));
}
if(tmp = rtc.answer){
if(!(peer = opt.peers[rtc.id] || open[rtc.id]) || peer.remoteSet){ return }
tmp.sdp = tmp.sdp.replace(/\\r\\n/g, '\r\n');
return peer.setRemoteDescription(peer.remoteSet = new opt.RTCSessionDescription(tmp));
}
if(tmp = rtc.candidate){
peer = opt.peers[rtc.id] || open[rtc.id] || open({ok: {rtc: {id: rtc.id}}});
return peer.addIceCandidate(new opt.RTCIceCandidate(tmp));
if(tmp = rtc.offer){
rtc.offer.sdp = rtc.offer.sdp.replace(/\\r\\n/g, '\r\n');
peer.setRemoteDescription(new opt.RTCSessionDescription(tmp));
return peer.createAnswer(function(answer){
peer.setLocalDescription(answer);
root.on('out', {'@': msg['#'], ok: {rtc: {answer: answer, id: opt.pid}}});
}, function(){}, opt.rtc.sdp);
}
//if(opt.peers[rtc.id]){ return }
if(open[rtc.id]){ return }
}
function open(msg, rtc, peer){
if(peer = opt.peers[rtc.id] || open[rtc.id]){ return peer }
(peer = new opt.RTCPeerConnection(opt.rtc)).id = rtc.id;
var wire = peer.wire = peer.createDataChannel('dc', opt.rtc.dataChannel);
function rtceve(eve){ eve.peer = peer; gun.on('rtc', eve) }
peer.$ = gun;
open[rtc.id] = peer;
peer.ontrack = rtceve;
peer.onremovetrack = rtceve;
peer.onconnectionstatechange = rtceve;
wire.to = setTimeout(function(){delete open[rtc.id]},1000*60);
wire.onclose = function(){ mesh.bye(peer) };
wire.onerror = function(err){ };
@ -80,33 +106,29 @@
}
wire.onmessage = function(msg){
if(!msg){ return }
//console.log('via rtc');
mesh.hear(msg.data || msg, peer);
};
peer.onicecandidate = function(e){ // source: EasyRTC!
peer.onicecandidate = function(e){ rtceve(e);
if(!e.candidate){ return }
root.on('out', {'@': msg['#'], ok: {rtc: {candidate: e.candidate, id: opt.pid}}});
root.on('out', {'@': (msg||'')['#'], '#': root.ask(plan), ok: {rtc: {candidate: e.candidate, id: opt.pid}}});
}
peer.ondatachannel = function(e){
peer.ondatachannel = function(e){ rtceve(e);
var rc = e.channel;
rc.onmessage = wire.onmessage;
rc.onopen = wire.onopen;
rc.onclose = wire.onclose;
}
if(tmp = rtc.offer){
rtc.offer.sdp = rtc.offer.sdp.replace(/\\r\\n/g, '\r\n')
peer.setRemoteDescription(new opt.RTCSessionDescription(tmp));
peer.createAnswer(function(answer){
peer.setLocalDescription(answer);
root.on('out', {'@': msg['#'], ok: {rtc: {answer: answer, id: opt.pid}}});
}, function(){}, opt.rtc.sdp);
return;
if(rtc.offer){ return peer }
for(var m in open.media){ m = open.media[m];
m.getTracks().forEach(track => {
peer.addTrack(track, m);
});
}
peer.createOffer(function(offer){
peer.setLocalDescription(offer);
root.on('out', {'@': msg['#'], '#': root.ask(open), ok: {rtc: {offer: offer, id: opt.pid}}});
root.on('out', {'@': (msg||'')['#'], '#': root.ask(plan), ok: {rtc: {offer: offer, id: opt.pid}}});
}, function(){}, opt.rtc.sdp);
return peer;
}
});
}());
}());

View File

@ -49,36 +49,44 @@ var Gun = require('../gun');
*/
Gun.on('opt', function(root){
Gun.on('opt', function (root) {
var opt = root.opt;
if(false === opt.ws || opt.once){
if (false === opt.ws || opt.once) {
this.to.next(root);
return;
}
var url = require('url');
}
opt.mesh = opt.mesh || Gun.Mesh(root);
opt.WebSocket = opt.WebSocket || require('ws');
var ws = opt.ws = opt.ws || {};
ws.path = ws.path || '/gun';
// if we DO need an HTTP server, then choose ws specific one or GUN default one.
if(!ws.noServer){
ws.server = ws.server || opt.web;
if(!ws.server){ this.to.next(root); return } // ugh, bug fix for @jamierez & unstoppable ryan.
if (!opt.web || ws.noServer) {
this.to.next(root);
return;// no server no sockets
}
ws.web = ws.web || new opt.WebSocket.Server(ws); // we still need a WS server.
ws.web.on('connection', function(wire, req){ var peer;
wire.headers = wire.headers || (req||'').headers || '';
ws.noServer = true;//workaround for ws.path
ws.web = ws.web || new opt.WebSocket.Server(ws);
opt.web.on('upgrade', (req, socket, head) => {
opt.web.host = opt.web.host || (req.headers||'').origin || (req.headers||'').host;
if (req.url == ws.path) {
ws.web.handleUpgrade(req, socket, head, function done(ws) {
open(ws, req);
});
}
});
function open(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.hi(peer = { wire: wire });
wire.on('message', function (msg) {
opt.mesh.hear(msg.data || msg, peer);
});
wire.on('close', function(){
wire.on('close', function () {
opt.mesh.bye(peer);
});
wire.on('error', function(e){});
setTimeout(function heart(){ if(!opt.peers[peer.id]){ return } try{ wire.send("[]") }catch(e){} ;setTimeout(heart, 1000 * 20) }, 1000 * 20); // Some systems, like Heroku, require heartbeats to not time out. // TODO: Make this configurable? // TODO: PERF: Find better approach than try/timeouts?
});
wire.on('error', function (e) { });
setTimeout(function heart() { if (!opt.peers[peer.id]) { return } try { wire.send("[]") } catch (e) { }; setTimeout(heart, 1000 * 20) }, 1000 * 20); // Some systems, like Heroku, require heartbeats to not time out. // TODO: Make this configurable? // TODO: PERF: Find better approach than try/timeouts?
}
this.to.next(root);
});
});

14
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "gun",
"version": "0.2020.1237",
"version": "0.2020.1239",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -48,7 +48,7 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/addressparser/-/addressparser-0.3.2.tgz",
"integrity": "sha1-WYc/Nej89sc2HBAjkmHXbhU0i7I=",
"optional": true
"dev": true
},
"ansi-colors": {
"version": "3.2.3",
@ -299,7 +299,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/emailjs/-/emailjs-2.2.0.tgz",
"integrity": "sha1-ulsj5KSwpFEPZS6HOxVOlAe2ygM=",
"optional": true,
"dev": true,
"requires": {
"addressparser": "^0.3.2",
"emailjs-mime-codec": "^2.0.7"
@ -309,13 +309,13 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/emailjs-base64/-/emailjs-base64-1.1.4.tgz",
"integrity": "sha512-4h0xp1jgVTnIQBHxSJWXWanNnmuc5o+k4aHEpcLXSToN8asjB5qbXAexs7+PEsUKcEyBteNYsSvXUndYT2CGGA==",
"optional": true
"dev": true
},
"emailjs-mime-codec": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/emailjs-mime-codec/-/emailjs-mime-codec-2.0.9.tgz",
"integrity": "sha512-7qJo4pFGcKlWh/kCeNjmcgj34YoJWY0ekZXEHYtluWg4MVBnXqGM4CRMtZQkfYwitOhUgaKN5EQktJddi/YIDQ==",
"optional": true,
"dev": true,
"requires": {
"emailjs-base64": "^1.1.4",
"ramda": "^0.26.1",
@ -810,7 +810,7 @@
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
"integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==",
"optional": true
"dev": true
},
"require-directory": {
"version": "2.1.1",
@ -887,7 +887,7 @@
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz",
"integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==",
"optional": true
"dev": true
},
"tslib": {
"version": "2.3.0",

199
rad.js Normal file
View File

@ -0,0 +1,199 @@
;(function(){ // RAD
console.log("Warning: Experimental rewrite of RAD to use Book. It is not API compatible with RAD yet and is very alpha.");
var sT = setTimeout, Book = sT.Book || require('gun/src/book'), RAD = sT.RAD || (sT.RAD = function(opt){
opt = opt || {};
opt.file = String(opt.file || 'radata');
var log = opt.log || console.log
var has = (sT.RAD.has || (sT.RAD.has = {}))[opt.file];
if(has){ return has } // TODO: BUG? Not reuses same instance?
var r = function rad(word, is, reply){ r.word = word;
if(!b){ start(word, is, reply); return r }
if(is === undefined || 'function' == typeof is){ // THIS IS A READ:
var page = b.page(word);
if(page.from){ return is && is(page, null), r }
return read(word, is, page), r; // get from disk
}
//console.log("OFF");return;
// ON WRITE:
// batch until read from disk is done (and if a write was going, do that first)
//if(!valid(word, is, reply)){ return }
b(word, is);
write(word, reply);
return r;
}, /** @param b the book */ b;
r.then = function(cb, p){ return p = (new Promise(function(yes, no){ r(r.word, yes) })), cb? p.then(cb) : p }
r.read = r.results = function(cb){ return (new Promise(async function(yes, no){ yes((await r(r.word)).read(cb)) })) }
async function read(word, reply, page){ // TODO: this function doesn't do much, inline it???
if(!reply){ return }
var p = page || b.page(word);
get(p, function(err, disk){
if(err){ log("ERR! in read() get() cb", err); reply(p.no, err); return }
p.from = disk || p.from;
reply(p, null, b);
})
}
function write(word, reply){
var p = b.page(word), tmp;
if(tmp=p.saving){(reply||!tmp.length)&&(p.saving=tmp.concat(reply));return} // TODO: PERF! Rogowski points out concat is slow. BUG??? I HAVE NO clue how/why this if statement being called from recursion yet not set to 0.
p.saving = ('function' == typeof reply)? [reply] : reply || [];
get(p, function(err, disk){
if(err){ log("ERR! in write() get() cb ", err); return } // TODO: BUG!!! Unhandled, no callbacks called.
p.from = disk || p.from;
tmp = p.saving; p.saving = [];
put(p, ''+p, function(err, ok){
sT.each(tmp, function(cb){ cb && cb(err, ok) });
tmp = p.saving; p.saving = 0;
if(tmp.length){ write(word, tmp) }
});
}, p);
}
function put(file, data, cb){
put[file = fname(file)] = { data: data };
RAD.put(file, data, function(err, ok){
delete put[file];
cb && cb(err, ok);
}, opt);
};
function get(file, cb){
var tmp;
if(!file){ return } // TODO: HANDLE ERROR!!
if(file.from){ cb(null, file.from); return }
if(b&&1==b.list.length){ file.first = (file.first < '!')? file.first : '!'; } // TODO: BUG!!!! This cleanly makes for a common first file, but SAVING INVISIBLE ASCII KEYS IS COMPLETELY UNTESTED and guaranteed to have bugs/corruption issues.
if(tmp = put[file = fname(file)]){ cb(u, tmp.data); return }
if(tmp = get[file]){ tmp.push(cb); return } get[file] = [cb];
RAD.get(file, function(err, data){
tmp = get[file]||''; delete get[file];
sT.each(tmp, function(cb){ cb && cb(err, data) });
}, opt);
};
function start(word, is, reply){
if(b){ r(word, is, reply); return }
get(' ', function(err, d){
if(err){ log('ERR! in start() get()', err); reply && reply(err); return }
if(b){ r(word, is, reply); return }
b = r.book = Book();
if((d = Book.slot(d)).length){ b.list = d } // TODO: BUG! Add some other sort of corrupted/error check here?
watch(b).parse = function(t){ return ('string' == typeof t)? Book.decode(Book.slot(t)[0]) : t } // TODO: This was ugly temporary, but is necessary, and is logically correct, but is there a cleaner, nicer, less assumptiony way to do it? // TODO: SOLUTION?! I think this needs to be in Book, not RAD.
r(word, is, reply);
})
}
function watch(b){ // SPLIT LOGIC!
var split = b.split;
b.list.toString = function(){
//console.time();
var i = -1, t = '', p; while (p = this[++i]){
t += "|" +"`"+Book.encode(p.substring())+"`"+Book.encode(p.meta||null)+"`"
}
t += "|";
//console.timeEnd();
return t;
}
b.split = function(next, page){
put(' ', '' + b.list, function(err, ok){
if(err){ console.log("ERR!"); return }
// ??
});
}
return b;
}
function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A').slice(0, 250) }
//function fname(p){ return opt.file + '/' + ename(p.substring()) }
function fname(p){ return ename(p.substring()) }
function valid(word, is, reply){
if(is !== is){ reply(word +" cannot be NaN!"); return }
return true;
}
return r;
}), MAX = 1000/* 300000000 */;
sT.each = sT.each || function(l,f){l.forEach(f)};
try { module.exports = RAD } catch (e){ }
/*
// junk below that needs to be cleaned up and corrected for the actual correct RAD API.
var env = {}, nope = function(){ }, nah = function(){ return nope }, u;
env.require = (typeof require !== '' + u && require) || nope;
env.process = (typeof process != '' + u && process) || { memoryUsage: nah };
env.os = env.require('os') || { totalmem: nope, freemem: nope };
env.v8 = env.require('v8') || { getHeapStatistics: nah };
env.fs = env.require('fs') || { writeFile: nope, readFile: nope };
env.max = env.v8.getHeapStatistics().total_available_size / (2 ** 12);
env.count = env.last = 0;
return;
//if(err && 'ENOENT' === (err.code||'').toUpperCase()){ err = null }
setInterval(function(){
var stats = { memory: {} };
stats.memory.total = env.os.totalmem() / 1024 / 1024; // in MB
stats.memory.free = env.os.freemem() / 1024 / 1024; // in MB
stats.memory.hused = env.v8.getHeapStatistics().used_heap_size / 1024 / 1024; // in MB
stats.memory.used = env.process.memoryUsage().rss / 1024 / 1024; // in MB
console.log(stats.memory);
}, 9);
*/
}());
; (function(){ // temporary fs storage plugin, needs to be refactored to use the actual RAD plugin interface.
var fs;
try { fs = require('fs') } catch (e){ };
if(!fs){ return }
var sT = setTimeout, RAD = sT.RAD;
RAD.put = function(file, data, cb, opt){
fs.writeFile(opt.file+'/'+file, data, cb);
}
RAD.get = function(file, cb, opt){
fs.readFile(opt.file+'/'+file, function(err, data){
if(err && 'ENOENT' === (err.code||'').toUpperCase()){ return cb() }
cb(err, (data||'').toString()||data);
});
}
}());
;(function(){ // temporary fs storage plugin, needs to be refactored to use the actual RAD plugin interface.
var lS;
try { lS = localStorage } catch (e){ };
if(!lS){ return }
var sT = setTimeout, RAD = sT.RAD;
RAD.put = function(file, data, cb, opt){
setTimeout(function(){
lS[opt.file+'/'+file] = data;
cb(null, 1);
},1);
}
RAD.get = function(file, cb, opt){
setTimeout(function(){
cb(null, lS[opt.file+'/'+file]);
},1);
}
}());
;(function(){ return;
var get;
try { get = fetch } catch (e){ console.log("WARNING! need `npm install node-fetch@2.6`"); get = fetch = require('node-fetch') };
if(!get){ return }
var sT = setTimeout, RAD = sT.RAD, put = RAD.put, get = RAD.get;
RAD.put = function(file, data, cb, opt){ put && put(file, data, cb, opt);
cb(401)
}
RAD.get = async function(file, cb, opt){ get && get(file, cb, opt);
var t = (await (await fetch('http://localhost:8765/gun/authorsData/'+file)).text());
if('404' == t){ cb(); return }
cb(null, t);
}
}());

13
sea.js
View File

@ -37,7 +37,9 @@
if(location.protocol.indexOf('s') < 0
&& location.host.indexOf('localhost') < 0
&& ! /^127\.\d+\.\d+\.\d+$/.test(location.hostname)
&& location.protocol.indexOf('file:') < 0){
&& location.protocol.indexOf('blob:') < 0
&& location.protocol.indexOf('file:') < 0
&& location.origin != 'null'){
console.warn('HTTPS needed for WebCrypto in SEA, redirecting...');
location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS!
}
@ -968,7 +970,8 @@
var pass = (alias || (pair && !(pair.priv && pair.epriv))) && 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 retries = typeof opt.retries === 'number' ? opt.retries : 9;
var gun = this, cat = (gun._), root = gun.back(-1);
if(cat.ing){
@ -977,7 +980,7 @@
}
cat.ing = true;
var act = {}, u, tries = 9;
var act = {}, u;
act.a = function(data){
if(!data){ return act.b() }
if(!data.pub){
@ -991,7 +994,7 @@
var get = (act.list = (act.list||[]).concat(list||[])).shift();
if(u === get){
if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
if(alias && tries--){
if(alias && retries--){
root.get('~@'+alias).once(act.a);
return;
}
@ -1386,7 +1389,7 @@
if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.") // certificate expired
// "data.c" = a list of certificants/certified users
// "data.w" = lex WRITE permission, in the future, there will be "data.r" which means lex READ permission
if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*' || certificant) > -1)) {
if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*') > -1 || data.c.indexOf(certificant) > -1)) {
// ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path
let path = soul.indexOf('/') > -1 ? soul.replace(soul.substring(0, soul.indexOf('/') + 1), '') : ''
String.match = String.match || Gun.text.match

View File

@ -1,3 +1,4 @@
;(function(){
var shim = require('./shim');
var S = require('./settings');
@ -13,4 +14,5 @@
return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt'])
}
module.exports = importGen;
}());

View File

@ -1,3 +1,4 @@
;(function(){
require('./base64');
// This is Array extended to have .toString(['utf8'|'hex'|'base64'])
@ -22,4 +23,5 @@
}
}
module.exports = SeaArray;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var User = require('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
// now that we have created a user, we want to authenticate them!
@ -7,7 +8,8 @@
var pass = (alias || (pair && !(pair.priv && pair.epriv))) && 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 retries = typeof opt.retries === 'number' ? opt.retries : 9;
var gun = this, cat = (gun._), root = gun.back(-1);
if(cat.ing){
@ -30,6 +32,10 @@
var get = (act.list = (act.list||[]).concat(list||[])).shift();
if(u === get){
if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
if(alias && retries--){
root.get('~@'+alias).once(act.a);
return;
}
return act.err('Wrong user or password.')
}
root.get(get).once(act.a);
@ -74,7 +80,7 @@
if(SEA.window && ((gun.back('user')._).opt||opt).remember){
// TODO: this needs to be modular.
try{var sS = {};
sS = window.sessionStorage; // TODO: FIX BUG putting on `.is`!
sS = SEA.window.sessionStorage; // TODO: FIX BUG putting on `.is`!
sS.recall = true;
sS.pair = JSON.stringify(pair); // auth using pair is more reliable than alias/pass
}catch(e){}
@ -154,4 +160,5 @@
}catch(e){o={}};
return o;
}
}());

View File

@ -1,3 +1,4 @@
;(function(){
var u;
if(u+''== typeof btoa){
@ -7,4 +8,5 @@
global.btoa = function(data){ return Buffer.from(data, "binary").toString("base64") };
global.atob = function(data){ return Buffer.from(data, "base64").toString("binary") };
}
}());

View File

@ -1,3 +1,4 @@
;(function(){
require('./base64');
// This is Buffer implementation used in SEA. Functionality is mostly
@ -76,4 +77,5 @@
SafeBuffer.prototype.toString = SeaArray.prototype.toString
module.exports = SafeBuffer;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./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
@ -69,4 +70,5 @@
}});
module.exports = SEA.certify;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var User = require('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
@ -94,11 +95,12 @@
}
if(SEA.window){
try{var sS = {};
sS = window.sessionStorage;
sS = SEA.window.sessionStorage;
delete sS.recall;
delete sS.pair;
}catch(e){};
}
return gun;
}
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -39,4 +40,5 @@
}});
module.exports = SEA.decrypt;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -37,4 +38,5 @@
}});
module.exports = SEA.encrypt;
}());

View File

@ -1,12 +1,16 @@
;(function(){
var SEA = require('./root');
try{ if(SEA.window){
if(location.protocol.indexOf('s') < 0
&& location.host.indexOf('localhost') < 0
&& ! /^127\.\d+\.\d+\.\d+$/.test(location.hostname)
&& location.protocol.indexOf('file:') < 0){
&& location.protocol.indexOf('blob:') < 0
&& location.protocol.indexOf('file:') < 0
&& location.origin != 'null'){
console.warn('HTTPS needed for WebCrypto in SEA, redirecting...');
location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS!
}
} }catch(e){}
}());

View File

@ -1,6 +1,7 @@
;(function(){
var SEA = require('./sea'), S = require('./settings'), noop = function() {}, u;
var Gun = (''+u != typeof window)? (window.Gun||{on:noop}) : require((''+u === typeof MODULE?'.':'')+'./gun', 1);
var Gun = (SEA.window||'').GUN || require((''+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)
@ -66,7 +67,7 @@
check.any(eve, msg, val, key, soul, at, no, at.user||''); return;
eve.to.next(msg); // not handled
}
check.hash = function(eve, msg, val, key, soul, at, no){
check.hash = function(eve, msg, val, key, soul, at, no){ // mark unbuilt @i001962 's epic hex contrib!
SEA.work(val, null, function(data){
function hexToBase64(hexStr) {
let base64 = "";
@ -246,3 +247,4 @@
// TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -70,4 +71,5 @@
}});
module.exports = SEA.pair;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var User = require('./user'), SEA = User.SEA, Gun = User.GUN;
User.prototype.recall = function(opt, cb){
@ -7,7 +8,7 @@
if(SEA.window){
try{
var sS = {};
sS = window.sessionStorage; // TODO: FIX BUG putting on `.is`!
sS = SEA.window.sessionStorage; // TODO: FIX BUG putting on `.is`!
if(sS){
(root._).opt.remember = true;
((gun.back('user')._).opt||opt).remember = true;
@ -24,4 +25,5 @@
*/
return gun;
}
}());

View File

@ -1,9 +1,11 @@
;(function(){
// Security, Encryption, and Authorization: SEA.js
// MANDATORY READING: https://gun.eco/explainers/data/security.html
// IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
// THIS IS AN EARLY ALPHA!
if(typeof self !== "undefined"){ module.window = self } // should be safe for at least browser/worker/nodejs, need to check other envs like RN etc.
if(typeof window !== "undefined"){ module.window = window }
var tmp = module.window || module, u;
@ -13,4 +15,5 @@
try{ if(u+'' !== typeof MODULE){ MODULE.exports = SEA } }catch(e){}
module.exports = SEA;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var shim = require('./shim');
// Practical examples about usage found in tests.
@ -16,7 +17,7 @@
// For documentation see https://nodejs.org/api/buffer.html
SEA.Buffer = SEA.Buffer || require('./buffer');
// These SEA functions support now only Promises or
// These SEA functions support now ony Promises or
// async/await (compatible) code, use those like Promises.
//
// Creates a wrapper library around Web Crypto API
@ -45,7 +46,7 @@
// Obviously it is missing MANY necessary features. This is only an alpha release.
// Please experiment with it, audit what I've done so far, and complain about what needs to be added.
// SEA should be a full suite that is easy and seamless to use.
// Again, scroll near the top, where I provide an EXAMPLE of how to create a user and sign in.
// Again, scroll naer the top, where I provide an EXAMPLE of how to create a user and sign in.
// Once logged in, the rest of the code you just read handled automatically signing/validating data.
// But all other behavior needs to be equally easy, like opinionated ways of
// Adding friends (trusted public keys), sending private messages, etc.
@ -55,4 +56,5 @@
module.exports = SEA
// -------------- END SEA MODULES --------------------
// -- BEGIN SEA+GUN MODULES: BUNDLED BY DEFAULT UNTIL OTHERS USE SEA ON OWN -------
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -50,4 +51,5 @@
}
module.exports = SEA.secret;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -41,4 +42,5 @@
SEA.opt = s;
module.exports = s
}());

View File

@ -1,3 +1,4 @@
;(function(){
// This internal func returns SHA-1 hashed data for KeyID generation
const __shim = require('./shim')
@ -5,4 +6,5 @@
const ossl = __shim.ossl ? __shim.ossl : subtle
const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b))
module.exports = sha1hash
}());

View File

@ -1,3 +1,4 @@
;(function(){
var shim = require('./shim');
module.exports = async function(d, o){
@ -5,4 +6,5 @@
var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t));
return shim.Buffer.from(hash);
}
}());

View File

@ -1,3 +1,4 @@
;(function(){
var User = require('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
User.prototype.pair = function(){
@ -133,4 +134,5 @@
}
*/
module.exports = User
}());

View File

@ -1,3 +1,4 @@
;(function(){
const SEA = require('./root')
const api = {Buffer: require('./buffer')}
@ -15,10 +16,10 @@
})}
if(SEA.window){
api.crypto = window.crypto || window.msCrypto
api.crypto = SEA.window.crypto || SEA.window.msCrypto
api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
api.TextEncoder = window.TextEncoder;
api.TextDecoder = window.TextDecoder;
api.TextEncoder = SEA.window.TextEncoder;
api.TextDecoder = SEA.window.TextDecoder;
api.random = (len) => api.Buffer.from(api.crypto.getRandomValues(new Uint8Array(api.Buffer.alloc(len))));
}
if(!api.TextDecoder)
@ -44,4 +45,5 @@
}}
module.exports = api
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -41,4 +42,5 @@
}});
module.exports = SEA.sign;
}());

View File

@ -1,9 +1,11 @@
;(function(){
var u, Gun = (''+u != typeof window)? (window.Gun||{chain:{}}) : require((''+u === typeof MODULE?'.':'')+'./gun', 1);
var u, Gun = (''+u != typeof GUN)? (GUN||{chain:{}}) : require((''+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;
}
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./sea'), Gun, u;
if(SEA.window){
@ -38,4 +39,5 @@
User.GUN = Gun;
User.SEA = Gun.SEA = SEA;
module.exports = User;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -77,4 +78,5 @@
}
SEA.opt.fallback = 2;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var SEA = require('./root');
var shim = require('./shim');
@ -39,4 +40,5 @@
}});
module.exports = SEA.work;
}());

View File

@ -1,3 +1,4 @@
;(function(){
// request / response module, for asking and acking messages.
require('./onto'); // depends upon onto!
@ -24,4 +25,5 @@ module.exports = function ask(cb, as){
return id;
}
var random = String.random || function(){ return Math.random().toString(36).slice(2) }
}());

View File

@ -1,3 +1,4 @@
;(function(){
var Gun = require('./root');
Gun.chain.back = function(n, opt){ var tmp;
@ -37,4 +38,5 @@ Gun.chain.back = function(n, opt){ var tmp;
return this;
}
var empty = {}, u;
}());

215
src/book.js Normal file
View File

@ -0,0 +1,215 @@
;(function(){
// TODO: BUG! Unbuild will make these globals... CHANGE unbuild to wrap files in a function.
// Book is a replacement for JS objects, maps, dictionaries.
var sT = setTimeout, B = sT.Book || (sT.Book = function(text){
var b = function book(word, is){
var has = b.all[word], p;
if(is === undefined){ return (has && has.is) || b.get(has || word) }
if(has){
if(p = has.page){
p.size += size(is) - size(has.is);
p.text = '';
}
has.text = '';
has.is = is;
return b;
}
//b.all[word] = {is: word}; return b;
return b.set(word, is);
};
// TODO: if from text, preserve the separator symbol.
b.list = [{from: text, size: (text||'').length, substring: sub, toString: to, book: b, get: b, read: list}];
b.page = page;
b.set = set;
b.get = get;
b.all = {};
return b;
}), PAGE = 2**12;
function page(word){
var b = this, l = b.list, i = spot(word, l, b.parse), p = l[i];
if('string' == typeof p){ l[i] = p = {size: -1, first: b.parse? b.parse(p) : p, substring: sub, toString: to, book: b, get: b, read: list} } // TODO: test, how do we arrive at this condition again?
//p.i = i;
return p;
// TODO: BUG! What if we get the page, it turns out to be too big & split, we must then RE get the page!
}
function get(word){
if(!word){ return }
if(undefined !== word.is){ return word.is } // JS falsey values!
var b = this, has = b.all[word];
if(has){ return has.is }
// get does an exact match, so we would have found it already, unless parseless page:
var page = b.page(word), l, has, a, i;
if(!page || !page.from){ return } // no parseless data
return got(word, page);
}
function got(word, page){
var b = page.book, l, has, a, i;
if(l = from(page)){ has = l[got.i = i = spot(word, l, B.decode)]; } // TODO: POTENTIAL BUG! This assumes that each word on a page uses the same serializer/formatter/structure. // TOOD: BUG!!! Not actually, but if we want to do non-exact radix-like closest-word lookups on a page, we need to check limbo & potentially sort first.
// parseless may return -1 from actual value, so we may need to test both. // TODO: Double check? I think this is correct.
if(has && word == has.word){ return (b.all[word] = has).is }
if('string' != typeof has){ has = l[got.i = i+=1] }
if(has && word == has.word){ return (b.all[word] = has).is }
a = slot(has) // Escape!
if(word != B.decode(a[0])){
has = l[got.i = i+=1]; // edge case bug?
a = slot(has); // edge case bug?
if(word != B.decode(a[0])){ return }
}
has = l[i] = b.all[word] = {word: ''+word, is: B.decode(a[1]), page: page, substring: subt, toString: tot}; // TODO: convert to a JS value!!! Maybe index! TODO: BUG word needs a page!!!! TODO: Check for other types!!!
return has.is;
}
function spot(word, sorted, parse){ parse = parse || spot.no || (spot.no = function(t){ return t }); // TODO: BUG???? Why is there substring()||0 ? // TODO: PERF!!! .toString() is +33% faster, can we combine it with the export?
var L = sorted, min = 0, page, found, l = (word=''+word).length, max = L.length, i = max/2;
while(((word < (page = (parse(L[i=i>>0])||'').substring())) || ((parse(L[i+1])||'').substring() <= word)) && i != min){ // L[i] <= word < L[i+1]
i += (page <= word)? (max - (min = i))/2 : -((max = i) - min)/2;
}
return i;
}
function from(a, t, l){
if('string' != typeof a.from){ return a.from }
//(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])); // slot
(l = a.from = slot(t = t||a.from||''));
return l;
}
function list(each){ each = each || function(x){return x}
var i = 0, l = sort(this), w, r = [], p = this.book.parse || function(){};
//while(w = l[i++]){ r.push(each(slot(w)[1], p(w)||w, this)) }
while(w = l[i++]){ r.push(each(this.get(w = w.word||p(w)||w), w, this)) } // TODO: BUG! PERF?
return r;
}
function set(word, is){
var b = this, has = b.all[word];
if(has){ return b(word, is) } // updates to in-memory items will always match exactly.
var page = b.page(word=''+word), tmp; // before we assume this is an insert tho, we need to check
if(page && page.from){ // if it could be an update to an existing word from parseless.
b.get(word);
if(b.all[word]){ return b(word, is) }
}
// MUST be an insert:
has = b.all[word] = {word: word, is: is, page: page, substring: subt, toString: tot};
page.first = (page.first < word)? page.first : word;
if(!page.limbo){ (page.limbo = []) }
page.limbo.push(has);
b(word, is);
page.size += size(word) + size(is);
if((b.PAGE || PAGE) < page.size){ split(page, b) }
return b;
}
function split(p, b){ // TODO: use closest hash instead of half.
//console.time();
var L = sort(p), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp;
//console.timeEnd();
var next = {first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, f = next.from = [];
//console.time();
while(tmp = L[i++]){
f.push(tmp);
next.size += (tmp.is||'').length||1;
tmp.page = next;
}
//console.timeEnd(); console.time();
p.from = p.from.slice(0, j);
p.size -= next.size;
b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. // TODO: BUG! spot may need parse too?
//console.timeEnd();
if(b.split){ b.split(next, p) }
}
function slot(t){ return heal((t=t||'').substring(1, t.length-1).split(t[0]), t[0]) } B.slot = slot; // TODO: check first=last & pass `s`.
function heal(l, s){ var i, e;
if(0 > (i = l.indexOf(''))){ return l } // ~700M ops/sec on 4KB of Math.random()s, even faster if escape does exist.
if('' == l[0] && 1 == l.length){ return [] } // annoying edge cases! how much does this slow us down?
//if((c=i+2+parseInt(l[i+1])) != c){ return [] } // maybe still faster than below?
if((e=i+2+parseInt((e=l[i+1]).substring(0, e.indexOf('"'))||e)) != e){ return [] } // NaN check in JS is weird.
l[i] = l.slice(i, e).join(s||'|'); // rejoin the escaped value
return l.slice(0,i+1).concat(heal(l.slice(e), s)); // merge left with checked right.
}
function size(t){ return (t||'').length||1 } // bits/numbers less size? Bug or feature?
function subt(i,j){ return this.word }
//function tot(){ return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'" }
function tot(){ var tmp = {};
//if((tmp = this.page) && tmp.saving){ delete tmp.book.all[this.word]; } // TODO: BUG! Book can't know about RAD, this was from RAD, so this MIGHT be correct but we need to refactor. Make sure to add tests that will re-trigger this.
return this.text = this.text || ":"+B.encode(this.word)+":"+B.encode(this.is)+":";
tmp[this.word] = this.is;
return this.text = this.text || B.encode(tmp,'|',':').slice(1,-1);
//return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'";
}
function sub(i,j){ return (this.first||this.word||B.decode((from(this)||'')[0]||'')).substring(i,j) }
function to(){ return this.text = this.text || text(this) }
function text(p){ // PERF: read->[*] : text->"*" no edit waste 1 time perf.
if(p.limbo){ sort(p) } // TODO: BUG? Empty page meaning? undef, '', '||'?
return ('string' == typeof p.from)? p.from : '|'+(p.from||[]).join('|')+'|';
}
function sort(p, l){
var f = p.from = ('string' == typeof p.from)? slot(p.from) : p.from||[];
if(!(l = l || p.limbo)){ return f }
return mix(p).sort(function(a,b){
return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1;
});
}
function mix(p, l){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push(
l = l || p.limbo || []; p.limbo = null;
var j = 0, i, f = p.from;
while(i = l[j++]){
if(got(i.word, p)){
f[got.i] = i; // TODO: Trick: allow for a GUN'S HAM CRDT hook here.
} else {
f.push(i);
}
}
return f;
}
B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32);
switch(typeof d){
case 'string': // text
var i = d.indexOf(s), c = 0;
while(i != -1){ c++; i = d.indexOf(s, i+1) }
return (c?s+c:'')+ '"' + d;
case 'number': return (d < 0)? ''+d : '+'+d;
case 'boolean': return d? '+' : '-';
case 'object': if(!d){ return ' ' } // TODO: BUG!!! Nested objects don't slot correctly
var l = Object.keys(d).sort(), i = 0, t = s, k, v;
while(k = l[i++]){ t += u+B.encode(k,s,u)+u+B.encode(d[k],s,u)+u+s }
return t;
}
}
B.decode = function(t, s){ s = s || "|";
if('string' != typeof t){ return }
switch(t){ case ' ': return null; case '-': return false; case '+': return true; }
switch(t[0]){
case '-': case '+': return parseFloat(t);
case '"': return t.slice(1);
}
return t.slice(t.indexOf('"')+1);
}
B.hash = function(s, c){ // via SO
if(typeof s !== 'string'){ return }
c = c || 0; // CPU schedule hashing by
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;
}
function record(key, val){ return key+B.encode(val)+"%"+key.length }
function decord(t){
var o = {}, i = t.lastIndexOf("%"), c = parseFloat(t.slice(i+1));
o[t.slice(0,c)] = B.decode(t.slice(c,i));
return o;
}
try{module.exports=B}catch(e){}
}());

View File

@ -1,3 +1,4 @@
;(function(){
// WARNING: GUN is very simple, but the JavaScript chaining API around GUN
// is complicated and was extremely hard to build. If you port GUN to another
@ -28,6 +29,7 @@ function output(msg){
if(at.lex){ Object.keys(at.lex).forEach(function(k){ tmp[k] = at.lex[k] }, tmp = msg.get = msg.get || {}) }
if(get['#'] || at.soul){
get['#'] = get['#'] || at.soul;
//root.graph[get['#']] = root.graph[get['#']] || {_:{'#':get['#'],'>':{}}};
msg['#'] || (msg['#'] = text_rand(9)); // A3120 ?
back = (root.$.get(get['#'])._);
if(!(get = get['.'])){ // soul
@ -247,4 +249,5 @@ function ack(msg, ev){
}
var empty = {}, u, text_rand = String.random, valid = Gun.valid, obj_has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) }, state = Gun.state, state_is = state.is, state_ify = state.ify;
}());

10
src/core.js Normal file
View File

@ -0,0 +1,10 @@
;(function(){
var Gun = require('./root');
require('./chain');
require('./back');
require('./put');
require('./get');
module.exports = Gun;
}());

View File

@ -1,3 +1,4 @@
;(function(){
require('./shim');
function Dup(opt){
@ -11,6 +12,7 @@ function Dup(opt){
var it = s[id] || (s[id] = {});
it.was = dup.now = +new Date;
if(!dup.to){ dup.to = setTimeout(dup.drop, opt.age + 9) }
if(dt.ed){ dt.ed(id) }
return it;
}
dup.drop = function(age){
@ -26,4 +28,5 @@ function Dup(opt){
return dup;
}
module.exports = Dup;
}());

View File

@ -1,3 +1,4 @@
;(function(){
var Gun = require('./root');
Gun.chain.get = function(key, cb, as){
@ -110,6 +111,7 @@ function cache(key, back){
next[at.get = key] = at;
if(back === cat.root.$){
at.soul = key;
//at.put = {};
} else
if(cat.soul || cat.has){
at.has = key;
@ -153,4 +155,5 @@ function rid(at){
return;
}
var empty = {}, valid = Gun.valid, u;
}());

View File

@ -1,8 +1,20 @@
;(function(){
var Gun = require('./root');
require('./chain');
require('./back');
require('./put');
require('./get');
require('./shim');
require('./onto');
require('./book');
require('./valid');
require('./state');
require('./dup');
require('./ask');
require('./core');
require('./on');
require('./map');
require('./set');
require('./mesh');
require('./websocket');
require('./localStorage');
module.exports = Gun;
}());

View File

@ -1,3 +1,4 @@
;(function(){
if(typeof Gun === 'undefined'){ return }
@ -64,4 +65,5 @@ Gun.on('create', function lg(root){
}
});
}());

View File

@ -1,5 +1,6 @@
;(function(){
var Gun = require('./index'), next = Gun.chain.get.next;
var Gun = require('./root'), next = Gun.chain.get.next;
Gun.chain.get.next = function(gun, lex){ var tmp;
if(!Object.plain(lex)){ return (next||noop)(gun, lex) }
if(tmp = ((tmp = lex['#'])||'')['='] || tmp){ return gun.get(tmp) }
@ -41,4 +42,5 @@ function map(msg){ this.to.next(msg);
Gun.on.link(msg, cat);
}
var noop = function(){}, event = {stun: noop, off: noop}, u;
}());

View File

@ -1,3 +1,4 @@
;(function(){
require('./shim');
@ -84,12 +85,17 @@ function Mesh(root){
if(tmp = msg.ok){ msg._.near = tmp['/'] }
var S = +new Date;
DBG && (DBG.is = S); peer.SI = id;
dup_track.ed = function(d){
if(id !== d){ return }
dup_track.ed = 0;
if(!(d = dup.s[id])){ return }
d.via = peer;
if(msg.get){ d.it = msg }
}
root.on('in', mesh.last = msg);
//ECHO = msg.put || ECHO; !(msg.ok !== -3740) && mesh.say({ok: -3740, put: ECHO, '@': msg['#']}, peer);
DBG && (DBG.hd = +new Date);
console.STAT && console.STAT(S, +new Date - S, msg.get? 'msg get' : msg.put? 'msg put' : 'msg');
(tmp = dup_track(id)).via = peer; // don't dedup message ID till after, cause GUN has internal dedup check.
if(msg.get){ tmp.it = msg }
dup_track(id); // in case 'in' does not call track.
if(ash){ dup_track(ash) } //dup.track(tmp+hash, true).it = it(msg);
mesh.leap = mesh.last = null; // warning! mesh.leap could be buggy.
}
@ -129,12 +135,13 @@ function Mesh(root){
!loop && dup_track(id);//.it = it(msg); // track for 9 seconds, default. Earth<->Mars would need more! // always track, maybe move this to the 'after' logic if we split function.
//if(msg.put && (msg.err || (dup.s[id]||'').err)){ return false } // TODO: in theory we should not be able to stun a message, but for now going to check if it can help network performance preventing invalid data to relay.
if(!(hash = msg['##']) && u !== msg.put && !meta.via && ack){ mesh.hash(msg, peer); return } // TODO: Should broadcasts be hashed?
if(!peer && ack){ peer = ((tmp = dup.s[ack]) && (tmp.via || ((tmp = tmp.it) && (tmp = tmp._) && tmp.via))) || ((tmp = mesh.last) && ack === tmp['#'] && mesh.leap) } // warning! mesh.leap could be buggy! mesh last check reduces this.
if(!peer && ack){ peer = ((tmp = dup.s[ack]) && (tmp.via || ((tmp = tmp.it) && (tmp = tmp._) && tmp.via))) || ((tmp = mesh.last) && ack === tmp['#'] && mesh.leap) } // warning! mesh.leap could be buggy! mesh last check reduces this. // TODO: CLEAN UP THIS LINE NOW? `.it` should be reliable.
if(!peer && ack){ // still no peer, then ack daisy chain 'tunnel' got lost.
if(dup.s[ack]){ return } // in dups but no peer hints that this was ack to ourself, ignore.
console.STAT && console.STAT(+new Date, ++SMIA, 'total no peer to ack to'); // TODO: Delete this now. Dropping lost ACKs is protocol fine now.
return false;
} // TODO: Temporary? If ack via trace has been lost, acks will go to all peers, which trashes browser bandwidth. Not relaying the ack will force sender to ask for ack again. Note, this is technically wrong for mesh behavior.
if(ack && !msg.put && !hash && ((dup.s[ack]||'').it||'')['##']){ return false } // If we're saying 'not found' but a relay had data, do not bother sending our not found. // Is this correct, return false? // NOTE: ADD PANIC TEST FOR THIS!
if(!peer && mesh.way){ return mesh.way(msg) }
DBG && (DBG.yh = +new Date);
if(!(raw = meta.raw)){ mesh.raw(msg, peer); return }
@ -195,7 +202,7 @@ function Mesh(root){
var hash = msg['##'], ack = msg['@'];
if(hash && ack){
if(!meta.via && dup_check(ack+hash)){ return false } // for our own out messages, memory & storage may ack the same thing, so dedup that. Tho if via another peer, we already tracked it upon hearing, so this will always trigger false positives, so don't do that!
if((tmp = (dup.s[ack]||'').it) || ((tmp = mesh.last) && ack === tmp['#'])){
if(tmp = (dup.s[ack]||'').it){
if(hash === tmp['##']){ return false } // if ask has a matching hash, acking is optional.
if(!tmp['##']){ tmp['##'] = hash } // if none, add our hash to ask so anyone we relay to can dedup. // NOTE: May only check against 1st ack chunk, 2nd+ won't know and still stream back to relaying peers which may then dedup. Any way to fix this wasted bandwidth? I guess force rate limiting breaking change, that asking peer has to ask for next lexical chunk.
}
@ -343,4 +350,5 @@ function Mesh(root){
try{ module.exports = Mesh }catch(e){}
}());

View File

@ -1,5 +1,6 @@
;(function(){
var Gun = require('./index');
var Gun = require('./root');
Gun.chain.on = function(tag, arg, eas, as){ // don't rewrite!
var gun = this, cat = gun._, root = cat.root, act, off, id, tmp;
if(typeof tag === 'string'){
@ -103,6 +104,10 @@ Gun.chain.off = function(){
}
}
// TODO: delete cat.one[map.id]?
if (tmp = cat.any) {
delete cat.any;
cat.any = {};
}
if(tmp = cat.ask){
delete tmp[at.get];
}
@ -128,4 +133,5 @@ Gun.chain.off = function(){
return gun;
}
var empty = {}, noop = function(){}, u;
}());

View File

@ -1,3 +1,4 @@
;(function(){
// On event emitter generic javascript utility.
module.exports = function onto(tag, arg, as){
@ -33,4 +34,5 @@ module.exports = function onto(tag, arg, as){
if((tag = tag.to) && u !== arg){ tag.next(arg) }
return tag;
};
}());

View File

@ -1,3 +1,4 @@
;(function(){
var Gun = require('./root');
Gun.chain.put = function(data, cb, as){ // I rewrote it :)
@ -152,4 +153,5 @@ function check(d, tmp){ return ((d && (tmp = d.constructor) && tmp.name) || type
var u, empty = {}, noop = function(){}, turn = setTimeout.turn, valid = Gun.valid, state_ify = Gun.state.ify;
var iife = function(fn,as){fn.call(as||empty)}
}());

View File

@ -1,3 +1,4 @@
;(function(){
function Gun(o){
@ -201,6 +202,9 @@ Gun.ask = require('./ask');
Gun.on.get = function(msg, gun){
var root = gun._, get = msg.get, soul = get['#'], node = root.graph[soul], has = get['.'];
var next = root.next || (root.next = {}), at = next[soul];
// TODO: Azarattum bug, what is in graph is not same as what is in next. Fix!
// queue concurrent GETs?
// TODO: consider tagging original message into dup for DAM.
// TODO: ^ above? In chat app, 12 messages resulted in same peer asking for `#user.pub` 12 times. (same with #user GET too, yipes!) // DAM note: This also resulted in 12 replies from 1 peer which all had same ##hash but none of them deduped because each get was different.
@ -221,10 +225,14 @@ Gun.ask = require('./ask');
}*/
var ctx = msg._||{}, DBG = ctx.DBG = msg.DBG;
DBG && (DBG.g = +new Date);
//console.log("GET:", get, node, has);
//console.log("GET:", get, node, has, at);
//if(!node && !at){ return root.on('get', msg) }
//if(has && node){ // replace 2 below lines to continue dev?
if(!node){ return root.on('get', msg) }
if(has){
if('string' != typeof has || u === node[has]){ return root.on('get', msg) }
if('string' != typeof has || u === node[has]){
if(!((at||'').next||'')[has]){ root.on('get', msg); return }
}
node = state_ify({}, has, state_is(node, has), node[has], soul);
// If we have a key in-memory, do we really need to fetch?
// Maybe... in case the in-memory key we have is a local write
@ -301,4 +309,5 @@ module.exports = Gun;
;"Please do not remove welcome log unless you are paying for a monthly sponsorship, thanks!";
Gun.log.once("welcome", "Hello wonderful person! :) Thanks for using GUN, please ask for help on http://chat.gun.eco if anything takes you longer than 5min to figure out!");
}());

View File

@ -1,5 +1,6 @@
;(function(){
var Gun = require('./index');
var Gun = require('./root');
Gun.chain.set = function(item, cb, opt){
var gun = this, root = gun.back(-1), soul, tmp;
cb = cb || function(){};
@ -20,4 +21,5 @@ Gun.chain.set = function(item, cb, opt){
})
return item;
}
}());

View File

@ -1,3 +1,4 @@
;(function(){
// Shim for generic javascript utilities.
String.random = function(l, c){
@ -82,4 +83,5 @@ Object.keys = Object.keys || function(o){
} e && e(r);
}())})();
}());
}());

View File

@ -1,3 +1,4 @@
;(function(){
require('./shim');
function State(){
@ -25,4 +26,5 @@ State.ify = function(n, k, s, v, soul){ // put a key's state on a node.
return n;
}
module.exports = State;
}());

View File

@ -1,9 +1,10 @@
;(function(){
// 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.
module.exports = function (v) {
module.exports = function(v){
// "deletes", nulling out keys.
return v === null ||
"string" === typeof v ||
@ -13,4 +14,5 @@ module.exports = function (v) {
("number" === typeof v && v != Infinity && v != -Infinity && v === v) ||
(!!v && "string" == typeof v["#"] && Object.keys(v).length === 1 && v["#"]);
}
}());

View File

@ -1,5 +1,6 @@
;(function(){
var Gun = require('./index');
var Gun = require('./root');
Gun.Mesh = require('./mesh');
// TODO: resync upon reconnect online/offline
@ -57,4 +58,5 @@ Gun.on('opt', function(root){
var doc = (''+u !== typeof document) && document;
});
var noop = function(){}, u;
}());

21
test/rad/book.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<style>html, body, textarea { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
<ul id='list'></ul>
<form id='form'>
<input id='who' placeholder='name'>
<input id='what' placeholder='say'>
<input type='submit' value='send'>
</form>
<script src="../../../gun/src/book.js"></script>
<script src="../../../gun/rad.js"></script><script>
search = setTimeout.RAD();
form.onsubmit = (eve)=>{
search(+new Date, (who.value+') '+what.value));
eve.preventDefault(what.value = "");
}
setInterval(async ()=>{
list.innerText = await search(+new Date).results();
},99);
</script>

View File

@ -86,9 +86,18 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
expect(B.decode(B.encode("bo\\|\|row"))).to.be("bo\\|\|row");
expect(B.decode(B.encode("||\áãbbçcddéẽffǵghhíĩj́jḱkĺlḿmńñóõṕpqqŕrśsttẃwúǘũxxýỹźzàbcdèfghìjklm̀ǹòpqrstùǜẁxỳz|"))).to.be("||\áãbbçcddéẽffǵghhíĩj́jḱkĺlḿmńñóõṕpqqŕrśsttẃwúǘũxxýỹźzàbcdèfghìjklm̀ǹòpqrstùǜẁxỳz|");
});
it('heal', function(){
//var obj = {a: null, b: false, c: true, d: 0, e: 42, f: Infinity, h: "hello"};
var page = '| |-|+|'+B.encode('he||o!')+'|+0|+42.69|'+B.encode('he|p')+'|+Infinity|';
expect(B.slot(page)).to.be.eql([' ', '-', '+', '|2"he||o!', '+0', '+42.69', '|1"he|p', '+Infinity']);
});
it.skip('encode decode object', function(){
expect(B.decode(B.encode({foo: 'bar', a: 1}))).to.be.eql({foo: 'bar', a: 1})
});
});
describe('BASIC API', function(done){
// TODO: Mark return here, slot("") slot("ab") causes infinite loop with heal, so need to detect not corrupted yet.
it('write', function(done){
rad('hello', 'world', function(err, ok){
@ -98,7 +107,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
});
it('read', function(done){
rad('hello', function(err, page){
rad('hello', function(page, err){
var val = page.get('hello');
expect(val).to.be('world');
done();
@ -135,7 +144,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
it('save '+type, done => { setTimeout(function(){
rad('type-'+type, type, function(err, ok){
expect(err).to.not.be.ok();
rad('type-'+type, function(err, page){
rad('type-'+type, function(page, err){
var val = page.get('type-'+type);
expect(val).to.be(type);
done();
@ -145,7 +154,8 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
});});
describe('error on invalid primitives', function(){
it('test invalid', done => {
console.log("TODO: TESTS! Add invalid data type tests, error checking. HINT: Maybe also add invisible ASCII character tests here too.");
it.skip('test invalid', done => {
rad('type-NaN', NaN, function(err, ok){
expect(err).to.be.ok();
done();
@ -160,8 +170,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
var prev = RAD(opt);
prev('helloz', 'world', function(err, ok){
prev('helloz', function(err, page){
prev('helloz', function(page, err){
prev('zalice', 'yay', function(err){
expect(page.text.split('helloz').length).to.be(2);
done();
@ -175,6 +184,25 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
*/
});
it('make sure word does not get duplicated when data is re-saved after read <', done => {
var opt = {file: 'azadata'}
var prev = RAD(opt);
prev('helloz', 'world', function(err, ok){
prev('helloz', function(page, err){
prev('azalice', 'yay', function(err){
expect(page.text.split('helloz').length).to.be(2);
done();
});
});
});
/*
(A) READ ONLY: we receive a message, we READ only - parseless is important.
(B) READ & WRITE: we write a page, and it already exists on disk.
(C) WRITE ONLY: we write a page, and it is new to disk.
*/
});
it('test if adding an in-memory word merges with previously written disk data', done => {
var prev = RAD(opt);
@ -183,14 +211,60 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
setTimeout(function(){
var rad = RAD(opt);
rad('pa-bob', 'banana', function(err, ok){
expect(err).to.not.be.ok();
var text = rad.book.list[0].text;
var i = text.indexOf('pa-alice');
expect(i).to.not.be(-1);
var ii = text.indexOf('hello');
expect((ii - i) < 10).to.be.ok();
expect((ii - i) < ('pa-alice'.length + 3)).to.be.ok();
done();
})
},99);
});
});
it('test if adding an in-memory word merges with previously written disk data <', done => {
var opt = {file: 'azadatab'}
var prev = RAD(opt);
prev('pa-alice', 'hello', function(err, ok){
expect(err).to.not.be.ok();
setTimeout(function(){
var rad = RAD(opt);
rad('pa-alex', 'banana', function(err, ok){
expect(err).to.not.be.ok();
var text = rad.book.list[0].text;
var i = text.indexOf('pa-alice');
expect(i).to.not.be(-1);
var ii = text.indexOf('hello');
expect((ii - i) < ('pa-alice'.length + 3)).to.be.ok();
done();
})
},99);
});
});
it('test if adding an in-memory escaped word merges with previously written disk data', done => {
var opt = {file:'badata'};
var prev = RAD(opt);
prev('ba-bob', 'hello', function(err, ok){
expect(err).to.not.be.ok();
setTimeout(function(){
var rad = RAD(opt);
rad('ba-a|ice', 'banana', function(err, ok){
expect(err).to.not.be.ok();
var text = rad.book.list[0].text;
var i = text.indexOf('ba-a|ice');
expect(i).to.not.be(-1);
var ii = text.indexOf('banana');
expect((ii - i) < ('ba-a|ice'.length + 3)).to.be.ok();
var iii = text.indexOf('ba-bob');
if(iii < i){ console.log("ERROR! Escaped word not sorted correctly!!!") }
expect(iii > i).to.be.ok();
done();
})
},99);
@ -198,17 +272,18 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
});
it('test if updating an in-memory word merges with previously written disk data', done => {
var opt = {file:'pu-data'};
var prev = RAD(opt);
prev('pu-zach', 'zap');
prev('pu-alex', 'yay');
prev('pu-alice', 'hello', function(err, ok){
expect(err).to.not.be.ok();
var rad = RAD(opt);
rad('pu-alice', 'cool', function(err, ok){
expect(err).to.not.be.ok();
var next = RAD(opt);
next('pu-alice', function(err, page){
next('pu-alice', function(page, err){
expect('cool').to.be(page.get('pu-alice'));
done();
})
@ -222,8 +297,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
function gen(val){ return val + String.random(99,'a') }
var opt = {file: 'gen'}
//var rad = window.names = Book();
var rad = window.names = RAD(opt);
var rad = RAD(opt);
it('Generate more than 1 page', done => {
var i = 0;
@ -238,13 +312,12 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
});
it('Make sure parseless lookup works with incrementally parsed values', done => {
rad = RAD(opt);
rad('adora', function(err, page){
rad('adora', function(page, err){
var n = page.get('adora');
expect(gen('adora')).to.be(n);
rad('aia', function(err, page){
rad('aia', function(page, err){
var n = page.get('aia');
expect(gen('aia')).to.be(n);
done();
@ -258,7 +331,7 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
rad = RAD(opt);
names.forEach(function(name){
name = name.toLowerCase();
rad(name, function(err, page){
rad(name+'a', function(page, err){
var n = page.get(name);
expect(gen(name)).to.be(n);
@ -282,9 +355,46 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam
rad('c', r);
});*/
it.skip('index metadata', done => {
localStorage.clear();
var B = setTimeout.Book;
var r = setTimeout.RAD();
//r('hello', 'world');
//return;
var i = 200; while(--i){ r('store'+i, Math.random()+'r'+Math.random()) }
console.log('switch test to a test of replication, maybe with panic');
r('store150', function(page, err){
console.log("<<<<<<<<<");
page.meta = 'https://localhost:9876,https://localhost:9877';
var i = 200; while(--i){ r('store'+i+'b', Math.random()+'r'+Math.random()) }
console.log(">>>>>>>>>");
})
});
});
describe('API usage checks', function(){
var opt = {file: 'search'}
var search = RAD(opt);
var b = Book();
it('read results from in-memory data', async done => {
b('hello', '1data');
var r = b.page('wat').read();
expect(r).to.be.eql(['1data']);
b('hello', '1dataZ');
r = b.page('wat').read();
expect(r).to.be.eql(['1dataZ']);
b('new', '2data');
r = b.page('wat').read();
expect(r).to.be.eql(['1dataZ','2data']);
done();
});
});
console.log("Performance Tests: 2023 Nov 12, 60M put/sec, 120M get/sec, 1M get/sec with splits.");
});
var ntmp = names;

View File

@ -17,8 +17,7 @@
<script src="../expect.js"></script>
<script></script>
<script src="../../gun.js"></script>
<script src="../../lib/book.js"></script>
<script src="../../lib/radisk3.js"></script>
<script src="../../rad.js"></script>
<script src="./book.js"></script>
<script>