feat(communication): add chat reactions emojis and images

This commit is contained in:
2026-10-03 17:56:07 +02:00
parent 59538f46b7
commit 29a9ec2242
25 changed files with 737 additions and 57 deletions

View File

@@ -1,7 +1,8 @@
import assert from "node:assert/strict"
import test from "node:test"
import { generateKeyPairSync, randomUUID } from "node:crypto"
import { generateKeyPairSync, randomBytes, randomUUID } from "node:crypto"
import Fastify from "fastify"
import sharp from "sharp"
import { CollaborationService } from "../src/modules/collaboration/service"
import { FederationService, federationRoutes } from "../src/modules/collaboration/federation"
import { instanceUrl, publicAddress, signedHeaders, verifyHeaders, Transport } from "../src/modules/collaboration/transport"
@@ -104,6 +105,38 @@ test("two independent instances exchange invitations and messages through signed
const history = (await b.federation.memberRequest(b.ctx, bindingId, "messages")).result.messages
assert.deepEqual(history.map((m: any) => m.body), ["Unpolled owner message", command.text])
})
await t.test("image-only messages and reactions cross signed routes, including retries and history restrictions", async () => {
const mediaRoom = await a.service.createRoom(a.ctx, { name: "Media" })
const image = { name: "Foto.webp", dataUrl: `data:image/webp;base64,${(await sharp(randomBytes(700 * 500 * 3), { raw: { width: 700, height: 500, channels: 3 } }).webp({ quality: 85 }).toBuffer()).toString('base64')}` }
assert.ok(image.dataUrl.length > 128 * 1024)
const old = await a.service.send(a.ctx.principal, mediaRoom.id, { clientId: randomUUID(), image })
const invitation = await a.service.invite(a.ctx, mediaRoom.id, contact, {})
const job = (await a.pool.query("SELECT * FROM collaboration_outbox WHERE dedup_key=$1", [`invite:${invitation.id}`])).rows[0]
await a.federation.deliver(job)
const binding = (await b.federation.list(b.ctx)).invitations.find(i => i.name === "Media")!.id
await b.federation.accept(b.ctx, binding)
await assert.rejects(b.federation.memberRequest(b.ctx, binding, "image", { messageId: old.id }), /kein Zugriff/)
const command = { clientId: randomUUID(), image }
loseReply = true
assert.equal((await b.federation.memberRequest(b.ctx, binding, "send", command)).result.pending, true)
const confirmed = (await b.federation.memberRequest(b.ctx, binding, "send", command)).result
assert.ok(confirmed.seq)
assert.equal(confirmed.body, "")
assert.equal(confirmed.image.dataUrl, undefined)
assert.equal((await a.service.messages(a.ctx.principal, mediaRoom.id)).messages.length, 2)
const photo = (await b.federation.memberRequest(b.ctx, binding, "image", { messageId: confirmed.id })).result
assert.match(photo.dataUrl, /^data:image\/webp;base64,/)
await b.federation.memberRequest(b.ctx, binding, "react", { messageId: confirmed.id, emoji: "🎉", active: true })
await a.service.react(a.ctx.principal, mediaRoom.id, { messageId: confirmed.id, emoji: "🎉", active: true })
const reactions = (await b.federation.memberRequest(b.ctx, binding, "reactions", { ids: confirmed.id })).result.reactions
assert.deepEqual(reactions[confirmed.id], [{ emoji: "🎉", count: 2, own: true }])
await b.federation.memberRequest(b.ctx, binding, "react", { messageId: confirmed.id, emoji: "🎉", active: false })
assert.deepEqual((await a.service.reactions(a.ctx.principal, mediaRoom.id, { ids: confirmed.id })).reactions[confirmed.id], [{ emoji: "🎉", count: 1, own: true }])
const member = (await a.service.management(a.ctx, mediaRoom.id)).members.find(m => m.principalId === contact.id)
await a.service.revoke(a.ctx, mediaRoom.id, member.id)
await assert.rejects(b.federation.memberRequest(b.ctx, binding, "image", { messageId: confirmed.id }), /kein Zugriff/)
await assert.rejects(b.federation.memberRequest(b.ctx, binding, "react", { messageId: confirmed.id, emoji: "🎉", active: true }))
})
await t.test("only explicit overview is live-shared; offline cache omits it", async () => {
await a.service.updateRoom(a.ctx, room.id, { overview: { title: "Freigegeben", description: "Treffpunkt Tor 2" } })
const live = await b.federation.memberRequest(b.ctx, bindingId, "room")