patchboot
Version:
A Scuttlebutt bootloader
1,921 lines (1,693 loc) • 237 kB
JavaScript
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getAugmentedNamespace(n) {
if (n.__esModule) return n;
var a = Object.defineProperty({}, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
function createCommonjsModule(fn) {
var module = { exports: {} };
return fn(module, module.exports), module.exports;
}
var abortCb = function abortCb(cb, abort, onAbort) {
cb(abort);
onAbort && onAbort(abort === true ? null: abort);
return
};
var values = function values (array, onAbort) {
if(!array)
return function (abort, cb) {
if(abort) return abortCb(cb, abort, onAbort)
return cb(true)
}
if(!Array.isArray(array))
array = Object.keys(array).map(function (k) {
return array[k]
});
var i = 0;
return function (abort, cb) {
if(abort)
return abortCb(cb, abort, onAbort)
if(i >= array.length)
cb(true);
else
cb(null, array[i++]);
}
};
var keys = function (object) {
return values(Object.keys(object))
};
var once = function once (value, onAbort) {
return function (abort, cb) {
if(abort)
return abortCb(cb, abort, onAbort)
if(value != null) {
var _value = value; value = null;
cb(null, _value);
} else
cb(true);
}
};
var count = function count (max) {
var i = 0; max = max || Infinity;
return function (end, cb) {
if(end) return cb && cb(end)
if(i > max)
return cb(true)
cb(null, i++);
}
};
var infinite = function infinite (generate) {
generate = generate || Math.random;
return function (end, cb) {
if(end) return cb && cb(end)
return cb(null, generate())
}
};
//a stream that ends immediately.
var empty = function empty () {
return function (abort, cb) {
cb(true);
}
};
//a stream that errors immediately.
var error = function error (err) {
return function (abort, cb) {
cb(err);
}
};
var sources = {
keys: keys,
once: once,
values: values,
count: count,
infinite: infinite,
empty: empty,
error: error
};
var drain = function drain (op, done) {
var read, abort;
function sink (_read) {
read = _read;
if(abort) return sink.abort()
//this function is much simpler to write if you
//just use recursion, but by using a while loop
//we do not blow the stack if the stream happens to be sync.
;(function next() {
var loop = true, cbed = false;
while(loop) {
cbed = false;
read(null, function (end, data) {
cbed = true;
if(end = end || abort) {
loop = false;
if(done) done(end === true ? null : end);
else if(end && end !== true)
throw end
}
else if(op && false === op(data) || abort) {
loop = false;
read(abort || true, done || function () {});
}
else if(!loop){
next();
}
});
if(!cbed) {
loop = false;
return
}
}
})();
}
sink.abort = function (err, cb) {
if('function' == typeof err)
cb = err, err = true;
abort = err || true;
if(read) return read(abort, cb || function () {})
};
return sink
};
var onEnd = function onEnd (done) {
return drain(null, done)
};
var log = function log (done) {
return drain(function (data) {
console.log(data);
}, done)
};
var prop = function prop (key) {
return key && (
'string' == typeof key
? function (data) { return data[key] }
: 'object' === typeof key && 'function' === typeof key.exec //regexp
? function (data) { var v = key.exec(data); return v && v[0] }
: key
)
};
function id (e) { return e }
var find = function find (test, cb) {
var ended = false;
if(!cb)
cb = test, test = id;
else
test = prop(test) || id;
return drain(function (data) {
if(test(data)) {
ended = true;
cb(null, data);
return false
}
}, function (err) {
if(ended) return //already called back
cb(err === true ? null : err, null);
})
};
var reduce = function reduce (reducer, acc, cb ) {
if(!cb) cb = acc, acc = null;
var sink = drain(function (data) {
acc = reducer(acc, data);
}, function (err) {
cb(err, acc);
});
if (arguments.length === 2)
return function (source) {
source(null, function (end, data) {
//if ended immediately, and no initial...
if(end) return cb(end === true ? null : end)
acc = data; sink(source);
});
}
else
return sink
};
var collect = function collect (cb) {
return reduce(function (arr, item) {
arr.push(item);
return arr
}, [], cb)
};
var concat = function concat (cb) {
return reduce(function (a, b) {
return a + b
}, '', cb)
};
var sinks = {
drain: drain,
onEnd: onEnd,
log: log,
find: find,
reduce: reduce,
collect: collect,
concat: concat
};
function id$1 (e) { return e }
var map = function map (mapper) {
if(!mapper) return id$1
mapper = prop(mapper);
return function (read) {
return function (abort, cb) {
read(abort, function (end, data) {
try {
data = !end ? mapper(data) : null;
} catch (err) {
return read(err, function () {
return cb(err)
})
}
cb(end, data);
});
}
}
};
function id$2 (e) { return e }
var asyncMap = function asyncMap (map) {
if(!map) return id$2
map = prop(map);
var busy = false, abortCb, aborted;
return function (read) {
return function next (abort, cb) {
if(aborted) return cb(aborted)
if(abort) {
aborted = abort;
if(!busy) read(abort, function (err) {
//incase the source has already ended normally,
//we should pass our own error.
cb(abort);
});
else read(abort, function (err) {
//if we are still busy, wait for the mapper to complete.
if(busy) abortCb = cb;
else cb(abort);
});
}
else
read(null, function (end, data) {
if(end) cb(end);
else if(aborted) cb(aborted);
else {
busy = true;
map(data, function (err, data) {
busy = false;
if(aborted) {
cb(aborted);
abortCb && abortCb(aborted);
}
else if(err) next (err, cb);
else cb(null, data);
});
}
});
}
}
};
function id$3 (e) { return e }
var tester = function tester (test) {
return (
'object' === typeof test && 'function' === typeof test.test //regexp
? function (data) { return test.test(data) }
: prop (test) || id$3
)
};
var filter = function filter (test) {
//regexp
test = tester(test);
return function (read) {
return function next (end, cb) {
var sync, loop = true;
while(loop) {
loop = false;
sync = true;
read(end, function (end, data) {
if(!end && !test(data))
return sync ? loop = true : next(end, cb)
cb(end, data);
});
sync = false;
}
}
}
};
var filterNot = function filterNot (test) {
test = tester(test);
return filter(function (data) { return !test(data) })
};
//a pass through stream that doesn't change the value.
var through = function through (op, onEnd) {
var a = false;
function once (abort) {
if(a || !onEnd) return
a = true;
onEnd(abort === true ? null : abort);
}
return function (read) {
return function (end, cb) {
if(end) once(end);
return read(end, function (end, data) {
if(!end) op && op(data);
else once(end);
cb(end, data);
})
}
}
};
//read a number of items and then stop.
var take = function take (test, opts) {
opts = opts || {};
var last = opts.last || false; // whether the first item for which !test(item) should still pass
var ended = false;
if('number' === typeof test) {
last = true;
var n = test; test = function () {
return --n
};
}
return function (read) {
function terminate (cb) {
read(true, function (err) {
last = false; cb(err || true);
});
}
return function (end, cb) {
if(ended && !end) last ? terminate(cb) : cb(ended);
else if(ended = end) read(ended, cb);
else
read(null, function (end, data) {
if(ended = ended || end) {
//last ? terminate(cb) :
cb(ended);
}
else if(!test(data)) {
ended = true;
last ? cb(null, data) : terminate(cb);
}
else
cb(null, data);
});
}
}
};
function id$4 (e) { return e }
//drop items you have already seen.
var unique = function unique (field, invert) {
field = prop(field) || id$4;
var seen = {};
return filter(function (data) {
var key = field(data);
if(seen[key]) return !!invert //false, by default
else seen[key] = true;
return !invert //true by default
})
};
//passes an item through when you see it for the second time.
var nonUnique = function nonUnique (field) {
return unique(field, true)
};
//convert a stream of arrays or streams into just a stream.
var flatten = function flatten () {
return function (read) {
var _read;
return function (abort, cb) {
if (abort) { //abort the current stream, and then stream of streams.
_read ? _read(abort, function(err) {
read(err || abort, cb);
}) : read(abort, cb);
}
else if(_read) nextChunk();
else nextStream();
function nextChunk () {
_read(null, function (err, data) {
if (err === true) nextStream();
else if (err) {
read(true, function(abortErr) {
// TODO: what do we do with the abortErr?
cb(err);
});
}
else cb(null, data);
});
}
function nextStream () {
_read = null;
read(null, function (end, stream) {
if(end)
return cb(end)
if(Array.isArray(stream) || stream && 'object' === typeof stream)
stream = values(stream);
else if('function' != typeof stream)
stream = once(stream);
_read = stream;
nextChunk();
});
}
}
}
};
var throughs = {
map: map,
asyncMap: asyncMap,
filter: filter,
filterNot: filterNot,
through: through,
take: take,
unique: unique,
nonUnique: nonUnique,
flatten: flatten
};
var pull = function pull (a) {
var length = arguments.length;
if (typeof a === 'function' && a.length === 1) {
var args = new Array(length);
for(var i = 0; i < length; i++)
args[i] = arguments[i];
return function (read) {
if (args == null) {
throw new TypeError("partial sink should only be called once!")
}
// Grab the reference after the check, because it's always an array now
// (engines like that kind of consistency).
var ref = args;
args = null;
// Prioritize common case of small number of pulls.
switch (length) {
case 1: return pull(read, ref[0])
case 2: return pull(read, ref[0], ref[1])
case 3: return pull(read, ref[0], ref[1], ref[2])
case 4: return pull(read, ref[0], ref[1], ref[2], ref[3])
default:
ref.unshift(read);
return pull.apply(null, ref)
}
}
}
var read = a;
if (read && typeof read.source === 'function') {
read = read.source;
}
for (var i = 1; i < length; i++) {
var s = arguments[i];
if (typeof s === 'function') {
read = s(read);
} else if (s && typeof s === 'object') {
s.sink(read);
read = s.source;
}
}
return read
};
var pullStream = createCommonjsModule(function (module, exports) {
exports = module.exports = pull;
exports.pull = exports;
for(var k in sources)
exports[k] = sources[k];
for(var k in throughs)
exports[k] = throughs[k];
for(var k in sinks)
exports[k] = sinks[k];
});
pullStream.paraMap = pullStream.paraMap;
class VotesManager {
constructor(sbot) {
this.sbot = sbot;
}
vote(id, value) {
return new Promise((resolve, reject) => {
this.sbot.publish({
type: 'vote',
vote: {
'link': id,
value,
expression: value ? 'Like' : 'Unlike'
}
}, function (err, msg) {
if (err) {
reject(err);
} else {
resolve(true);
}
});
})
}
getVotes(id) {
const queryOpts = {
reverse: false,
query: [{
$filter: {
value: {
content: {
type: 'vote',
vote: {
link: id
}
}
}
}
},
{
$map: {
author: ['value', 'author'],
value: ['value', 'content', 'vote', 'value']
}
}]
};
const backlinksOpts = {
reverse: false,
query: [{
$filter: {
dest: id,
value: {
content: {
type: 'vote',
vote: {
link: id
}
}
}
}
},
{
$map: {
author: ['value', 'author'],
value: ['value', 'content', 'vote', 'value']
}
}]
};
const votesMapPromises = new Promise((resolve, reject) => {
const votes = new Map();
pullStream(
this.sbot.backlinks ? this.sbot.backlinks.read(backlinksOpts) : this.sbot.query.read(queryOpts),
/*pull.drain((msg) => {
votes.set(msg.author, msg.value)
},
() => {
resolve(votes);
})*/
pullStream.collect((err, msgs) => {
if (err) {
reject(err);
} else {
msgs.forEach(msg => {
//console.log('msg', msg)
votes.set(msg.author, msg.value);
});
resolve(votes);
}
})
);
});
return votesMapPromises.then(votesMap => votesMap.entries())
.then(entries => [...entries].filter(mapping => mapping[1] > 0))
.then(filtered => filtered.map(tuple => tuple[0]))
}
getOwnVote(msgID) {
return new Promise((resolve, reject) => {
this.sbot.whoami().then(thisisme => {
const feedID = thisisme.id;
return this.getVotes(msgID).then(votes => {
resolve(votes.indexOf(feedID) > -1);
})
}).catch(reject);
})
}
}
pullStream.paraMap = pullStream.paraMap;
class IdentityManager {
constructor(sbot) {
this.sbot = sbot;
}
/** returns a promise for the self-assigned name of the user */
getSelfAssignedName(id) {
const queryOpts = {
reverse: true,
limit: 1,
query: [{
$filter: {
value: {
author: id,
content: {
type: 'about',
about: id,
name: { $is: 'string' }
}
}
}
},
{
$map: {
name: ['value', 'content', 'name']
}
}]
};
const backlinksOpts = {
reverse: true,
limit: 1,
query: [{
$filter: {
dest: id,
value: {
author: id,
content: {
type: 'about',
about: id,
name: { $is: 'string' }
}
}
}
},
{
$map: {
name: ['value', 'content', 'name']
}
}]
};
return new Promise((resolve, reject) => {
pullStream(
this.sbot.backlinks ? this.sbot.backlinks.read(backlinksOpts) : this.sbot.query.read(queryOpts),
pullStream.collect((err, data) => {
if (err) {
reject(err);
} else {
if (data.length > 0) {
resolve(data[0].name);
} else {
reject('the user hasn\'t assigned a name to themself yet');
}
}
})
);
})
}
}
const _IdentityManager = IdentityManager;
class AppController extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const appDescription = this.msg.value;
const votesManager = new VotesManager(this.sbot);
const controllerArea = this.attachShadow({ mode: 'open' });
controllerArea.innerHTML = `
<style>
* {
box-sizing: border-box;
}
#app {
font-size: 16px;
font-family: Inter, 'Helvetica Neue', Arial, Helvetica, sans-serif;
border-bottom: 1px solid gray;
width: 100%;
margin: 0;
padding: 8px 6px 0 8px;
}
.bar {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
padding-bottom: 8px;
white-space: nowrap;
}
.info > * {
text-overflow: ellipsis;
overflow-x: hidden;
}
.details {
padding-bottom: 0;
overflow-x: hidden;
height: 0;
white-space: normal;
}
#app.expanded .info * {
height: unset;
white-space: normal;
}
#app.expanded .details {
padding-bottom: 8px;
height: auto;
}
.name {
font-size: 17px;
line-height: 20px;
height: 20px;
font-weight: 600;
}
#author,
.time,
#author-id {
font-size: 13px;
line-height: 16px;
height: 16px;
}
.time:not(:empty)::before {
content: 'Published on ';
color: gray;
}
#author-id {
font-family: monospace;
color: gray;
}
.details .actions {
float: right;
}
.actions {
display: flex;
margin-left: 6px;
}
.actions button {
margin: 0 2px;
padding: 6px;
border: none;
border-radius: 50%;
height: 36px;
background-color: #f8f8f8;
}
.actions button:hover {
background-color: #dddddd;
}
.actions button svg {
display: block;
height: 24px;
width: 24px;
}
.svghover .onhover {
display: none;
}
.svghover:hover path {
display: none;
}
.svghover:hover .onhover {
display: unset;
}
.hidden {
display: none;
}
.count {
position: relative;
}
.count[data-count]::before {
content: attr(data-count);
position: absolute;
bottom: 0;
left: 0;
font-size: 13px;
line-height: 13px;
font-weight: 600;
padding: 0 0 4px 4px;
border-top-right-radius: 4px;
/* color: #ff2f92; */
background-color: #f8f8f8;
background: linear-gradient(45deg, rgba(255,255,255,0) 0%, #f8f8f8 100%);
}
.count[data-count]:hover::before {
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 50%, rgba(221, 221, 221, 1) 100%);
}
</style>
<div id="app">
<div class="bar">
<div class="info">
<div class="name">${appDescription.content.name || appDescription.content.mentions[0].name || appDescription.content.comment || ''}</div>
<div id="author"></div>
</div>
<div class="actions">
<button title="Like" id="like" class="svghover hidden count">
<svg width="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,6 11.07,7.36H12.93C13.46,6 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55M16.5,3C14.76,3 13.09,3.81 12,5.08C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.41 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.27 22,8.5C22,5.41 19.58,3 16.5,3Z" />
<path class="onhover" fill="currentColor"
d="M12.67 20.74L12 21.35L10.55 20.03C5.4 15.36 2 12.27 2 8.5C2 5.41 4.42 3 7.5 3C9.24 3 10.91 3.81 12 5.08C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.41 22 8.5C22 9.93 21.5 11.26 20.62 12.61C20 12.31 19.31 12.11 18.59 12.04C19.5 10.8 20 9.65 20 8.5C20 6.5 18.5 5 16.5 5C14.96 5 13.46 6 12.93 7.36H11.07C10.54 6 9.04 5 7.5 5C5.5 5 4 6.5 4 8.5C4 11.39 7.14 14.24 11.89 18.55L12 18.65L12.04 18.61C12.12 19.37 12.34 20.09 12.67 20.74M17 14V17H14V19H17V22H19V19H22V17H19V14H17Z" />
</svg>
</button>
<button title="Unlike" id="unlike" class="svghover hidden count">
<svg width="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" />
<path class="onhover" fill="currentColor"
d="M12 18C12 19 12.25 19.92 12.67 20.74L12 21.35L10.55 20.03C5.4 15.36 2 12.27 2 8.5C2 5.41 4.42 3 7.5 3C9.24 3 10.91 3.81 12 5.08C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.41 22 8.5C22 9.93 21.5 11.26 20.62 12.61C19.83 12.23 18.94 12 18 12C14.69 12 12 14.69 12 18M14 17V19H22V17H14Z" />
</svg>
</button>
<button title="Run" id="run">
<svg width="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" />
</svg>
</button>
</div>
</div>
<div class="details bar">
<div class="info">
<div class="actions">
<button title="Revoke App" id="revoke" class="hidden">
<svg width="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" />
</svg>
</button>
<button title="View Source" id="source">
<svg width="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6.12,15.5L9.86,19.24L11.28,17.83L8.95,15.5L11.28,13.17L9.86,11.76L6.12,15.5M17.28,15.5L13.54,11.76L12.12,13.17L14.45,15.5L12.12,17.83L13.54,19.24L17.28,15.5Z" />
</svg>
</button>
</div>
<div class="comment">${appDescription.content.comment || ''}</div>
<div id="author-id">${appDescription.author}</div>
<div class="time">${(new Date(appDescription.timestamp)).toLocaleString() || ''}</div>
</div>
</div>
</div>`;
const appEl = controllerArea.getElementById('app');
appEl.addEventListener('click', e => {
appEl.classList.toggle('expanded');
});
const renderLikesStuff = () => {
votesManager.getVotes(this.msg.key).then(likes => {
const count = likes.length;
if (count > 0) {
controllerArea.querySelectorAll('.count').forEach(e => e.setAttribute('data-count', count));
} else {
controllerArea.querySelectorAll('.count').forEach(e => e.removeAttribute('data-count'));
}
});
votesManager.getOwnVote(this.msg.key).then(liked => {
if (liked) {
this.classList.add('liked');
controllerArea.getElementById('like').classList.add('hidden');
controllerArea.getElementById('unlike').classList.remove('hidden');
} else {
this.classList.remove('liked');
controllerArea.getElementById('like').classList.remove('hidden');
controllerArea.getElementById('unlike').classList.add('hidden');
}
}, e => {
console.log("error getting own vote:", e);
});
};
renderLikesStuff();
this.sbot.whoami().then(
currentUser => this.msg.value.author === currentUser.id)
.then(own => {
if (own) {
controllerArea.getElementById('revoke').classList.remove('hidden');
}
})
;(new _IdentityManager(this.sbot)).getSelfAssignedName(appDescription.author).then(name => {
controllerArea.getElementById('author').innerHTML = name;
}).catch(e => console.log(e));
controllerArea.getElementById('run').addEventListener('click', e => {
e.stopPropagation();
this.dispatchEvent(new Event('run'));
});
controllerArea.getRootNode().getElementById('source').addEventListener('click', e => {
e.stopPropagation();
this.dispatchEvent(new Event('view-source'));
});
controllerArea.getRootNode().getElementById('like').addEventListener('click', async e => {
e.stopPropagation();
await votesManager.vote(this.msg.key, 1);
renderLikesStuff();
this.dispatchEvent(new Event('like'));
});
controllerArea.getRootNode().getElementById('unlike').addEventListener('click', async e => {
e.stopPropagation();
await votesManager.vote(this.msg.key, 0);
renderLikesStuff();
this.dispatchEvent(new Event('unlike'));
});
controllerArea.getRootNode().getElementById('revoke').addEventListener('click', async e => {
e.stopPropagation();
if (!confirm("Revoke App? This action cannot be undone.")) {
return;
}
await this.revoke();
this.parentElement.removeChild(this);
this.dispatchEvent(new Event('revoked'));
});
}
revoke() {
return new Promise((resolve, reject) => {
this.sbot.publish({
type: 'about',
about: this.msg.key,
status: 'revoked'
}, function (err, msg) {
if (err) {
reject(err);
} else {
resolve(true);
}
});
})
}
}
customElements.define("app-controller", AppController);
class FollowScuttleboot extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const area = this.attachShadow({ mode: 'open' });
area.innerHTML = `
<div>
You might see more apps by following an connecting to scuttleboot.app.
<button id="follow">Follow scuttleboot.app</button>
</div>`;
const button = area.getElementById('follow');
button.addEventListener('click', () => {
this.sbot.publish({
"type": "contact",
"following": true,
"contact": "@luoZnBKHXeJl4kB39uIkZnQD4L0zl6Vd+Pe75gKS4fo=.ed25519"
}, console.log);
const multiAddr = 'wss://scuttleboot.app~shs:luoZnBKHXeJl4kB39uIkZnQD4L0zl6Vd+Pe75gKS4fo=;net:scuttleboot.app:8088~shs:luoZnBKHXeJl4kB39uIkZnQD4L0zl6Vd+Pe75gKS4fo=';
if (this.sbot.conn?.connect) {
this.sbot.conn.remember(multiAddr, {
'type': 'pub',
'autoconnect': true
});
this.sbot.conn.connect(multiAddr, console.log);
} else {
if (this.sbot.gossip?.add) {
this.sbot.gossip.add(multiAddr, console.log);
}
}
area.innerHTML = '';
});
}
}
customElements.define("follow-scuttleboot", FollowScuttleboot);
class AppSelector extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const controllerArea = this.attachShadow({ mode: 'open' });
const view = document.getElementById('view');
const opts = {
live: true,
reverse: false,
query: [
{
$filter: {
value: {
content: { type: {$prefix: 'patchboot-'} }
}
}
},
{
$filter: {
value: {
content: { type: {$in: ['patchboot-app','patchboot-webapp'] } }
}
}
}
]
};
controllerArea.innerHTML = `
<style>
* {
box-sizing: border-box;
overflow-wrap: anywhere;
}
app-controller {
--spacing: 0.5rem;
--lineColor: var(--lineColor2);
}
#apps {
border-radius: 0;
padding: 0;
min-height: 1rem;
max-height: 100%;
overflow-y: scroll;
}
.show-only-liked app-controller:not(.liked) {
display: none;
}
.top {
border-bottom: 1px solid gray;
width: 100%;
display: block;
}
</style>
<label class="top"><input type="checkbox" id="showLiked" />Show only apps I like</label>`;
const appsGrid = document.createElement('div');
appsGrid.id = 'apps';
controllerArea.appendChild(appsGrid);
this.sbot.whoami().then(keys => this.sbot.friends.isFollowing({
source: keys.id,
dest: '@luoZnBKHXeJl4kB39uIkZnQD4L0zl6Vd+Pe75gKS4fo=.ed25519'
})).then(followingSboot => {
if (!followingSboot) {
const followScuttleboot = document.createElement('follow-scuttleboot');
followScuttleboot.sbot = this.sbot;
controllerArea.append(followScuttleboot);
} else {
console.log('Allready following scuttleboot.app');
}
});
const showLikedcheckbox = controllerArea.getElementById('showLiked');
showLikedcheckbox.addEventListener('change', (e) => {
if (showLikedcheckbox.checked) {
appsGrid.classList.add('show-only-liked');
} else {
appsGrid.classList.remove('show-only-liked');
}
});
const sbot = this.sbot;
pullStream(sbot.query.read(opts), pullStream.drain((msg) => {
if (!msg.value) {
return;
}
ensureNotRevoked(sbot, msg).then(() => {
const controller = document.createElement('app-controller');
controller.msg = msg;
controller.sbot = sbot;
appsGrid.insertBefore(controller, appsGrid.firstChild);
const blobId = msg.value.content.link || msg.value.content.mentions[0].link;
controller.addEventListener('run', () => {
this.dispatchEvent(new CustomEvent('run', {detail: msg.value.content}));
});
controller.addEventListener('view-source', () => {
this.dispatchEvent(new CustomEvent('show-source', {detail: msg.value.content}));
});
controller.addEventListener('like', async () => {
try {
console.log(await VotesManager.getVotes(msg.key));
} catch (e) {
console.log('error', e);
}
return true
});
controller.addEventListener('unlike', () => {
//vote(msg.key, 0)
});
}).catch(() => { });
}, () => console.log('End of apps stream reached.')));
}
}
function ensureNotRevoked(sbot, msg) {
return new Promise((resolve, reject) => {
const queryOpts = {
reverse: true,
query: [
{
$filter: {
value: {
content: {
about: msg.key,
type: 'about',
status: 'revoked'
}
}
}
}
],
limit: 1
};
const backlinksOpts = {
reverse: true,
query: [
{
$filter: {
dest: msg.key,
value: {
content: {
about: msg.key,
type: 'about',
status: 'revoked'
}
}
}
}
],
limit: 1
};
pullStream(
sbot.backlinks ? sbot.backlinks.read(backlinksOpts) : sbot.query.read(queryOpts),
pullStream.collect((err, revocations) => {
if (err) {
reject(err);
} else {
if (revocations.length > 0) {
reject();
} else {
resolve();
}
}
}));
})
}
customElements.define("app-selector", AppSelector);
var global$1 = (typeof global !== "undefined" ? global :
typeof self !== "undefined" ? self :
typeof window !== "undefined" ? window : {});
var lookup = [];
var revLookup = [];
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
var inited = false;
function init () {
inited = true;
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
for (var i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i];
revLookup[code.charCodeAt(i)] = i;
}
revLookup['-'.charCodeAt(0)] = 62;
revLookup['_'.charCodeAt(0)] = 63;
}
function toByteArray (b64) {
if (!inited) {
init();
}
var i, j, l, tmp, placeHolders, arr;
var len = b64.length;
if (len % 4 > 0) {
throw new Error('Invalid string. Length must be a multiple of 4')
}
// the number of equal signs (place holders)
// if there are two placeholders, than the two characters before it
// represent one byte
// if there is only one, then the three characters before it represent 2 bytes
// this is just a cheap hack to not do indexOf twice
placeHolders = b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0;
// base64 is 4/3 + up to two characters of the original data
arr = new Arr(len * 3 / 4 - placeHolders);
// if there are placeholders, only get up to the last complete 4 chars
l = placeHolders > 0 ? len - 4 : len;
var L = 0;
for (i = 0, j = 0; i < l; i += 4, j += 3) {
tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)];
arr[L++] = (tmp >> 16) & 0xFF;
arr[L++] = (tmp >> 8) & 0xFF;
arr[L++] = tmp & 0xFF;
}
if (placeHolders === 2) {
tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4);
arr[L++] = tmp & 0xFF;
} else if (placeHolders === 1) {
tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2);
arr[L++] = (tmp >> 8) & 0xFF;
arr[L++] = tmp & 0xFF;
}
return arr
}
function tripletToBase64 (num) {
return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
}
function encodeChunk (uint8, start, end) {
var tmp;
var output = [];
for (var i = start; i < end; i += 3) {
tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
output.push(tripletToBase64(tmp));
}
return output.join('')
}
function fromByteArray (uint8) {
if (!inited) {
init();
}
var tmp;
var len = uint8.length;
var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
var output = '';
var parts = [];
var maxChunkLength = 16383; // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)));
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1];
output += lookup[tmp >> 2];
output += lookup[(tmp << 4) & 0x3F];
output += '==';
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + (uint8[len - 1]);
output += lookup[tmp >> 10];
output += lookup[(tmp >> 4) & 0x3F];
output += lookup[(tmp << 2) & 0x3F];
output += '=';
}
parts.push(output);
return parts.join('')
}
function read (buffer, offset, isLE, mLen, nBytes) {
var e, m;
var eLen = nBytes * 8 - mLen - 1;
var eMax = (1 << eLen) - 1;
var eBias = eMax >> 1;
var nBits = -7;
var i = isLE ? (nBytes - 1) : 0;
var d = isLE ? -1 : 1;
var s = buffer[offset + i];
i += d;
e = s & ((1 << (-nBits)) - 1);
s >>= (-nBits);
nBits += eLen;
for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
m = e & ((1 << (-nBits)) - 1);
e >>= (-nBits);
nBits += mLen;
for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
if (e === 0) {
e = 1 - eBias;
} else if (e === eMax) {
return m ? NaN : ((s ? -1 : 1) * Infinity)
} else {
m = m + Math.pow(2, mLen);
e = e - eBias;
}
return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
}
function write (buffer, value, offset, isLE, mLen, nBytes) {
var e, m, c;
var eLen = nBytes * 8 - mLen - 1;
var eMax = (1 << eLen) - 1;
var eBias = eMax >> 1;
var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0);
var i = isLE ? 0 : (nBytes - 1);
var d = isLE ? 1 : -1;
var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
value = Math.abs(value);
if (isNaN(value) || value === Infinity) {
m = isNaN(value) ? 1 : 0;
e = eMax;
} else {
e = Math.floor(Math.log(value) / Math.LN2);
if (value * (c = Math.pow(2, -e)) < 1) {
e--;
c *= 2;
}
if (e + eBias >= 1) {
value += rt / c;
} else {
value += rt * Math.pow(2, 1 - eBias);
}
if (value * c >= 2) {
e++;
c /= 2;
}
if (e + eBias >= eMax) {
m = 0;
e = eMax;
} else if (e + eBias >= 1) {
m = (value * c - 1) * Math.pow(2, mLen);
e = e + eBias;
} else {
m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
e = 0;
}
}
for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
e = (e << mLen) | m;
eLen += mLen;
for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
buffer[offset + i - d] |= s * 128;
}
var toString = {}.toString;
var isArray = Array.isArray || function (arr) {
return toString.call(arr) == '[object Array]';
};
var INSPECT_MAX_BYTES = 50;
/**
* If `Buffer.TYPED_ARRAY_SUPPORT`:
* === true Use Uint8Array implementation (fastest)
* === false Use Object implementation (most compatible, even IE6)
*
* Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
* Opera 11.6+, iOS 4.2+.
*
* Due to various browser bugs, sometimes the Object implementation will be used even
* when the browser supports typed arrays.
*
* Note:
*
* - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
* See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
*
* - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
*
* - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
* incorrect length in some situations.
* We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
* get the Object implementation, which is slower but behaves correctly.
*/
Buffer.TYPED_ARRAY_SUPPORT = global$1.TYPED_ARRAY_SUPPORT !== undefined
? global$1.TYPED_ARRAY_SUPPORT
: true;
function kMaxLength () {
return Buffer.TYPED_ARRAY_SUPPORT
? 0x7fffffff
: 0x3fffffff
}
function createBuffer (that, length) {
if (kMaxLength() < length) {
throw new RangeError('Invalid typed array length')
}
if (Buffer.TYPED_ARRAY_SUPPORT) {
// Return an augmented `Uint8Array` instance, for best performance
that = new Uint8Array(length);
that.__proto__ = Buffer.prototype;
} else {
// Fallback: Return an object instance of the Buffer class
if (that === null) {
that = new Buffer(length);
}
that.length = length;
}
return that
}
/**
* The Buffer constructor returns instances of `Uint8Array` that have their
* prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
* `Uint8Array`, so the returned instances will have all the node `Buffer` methods
* and the `Uint8Array` methods. Square bracket notation works as expected -- it
* returns a single octet.
*
* The `Uint8Array` prototype remains unmodified.
*/
function Buffer (arg, encodingOrOffset, length) {
if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
return new Buffer(arg, encodingOrOffset, length)
}
// Common case.
if (typeof arg === 'number') {
if (typeof encodingOrOffset === 'string') {
throw new Error(
'If encoding is specified then the first argument must be a string'
)
}
return allocUnsafe(this, arg)
}
return from(this, arg, encodingOrOffset, length)
}
Buffer.poolSize = 8192; // not used by this implementation
// TODO: Legacy, not needed anymore. Remove in next major version.
Buffer._augment = function (arr) {
arr.__proto__ = Buffer.prototype;
return arr
};
function from (that, value, encodingOrOffset, length) {
if (typeof value === 'number') {
throw new TypeError('"value" argument must not be a number')
}
if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
return fromArrayBuffer(that, value, encodingOrOffset, length)
}
if (typeof value === 'string') {
return fromString(that, value, encodingOrOffset)
}
return fromObject(that, value)
}
/**
* Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
* if value is a number.
* Buffer.from(str[, encoding])
* Buffer.from(array)
* Buffer.from(buffer)
* Buffer.from(arrayBuffer[, byteOffset[, length]])
**/
Buffer.from = function (value, encodingOrOffset, length) {
return from(null, value, encodingOrOffset, length)
};
if (Buffer.TYPED_ARRAY_SUPPORT) {
Buffer.prototype.__proto__ = Uint8Array.prototype;
Buffer.__proto__ = Uint8Array;
}
function assertSize (size) {
if (typeof size !== 'number') {
throw new TypeError('"size" argument must be a number')
} else if (size < 0) {
throw new RangeError('"size" argument must not be negative')
}
}
function alloc (that, size, fill, encoding) {
assertSize(size);
if (size <= 0) {
return createBuffer(that, size)
}
if (fill !== undefined) {
// Only pay attention to encoding if it's a string. This
// prevents accidentally sending in a number that would
// be interpretted as a start offset.
return typeof encoding === 'string'
? createBuffer(that, size).fill(fill, encoding)
: createBuffer(that, size).fill(fill)
}
return createBuffer(that, size)
}
/**
* Creates a new filled Buffer instance.
* alloc(size[, fill[, encoding]])
**/
Buffer.alloc = function (size, fill, encoding) {
return alloc(null, size, fill, encoding)
};
function allocUnsafe (that, size) {
assertSize(size);
that = createBuffer(that, size < 0 ? 0 : checked(size) | 0);
if (!Buffer.TYPED_ARRAY_SUPPORT) {
for (var i = 0; i < size; ++i) {
that[i] = 0;
}
}
return that
}
/**
* Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
* */
Buffer.allocUnsafe = function (size) {
return allocUnsafe(null, size)
};
/**
* Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
*/
Buffer.allocUnsafeSlow = function (size) {
return allocUnsafe(null, size)
};
function fromString (that, string, encoding) {
if (typeof encoding !== 'string' || encoding === '') {
encoding = 'utf8';
}
if (!Buffer.isEncoding(encoding)) {
throw new TypeError('"encoding" must be a valid string encoding')
}
var length = byteLength(string, encoding) | 0;
that = createBuffer(that, length);
var actual = that.write(string, encoding);
if (actual !== length) {
// Writing a hex string, for example, that contains invalid characters will
// cause everything after the first invalid character to be ignored. (e.g.
// 'abxxcd' will be treated as 'ab')
that = that.slice(0, actual);
}
return that
}
function fromArrayLike (that, array) {
var length = array.length < 0 ? 0 : checked(array.length) | 0;
that = createBuffer(that, length);
for (var i = 0; i < length; i += 1) {
that[i] = array[i] & 255;
}
return that
}
function fromArrayBuffer (that, array, byteOffset, length) {
array.byteLength; // this throws if `array` is not a valid ArrayBuffer
if (byteOffset < 0 || array.byteLength < byteOffset) {
throw new RangeError('\'offset\' is out of bounds')
}
if (array.byteLength < byteOffset + (length || 0)) {
throw new RangeError('\'length\' is out of bounds')
}
if (byteOffset === undefined && length === undefined) {
array = new Uint8Array(array);
} else if (length === undefined) {
array = new Uint8Array(array, byteOffset);
} else {
array = new Uint8Array(array, byteOffset, length);
}
if (Buffer.TYPED_ARRAY_SUPPORT) {
// Return an augmented `Uint8Array` instance, for best performance
that = array;
that.__proto__ = Buffer.prototype;
} else {
// Fallback: Return an object instance of the Buffer class
that = fromArrayLike(that, array);
}
return that
}
function fromObject (that, obj) {
if (internalIsBuffer(obj)) {
var len = checked(obj.length) | 0;
that = createBuffer(that, len);
if (that.length === 0) {
return that
}
obj.copy(that, 0, 0, len);
return that
}
if (obj) {
if ((typeof ArrayBuffer !== 'undefined' &&
obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
if (typeof obj.length !== 'number' || isnan(obj.length)) {
return createBuffer(that, 0)
}
return fromArrayLike(that, obj)
}
if (obj.type === 'Buffer' && isArray(obj.data)) {
return fromArrayLike(that, obj.data)
}
}
throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
}
function checked (length) {
// Note: cannot use `length < kMaxLength()` here because that fails when
// length is NaN (which is otherwise coerced to zero.)
if (length >= kMaxLength()) {
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
'size: 0x' + kMaxLength().toString(16) + ' bytes')
}
return length | 0
}
Buffer.isBuffer = isBuffer;
function internalIsBuffer (b) {
return !!(b != null && b._isBuffer)
}
Buffer.compare = function compare (a, b) {
if (!internalIsBuffer(a) || !internalIsBuffer(b)) {
throw new TypeError('Arguments must be Buffers')
}
if (a === b) return 0
var x = a.length;
var y = b.length;
for (var i = 0, len = Math.min(x, y); i < len; ++i) {
if (a[i] !== b[i]) {
x = a[i];
y = b[i];
break
}
}
if (x < y) return -1
if (y < x) return 1
return 0
};
Buffer.isEncoding = function isEncoding (encoding) {
switch (String(encoding).toLowerCase()) {
case 'hex':
case 'utf8':
case 'utf-8':
case 'ascii':
case 'latin1':
case 'binary':
case 'base64':
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return true
default:
return false
}
};
Buffer.concat = function concat (list, length) {
if (!isArray(list)) {
throw new TypeError('"list" argument must be an Array of Buffers')
}
if (list.length === 0) {
return Buffer.alloc(0)
}
var i;
if (length === undefined) {
length = 0;
for (i = 0; i < list.length; ++i) {
length += list[i].length;
}
}
var buffer = Buffer.allocUnsafe(length);
var pos = 0;
for (i = 0; i < list.length; ++i) {
var buf = list[i];
if (!internalIsBuffer(buf)) {
throw new TypeError('"list" argument must be an Array of Buffers')
}
buf.copy(buffer, pos);
pos += buf.length;
}
return buffer
};
function byteLength (string, encoding) {
if (internalIsBuffer(string)) {
return string.length
}
if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
(ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
return string.byteLength
}
if (typeof string !== 'string') {
string = '' + string;
}
var len = string.length;
if (len === 0) return 0
// Use a for loop to avoid recursion
var loweredCase = false;
for (;;) {
switch (encoding) {
case 'ascii':
case 'latin1':
case 'binary':
return len
case 'utf8':
case 'utf-8':
case undefined:
return utf8ToBytes(string).length
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return len * 2
case 'hex':
return len >>> 1
case 'base64':
return base64ToBytes(string).length
default:
if (loweredCase) return utf8ToBytes(string).length // assume utf8
encoding = ('' + encoding).toLowerCase();
loweredCase = true;
}
}
}
Buffer.byteLength = byteLength;
function slowToString (encoding, start, end) {
var loweredCase = false;
// No need to verify that "this.length <= MAX_UINT32" since it's a read-only
// property of a typed array.
// This behaves neither like String nor Uint8Array in that we set start/end
// to their upper/lower bounds if the value passed is out of range.
// undefined is handled specially as per ECMA-262 6th Edition,
// Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
if (start === undefined || start < 0) {
start = 0;
}
// Return early if start > this.length. Done here to prevent potential uint32
// coercion fail below.
if (start > this.length) {
return ''
}
if (end === undefined || end > this.length) {
end = this.length;
}
if (end <= 0) {
return ''
}
// Force coersion to uint32. This will also coerce falsey/NaN values to 0.
end >>>= 0;
start >>>= 0;
if (end <= start) {
return ''
}
if (!encoding) encoding = 'utf8';
while (true) {
switch (encoding) {
case 'hex':
return hexSlice(this, start, end)
case 'utf8':
case 'utf-8':
return utf8Slice(this, start, end)
case 'ascii':
return asciiSlice(this, start, end)
case 'latin1':
case 'binary':
return latin1Slice(this, start, end)
case 'base64':
return base64Slice(this, start, end)
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return utf16leSlice(this, start, end)
default:
if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
encoding = (encoding + '').toLowerCase();
loweredCase = true;
}
}
}
// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
// Buffer instances.
Buffer.prototype._isBuffer = true;
function swap (b, n, m) {
var i = b[n];
b[n] = b[m];
b[m] = i;
}
Buffer.prototype.swap16 = function swap16 () {
var len = this.length;
if (len % 2 !== 0) {
throw new RangeError('Buffer size