forked from cory/tildefriends
Merge branches/quickjs to trunk. This is the way.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3621 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
378
apps/cory/index/app.js
Normal file
378
apps/cory/index/app.js
Normal file
@@ -0,0 +1,378 @@
|
||||
"use strict";
|
||||
|
||||
const k_posts_max = 20;
|
||||
const k_votes_max = 100;
|
||||
|
||||
async function following(db, id) {
|
||||
var o = await db.get(id + ":following");
|
||||
const k_version = 4;
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {users: [], sequence: 0, version: k_version};
|
||||
}
|
||||
f.users = new Set(f.users);
|
||||
await ssb.sqlStream(
|
||||
"SELECT "+
|
||||
" sequence, "+
|
||||
" json_extract(content, '$.contact') AS contact, "+
|
||||
" json_extract(content, '$.following') AS following "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" author = ?1 AND "+
|
||||
" sequence > ?2 AND "+
|
||||
" json_extract(content, '$.type') = 'contact' "+
|
||||
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
|
||||
"ORDER BY sequence",
|
||||
[id, f.sequence],
|
||||
async function(row) {
|
||||
if (row.following) {
|
||||
f.users.add(row.contact);
|
||||
} else {
|
||||
f.users.delete(row.contact);
|
||||
}
|
||||
f.sequence = row.sequence;
|
||||
});
|
||||
f.users = Array.from(f.users);
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":following", j);
|
||||
}
|
||||
return f.users;
|
||||
}
|
||||
|
||||
async function followingDeep(db, seed_ids, depth) {
|
||||
if (depth <= 0) {
|
||||
return seed_ids;
|
||||
}
|
||||
var f = await Promise.all(seed_ids.map(x => following(db, x)));
|
||||
var ids = [].concat(...f);
|
||||
var x = await followingDeep(db, [...new Set(ids)].sort(), depth - 1);
|
||||
x = [].concat(...x, ...seed_ids);
|
||||
return x;
|
||||
}
|
||||
|
||||
async function followers(db, id) {
|
||||
var o = await db.get(id + ":followers");
|
||||
const k_version = 2;
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {users: [], rowid: 0, version: k_version};
|
||||
}
|
||||
f.users = new Set(f.users);
|
||||
await ssb.sqlStream(
|
||||
"SELECT "+
|
||||
" rowid, "+
|
||||
" author AS contact, "+
|
||||
" json_extract(content, '$.following') AS following "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" rowid > $1 AND "+
|
||||
" json_extract(content, '$.type') = 'contact' AND "+
|
||||
" json_extract(content, '$.contact') = $2 "+
|
||||
"UNION SELECT MAX(rowid) as rowid, NULL, NULL FROM messages "+
|
||||
"ORDER BY rowid",
|
||||
[f.rowid, id],
|
||||
async function(row) {
|
||||
if (row.following) {
|
||||
f.users.add(row.contact);
|
||||
} else {
|
||||
f.users.delete(row.contact);
|
||||
}
|
||||
f.rowid = row.rowid;
|
||||
});
|
||||
f.users = Array.from(f.users);
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":followers", j);
|
||||
}
|
||||
return f.users;
|
||||
}
|
||||
|
||||
async function sendUser(db, id) {
|
||||
return Promise.all([
|
||||
following(db, id).then(async function(following) {
|
||||
return app.postMessage({following: {id: id, users: following}});
|
||||
}),
|
||||
followers(db, id).then(async function(followers) {
|
||||
return app.postMessage({followers: {id: id, users: followers}});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
async function pubsByUser(db, id) {
|
||||
var o = await db.get(id + ":pubs");
|
||||
const k_version = 2;
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {pubs: [], sequence: 0, version: k_version};
|
||||
}
|
||||
f.pubs = Object.fromEntries(f.pubs.map(x => [JSON.stringify(x), x]));
|
||||
await ssb.sqlStream(
|
||||
"SELECT "+
|
||||
" sequence, "+
|
||||
" json_extract(content, '$.address.host') AS host, "+
|
||||
" json_extract(content, '$.address.port') AS port, "+
|
||||
" json_extract(content, '$.address.key') AS key "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" sequence > ?1 AND "+
|
||||
" author = ?2 AND "+
|
||||
" json_extract(content, '$.type') = 'pub' "+
|
||||
"UNION SELECT MAX(sequence) as sequence, NULL, NULL, NULL FROM messages WHERE author = ?2 "+
|
||||
"ORDER BY sequence",
|
||||
[f.sequence, id],
|
||||
async function(row) {
|
||||
f.sequence = row.sequence;
|
||||
if (row.host) {
|
||||
row = {host: row.host, port: row.port, key: row.key};
|
||||
f.pubs[JSON.stringify(row)] = row;
|
||||
}
|
||||
});
|
||||
f.pubs = Object.values(f.pubs);
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":pubs", j);
|
||||
}
|
||||
return f.pubs;
|
||||
}
|
||||
|
||||
async function visiblePubs(db, id) {
|
||||
var ids = [id].concat(await following(db, id));
|
||||
var pubs = {};
|
||||
for (var follow of ids) {
|
||||
var followPubs = await pubsByUser(db, follow);
|
||||
for (var pub of followPubs) {
|
||||
pubs[JSON.stringify(pub)] = pub;
|
||||
}
|
||||
}
|
||||
return Object.values(pubs);
|
||||
}
|
||||
|
||||
async function getAbout(db, id) {
|
||||
var o = await db.get(id + ":about");
|
||||
const k_version = 3;
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {about: {}, sequence: 0, version: k_version};
|
||||
}
|
||||
await ssb.sqlStream(
|
||||
"SELECT "+
|
||||
" sequence, "+
|
||||
" content "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" sequence > ?1 AND "+
|
||||
" author = ?2 AND "+
|
||||
" json_extract(content, '$.type') = 'about' AND "+
|
||||
" json_extract(content, '$.about') = author "+
|
||||
"UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?2 "+
|
||||
"ORDER BY sequence",
|
||||
[f.sequence, id],
|
||||
async function(row) {
|
||||
f.sequence = row.sequence;
|
||||
if (row.content) {
|
||||
var about = {};
|
||||
try {
|
||||
about = JSON.parse(row.content);
|
||||
} catch {
|
||||
}
|
||||
delete about.about;
|
||||
delete about.type;
|
||||
f.about = Object.assign(f.about, about);
|
||||
}
|
||||
});
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":about", j);
|
||||
}
|
||||
return f.about;
|
||||
}
|
||||
|
||||
function fnv32a(value)
|
||||
{
|
||||
var result = 0x811c9dc5;
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
result ^= value.charCodeAt(i);
|
||||
result += (result << 1) + (result << 4) + (result << 7) + (result << 8) + (result << 24);
|
||||
}
|
||||
return result >>> 0;
|
||||
}
|
||||
|
||||
async function getRecentPostIds(db, id, ids, limit) {
|
||||
const k_version = 6;
|
||||
var o = await db.get(id + ':recent_posts');
|
||||
var recent = [];
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
var ids_hash = fnv32a(JSON.stringify(ids));
|
||||
if (!f || f.version != k_version || f.ids_hash != ids_hash) {
|
||||
f = {recent: [], rowid: 0, version: k_version, ids_hash: ids_hash};
|
||||
}
|
||||
await ssb.sqlStream(
|
||||
"SELECT "+
|
||||
" rowid, "+
|
||||
" id "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" rowid > ? AND "+
|
||||
" author IN (" + ids.map(x => '?').join(", ") + ") AND "+
|
||||
" json_extract(content, '$.type') = 'post' "+
|
||||
"UNION SELECT MAX(rowid) as rowid, NULL FROM messages "+
|
||||
"ORDER BY rowid DESC LIMIT ?",
|
||||
[].concat([f.rowid], ids, [limit + 1]),
|
||||
function(row) {
|
||||
if (row.id) {
|
||||
recent.push(row.id);
|
||||
}
|
||||
if (row.rowid) {
|
||||
f.rowid = row.rowid;
|
||||
}
|
||||
});
|
||||
f.recent = [].concat(recent, f.recent).slice(0, limit);
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":recent_posts", j);
|
||||
}
|
||||
return f.recent;
|
||||
}
|
||||
|
||||
async function getVotes(db, id) {
|
||||
var o = await db.get(id + ":votes");
|
||||
const k_version = 2;
|
||||
var votes = [];
|
||||
var f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {votes: [], rowid: 0, version: k_version};
|
||||
}
|
||||
await ssb.sqlStream(
|
||||
"SELECT "+
|
||||
" rowid, "+
|
||||
" author, "+
|
||||
" id, "+
|
||||
" sequence, "+
|
||||
" timestamp, "+
|
||||
" content "+
|
||||
"FROM messages "+
|
||||
"WHERE "+
|
||||
" rowid > ? AND "+
|
||||
" author = ? AND "+
|
||||
" json_extract(content, '$.type') = 'vote' "+
|
||||
"UNION SELECT MAX(rowid) as rowid, NULL, NULL AS id, NULL, NULL, NULL FROM messages "+
|
||||
"ORDER BY rowid DESC LIMIT ?",
|
||||
[f.rowid, id, k_votes_max],
|
||||
async function(row) {
|
||||
if (row.id) {
|
||||
votes.push(row);
|
||||
} else {
|
||||
f.rowid = row.rowid;
|
||||
}
|
||||
});
|
||||
f.votes = [].concat(votes.reverse(), f.votes).slice(0, k_votes_max);
|
||||
var j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ":votes", j);
|
||||
}
|
||||
return f.votes;
|
||||
}
|
||||
|
||||
async function getPosts(db, ids) {
|
||||
var posts = [];
|
||||
if (ids.length) {
|
||||
await ssb.sqlStream(
|
||||
"SELECT * FROM messages WHERE id IN (" + ids.map(x => "?").join(", ") + ")",
|
||||
ids,
|
||||
async function(row) {
|
||||
try {
|
||||
posts.push(row);
|
||||
} catch {
|
||||
}
|
||||
});
|
||||
}
|
||||
return posts;
|
||||
}
|
||||
|
||||
async function ready() {
|
||||
var whoami = await ssb.whoami();
|
||||
var db = await database("ssb");
|
||||
await Promise.all([
|
||||
app.postMessage({whoami: whoami}),
|
||||
app.postMessage({pubs: await visiblePubs(db, whoami)}),
|
||||
app.postMessage({broadcasts: await ssb.getBroadcasts()}),
|
||||
app.postMessage({connections: await ssb.connections()}),
|
||||
followingDeep(db, [whoami], 2).then(function(f) {
|
||||
getRecentPostIds(db, whoami, [].concat([whoami], f), k_posts_max).then(async function(ids) {
|
||||
return getPosts(db, ids);
|
||||
}).then(async function(posts) {
|
||||
var roots = posts.map(function(x) {
|
||||
try {
|
||||
return JSON.parse(x.content).root;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
roots = roots.filter(function(root) {
|
||||
return root && posts.every(post => post.id != root);
|
||||
});
|
||||
return [].concat(posts, await getPosts(db, roots));
|
||||
}).then(async function(posts) {
|
||||
posts.forEach(async function(post) {
|
||||
await app.postMessage({message: post});
|
||||
});
|
||||
});
|
||||
f.forEach(async function(id) {
|
||||
await Promise.all([
|
||||
getVotes(db, id).then(async function(votes) {
|
||||
return Promise.all(votes.map(vote => app.postMessage({vote: vote})));
|
||||
}),
|
||||
getAbout(db, id).then(async function(user) {
|
||||
return app.postMessage({user: {user: id, about: user}});
|
||||
}),
|
||||
]);
|
||||
});
|
||||
}),
|
||||
sendUser(db, whoami),
|
||||
]);
|
||||
}
|
||||
|
||||
core.register('onBroadcastsChanged', async function() {
|
||||
await app.postMessage({broadcasts: await ssb.getBroadcasts()});
|
||||
});
|
||||
|
||||
core.register('onConnectionsChanged', async function() {
|
||||
var connections = await ssb.connections();
|
||||
await app.postMessage({connections: connections});
|
||||
});
|
||||
|
||||
async function refresh() {
|
||||
var db = await database("ssb");
|
||||
var whoami = await ssb.whoami();
|
||||
var ids = await followingDeep(db, [whoami], 2);
|
||||
for (var id of ids) {
|
||||
await ssb.createHistoryStream(id);
|
||||
}
|
||||
}
|
||||
|
||||
core.register('message', async function(m) {
|
||||
if (m.message == 'ready') {
|
||||
await ready();
|
||||
} else if (m.message) {
|
||||
if (m.message.connect) {
|
||||
await ssb.connect(m.message.connect);
|
||||
} else if (m.message.post) {
|
||||
await ssb.post(m.message.post);
|
||||
} else if (m.message.appendMessage) {
|
||||
await ssb.appendMessage(m.message.appendMessage);
|
||||
} else if (m.message.user) {
|
||||
await sendUser(await database("ssb"), m.message.user);
|
||||
} else if (m.message.refresh) {
|
||||
await refresh();
|
||||
}
|
||||
} else {
|
||||
print(JSON.stringify(m));
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
await app.setDocument(utf8Decode(await getFile("index.html")));
|
||||
}
|
||||
|
||||
main();
|
Reference in New Issue
Block a user