aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-06 23:46:17 -0800
committerAdam Soltys <[email protected]>2026-03-06 23:46:17 -0800
commit797d30c172fbec5e98e3a8d77d24f8e708aaa0bd (patch)
tree0413da05c2ff55e5c50ec83c092eb40de1eaa319
parent76e0e08ce6575b43901b777ee75b048dce9097ff (diff)
downloadcoinos-ui-797d30c172fbec5e98e3a8d77d24f8e708aaa0bd.tar.gz
coinos-ui-797d30c172fbec5e98e3a8d77d24f8e708aaa0bd.zip
render images from MLS messages by preserving rumor tags
Extract tags from decrypted MLS rumors and pass them through to ChatMessage. Handle imeta tags (NIP-92) for image attachments sent from clients like White Noise. Co-Authored-By: Claude Opus 4.6 <[email protected]>
-rw-r--r--src/components/ChatMessage.svelte10
-rw-r--r--src/lib/mls-relay.ts8
2 files changed, 14 insertions, 4 deletions
diff --git a/src/components/ChatMessage.svelte b/src/components/ChatMessage.svelte
index 7283430f..dbdde5a0 100644
--- a/src/components/ChatMessage.svelte
+++ b/src/components/ChatMessage.svelte
@@ -3,7 +3,15 @@
let { content, tags = [] }: { content: string; tags?: any[] } = $props();
- let parts = $derived(parseContent({ content, tags }));
+ // Extract image URLs from imeta tags not already in content
+ let imetaUrls = $derived(
+ tags
+ .filter((t) => t[0] === "imeta")
+ .map((t) => t.find((v) => v.startsWith("url "))?.slice(4))
+ .filter((url): url is string => !!url && !content.includes(url)),
+ );
+
+ let parts = $derived(parseContent({ content: [content, ...imetaUrls].join("\n"), tags }));
const truncUrl = (url: string) => {
const short = url.replace(/^https?:\/\/(www\.)?/, "");
diff --git a/src/lib/mls-relay.ts b/src/lib/mls-relay.ts
index 8ca18b15..8ab89e98 100644
--- a/src/lib/mls-relay.ts
+++ b/src/lib/mls-relay.ts
@@ -592,10 +592,12 @@ async function handleGroupMessageEvent(
const raw = new TextDecoder().decode(result.result.message);
let content: string;
let senderPubkey: string;
+ let tags: string[][] = [];
try {
const rumor = JSON.parse(raw);
content = rumor.content ?? raw;
senderPubkey = rumor.pubkey ?? event.pubkey;
+ if (Array.isArray(rumor.tags)) tags = rumor.tags;
} catch {
content = raw;
senderPubkey = event.pubkey;
@@ -609,7 +611,7 @@ async function handleGroupMessageEvent(
return null;
}
- const rumour = makeRumour(content, senderPubkey, mlsGroupIdHex);
+ const rumour = makeRumour(content, senderPubkey, mlsGroupIdHex, tags);
cacheGroupRumour(mlsGroupIdHex, rumour);
processedEventIds.add(event.id);
saveLocalCache();
@@ -688,14 +690,14 @@ export async function fetchGroupHistory(user: any, relays: string[] = DM_RELAYS)
// Helpers
// ---------------------------------------------------------------------------
-function makeRumour(content: string, senderPubkey: string, groupId: string): any {
+function makeRumour(content: string, senderPubkey: string, groupId: string, tags: string[][] = []): any {
return {
id: bytesToHex(crypto.getRandomValues(new Uint8Array(32))),
kind: 14,
created_at: Math.floor(Date.now() / 1000),
pubkey: senderPubkey,
content,
- tags: [],
+ tags,
_groupId: groupId,
};
}