From dc8f4b841ff57f4ef74597c2f80b6f90b352db13 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 3 Aug 2026 21:04:09 -0700 Subject: Messages in the header, and a room you can tell is alive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header carries a message button again, left of the wallet selector, with a red dot when a conversation is waiting. Unread is a per-thread watermark on the newest message from someone else, kept in the rumor's own created_at so it means the same thing on every device it syncs to. Rooms subscribe at startup now, or the dot could not speak for a community nobody was listening to. Communities no longer buzz a phone by default: push registration only carries the stream keys of communities you have opted in, per community, from a bell in the room header. DMs and payments are unchanged. Presence and typing ride the channel stream as 21059 wraps, so who is around stays inside the community's encryption rather than becoming a public status. Measured against relay.coinos.io and nos.lol, neither actually drops NIP-01 ephemeral kinds, so beats carry a NIP-40 expiration and relay.coinos.io sweeps kind 21059 hourly — and presence is dated by the beat, never by arrival, or a replayed beat would light up the whole member list. The member list follows Telegram: online, then last seen in minutes and hours, then a vague 'recently' rather than advertising how long someone has been gone. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi --- src/concord.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/concord.js') diff --git a/src/concord.js b/src/concord.js index 095f6d8..09e4f72 100644 --- a/src/concord.js +++ b/src/concord.js @@ -95,13 +95,17 @@ export const makeSeal = async (rumor, signer, streamConvKey, { plaintext = false return signer instanceof Uint8Array ? finalizeEvent(seal, signer) : signer.signEvent(seal); }; -export const makeWrap = (seal, stream, { ephemeral = false } = {}) => { +// `expiration` is NIP-40, in seconds: for wraps whose whole value is being +// current (presence, typing), it tells a relay it may drop this rather than +// keep it forever. It has to be in the event before signing, so it lives here. +export const makeWrap = (seal, stream, { ephemeral = false, expiration = 0 } = {}) => { let wrap = { kind: ephemeral ? 21059 : 1059, content: nip44.encrypt(JSON.stringify(seal), stream.convKey), tags: [["p", getPublicKey(generateSecretKey())]], created_at: seal.created_at, }; + if (expiration) wrap.tags.push(["expiration", String(expiration)]); return finalizeEvent(wrap, stream.sk); }; -- cgit v1.2.3