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,8 +1,49 @@
|
|||
const pull = require('../server/node_modules/pull-stream');
|
||||
const { getConfig } = require('../configs/config-manager.js');
|
||||
const { buildValidatedTombstoneSet } = require('./tombstone_validator');
|
||||
const { readTyped } = require('./typed_log');
|
||||
const logLimit = getConfig().ssbLogStream?.limit || 1000;
|
||||
|
||||
const PIXELIA_TYPES = ['pixelia', 'tombstone'];
|
||||
const COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
||||
|
||||
const pixelCoordinate = (c) => {
|
||||
if (!Number.isInteger(c.x) || !Number.isInteger(c.y) || c.x < 0 || c.x > 49 || c.y < 0 || c.y > 199) return null;
|
||||
const key = `${c.x + 1}:${c.y + 1}`;
|
||||
if (c.coordinateKey != null && c.coordinateKey !== key) return null;
|
||||
return key;
|
||||
};
|
||||
|
||||
const resolvePixels = (messages) => {
|
||||
const tombstoned = buildValidatedTombstoneSet(messages);
|
||||
const byKey = new Map();
|
||||
|
||||
for (const m of messages) {
|
||||
const c = m.value?.content;
|
||||
if (!c || c.type !== 'pixelia' || !m.key) continue;
|
||||
const coordinateKey = pixelCoordinate(c);
|
||||
if (!coordinateKey || typeof c.color !== 'string' || !COLOR_RE.test(c.color)) continue;
|
||||
byKey.set(m.key, { m, coordinateKey });
|
||||
}
|
||||
|
||||
const replaced = new Set();
|
||||
for (const { m, coordinateKey } of byKey.values()) {
|
||||
const prev = byKey.get(m.value.content.replaces);
|
||||
if (prev && prev.coordinateKey === coordinateKey) replaced.add(prev.m.key);
|
||||
}
|
||||
|
||||
const latest = new Map();
|
||||
for (const [k, { m, coordinateKey }] of byKey) {
|
||||
if (replaced.has(k) || tombstoned.has(k)) continue;
|
||||
const cur = latest.get(coordinateKey);
|
||||
const ts = m.value.timestamp || 0;
|
||||
const curTs = cur ? cur.value.timestamp || 0 : 0;
|
||||
if (!cur || ts > curTs || (ts === curTs && k > cur.key)) latest.set(coordinateKey, m);
|
||||
}
|
||||
|
||||
return latest;
|
||||
};
|
||||
|
||||
module.exports = ({ cooler }) => {
|
||||
let ssb;
|
||||
|
||||
|
|
@ -13,52 +54,22 @@ module.exports = ({ cooler }) => {
|
|||
|
||||
const getPixelByCoordinate = async (coordinateKey) => {
|
||||
const ssbClient = await openSsb();
|
||||
const messages = await new Promise((res, rej) => {
|
||||
pull(
|
||||
ssbClient.createLogStream({ limit: logLimit }),
|
||||
pull.collect((err, msgs) => err ? rej(err) : res(msgs))
|
||||
);
|
||||
});
|
||||
|
||||
const tombstoned = buildValidatedTombstoneSet(messages);
|
||||
|
||||
const replaces = new Map();
|
||||
const byId = new Map();
|
||||
|
||||
for (const m of messages) {
|
||||
const c = m.value?.content;
|
||||
const k = m.key;
|
||||
if (!c || c.type !== 'pixelia' || c.coordinateKey !== coordinateKey) continue;
|
||||
if (tombstoned.has(k)) continue;
|
||||
if (c.replaces) replaces.set(c.replaces, k);
|
||||
byId.set(k, m);
|
||||
}
|
||||
|
||||
for (const [replacedId, replacingId] of replaces.entries()) {
|
||||
const orig = byId.get(replacedId);
|
||||
if (!orig) continue;
|
||||
const rep = byId.get(replacingId);
|
||||
if (!rep || rep.value.author !== orig.value.author) {
|
||||
byId.delete(replacingId);
|
||||
continue;
|
||||
}
|
||||
byId.delete(replacedId);
|
||||
}
|
||||
|
||||
return [...byId.values()][0] || null;
|
||||
const messages = await readTyped(ssbClient, PIXELIA_TYPES, { limit: logLimit });
|
||||
return resolvePixels(messages).get(coordinateKey) || null;
|
||||
};
|
||||
|
||||
const paintPixel = async (x, y, color) => {
|
||||
if (x < 1 || x > 50 || y < 1 || y > 200) {
|
||||
if (!Number.isInteger(x) || !Number.isInteger(y) || x < 1 || x > 50 || y < 1 || y > 200) {
|
||||
throw new Error('Coordinates out of bounds. Please use x (1-50) and y (1-200)');
|
||||
}
|
||||
if (typeof color !== 'string' || !COLOR_RE.test(color)) throw new Error('Invalid color');
|
||||
|
||||
const ssbClient = await openSsb();
|
||||
const userId = ssbClient.id;
|
||||
const coordinateKey = `${x}:${y}`;
|
||||
const existingPixel = await getPixelByCoordinate(coordinateKey);
|
||||
|
||||
if (existingPixel) {
|
||||
if (existingPixel && existingPixel.value.author === userId) {
|
||||
const tombstone = {
|
||||
type: 'tombstone',
|
||||
target: existingPixel.key,
|
||||
|
|
@ -69,7 +80,7 @@ module.exports = ({ cooler }) => {
|
|||
);
|
||||
}
|
||||
|
||||
const contributors = existingPixel?.value?.content?.contributors_inhabitants || [];
|
||||
const contributors = Array.isArray(existingPixel?.value?.content?.contributors_inhabitants) ? existingPixel.value.content.contributors_inhabitants : [];
|
||||
const contributors_inhabitants = contributors.includes(userId)
|
||||
? contributors
|
||||
: [...contributors, userId];
|
||||
|
|
@ -93,46 +104,14 @@ module.exports = ({ cooler }) => {
|
|||
|
||||
const listPixels = async () => {
|
||||
const ssbClient = await openSsb();
|
||||
const messages = await new Promise((res, rej) => {
|
||||
pull(
|
||||
ssbClient.createLogStream({ limit: logLimit }),
|
||||
pull.collect((err, msgs) => err ? rej(err) : res(msgs))
|
||||
);
|
||||
});
|
||||
const messages = await readTyped(ssbClient, PIXELIA_TYPES, { limit: logLimit });
|
||||
|
||||
const tombstoned = buildValidatedTombstoneSet(messages);
|
||||
const replaces = new Map();
|
||||
const byKey = new Map();
|
||||
|
||||
for (const m of messages) {
|
||||
const c = m.value?.content;
|
||||
const k = m.key;
|
||||
if (!c) continue;
|
||||
if (c.type === 'tombstone') continue;
|
||||
if (c.type === 'pixelia') {
|
||||
if (tombstoned.has(k)) continue;
|
||||
if (c.replaces) replaces.set(c.replaces, k);
|
||||
byKey.set(k, m);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [replacedId, replacingId] of replaces.entries()) {
|
||||
const orig = byKey.get(replacedId);
|
||||
if (!orig) continue;
|
||||
const rep = byKey.get(replacingId);
|
||||
if (!rep || rep.value.author !== orig.value.author) {
|
||||
byKey.delete(replacingId);
|
||||
continue;
|
||||
}
|
||||
byKey.delete(replacedId);
|
||||
}
|
||||
|
||||
return Array.from(byKey.values()).map(m => ({
|
||||
return Array.from(resolvePixels(messages).values()).map(m => ({
|
||||
x: m.value.content.x + 1,
|
||||
y: m.value.content.y + 1,
|
||||
color: m.value.content.color,
|
||||
author: m.value.author,
|
||||
contributors_inhabitants: m.value.content.contributors_inhabitants || [],
|
||||
contributors_inhabitants: [...new Set([...(Array.isArray(m.value.content.contributors_inhabitants) ? m.value.content.contributors_inhabitants : []).filter(id => typeof id === 'string'), m.value.author])],
|
||||
timestamp: m.value.timestamp
|
||||
}));
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue