upstream: integrar Oasis 1.3.1
Base del merge a tres bandas: git 0.9.5 de epsylon, esta rama y el src de la
APK oficial 1.3.1 (la variante movil: OASIS_MOBILE, regex sin \p{}, audio y
video de Android). Corre en el mismo nodejs-mobile 18.20.4 que ya usabamos.
Se conserva de la rama: Karvan, identidades y multicuenta, topbar y barra
inferior, borrado de EXIF sin sharp, filtro hot del foro, RTC sin STUN y los
modulos apagados en movil (wallet, banking, torrents).
Se toma de upstream lo que ya hace de serie: respuesta citada en chats, ssb-lan
en movil, configuracion en ~/.ssb/oasis (sobrevive a reinstalar el backend).
El asistente de bienvenida deja elegir workflow tambien en movil.
This commit is contained in:
parent
8d838dd8b1
commit
c332afd6ea
265 changed files with 80970 additions and 29167 deletions
|
|
@ -1,6 +1,8 @@
|
|||
const pull = require('../server/node_modules/pull-stream');
|
||||
const { getConfig } = require('../configs/config-manager.js');
|
||||
const { buildValidatedTombstoneSet } = require('./tombstone_validator');
|
||||
const { readTyped, memoIndex } = require('./typed_log');
|
||||
const { versionGroups } = require('../backend/dedupe');
|
||||
|
||||
const logLimit = getConfig().ssbLogStream?.limit || 1000;
|
||||
|
||||
|
|
@ -37,13 +39,9 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n
|
|||
let ssb;
|
||||
const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb; };
|
||||
|
||||
const getAllMessages = async (ssbClient) =>
|
||||
new Promise((resolve, reject) => {
|
||||
pull(
|
||||
ssbClient.createLogStream({ limit: logLimit }),
|
||||
pull.collect((err, msgs) => (err ? reject(err) : resolve(msgs)))
|
||||
);
|
||||
});
|
||||
const POLLS_TYPES = [POLL_TYPE, VOTE_TYPE, OPINION_TYPE, 'pollClose', 'post', 'tombstone'];
|
||||
|
||||
const getAllMessages = async (ssbClient) => readTyped(ssbClient, POLLS_TYPES, { limit: logLimit });
|
||||
|
||||
const decryptField = (value, keys) => {
|
||||
if (!tribeCrypto || typeof value !== 'string' || !value) return null;
|
||||
|
|
@ -65,82 +63,61 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n
|
|||
const buildIndex = (messages) => {
|
||||
const tomb = buildValidatedTombstoneSet(messages);
|
||||
const nodes = new Map();
|
||||
const replaced = new Map();
|
||||
const votesByRoot = new Map();
|
||||
const opinionsByRoot = new Map();
|
||||
const closedRoots = new Set();
|
||||
const commentsByRoot = new Map();
|
||||
const authorByKey = new Map();
|
||||
const others = [];
|
||||
|
||||
for (const m of messages) {
|
||||
const v = m && m.value;
|
||||
const c = v && v.content;
|
||||
if (!c || typeof c !== 'object') continue;
|
||||
authorByKey.set(m.key, v.author);
|
||||
if (c.type === POLL_TYPE) nodes.set(m.key, { key: m.key, author: v.author, seq: v.sequence || 0, ts: v.timestamp || m.timestamp || 0, c });
|
||||
else others.push(m);
|
||||
}
|
||||
|
||||
if (c.type === POLL_TYPE) {
|
||||
nodes.set(m.key, { key: m.key, author: v.author, ts: v.timestamp || m.timestamp || 0, c });
|
||||
if (typeof c.replaces === 'string') replaced.set(c.replaces, m.key);
|
||||
continue;
|
||||
}
|
||||
const vg = versionGroups(Array.from(nodes.values()).map(n => ({ key: n.key, author: n.author, seq: n.seq, ts: n.ts, replaces: typeof n.c.replaces === 'string' ? n.c.replaces : null, claimedAuthor: typeof n.c.author === 'string' ? n.c.author : null })));
|
||||
const rootOf = vg.rootOf;
|
||||
const tipOf = vg.tipOf;
|
||||
|
||||
for (const m of others) {
|
||||
const v = m.value;
|
||||
const c = v.content;
|
||||
if (c.type === VOTE_TYPE && typeof c.target === 'string') {
|
||||
const entry = votesByRoot.get(c.target) || new Map();
|
||||
const prev = entry.get(v.author);
|
||||
const ts = v.timestamp || m.timestamp || 0;
|
||||
if (!prev || ts >= prev.ts) {
|
||||
entry.set(v.author, {
|
||||
ts,
|
||||
choices: Array.isArray(c.choices) ? c.choices : [],
|
||||
encryptedChoices: typeof c.encryptedChoices === 'string' ? c.encryptedChoices : null
|
||||
});
|
||||
}
|
||||
votesByRoot.set(c.target, entry);
|
||||
const target = rootOf(c.target);
|
||||
const entry = votesByRoot.get(target) || new Map();
|
||||
const list = entry.get(v.author) || [];
|
||||
list.push({
|
||||
ts: v.timestamp || m.timestamp || 0,
|
||||
choices: Array.isArray(c.choices) ? c.choices : [],
|
||||
encryptedChoices: typeof c.encryptedChoices === 'string' ? c.encryptedChoices : null
|
||||
});
|
||||
entry.set(v.author, list);
|
||||
votesByRoot.set(target, entry);
|
||||
continue;
|
||||
}
|
||||
if (c.type === OPINION_TYPE && typeof c.target === 'string') {
|
||||
const entry = opinionsByRoot.get(c.target) || { counts: {}, voters: [] };
|
||||
const target = rootOf(c.target);
|
||||
const entry = opinionsByRoot.get(target) || { counts: {}, voters: [] };
|
||||
if (!entry.voters.includes(v.author)) {
|
||||
entry.voters.push(v.author);
|
||||
entry.counts[c.category] = (entry.counts[c.category] || 0) + 1;
|
||||
}
|
||||
opinionsByRoot.set(c.target, entry);
|
||||
opinionsByRoot.set(target, entry);
|
||||
continue;
|
||||
}
|
||||
if (c.type === 'pollClose' && typeof c.target === 'string') {
|
||||
closedRoots.add(`${v.author}|${c.target}`);
|
||||
closedRoots.add(`${v.author}|${rootOf(c.target)}`);
|
||||
continue;
|
||||
}
|
||||
if (c.type === 'post' && typeof c.root === 'string') {
|
||||
commentsByRoot.set(c.root, (commentsByRoot.get(c.root) || 0) + 1);
|
||||
const target = rootOf(c.root);
|
||||
commentsByRoot.set(target, (commentsByRoot.get(target) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const rootOf = (key) => {
|
||||
let cur = key;
|
||||
const seen = new Set();
|
||||
while (nodes.has(cur) && nodes.get(cur).c.replaces && !seen.has(cur)) {
|
||||
seen.add(cur);
|
||||
const next = nodes.get(cur).c.replaces;
|
||||
if (!nodes.has(next)) break;
|
||||
cur = next;
|
||||
}
|
||||
return cur;
|
||||
};
|
||||
|
||||
const tipOf = (root) => {
|
||||
let cur = root;
|
||||
const seen = new Set();
|
||||
while (replaced.has(cur) && !seen.has(cur)) {
|
||||
seen.add(cur);
|
||||
const next = replaced.get(cur);
|
||||
const node = nodes.get(next);
|
||||
if (!node || node.author !== nodes.get(cur).author) break;
|
||||
cur = next;
|
||||
}
|
||||
return cur;
|
||||
};
|
||||
|
||||
return { tomb, nodes, replaced, votesByRoot, opinionsByRoot, closedRoots, commentsByRoot, rootOf, tipOf };
|
||||
return { tomb, nodes, vg, votesByRoot, opinionsByRoot, closedRoots, commentsByRoot, rootOf, tipOf };
|
||||
};
|
||||
|
||||
const isExpired = (c) => {
|
||||
|
|
@ -165,7 +142,13 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n
|
|||
try { rawOptions = o ? JSON.parse(o) : []; } catch (_) { rawOptions = []; }
|
||||
}
|
||||
const options = normalizeOptions(rawOptions);
|
||||
const voteMap = idx.votesByRoot.get(rootId) || new Map();
|
||||
const opensAt = (idx.nodes.get(rootId) || {}).ts || 0;
|
||||
const closesAt = c.deadline ? Date.parse(c.deadline) : NaN;
|
||||
const voteMap = new Map();
|
||||
for (const [author, list] of (idx.votesByRoot.get(rootId) || new Map()).entries()) {
|
||||
const inWindow = list.filter(b => b.ts >= opensAt && (!Number.isFinite(closesAt) || b.ts <= closesAt));
|
||||
if (inWindow.length) voteMap.set(author, inWindow.reduce((a, b) => (b.ts >= a.ts ? b : a)));
|
||||
}
|
||||
|
||||
const counts = {};
|
||||
for (const o of options) counts[o] = 0;
|
||||
|
|
@ -223,9 +206,7 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n
|
|||
const collect = async (viewerId) => {
|
||||
const ssbClient = await openSsb();
|
||||
const messages = await getAllMessages(ssbClient);
|
||||
const idx = buildIndex(messages);
|
||||
const roots = new Set();
|
||||
for (const key of idx.nodes.keys()) roots.add(idx.rootOf(key));
|
||||
const idx = memoIndex('polls', messages, buildIndex);
|
||||
|
||||
const keyCache = new Map();
|
||||
const keysFor = async (c) => {
|
||||
|
|
@ -239,7 +220,7 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n
|
|||
};
|
||||
|
||||
const list = [];
|
||||
for (const rootId of roots) {
|
||||
for (const rootId of idx.vg.roots) {
|
||||
const tipId = idx.tipOf(rootId);
|
||||
if (idx.tomb.has(tipId) || idx.tomb.has(rootId)) continue;
|
||||
const node = idx.nodes.get(tipId);
|
||||
|
|
@ -275,6 +256,7 @@ module.exports = ({ cooler, isPublic = false, tribeCrypto = null, chatsModel = n
|
|||
else if (f === 'OPEN') out = out.filter(p => p.status === 'OPEN');
|
||||
else if (f === 'CLOSED') out = out.filter(p => p.status === 'CLOSED');
|
||||
else if (f === 'VOTED') out = out.filter(p => p.hasVoted);
|
||||
else if (f === 'RECENT') out = out.filter(p => (Date.parse(p.createdAt || '') || 0) >= Date.now() - 86400000);
|
||||
else if (f === 'FAVORITES') {
|
||||
const fav = new Set((opts.favorites || []).map(String));
|
||||
out = out.filter(p => fav.has(String(p.id)));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue