[0.9.1] FORK_IA_UX: Fase 2 (invite) — invite a contact to a Karvan room by feed id over SSB

Reuses Oasis's own private-message invite pattern (like the industry module:
pmModel.sendMessage([feed], 'KARVAN_INVITE', '... -> /karvan/<id>')), so the invite
lands in the contact's inbox with a link — no new SSB code, no touching the SSB
startup. Joining a room by link adopts a local mirror (karvanModel.adoptRoom, the
id is the capability). Real-time cross-device signaling (media/text sync between
the two mirrors) is the remaining Fase 2 piece (muxrpc ephemeral), deferred.
Verified single-node: invite publishes to the inbox, validation, adopt, form; 13/13
model tests (added adoptRoom).
This commit is contained in:
s1to 2026-08-07 20:27:45 +02:00
parent 813bf16958
commit af160dbd26
5 changed files with 81 additions and 4 deletions

View file

@ -3654,11 +3654,27 @@ router
})
.get('/karvan/:id', async (ctx) => {
if (!checkMod(ctx, 'karvanMod')) { ctx.redirect('/modules'); return; }
const room = karvanModel.getRoom(ctx.params.id);
let room = karvanModel.getRoom(ctx.params.id);
if (!room) room = karvanModel.adoptRoom({ id: ctx.params.id }); // unirse por enlace/invitación (espejo local)
if (!room) { ctx.redirect('/karvan'); return; }
let selfId = ''; try { selfId = getViewerId(); } catch (e) {}
const msgs = karvanModel.listMessages(ctx.params.id, 0) || [];
ctx.body = karvanRoomView(room, msgs, { selfId });
const msgs = karvanModel.listMessages(room.id, 0) || [];
ctx.body = karvanRoomView(room, msgs, { selfId, invite: ctx.query.invite || '' });
})
.post('/karvan/:id/invite', koaBody(), async (ctx) => {
if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
const id = ctx.params.id;
const room = karvanModel.getRoom(id);
if (!room) { ctx.redirect('/karvan'); return; }
const to = String((ctx.request.body || {}).to || '').trim();
if (!/^@[A-Za-z0-9+/=]{20,}\.ed25519$/.test(to)) { ctx.redirect('/karvan/' + id + '?invite=bad'); return; }
try {
// reutiliza el patrón de invitación por mensaje privado de Oasis (como el módulo industry):
// le llega al INBOX del contacto con un enlace a la sala. Sin código SSB nuevo.
await pmModel.sendMessage([to], 'KARVAN_INVITE',
'You have been invited to an ephemeral Karvan room' + (room.title ? ' ("' + room.title + '")' : '') + ' -> /karvan/' + id);
ctx.redirect('/karvan/' + id + '?invite=ok');
} catch (e) { ctx.redirect('/karvan/' + id + '?invite=fail'); }
})
.post('/karvan/:id/msg', koaBody(), async (ctx) => {
if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }