From b14d5da5584fd26a86cc22d9ac8172cb107d339e Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Fri, 7 Apr 2023 00:16:19 -0700 Subject: [PATCH 01/31] Thank you Murage Martin @murageyun for donating!!! --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 356eef060..3eb99b6f0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **GUN** is an [ecosystem](https://gun.eco/docs/Ecosystem) of **tools** that let you build [community run](https://www.nbcnews.com/tech/tech-news/these-technologists-think-internet-broken-so-they-re-building-another-n1030136) and [encrypted applications](https://gun.eco/docs/Cartoon-Cryptography) - like an Open Source Firebase or a Decentralized Dropbox. -The [Internet Archive](https://news.ycombinator.com/item?id=17685682) and [100s of other apps](https://github.com/amark/gun/wiki/awesome-gun) run GUN in-production. GUN is also part of [Twitter's Bluesky](https://blueskycommunity.net/) initiative! +The [Internet Archive](https://news.ycombinator.com/item?id=17685682) and [100s of other apps](https://github.com/amark/gun/wiki/awesome-gun) run GUN in-production. GUN was also part of [Twitter's bluesky](https://blueskycommunity.net/) initiative! + Multiplayer by default with realtime p2p state synchronization! + Graph data lets you use key/value, tables, documents, videos, & more! @@ -155,7 +155,8 @@ Thanks to: <a href="https://hunterowens.com/">Hunter Owens</a>, <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/gabriellemon">Gabriel Lemon</a>, +<a href="https://github.com/murageyun">Murage Martin</a> </p> - Join others in sponsoring code: https://www.patreon.com/gunDB ! From 0cb97063937eba6ded3c1b5ccb42f76ab422580a Mon Sep 17 00:00:00 2001 From: ritchia1 <andrew.ritchie@estimateone.com> Date: Sat, 13 May 2023 04:33:40 +1000 Subject: [PATCH 02/31] Fix opt.s3.fakes3 parsing issue (#1318) * Fix opt.s3.fakes3 parsing issue * Fix second typo within if block --- lib/rs3.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rs3.js b/lib/rs3.js index 80c348a69..76fd455b2 100644 --- a/lib/rs3.js +++ b/lib/rs3.js @@ -24,8 +24,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'); } From 7f6a0b27c39e888b67dba3296f04e2edd05be65f Mon Sep 17 00:00:00 2001 From: ritchia1 <andrew.ritchie@estimateone.com> Date: Sun, 21 May 2023 13:47:39 +1000 Subject: [PATCH 03/31] Support variable number of auth retry attempts through opt.retries (#1325) Maintain default to 9 to ensure backwards compatibility --- sea.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sea.js b/sea.js index af2278216..dd24f41e0 100644 --- a/sea.js +++ b/sea.js @@ -968,7 +968,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 +978,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 +992,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; } From f882b86889b2b1c7249b9ad5dec74368c88fdecd Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Fri, 26 May 2023 19:23:47 -0700 Subject: [PATCH 04/31] Thanks Jason Stallings @octalmage !!! --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3eb99b6f0..a4d74314e 100644 --- a/README.md +++ b/README.md @@ -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 ! From 1862fb69fd37adf680d10f642996f06deb3073fb Mon Sep 17 00:00:00 2001 From: Anton <dev@atjn.dk> Date: Wed, 18 Oct 2023 03:53:51 +0300 Subject: [PATCH 05/31] Remove unused imports (#1337) --- lib/rs3.js | 1 - lib/wire.js | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/rs3.js b/lib/rs3.js index 76fd455b2..2153389ed 100644 --- a/lib/rs3.js +++ b/lib/rs3.js @@ -1,6 +1,5 @@ var Gun = require('../gun'); var Radisk = require('./radisk'); -var fs = require('fs'); var Radix = Radisk.Radix; var u, AWS; diff --git a/lib/wire.js b/lib/wire.js index ee1c0d695..fcc789955 100644 --- a/lib/wire.js +++ b/lib/wire.js @@ -56,7 +56,6 @@ Gun.on('opt', function(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 || {}; From 0251de11ec10436a093d76f487657732b0d7070f Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Thu, 19 Oct 2023 11:43:47 -0700 Subject: [PATCH 06/31] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4d74314e..9abd34d71 100644 --- a/README.md +++ b/README.md @@ -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/stats) +[](https://www.jsdelivr.com/package/npm/gun)  [](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> From 96b1402a657888e38ee31ddb838fc0e7370810e0 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Fri, 10 Nov 2023 04:24:26 -0800 Subject: [PATCH 07/31] yay format change --- lib/book.js | 132 +++++++++++---- lib/radisk3.js | 131 +++++++------- test/rad/book.js | 403 ++++++++++++++++++++++++++++++++++++++++++++ test/rad/mocha.html | 54 ++++++ 4 files changed, 624 insertions(+), 96 deletions(-) create mode 100644 test/rad/book.js create mode 100644 test/rad/mocha.html diff --git a/lib/book.js b/lib/book.js index 07abcdbcb..f63f61b0b 100644 --- a/lib/book.js +++ b/lib/book.js @@ -16,7 +16,8 @@ var sT = setTimeout, B = sT.Book || (sT.Book = function(text){ //b.all[word] = {is: word}; return b; return b.set(word, is); }; - b.list = [{from: text, size: (text||'').length, substring: sub, toString: to, book: b}]; + // 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; @@ -25,8 +26,9 @@ var sT = setTimeout, B = sT.Book || (sT.Book = function(text){ }), 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} } // TODO: test, how do we arrive at this condition again? + var b = this, l = b.list, i = spot(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! } @@ -38,21 +40,27 @@ function get(word){ // 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)){ has = l[i = spot(B.encode(word), l)] } + 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. + // 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 } - if('string' != typeof has){ return } 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 } + 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: 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!!! + 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){ +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.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] + 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; @@ -60,7 +68,8 @@ function spot(word, sorted){ 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 + //(l = a.from = (t = a.from||'').substring(1, t.length-1).split(t[0])).toString = join; // slot + (l = a.from = slot(t = t||a.from||'')).toString = join; return l; } @@ -74,21 +83,23 @@ function set(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; + page.first = (page.first < word)? page.first : word; + if(!page.limbo){ (page.limbo = []).toString = join } + page.limbo.push(has); b(word, is); page.size += size(word) + size(is); - if(PAGE < page.size){ split(page, b) } + if((b.PAGE || PAGE) < page.size){ split(page, b) } return b; } -function split(p, b){ +function split(p, b){ // TODO: use closest hash instead of half. //console.time(); - var L = p.list = p.list.sort(), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + // TODO: BUG???? May need to do a SORTED merge with FROM. + var i = 0, L = p.limbo, tmp; + //while(tmp = L[i++]){ } + var L = p.limbo = sort(p.limbo), 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}, nl = next.list; + var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b}, nl = next.limbo; nl.toString = join; //console.time(); while(tmp = L[i++]){ @@ -98,38 +109,78 @@ function split(p, b){ } //console.timeEnd(); //console.time(); - p.list = p.list.slice(0, j); + p.limbo = p.limbo.slice(0, j); p.size -= next.size; p.sort = 0; - b.list.splice(spot(next.first, b.list)+1, 0, next); + b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. //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 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 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]; } - return this.text = this.text || "'"+(this.word)+"'"+(this.is)+"'"; + //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)+":"; + //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 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 join(){ return this.join('|') } -function text(p){ - if(!p.list){ return (p.from||'')+'' } - if(!p.from){ return '|'+((p.list && (p.list = p.list.sort()).join('|'))||'')+'|' } - return '|'+from(p).concat(p.list).sort().join('|')+'|'; +function text(p){ var l = p.limbo; + if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } + if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". + return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. +} +function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( + var j = 0, i; + while(i = l[j++]){ + if(got(i.word, p)){ + f[got.i] = i; + } else { + f.push(i); + } + } + return sort(f); +} +function sort(l){ //return l.sort(); + return l.sort(function(a,b){ + return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; + }); } -B.encode = function(d, _){ _ = _ || "'"; - if(typeof d == 'string'){ - var i = d.indexOf(_), t = ""; - while(i != -1){ t += _; i = d.indexOf(_, i+1) } - return t + _+d+_; +B.encode = function(d, s){ s = s || "|"; + 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': return d? "{TODO}" : ' '; + } +} +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 @@ -142,5 +193,12 @@ B.hash = function(s, c){ // via SO 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){} }()); diff --git a/lib/radisk3.js b/lib/radisk3.js index d4337746a..0d2f01206 100644 --- a/lib/radisk3.js +++ b/lib/radisk3.js @@ -1,17 +1,17 @@ -;(function () { // RAD +;(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) { + 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: + 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) { + if(page.from){ return is(null, page); } read(word, is, page); // get from disk @@ -20,107 +20,117 @@ //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); + async function read(word, reply, page){ + var p = page || b.page(word); reply = reply.call ? reply : () => { }; log(`read() ${word.slice(0, 40)}`); - get(p, function (err, disk) { - if (err) { log("ERR! in read() get() cb", err); reply(err); return } + get(p, function (err, disk){ + if(err){ log("ERR! in read() get() cb", err); reply(err); return } p.from = disk || p.from; reply(null, p, b); }) } - async function write(word, reply) { + 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]; + 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.'); - get(p, function (err, disk) { - if (err) { log("ERR! in write() get() cb ", err); return } + get(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) { + if(p.from && disk){ log(" get() merge: p.from ", p.toString().slice(0, 40), " disk.length", disk?.length || 0); } - p.from = disk || p.from; // TODO: NEED TO MERGE! AND HANDLE ERR! + p.from = disk || p.from; // p.list = p.text = p.from = 0; // p.first = p.first.word || p.first; tmp = p.saving; p.saving = []; - put(p, '' + p, function (err, ok) { + put(p, '' + 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? + // TODO: BUG: Confirmed! Only calls back first. Need to fix + use perf hack from old RAD. + sT.each(tmp, function(cb){ cb && cb(err, ok) }); + if(!p.saving.length){ p.saving = 0; return; } //p.saving = 0; // what? // log({ tmp }); + console.log("hm?", word, reply+''); write(word, reply); }); }) } - function put(file, data, cb) { + 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) { + RAD.put(file, data, function (err, ok){ delete put[file]; cb && cb(err, ok); }); }; - function get(file, cb) { + function get(file, cb){ var tmp; 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) { + 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? + var i = -1, f; while (f = tmp[++i]){ f(err, data) } // TODO: BUG! 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 } + 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! + function watch(b){ // SPLIT LOGIC! var split = b.split; - b.list.toString = function () { + b.list.toString = function (){ + console.log("hi'"); console.time(); - var i = -1, t = '', p; while (p = this[++i]) { + var i = -1, t = '', p; while (p = this[++i]){ t += "|" + p.substring(); } t += "|"; console.timeEnd(); return t; } - b.split = function (next, page) { + b.split = function (next, page){ log("SPLIT!!!!", b.list.length); - put(' ', '' + b.list, function (err, ok) { - if (err) { console.log("ERR!"); return } + 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 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) { } + 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; + 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 }; @@ -135,7 +145,7 @@ //if(err && 'ENOENT' === (err.code||'').toUpperCase()){ err = null } - setInterval(function () { + setInterval(function (){ var stats = { memory: {} }; stats.memory.total = env.os.totalmem() / 1024 / 1024; // in MB @@ -148,44 +158,47 @@ }()); -; (function () { // temporary fs storage plugin, needs to be refactored to use the actual RAD plugin interface. +; (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 } + try { fs = require('fs') } catch (e){ }; + if(!fs){ return } var sT = setTimeout, RAD = sT.RAD; - RAD.put = function (file, data, cb) { + 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() } + 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. +;(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 } + try { lS = localStorage } catch (e){ }; + if(!lS){ return } var sT = setTimeout, RAD = sT.RAD; - RAD.put = function (file, data, cb) { + RAD.put = function (file, data, cb){ + setTimeout(function(){ lS[file] = data; cb(null, 1); + },9); } - RAD.get = function (file, cb) { + RAD.get = function (file, cb){ + setTimeout(function(){ cb(null, lS[file]); + },9); } }()); -;(function(){ - return; +;(function(){ return; var get; - try { get = fetch } catch (e) { }; - if (!get) { return } + try { get = fetch } catch (e){ }; + if(!get){ return } var sT = setTimeout, RAD = sT.RAD; RAD.put = function(file, data, cb){ cb(401) } diff --git a/test/rad/book.js b/test/rad/book.js new file mode 100644 index 000000000..03cd174c1 --- /dev/null +++ b/test/rad/book.js @@ -0,0 +1,403 @@ +var root; +var Gun; +(function(){ + var env; + if(typeof global !== 'undefined'){ env = global } + if(typeof window !== 'undefined'){ env = window } + root = env.window? env.window : global; + try{ env.window && root.localStorage && root.localStorage.clear() }catch(e){} + //try{ indexedDB.deleteDatabase('radatatest') }catch(e){} + if(root.Gun){ + root.Gun = root.Gun; + root.Gun.TESTING = true; + } else { + try{ require('fs').unlinkSync('data.json') }catch(e){} + try{ require('../../lib/fsrm')('radatatest') }catch(e){} + root.Gun = require('../../gun'); + root.Gun.TESTING = true; + require('../../lib/store'); + require('../../lib/rfs'); + } + + try{ var expect = global.expect = require("../expect") }catch(e){} + +}(this)); + +;(function(){ +Gun = root.Gun + +describe('RAD', function(){ + +var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammamaria","Andy","Anselme","Ardeen","Armand","Ashelman","Aube","Averyl","Baker","Barger","Baten","Bee","Benia","Bernat","Bevers","Bittner","Bobbe","Bonny","Boyce","Breech","Brittaney","Bryn","Burkitt","Cadmann","Campagna","Carlee","Carver","Cavallaro","Chainey","Chaunce","Ching","Cianca","Claudina","Clyve","Colon","Cooke","Corrina","Crawley","Cullie","Dacy","Daniela","Daryn","Deedee","Denie","Devland","Dimitri","Dolphin","Dorinda","Dream","Dunham","Eachelle","Edina","Eisenstark","Elish","Elvis","Eng","Erland","Ethan","Evelyn","Fairman","Faus","Fenner","Fillander","Flip","Foskett","Fredette","Fullerton","Gamali","Gaspar","Gemina","Germana","Gilberto","Giuditta","Goer","Gotcher","Greenstein","Grosvenor","Guthrey","Haldane","Hankins","Harriette","Hayman","Heise","Hepsiba","Hewie","Hiroshi","Holtorf","Howlond","Hurless","Ieso","Ingold","Isidora","Jacoba","Janelle","Jaye","Jennee","Jillana","Johnson","Josy","Justinian","Kannan","Kast","Keeley","Kennett","Kho","Kiran","Knowles","Koser","Kroll","LaMori","Lanctot","Lasky","Laverna","Leff","Leonanie","Lewert","Lilybel","Lissak","Longerich","Lou","Ludeman","Lyman","Madai","Maia","Malvina","Marcy","Maris","Martens","Mathilda","Maye","McLain","Melamie","Meras","Micco","Millburn","Mittel","Montfort","Moth","Mutz","Nananne","Nazler","Nesta","Nicolina","Noellyn","Nuli","Ody","Olympie","Orlena","Other","Pain","Parry","Paynter","Pentheas","Pettifer","Phyllida","Plath","Posehn","Proulx","Quinlan","Raimes","Ras","Redmer","Renelle","Ricard","Rior","Rocky","Ron","Rosetta","Rubia","Ruttger","Salbu","Sandy","Saw","Scholz","Secor","September","Shanleigh","Shenan","Sholes","Sig","Sisely","Soble","Spanos","Stanwinn","Stevie","Stu","Suzanne","Tacy","Tanney","Tekla","Thackeray","Thomasin","Tilla","Tomas","Tracay","Tristis","Ty","Urana","Valdis","Vasta","Vezza","Vitoria","Wait","Warring","Weissmann","Whetstone","Williamson","Wittenburg","Wymore","Yoho","Zamir","Zimmermann"]; + + var opt = {}; + opt.file = 'radatatest'; + var RAD = (setTimeout.RAD) || require('../../lib/radisk'); + var Book = (setTimeout.Book) || require('../../lib/book'); + //opt.store = ((Gun.window && Gun.window.RindexedDB) || require('../../lib/rfs'))(opt); + opt.chunk = 1000; + var rad = RAD(opt), esc = String.fromCharCode(27); + + describe('Book', function(){ + this.timeout(1000 * 9); + + /*it('parse', function(done){ + this.timeout(60000); + if(Gun.window){ return done() } + var raw = require('fs').readFileSync(__dirname + '/parse.rad').toString(); + rad.parse('!', function(err, disk){ + console.log("!!!!", err); + }, raw); + return; + });*/ + + it('deleting old RAD tests (may take long time)', function(done){ + done(); // Mocha doesn't print test until after its done, so show this first. + }); + + it('deleted', function(done){ + this.timeout(60 * 1000); + if(!Gun.window){ return done() } + root.localStorage && root.localStorage.clear(); + //await new Promise(function(res){ indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ res() } } ); + indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ done() } + }); + + describe('Book Format', function(done){ + var B = Book; + it('encode decode', function(){ + expect(B.decode(B.encode(null))).to.be(null); + expect(B.decode(B.encode(false))).to.be(false); + expect(B.decode(B.encode(true))).to.be(true); + expect(B.decode(B.encode(0))).to.be(0); + expect(B.decode(B.encode(-Infinity))).to.be(-Infinity); + expect(B.decode(B.encode(Infinity))).to.be(Infinity); + expect(B.decode(B.encode(1))).to.be(1); + expect(B.decode(B.encode(2))).to.be(2); + expect(B.decode(B.encode(1.2))).to.be(1.2); + expect(B.decode(B.encode(1234.56789))).to.be(1234.56789); + expect(B.decode(B.encode(''))).to.be(''); + expect(B.decode(B.encode("hello world"))).to.be("hello world"); + expect(B.decode(B.encode("he||o"))).to.be("he||o"); + expect(B.decode(B.encode("ho|y ha|o"))).to.be("ho|y ha|o"); + expect(B.decode(B.encode("he||||y"))).to.be("he||||y"); + expect(B.decode(B.encode("ho|\|ow"))).to.be("ho|\|ow"); + expect(B.decode(B.encode("so\\rrow"))).to.be("so\\rrow"); + 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){ + expect(err).to.not.be.ok(); + done(); + }); + }); + + it('read', function(done){ + rad('hello', function(err, page){ + var val = page.get('hello'); + expect(val).to.be('world'); + done(); + }) + }); + }); + + var prim = [ + null, + 'string', + 728858, + BigInt(1000000000000000000000000000000000000000000000000000000000n), + true, + false, + -Infinity, + Infinity, + -0 + ]; + //var prim = ['alice', 'bob']; + //var prim = [null]; + root.rad = rad; + + describe('can in-memory write & read all primitives', done => { prim.forEach(function(type){ + var b = setTimeout.Book(); + it('save '+type, done => { setTimeout(function(){ + b('type-'+type, type); + var val = b('type-'+type); + expect(val).to.be(type); + done(); + },1); }); + });}); + + describe('can disk write & read all primitives', done => { prim.forEach(function(type){ + 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){ + var val = page.get('type-'+type); + expect(val).to.be(type); + done(); + }); + }); + },1); }); + });}); + + describe('error on invalid primitives', function(){ + it('test invalid', done => { + rad('type-NaN', NaN, function(err, ok){ + expect(err).to.be.ok(); + done(); + }); + }); + }); + + describe('Async Race Conditions', function(){ + + it('make sure word does not get duplicated when data is re-saved after read', done => { + var opt = {file: 'zadata'} + var prev = RAD(opt); + + prev('helloz', 'world', function(err, ok){ + prev('helloz', function(err, page){ + prev('zalice', '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('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(err, page){ + 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); + + prev('pa-alice', 'hello', function(err, ok){ + expect(err).to.not.be.ok(); + + 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) < ('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); + }); + }); + + 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(); + //return; + var next = RAD(opt); + next('pu-alice', function(err, page){ + expect('cool').to.be(page.get('pu-alice')); + done(); + }) + }); + }); + }); + + }); + + describe('Recursive Book Lookups', function(){ + + function gen(val){ return val + String.random(99,'a') } + var opt = {file: 'gen'} + //var rad = window.names = Book(); + var rad = window.names = RAD(opt); + it('Generate more than 1 page', done => { + + var i = 0; + names.forEach(function(name){ + name = name.toLowerCase(); + rad(name, gen(name)); + + clearTimeout(done.c) + done.c = setTimeout(done, 99); + }); + + }); + + it('Make sure parseless lookup works with incrementally parsed values', done => { + + rad = RAD(opt); + rad('adora', function(err, page){ + var n = page.get('adora'); + expect(gen('adora')).to.be(n); + + rad('aia', function(err, page){ + var n = page.get('aia'); + expect(gen('aia')).to.be(n); + done(); + }); + }); + + }); + + it('Read across the pages', done => { + + rad = RAD(opt); + names.forEach(function(name){ + name = name.toLowerCase(); + rad(name, function(err, page){ + var n = page.get(name); + expect(gen(name)).to.be(n); + + clearTimeout(done.c); + done.c = setTimeout(done, 99); + }); + }); + console.log("TODO: BUG!!! MARK & ROGOWSKI COME BACK HERE: NOTICED THAT INDEX IS NOT ESCAPED ALTHO THERE MAY BE OTHER THINGS TO DO FIRST!!!"); + + }); + + + /*it.skip('Correctly calculate size', done => { + + var r = String.random(1000); + rad('a', r); + + r = String.random(2000); + rad('b', r); + + r = String.random(3000); + rad('c', r); + + });*/ + + }); + + }); + + var ntmp = names; + describe.skip('RAD + GUN', function(){ return; + this.timeout(1000 * 9); + var ochunk = 1000; + Gun.on('opt', function(root){ + root.opt.localStorage = false; + Gun.window && console.log("RAD disabling localStorage during tests."); + this.to.next(root); + }) + var gun = Gun({chunk: ochunk}); + + /*it('deleting old tests (may take long time)', function(done){ + done(); // Mocha doesn't print test until after its done, so show this first. + }); it('deleted', function(done){ + this.timeout(60 * 1000); + if(!Gun.window){ return done() } + indexedDB.deleteDatabase('radatatest').onsuccess = function(e){ done() } + });*/ + + it('write contacts', function(done){ + var all = {}, to, start, tmp; + names.forEach(function(v,i){ + all[++i] = true; + tmp = v.toLowerCase(); + gun.get('names').get(tmp).put({name: v, age: i}, function(ack){ + expect(ack.err).to.not.be.ok(); + delete all[i]; + if(!Object.empty(all)){ return } + done(); + }) + }) + }); + + }); + +}); + +}()); diff --git a/test/rad/mocha.html b/test/rad/mocha.html new file mode 100644 index 000000000..22c7eace7 --- /dev/null +++ b/test/rad/mocha.html @@ -0,0 +1,54 @@ +<html> + <head> + <title>Gun Tests</title> + <link rel="stylesheet" href="../mocha.css"/> + <style> + </style> + </head> + </head> + <body> + Gun Tests + <div id="debug"></div> + <div id="mocha"></div> + + <script src="../json2.js"></script> + <script src="../mocha.js"></script> + <script>mocha.setup({ui:'bdd',globals:[]});</script> + <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="./book.js"></script> + <script> + if(location.search){ + Gun.debug = true; + console.log('async?', Gun.debug); + } + var run = mocha.run(function(a,b,c){ + document.body.prepend("TODO: localStorage gun/gap ???");return; + var yes = confirm("REFRESH BROWSER FOR ASYNC TESTS?"); + if(yes){ + if(location.search){ + location.search = ''; + } else { + location.search = '?async'; + } + return; + } + /*console.log("???????????", a); + //if(a !== 0){ return } + document.getElementById('mocha-stats').id = 'mocha-stats2'; + document.getElementById('mocha-report').style.display = 'none'; + document.getElementById('mocha-report').id = 'mocha-report2'; + Gun.debug = false; + mocha.run();*/ + }); + run.on("fail", function(test, err){ + console.log("!!!!!!!!!!!", test, err); + //alert(5); + }) + </script> + </body> +</html> \ No newline at end of file From efb2552997d4f0756a5fc6fff3a606433542a0c6 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Fri, 17 Nov 2023 03:44:20 -0800 Subject: [PATCH 08/31] encode objects --- lib/book.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/book.js b/lib/book.js index f63f61b0b..6598a54fa 100644 --- a/lib/book.js +++ b/lib/book.js @@ -138,7 +138,7 @@ function tot(){ var tmp; 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 join(){ return this.join('|') } -function text(p){ var l = p.limbo; +function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. @@ -160,7 +160,7 @@ function sort(l){ //return l.sort(); }); } -B.encode = function(d, s){ s = s || "|"; +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; @@ -168,7 +168,10 @@ B.encode = function(d, s){ s = s || "|"; return (c?s+c:'')+ '"' + d; case 'number': return (d < 0)? ''+d : '+'+d; case 'boolean': return d? '+' : '-'; - case 'object': return d? "{TODO}" : ' '; + case 'object': if(!d){ return ' ' } + var l = Object.keys(d).sort(), i = 0, t = s, k, v; + while(k = l[i++]){ t += u+B.encode(k)+u+B.encode(d[k])+u+s } + return t; } } B.decode = function(t, s){ s = s || "|"; @@ -201,4 +204,4 @@ function decord(t){ } try{module.exports=B}catch(e){} -}()); +}()); \ No newline at end of file From 5ff33b7ac3efa450194c0d641253ed9e03f2c4e3 Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Sat, 25 Nov 2023 18:54:46 -0500 Subject: [PATCH 09/31] WS ws.path fix (#1343) * Update wire.js * Update wire.js * Update wire.js --- lib/wire.js | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/wire.js b/lib/wire.js index fcc789955..870976e31 100644 --- a/lib/wire.js +++ b/lib/wire.js @@ -49,35 +49,43 @@ 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; - } - + } 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) => { + 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); -}); \ No newline at end of file +}); From 6dfaaf229bcd5dbdf9cf2b3ed833b1e1de32d1f4 Mon Sep 17 00:00:00 2001 From: Jay Byoun <jay8061@pm.me> Date: Sat, 25 Nov 2023 15:59:35 -0800 Subject: [PATCH 10/31] add one click deploy to readme (#1342) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9abd34d71..2b7290fc2 100644 --- a/README.md +++ b/README.md @@ -261,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: + +[](https://app.trydome.io/signup?package=gun) + ### [Heroku](https://www.heroku.com/) [](https://heroku.com/deploy?template=https://github.com/amark/gun) From c440a7cc88709f92ba1016a1529450e77b64bd2b Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Thu, 14 Dec 2023 21:11:32 -0500 Subject: [PATCH 11/31] update src/index (#1254) * update src/index * update * src/index fix * added src/core --- gun.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/gun.js b/gun.js index b1f3680e6..8e1a2efd9 100644 --- a/gun.js +++ b/gun.js @@ -1167,10 +1167,28 @@ USE('./put'); USE('./get'); module.exports = Gun; + })(USE, './core'); + + ;USE(function(module){ + var Gun = USE('./root'); + USE('./shim'); + USE('./onto'); + 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 +1324,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 +1369,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(){}; @@ -1728,7 +1746,7 @@ })(USE, './mesh'); ;USE(function(module){ - var Gun = USE('./index'); + var Gun = USE('./root'); Gun.Mesh = USE('./mesh'); // TODO: resync upon reconnect online/offline From 2b4f7503929f2639c8d2ecc35452835e84d0737a Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Thu, 28 Dec 2023 16:07:51 -0800 Subject: [PATCH 12/31] is ??? this a MVP of book & rad ???? thanks to @rogowski --- lib/book.js | 21 +++++++++++---------- lib/radisk3.js | 18 ++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/book.js b/lib/book.js index 6598a54fa..288567bc7 100644 --- a/lib/book.js +++ b/lib/book.js @@ -26,9 +26,9 @@ var sT = setTimeout, B = sT.Book || (sT.Book = function(text){ }), PAGE = 2**12; function page(word){ - var b = this, l = b.list, i = spot(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; + 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} } // 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! } @@ -58,12 +58,13 @@ function got(word, page){ 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.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; + 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; + return i; } function from(a, t, l){ @@ -112,7 +113,7 @@ function split(p, b){ // TODO: use closest hash instead of half. p.limbo = p.limbo.slice(0, j); p.size -= next.size; p.sort = 0; - b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. + 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) } } @@ -128,7 +129,7 @@ function heal(l, s){ var i, e; } 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 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. @@ -168,9 +169,9 @@ B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32); return (c?s+c:'')+ '"' + d; case 'number': return (d < 0)? ''+d : '+'+d; case 'boolean': return d? '+' : '-'; - case 'object': if(!d){ return ' ' } + 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)+u+B.encode(d[k])+u+s } + while(k = l[i++]){ t += u+B.encode(k,s,u)+u+B.encode(d[k],s,u)+u+s } return t; } } diff --git a/lib/radisk3.js b/lib/radisk3.js index 784544949..5acf06c37 100644 --- a/lib/radisk3.js +++ b/lib/radisk3.js @@ -1,6 +1,6 @@ ;(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){ + var sT = setTimeout, Book = sT.Book || require('gun/lib/book'), RAD = sT.RAD || (sT.RAD = function(opt){ opt = opt || {}; opt.file = String(opt.file || 'radata'); var log = opt.log || nope; @@ -64,7 +64,6 @@ }, 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]; @@ -75,7 +74,7 @@ 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(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){ @@ -89,22 +88,21 @@ 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] = "'!'"; + 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? r(word, is, reply); }) } function watch(b){ // SPLIT LOGIC! var split = b.split; b.list.toString = function(){ - console.log("hi'"); - console.time(); + //console.time(); var i = -1, t = '', p; while (p = this[++i]){ - t += "|" + p.substring(); + t += "|" +"`"+Book.encode(p.substring())+"`"+Book.encode(p.meta||null)+"`" } t += "|"; - console.timeEnd(); + //console.timeEnd(); return t; } b.split = function(next, page){ From d7f19473a428e6e29fb655daefb023db6c131d13 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Mon, 15 Jan 2024 16:46:18 -0800 Subject: [PATCH 13/31] book & rad APIs stabilizing --- lib/book.js | 22 ++++++++++----- lib/radisk3.js | 68 ++++++++++++++++++++++++----------------------- package-lock.json | 14 +++++----- test/rad/book.js | 44 ++++++++++++++++++++---------- 4 files changed, 87 insertions(+), 61 deletions(-) diff --git a/lib/book.js b/lib/book.js index 288567bc7..86932436b 100644 --- a/lib/book.js +++ b/lib/book.js @@ -17,7 +17,7 @@ var sT = setTimeout, B = sT.Book || (sT.Book = function(text){ 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.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; @@ -27,7 +27,7 @@ var sT = setTimeout, B = sT.Book || (sT.Book = function(text){ 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} } // TODO: test, how do we arrive at this condition again? + 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! @@ -55,12 +55,12 @@ function got(word, page){ 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!!! + 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.length, max = L.length, i = max/2; + 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; } @@ -73,11 +73,17 @@ function from(a, t, l){ (l = a.from = slot(t = t||a.from||'')).toString = join; return l; } +function list(each){ each = each || function(x){return x} + // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). + var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; + while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + 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), tmp; // before we assume this is an insert tho, we need to check + 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) } @@ -100,7 +106,7 @@ function split(p, b){ // TODO: use closest hash instead of half. //while(tmp = L[i++]){ } var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; //console.timeEnd(); - var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b}, nl = next.limbo; + var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; nl.toString = join; //console.time(); while(tmp = L[i++]){ @@ -131,9 +137,11 @@ function heal(l, s){ var i, e; 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; +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) } diff --git a/lib/radisk3.js b/lib/radisk3.js index 5acf06c37..9afc918d8 100644 --- a/lib/radisk3.js +++ b/lib/radisk3.js @@ -7,15 +7,12 @@ var has = (sT.RAD.has || (sT.RAD.has = {}))[opt.file]; if(has){ return has } - var r = function rad(word, is, reply){ + 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(null, page); - } - read(word, is, page); // get from disk - return + if(page.from){ return is && is(page, null), r } + return read(word, is, page), r; // get from disk } //console.log("OFF");return; // ON WRITE: @@ -25,15 +22,16 @@ 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){ + async function read(word, reply, page){ // TODO: this function doesn't do much, inline it??? + if(!reply){ return } var p = page || b.page(word); - reply = reply.call ? reply : () => { }; - log(`read() ${word.slice(0, 40)}`); get(p, function(err, disk){ - if(err){ log("ERR! in read() get() cb", err); reply(err); return } + if(err){ log("ERR! in read() get() cb", err); reply(p.no, err); return } p.from = disk || p.from; - reply(null, p, b); + reply(p, null, b); }) } @@ -68,7 +66,7 @@ RAD.put(file, data, function(err, ok){ delete put[file]; cb && cb(err, ok); - }); + }, opt); }; function get(file, cb){ var tmp; @@ -78,9 +76,9 @@ 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]; + tmp = get[file]||''; delete get[file]; var i = -1, f; while (f = tmp[++i]){ f(err, data) } // TODO: BUG! CPU SCHEDULE? - }); + }, opt); }; function start(word, is, reply){ @@ -90,7 +88,7 @@ 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? + 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); }) } @@ -116,7 +114,8 @@ } 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 opt.file + '/' + ename(p.substring()) } + function fname(p){ return ename(p.substring()) } function valid(word, is, reply){ @@ -131,6 +130,7 @@ return r; }), MAX = 1000/* 300000000 */; + sT.each = sT.each || function(l,f){l.forEach(f)}; try { module.exports = RAD } catch (e){ } @@ -169,13 +169,13 @@ if(!fs){ return } var sT = setTimeout, RAD = sT.RAD; - RAD.put = function(file, data, cb){ - fs.writeFile(file, data, cb); + RAD.put = function(file, data, cb, opt){ + fs.writeFile(opt.file+'/'+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()); + 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); }); } }()); @@ -187,28 +187,30 @@ if(!lS){ return } var sT = setTimeout, RAD = sT.RAD; - RAD.put = function(file, data, cb){ + RAD.put = function(file, data, cb, opt){ setTimeout(function(){ - lS[file] = data; + lS[opt.file+'/'+file] = data; cb(null, 1); - },9); + },1); } - RAD.get = function(file, cb){ + RAD.get = function(file, cb, opt){ setTimeout(function(){ - cb(null, lS[file]); - },9); + cb(null, lS[opt.file+'/'+file]); + },1); } }()); ;(function(){ return; var get; - try { get = fetch } catch (e){ }; + 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; - 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()); + 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:8766/gun/1data/'+file)).text()); if('404' == t){ cb(); return } cb(null, t); } diff --git a/package-lock.json b/package-lock.json index 092c1a5c6..0af741e54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/test/rad/book.js b/test/rad/book.js index 6c4f40209..8d9706964 100644 --- a/test/rad/book.js +++ b/test/rad/book.js @@ -107,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(); @@ -144,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(); @@ -154,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(); @@ -169,7 +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(); @@ -182,12 +183,13 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam (C) WRITE ONLY: we write a page, and it is new to disk. */ }); + 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(err, page){ + prev('helloz', function(page, err){ prev('azalice', 'yay', function(err){ expect(page.text.split('helloz').length).to.be(2); done(); @@ -280,9 +282,8 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam var rad = RAD(opt); rad('pu-alice', 'cool', function(err, ok){ expect(err).to.not.be.ok(); - //return; 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(); }) @@ -296,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; @@ -312,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(); @@ -332,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); @@ -340,7 +339,6 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam done.c = setTimeout(done, 99); }); }); - console.log("TODO: BUG!!! MARK & ROGOWSKI COME BACK HERE: NOTICED THAT INDEX IS NOT ESCAPED ALTHO THERE MAY BE OTHER THINGS TO DO FIRST!!!"); }); @@ -357,9 +355,27 @@ 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(">>>>>>>>>"); + }) + }); }); + console.log("Performance Tests: 2023 Nov 12, 60M put/sec, 120M get/sec, 1M get/sec with splits."); + }); var ntmp = names; From 78a40daf46807a4c38ab95254521ce42d86fb405 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Tue, 16 Jan 2024 02:54:07 -0800 Subject: [PATCH 14/31] RAD & Book promoted! + buggy example: test/rad/book.html --- gun.js | 221 ++++++++++++++++++++++++++++++++++++++- lib/book.js | 217 +------------------------------------- lib/radisk3.js => rad.js | 0 src/book.js | 218 ++++++++++++++++++++++++++++++++++++++ test/rad/book.html | 21 ++++ test/rad/mocha.html | 3 +- 6 files changed, 461 insertions(+), 219 deletions(-) rename lib/radisk3.js => rad.js (100%) create mode 100644 src/book.js create mode 100644 test/rad/book.html diff --git a/gun.js b/gun.js index 8e1a2efd9..2328d01bf 100644 --- a/gun.js +++ b/gun.js @@ -136,12 +136,231 @@ }; })(USE, './onto'); + ;USE(function(module){ ;(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. + // 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])).toString = join; // slot + (l = a.from = slot(t = t||a.from||'')).toString = join; + return l; + } + function list(each){ each = each || function(x){return x} + // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). + var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; + while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + 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 = []).toString = join } + 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(); + // TODO: BUG???? May need to do a SORTED merge with FROM. + var i = 0, L = p.limbo, tmp; + //while(tmp = L[i++]){ } + var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + //console.timeEnd(); + var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; + 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.limbo = p.limbo.slice(0, j); + p.size -= next.size; + p.sort = 0; + 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 join(){ return this.join('|') } + function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? + if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } + if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". + return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. + } + function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( + var j = 0, i; + while(i = l[j++]){ + if(got(i.word, p)){ + f[got.i] = i; + } else { + f.push(i); + } + } + return sort(f); + } + function sort(l){ //return l.sort(); + return l.sort(function(a,b){ + return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; + }); + } + + 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){} + }());//delete later. + })(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 || diff --git a/lib/book.js b/lib/book.js index 86932436b..63ae61bb0 100644 --- a/lib/book.js +++ b/lib/book.js @@ -1,216 +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, 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. - // 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])).toString = join; // slot - (l = a.from = slot(t = t||a.from||'')).toString = join; - return l; -} -function list(each){ each = each || function(x){return x} - // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). - var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; - while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } - 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 = []).toString = join } - 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(); - // TODO: BUG???? May need to do a SORTED merge with FROM. - var i = 0, L = p.limbo, tmp; - //while(tmp = L[i++]){ } - var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; - //console.timeEnd(); - var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; - 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.limbo = p.limbo.slice(0, j); - p.size -= next.size; - p.sort = 0; - 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 join(){ return this.join('|') } -function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? - if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } - if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". - return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. -} -function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( - var j = 0, i; - while(i = l[j++]){ - if(got(i.word, p)){ - f[got.i] = i; - } else { - f.push(i); - } - } - return sort(f); -} -function sort(l){ //return l.sort(); - return l.sort(function(a,b){ - return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; - }); -} - -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){} -}()); \ No newline at end of file +console.log("Officially moved to gun.js core, use gun/src/book.js on own."); \ No newline at end of file diff --git a/lib/radisk3.js b/rad.js similarity index 100% rename from lib/radisk3.js rename to rad.js diff --git a/src/book.js b/src/book.js new file mode 100644 index 000000000..415afaa1a --- /dev/null +++ b/src/book.js @@ -0,0 +1,218 @@ + ;(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. + // 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])).toString = join; // slot + (l = a.from = slot(t = t||a.from||'')).toString = join; + return l; +} +function list(each){ each = each || function(x){return x} + // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). + var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; + while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + 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 = []).toString = join } + 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(); + // TODO: BUG???? May need to do a SORTED merge with FROM. + var i = 0, L = p.limbo, tmp; + //while(tmp = L[i++]){ } + var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + //console.timeEnd(); + var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; + 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.limbo = p.limbo.slice(0, j); + p.size -= next.size; + p.sort = 0; + 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 join(){ return this.join('|') } +function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? + if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } + if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". + return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. +} +function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( + var j = 0, i; + while(i = l[j++]){ + if(got(i.word, p)){ + f[got.i] = i; + } else { + f.push(i); + } + } + return sort(f); +} +function sort(l){ //return l.sort(); + return l.sort(function(a,b){ + return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; + }); +} + +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){} +}());//delete later. + \ No newline at end of file diff --git a/test/rad/book.html b/test/rad/book.html new file mode 100644 index 000000000..67743cfd3 --- /dev/null +++ b/test/rad/book.html @@ -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> \ No newline at end of file diff --git a/test/rad/mocha.html b/test/rad/mocha.html index 22c7eace7..089830876 100644 --- a/test/rad/mocha.html +++ b/test/rad/mocha.html @@ -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> From 3688ba1cc6f680ccdcee65971384b5f59a44aec6 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Tue, 16 Jan 2024 03:40:40 -0800 Subject: [PATCH 15/31] bump path --- rad.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rad.js b/rad.js index 9afc918d8..e6c9fa936 100644 --- a/rad.js +++ b/rad.js @@ -1,6 +1,6 @@ ;(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/lib/book'), RAD = sT.RAD || (sT.RAD = function(opt){ + 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 || nope; From 203bd409325fbd6254ce0776e2bc1bdc3e2f7000 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Tue, 16 Jan 2024 21:08:14 -0800 Subject: [PATCH 16/31] cleaned up Book results & sorting & caching --- gun.js | 62 ++++++++++++++++++++++-------------------------- rad.js | 40 +++++++++---------------------- src/book.js | 62 ++++++++++++++++++++++-------------------------- test/rad/book.js | 19 +++++++++++++++ 4 files changed, 88 insertions(+), 95 deletions(-) diff --git a/gun.js b/gun.js index 2328d01bf..69171f470 100644 --- a/gun.js +++ b/gun.js @@ -183,7 +183,7 @@ } 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. + 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] } @@ -208,14 +208,14 @@ 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 - (l = a.from = slot(t = t||a.from||'')).toString = join; + //(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} - // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). - var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; - while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + 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; } @@ -230,7 +230,7 @@ // 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 = []).toString = join } + if(!page.limbo){ (page.limbo = []) } page.limbo.push(has); b(word, is); page.size += size(word) + size(is); @@ -240,24 +240,18 @@ function split(p, b){ // TODO: use closest hash instead of half. //console.time(); - // TODO: BUG???? May need to do a SORTED merge with FROM. - var i = 0, L = p.limbo, tmp; - //while(tmp = L[i++]){ } - var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + var L = sort(p), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; //console.timeEnd(); - var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; - nl.toString = join; + 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++]){ - nl.push(tmp); + f.push(tmp); next.size += (tmp.is||'').length||1; tmp.page = next; } - //console.timeEnd(); - //console.time(); - p.limbo = p.limbo.slice(0, j); + //console.timeEnd(); console.time(); + p.from = p.from.slice(0, j); p.size -= next.size; - p.sort = 0; 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) } @@ -285,27 +279,29 @@ } 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 join(){ return this.join('|') } - function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? - if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } - if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". - return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. - } - function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( - var j = 0, i; + 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; + f[got.i] = i; // TODO: Trick: allow for a GUN'S HAM CRDT hook here. } else { f.push(i); } } - return sort(f); - } - function sort(l){ //return l.sort(); - return l.sort(function(a,b){ - return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; - }); + return f; } B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32); diff --git a/rad.js b/rad.js index e6c9fa936..18f6909f3 100644 --- a/rad.js +++ b/rad.js @@ -6,7 +6,7 @@ var log = opt.log || nope; var has = (sT.RAD.has || (sT.RAD.has = {}))[opt.file]; - if(has){ return has } + 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: @@ -35,29 +35,18 @@ }) } - async function write(word, reply){ - log('write() word', word); + function write(word, reply){ 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.'); + 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 } - 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); - } + if(err){ log("ERR! in write() get() cb ", err); return } // TODO: BUG!!! Unhandled, no callbacks called. p.from = disk || p.from; - // p.list = p.text = p.from = 0; - // p.first = p.first.word || p.first; tmp = p.saving; p.saving = []; - put(p, '' + p, function(err, ok){ - env.count--; p.saved = +new Date; log(" ...wrote %d bytes in %dms", ('' + p).length, (p.saved = +new Date) - S); - // TODO: BUG: Confirmed! Only calls back first. Need to fix + use perf hack from old RAD. + put(p, ''+p, function(err, ok){ sT.each(tmp, function(cb){ cb && cb(err, ok) }); - if(!p.saving.length){ p.saving = 0; return; } //p.saving = 0; // what? - // log({ tmp }); - console.log("hm?", word, reply+''); - write(word, reply); + tmp = p.saving; p.saving = 0; + if(tmp.length){ write(word, tmp) } }); }, p); } @@ -71,13 +60,13 @@ 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! + 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]; - var i = -1, f; while (f = tmp[++i]){ f(err, data) } // TODO: BUG! CPU SCHEDULE? + sT.each(tmp, function(cb){ cb && cb(err, data) }); }, opt); }; @@ -104,7 +93,6 @@ return t; } b.split = function(next, page){ - log("SPLIT!!!!", b.list.length); put(' ', '' + b.list, function(err, ok){ if(err){ console.log("ERR!"); return } // ?? @@ -117,12 +105,6 @@ //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; - } - function valid(word, is, reply){ if(is !== is){ reply(word +" cannot be NaN!"); return } return true; @@ -210,7 +192,7 @@ cb(401) } RAD.get = async function(file, cb, opt){ get && get(file, cb, opt); - var t = (await (await fetch('http://localhost:8766/gun/1data/'+file)).text()); + var t = (await (await fetch('http://localhost:8765/gun/authorsData/'+file)).text()); if('404' == t){ cb(); return } cb(null, t); } diff --git a/src/book.js b/src/book.js index 415afaa1a..f339c30f1 100644 --- a/src/book.js +++ b/src/book.js @@ -45,7 +45,7 @@ function get(word){ } 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. + 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] } @@ -70,14 +70,14 @@ function spot(word, sorted, parse){ parse = parse || spot.no || (spot.no = funct 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 - (l = a.from = slot(t = t||a.from||'')).toString = join; + //(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} - // TODO: BUG!!! in limbo items need to get situated before calling this, if there are any. (obviously, we shouldn't do it again if limbo has previously been sorted). - var i = 0, l = from(this)||[], w, r = [], p = this.book.parse || function(){}; - while(w = l[i++]){ r.push(each(slot(w)[1],p(w)||w,this)) } + 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; } @@ -92,7 +92,7 @@ function set(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 = []).toString = join } + if(!page.limbo){ (page.limbo = []) } page.limbo.push(has); b(word, is); page.size += size(word) + size(is); @@ -102,24 +102,18 @@ function set(word, is){ function split(p, b){ // TODO: use closest hash instead of half. //console.time(); - // TODO: BUG???? May need to do a SORTED merge with FROM. - var i = 0, L = p.limbo, tmp; - //while(tmp = L[i++]){ } - var L = p.limbo = sort(p.limbo), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; + var L = sort(p), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; //console.timeEnd(); - var next = {limbo: [], first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, nl = next.limbo; - nl.toString = join; + 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++]){ - nl.push(tmp); + f.push(tmp); next.size += (tmp.is||'').length||1; tmp.page = next; } - //console.timeEnd(); - //console.time(); - p.limbo = p.limbo.slice(0, j); + //console.timeEnd(); console.time(); + p.from = p.from.slice(0, j); p.size -= next.size; - p.sort = 0; 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) } @@ -147,27 +141,29 @@ function tot(){ var tmp = {}; } 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 join(){ return this.join('|') } -function text(p){ var l = p.limbo; // TODO: BUG??? Shouldn't any stringify cause limbo to be reset? - if(!l){ return (typeof p.from == 'string')? (p.from||'')+'' : '|'+p.from+'|' } - if(!p.from){ return p.limbo = null, '|'+((l && sort(l).join('|'))||'')+'|' } // TODO: p.limbo should be reset each time we "flush". - return '|'+mix(l, from(p), p).join('|')+'|'; // commenting out this sub-portion of code fixed a more basic test, but will probably cause a bug with a FROM + MEMORY. -} -function mix(l, f, p){ // TODO: IMPROVE PERFORMANCE!!!! l[j] = i is 5X+ faster than .push( - var j = 0, i; +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; + f[got.i] = i; // TODO: Trick: allow for a GUN'S HAM CRDT hook here. } else { f.push(i); } } - return sort(f); -} -function sort(l){ //return l.sort(); - return l.sort(function(a,b){ - return (a.word||B.decode(''+a)) < (b.word||B.decode(''+b))? -1:1; - }); + return f; } B.encode = function(d, s, u){ s = s || "|"; u = u || String.fromCharCode(32); diff --git a/test/rad/book.js b/test/rad/book.js index 8d9706964..3205b7a4e 100644 --- a/test/rad/book.js +++ b/test/rad/book.js @@ -374,6 +374,25 @@ var names = ["Adalard","Adora","Aia","Albertina","Alfie","Allyn","Amabil","Ammam }); + 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."); }); From e07c9b21ecde84a0fa939d62aaa26c6ca0aa63ee Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Wed, 17 Jan 2024 12:45:09 -0500 Subject: [PATCH 17/31] sea blobs! (#1353) * sea blobs! * and null origins * null fix * null check is last --- sea.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sea.js b/sea.js index dd24f41e0..704cf1b7e 100644 --- a/sea.js +++ b/sea.js @@ -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! } From 7cb337c15849b5b944a45e9563d3b481e7136046 Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Wed, 17 Jan 2024 12:46:48 -0500 Subject: [PATCH 18/31] add a way to select stats file from url (#1351) --- examples/stats.html | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/stats.html b/examples/stats.html index 324ff8e67..fa8552408 100644 --- a/examples/stats.html +++ b/examples/stats.html @@ -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 From 5c52df2eee4a871d2661f3c1b0e4ef146ffbf5cd Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Wed, 17 Jan 2024 12:49:52 -0500 Subject: [PATCH 19/31] react-native detection, and load needed shims (#1349) * react-native detection * added lib mobile * changed back to gun. for another solution --- browser.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/browser.js b/browser.js index 2459a30ce..39553b9f9 100644 --- a/browser.js +++ b/browser.js @@ -1 +1,4 @@ -module.exports = require('./gun.js') \ No newline at end of file +if(!(typeof navigator == "undefined") && navigator.product == "ReactNative"){ + require("./lib/mobile.js"); +} +module.exports = require('./gun.js'); From 638c2c3c23125622fbca3db1404111e36012c715 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Wed, 17 Jan 2024 10:13:00 -0800 Subject: [PATCH 20/31] 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? --- gun.js | 6 +++--- lib/unbuild.js | 1 + sea/aeskey.js | 4 +++- sea/array.js | 4 +++- sea/auth.js | 13 ++++++++++--- sea/base64.js | 4 +++- sea/buffer.js | 4 +++- sea/certify.js | 4 +++- sea/create.js | 6 ++++-- sea/decrypt.js | 4 +++- sea/encrypt.js | 4 +++- sea/https.js | 8 ++++++-- sea/index.js | 6 ++++-- sea/pair.js | 4 +++- sea/recall.js | 6 ++++-- sea/root.js | 5 ++++- sea/sea.js | 8 +++++--- sea/secret.js | 4 +++- sea/settings.js | 4 +++- sea/sha1.js | 4 +++- sea/sha256.js | 4 +++- sea/share.js | 4 +++- sea/shim.js | 10 ++++++---- sea/sign.js | 4 +++- sea/then.js | 6 ++++-- sea/user.js | 4 +++- sea/verify.js | 4 +++- sea/work.js | 4 +++- src/ask.js | 4 +++- src/back.js | 4 +++- src/book.js | 7 ++++--- src/chain.js | 5 ++++- src/core.js | 10 ++++++++++ src/dup.js | 5 ++++- src/get.js | 5 ++++- src/index.js | 22 +++++++++++++++++----- src/localStorage.js | 4 +++- src/map.js | 6 ++++-- src/mesh.js | 20 ++++++++++++++------ src/on.js | 10 ++++++++-- src/onto.js | 4 +++- src/put.js | 4 +++- src/root.js | 15 ++++++++++++--- src/set.js | 6 ++++-- src/shim.js | 4 +++- src/state.js | 4 +++- src/valid.js | 6 ++++-- src/websocket.js | 6 ++++-- 48 files changed, 216 insertions(+), 78 deletions(-) create mode 100644 src/core.js diff --git a/gun.js b/gun.js index 69171f470..810dda61f 100644 --- a/gun.js +++ b/gun.js @@ -136,7 +136,7 @@ }; })(USE, './onto'); - ;USE(function(module){ ;(function(){ + ;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){ @@ -348,7 +348,6 @@ } try{module.exports=B}catch(e){} - }());//delete later. })(USE, './book'); ;USE(function(module){ @@ -716,7 +715,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); @@ -1388,6 +1387,7 @@ var Gun = USE('./root'); USE('./shim'); USE('./onto'); + USE('./book'); USE('./valid'); USE('./state'); USE('./dup'); diff --git a/lib/unbuild.js b/lib/unbuild.js index 197fb7237..f2b5f7146 100644 --- a/lib/unbuild.js +++ b/lib/unbuild.js @@ -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(); }()); diff --git a/sea/aeskey.js b/sea/aeskey.js index 20390b3f0..3f0a3d575 100644 --- a/sea/aeskey.js +++ b/sea/aeskey.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/array.js b/sea/array.js index e6fa57659..4de9dfb49 100644 --- a/sea/array.js +++ b/sea/array.js @@ -1,3 +1,4 @@ +;(function(){ require('./base64'); // This is Array extended to have .toString(['utf8'|'hex'|'base64']) @@ -22,4 +23,5 @@ } } module.exports = SeaArray; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/auth.js b/sea/auth.js index cdbdaea5e..76a0a70c1 100644 --- a/sea/auth.js +++ b/sea/auth.js @@ -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; } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/base64.js b/sea/base64.js index 5c4b4a1d4..95ceb3cc5 100644 --- a/sea/base64.js +++ b/sea/base64.js @@ -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") }; } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/buffer.js b/sea/buffer.js index 67fbe3188..6cd0ce6ab 100644 --- a/sea/buffer.js +++ b/sea/buffer.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/certify.js b/sea/certify.js index b6145d011..f052b190b 100644 --- a/sea/certify.js +++ b/sea/certify.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/create.js b/sea/create.js index da3174814..5036fc560 100644 --- a/sea/create.js +++ b/sea/create.js @@ -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; } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/decrypt.js b/sea/decrypt.js index 79fe6a90b..39f67b5a7 100644 --- a/sea/decrypt.js +++ b/sea/decrypt.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -39,4 +40,5 @@ }}); module.exports = SEA.decrypt; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/encrypt.js b/sea/encrypt.js index 4f0b6c9a8..50effa107 100644 --- a/sea/encrypt.js +++ b/sea/encrypt.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -37,4 +38,5 @@ }}); module.exports = SEA.encrypt; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/https.js b/sea/https.js index 4f12465f9..dd2438fa3 100644 --- a/sea/https.js +++ b/sea/https.js @@ -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){} - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/index.js b/sea/index.js index 4e5358f30..de21f0e51 100644 --- a/sea/index.js +++ b/sea/index.js @@ -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. +}()); \ No newline at end of file diff --git a/sea/pair.js b/sea/pair.js index b01aad31a..d0d6f04ca 100644 --- a/sea/pair.js +++ b/sea/pair.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -70,4 +71,5 @@ }}); module.exports = SEA.pair; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/recall.js b/sea/recall.js index c8e5976a9..64a31d3ba 100644 --- a/sea/recall.js +++ b/sea/recall.js @@ -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; } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/root.js b/sea/root.js index 8ec30aaf8..83c0540bd 100644 --- a/sea/root.js +++ b/sea/root.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/sea.js b/sea/sea.js index c870ed3c3..522783328 100644 --- a/sea/sea.js +++ b/sea/sea.js @@ -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 ------- - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/secret.js b/sea/secret.js index 949ff398d..42ca8b3ed 100644 --- a/sea/secret.js +++ b/sea/secret.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -50,4 +51,5 @@ } module.exports = SEA.secret; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/settings.js b/sea/settings.js index c90088229..2fc07fe35 100644 --- a/sea/settings.js +++ b/sea/settings.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -41,4 +42,5 @@ SEA.opt = s; module.exports = s - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/sha1.js b/sea/sha1.js index 9272cffc5..67ffeec52 100644 --- a/sea/sha1.js +++ b/sea/sha1.js @@ -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 - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/sha256.js b/sea/sha256.js index f64660de4..62885ff65 100644 --- a/sea/sha256.js +++ b/sea/sha256.js @@ -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); } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/share.js b/sea/share.js index 1fd24f82c..b0c942729 100644 --- a/sea/share.js +++ b/sea/share.js @@ -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 - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/shim.js b/sea/shim.js index e955e736e..bf9e92037 100644 --- a/sea/shim.js +++ b/sea/shim.js @@ -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 - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/sign.js b/sea/sign.js index f286dd0ca..0374a715b 100644 --- a/sea/sign.js +++ b/sea/sign.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -41,4 +42,5 @@ }}); module.exports = SEA.sign; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/then.js b/sea/then.js index 6c127ee39..bcbce33f0 100644 --- a/sea/then.js +++ b/sea/then.js @@ -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; } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/user.js b/sea/user.js index bbd9188fb..153108170 100644 --- a/sea/user.js +++ b/sea/user.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/verify.js b/sea/verify.js index b431badf5..794aa8ebc 100644 --- a/sea/verify.js +++ b/sea/verify.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -77,4 +78,5 @@ } SEA.opt.fallback = 2; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/sea/work.js b/sea/work.js index e6f68493a..6feeabaca 100644 --- a/sea/work.js +++ b/sea/work.js @@ -1,3 +1,4 @@ +;(function(){ var SEA = require('./root'); var shim = require('./shim'); @@ -39,4 +40,5 @@ }}); module.exports = SEA.work; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/ask.js b/src/ask.js index 5fa0a5cb4..c843aa3cb 100644 --- a/src/ask.js +++ b/src/ask.js @@ -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) } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/back.js b/src/back.js index 6f522cbd8..a9009432f 100644 --- a/src/back.js +++ b/src/back.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/book.js b/src/book.js index f339c30f1..9a0c3b95c 100644 --- a/src/book.js +++ b/src/book.js @@ -1,4 +1,5 @@ - ;(function(){ +;(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){ @@ -210,5 +211,5 @@ function decord(t){ } try{module.exports=B}catch(e){} -}());//delete later. - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/chain.js b/src/chain.js index d8278984f..84f401aa4 100644 --- a/src/chain.js +++ b/src/chain.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/core.js b/src/core.js new file mode 100644 index 000000000..3fca93b68 --- /dev/null +++ b/src/core.js @@ -0,0 +1,10 @@ +;(function(){ + +var Gun = require('./root'); +require('./chain'); +require('./back'); +require('./put'); +require('./get'); +module.exports = Gun; + +}()); \ No newline at end of file diff --git a/src/dup.js b/src/dup.js index 8015c5a85..fa500e5e1 100644 --- a/src/dup.js +++ b/src/dup.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/get.js b/src/get.js index a47f72eaf..c1a6b022f 100644 --- a/src/get.js +++ b/src/get.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/index.js b/src/index.js index f0cb99725..218919e64 100644 --- a/src/index.js +++ b/src/index.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/localStorage.js b/src/localStorage.js index ff966c907..454e67d7f 100644 --- a/src/localStorage.js +++ b/src/localStorage.js @@ -1,3 +1,4 @@ +;(function(){ if(typeof Gun === 'undefined'){ return } @@ -64,4 +65,5 @@ Gun.on('create', function lg(root){ } }); - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/map.js b/src/map.js index f929c73e5..f6c5f9eb2 100644 --- a/src/map.js +++ b/src/map.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/mesh.js b/src/mesh.js index dc0e53703..267096bdf 100644 --- a/src/mesh.js +++ b/src/mesh.js @@ -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){} - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/on.js b/src/on.js index c2fc13865..4731a4792 100644 --- a/src/on.js +++ b/src/on.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/onto.js b/src/onto.js index e510e1658..dbfc54490 100644 --- a/src/onto.js +++ b/src/onto.js @@ -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; }; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/put.js b/src/put.js index d0c955533..6caa43629 100644 --- a/src/put.js +++ b/src/put.js @@ -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)} - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/root.js b/src/root.js index f0ef4c030..6ad0a7a4f 100644 --- a/src/root.js +++ b/src/root.js @@ -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!"); - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/set.js b/src/set.js index 36a7cb255..e07bc75f3 100644 --- a/src/set.js +++ b/src/set.js @@ -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; } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/shim.js b/src/shim.js index 59963495a..c77947d16 100644 --- a/src/shim.js +++ b/src/shim.js @@ -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); }())})(); }()); - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/state.js b/src/state.js index 542a4c71f..a72555eb8 100644 --- a/src/state.js +++ b/src/state.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/valid.js b/src/valid.js index 84d21990e..098497c8e 100644 --- a/src/valid.js +++ b/src/valid.js @@ -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["#"]); } - \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/websocket.js b/src/websocket.js index ee3531df4..67d6d6cde 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -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; - \ No newline at end of file + +}()); \ No newline at end of file From 7eb6d38cfc2f3cced74c2b4a3277d7c1595b42b7 Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Wed, 17 Jan 2024 13:47:07 -0500 Subject: [PATCH 21/31] quick-fix (#1355) --- browser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser.js b/browser.js index 39553b9f9..d4418a637 100644 --- a/browser.js +++ b/browser.js @@ -1,4 +1,4 @@ -if(!(typeof navigator == "undefined") && navigator.product == "ReactNative"){ - require("./lib/mobile.js"); -} +// if(!(typeof navigator == "undefined") && navigator.product == "ReactNative"){ +// require("./lib/mobile.js"); +// } module.exports = require('./gun.js'); From 61df63c96e457a9b3d8c718dcdd276dc0d8362b0 Mon Sep 17 00:00:00 2001 From: mimiza <dev@mimiza.com> Date: Mon, 5 Feb 2024 18:01:31 +0700 Subject: [PATCH 22/31] Fix SEA certificate verification, allow multiple pubs (#1358) --- sea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sea.js b/sea.js index 704cf1b7e..839fb5e2c 100644 --- a/sea.js +++ b/sea.js @@ -1389,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 From 3bd809818f93f1aade4a4cce30ff9c269e5f85b5 Mon Sep 17 00:00:00 2001 From: Simardeep Singh <1003simar@gmail.com> Date: Mon, 5 Feb 2024 04:05:11 -0700 Subject: [PATCH 23/31] Create SECURITY.md (#1364) --- SECURITY.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..f266c209e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,50 @@ +# 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 | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :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. + From 3070627c83ea910b1fc2b8c55dce79f324575ace Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Mon, 5 Feb 2024 06:06:38 -0500 Subject: [PATCH 24/31] ... works (#1357) --- rad.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rad.js b/rad.js index 18f6909f3..bdd39a184 100644 --- a/rad.js +++ b/rad.js @@ -3,7 +3,7 @@ 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 || nope; + 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? @@ -115,7 +115,7 @@ 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; @@ -141,7 +141,7 @@ stats.memory.used = env.process.memoryUsage().rss / 1024 / 1024; // in MB console.log(stats.memory); }, 9); - +*/ }()); From e584906a653dd6afb1658505e6db311aae234af8 Mon Sep 17 00:00:00 2001 From: Bradley Matusiak <bmatusiak@gmail.com> Date: Mon, 5 Feb 2024 06:10:35 -0500 Subject: [PATCH 25/31] Loading fix (#1356) * does this load better * check window.Gun too in rfs --- lib/radisk.js | 18 ++++++++---------- lib/radix.js | 11 ++++++----- lib/rfs.js | 4 ++-- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/radisk.js b/lib/radisk.js index cdb8e2d25..fd83fddbf 100644 --- a/lib/radisk.js +++ b/lib/radisk.js @@ -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); }()); \ No newline at end of file diff --git a/lib/radix.js b/lib/radix.js index e60789e4a..31bf0b7a2 100644 --- a/lib/radix.js +++ b/lib/radix.js @@ -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 } diff --git a/lib/rfs.js b/lib/rfs.js index 7ab326971..c43b14f80 100644 --- a/lib/rfs.js +++ b/lib/rfs.js @@ -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; \ No newline at end of file From c47800f4d84a03636073d76a88084d549435a47b Mon Sep 17 00:00:00 2001 From: Simardeep Singh <1003simar@gmail.com> Date: Tue, 6 Feb 2024 21:21:18 -0700 Subject: [PATCH 26/31] update SECURITY.md file and change the versions to 0.2020.x (#1365) --- SECURITY.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index f266c209e..7a0afd6db 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,10 +10,8 @@ We provide security updates for the following versions of our project: | Version | Supported | | ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | +| 0.2020.x| :white_check_mark: | +| < 0.2020| :x: | ## Reporting a Vulnerability @@ -46,5 +44,4 @@ We will communicate any security updates through our standard communication chan ## 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. - +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. \ No newline at end of file From 7a2767a7635c526db94aa9335d00cb31f127d3a0 Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Mon, 11 Mar 2024 13:50:17 -0700 Subject: [PATCH 27/31] webrtc accept getUserMedia streams as peer --- examples/basic/meet.html | 38 +++++++++++++++++++ gun.js | 12 +++--- lib/webrtc.js | 79 +++++++++++++++++++++++++--------------- 3 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 examples/basic/meet.html diff --git a/examples/basic/meet.html b/examples/basic/meet.html new file mode 100644 index 000000000..e91f8f276 --- /dev/null +++ b/examples/basic/meet.html @@ -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> \ No newline at end of file diff --git a/gun.js b/gun.js index 810dda61f..fc34f07ba 100644 --- a/gun.js +++ b/gun.js @@ -220,6 +220,7 @@ } 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 @@ -240,21 +241,22 @@ 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 = []; - //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) } + //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`. @@ -1684,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['/'] } @@ -1980,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(){ diff --git a/lib/webrtc.js b/lib/webrtc.js index b746466ee..60fe0e490 100644 --- a/lib/webrtc.js +++ b/lib/webrtc.js @@ -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; @@ -33,44 +33,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,31 +105,27 @@ } 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; } From 1c095b13e9226c3090a17f0e7d5bf41b82926abc Mon Sep 17 00:00:00 2001 From: Malcolm Blaney <mblaney@gmail.com> Date: Fri, 5 Apr 2024 04:01:15 +1000 Subject: [PATCH 28/31] Check atom exists in graph when deciding to read from disk (#1371) --- lib/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/store.js b/lib/store.js index f86634bf7..8c567a548 100644 --- a/lib/store.js +++ b/lib/store.js @@ -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 } } From 03735dc09cf760ec9283076667d23b9076340742 Mon Sep 17 00:00:00 2001 From: Andreas Heissenberger <andreas@heissenberger.at> Date: Mon, 15 Apr 2024 21:42:03 +0200 Subject: [PATCH 29/31] fix: ERROR: Radisk needs `store.put` interface (#1374) --- lib/rfs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rfs.js b/lib/rfs.js index c43b14f80..8e37fe5af 100644 --- a/lib/rfs.js +++ b/lib/rfs.js @@ -83,7 +83,7 @@ Gun.on('create', function(root){ this.to.next(root); var opt = root.opt; if(opt.rfs === false){ return } - opt.store = opt.store || (!Gun.window || opt.rfs === true && Store(opt)); + opt.store = opt.store || ((!Gun.window || opt.rfs === true) && Store(opt)); }); module.exports = Store; \ No newline at end of file From 7cc4cce1a34cd0f7ebaf90b7de221688dc23cc54 Mon Sep 17 00:00:00 2001 From: carlin978 <120719190+carlin978@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:56:40 +0100 Subject: [PATCH 30/31] Update STUN servers (#1381) Commented out sipgate.net STUN server. Added Cloudflare STUN server. --- lib/webrtc.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/webrtc.js b/lib/webrtc.js index 60fe0e490..77882a516 100644 --- a/lib/webrtc.js +++ b/lib/webrtc.js @@ -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"}, @@ -130,4 +131,4 @@ return peer; } }); -}()); \ No newline at end of file +}()); From 90b88959d0de8a5db6f58aba3e4dbe2e1f31a60f Mon Sep 17 00:00:00 2001 From: Mark Nadal <mark@gun.eco> Date: Sat, 23 Nov 2024 17:47:13 -0800 Subject: [PATCH 31/31] universal notification system --- lib/axe.js | 31 ++++++++++++++++++++++++++++++- lib/wire.js | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/axe.js b/lib/axe.js index 1d84382df..e004ad419 100644 --- a/lib/axe.js +++ b/lib/axe.js @@ -238,7 +238,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; diff --git a/lib/wire.js b/lib/wire.js index 870976e31..f01eae161 100644 --- a/lib/wire.js +++ b/lib/wire.js @@ -67,6 +67,7 @@ Gun.on('opt', function (root) { 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);