A timer on one message, not just the conversation (§6.17)
The rule that keeps this from making the product worse: the shorter timer always wins. Without it a sender could defeat the recipient's conversation timer by putting a long secs on every message — turning a setting the recipient chose, on their own device, into something the other party overrules. 🔴 pruneExpired returned early when the CONVERSATION had no timer, so a timed message in an untimed thread would have been kept for ever — the feature working on the sending phone and doing nothing on the receiving one. Pruning is per message now. ⚠️ Content.text returns raw bytes with no envelope when it has nothing to carry, so the timer had to join that decision or it would be dropped and the message would live for ever. The sender's own copy expires too: a timer that only cleared the recipient's phone would leave the message with the person who set it. Refs paulhitt/flamenet-messenger-ios#29 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
edd53bc771
commit
fc8d40ecf6
@@ -115,6 +115,13 @@ class Session(
|
||||
* to know anything had been dropped.
|
||||
*/
|
||||
val about: Content.Reference? = null,
|
||||
/**
|
||||
* A timer this one message carries, in seconds (§6.17); 0 for none.
|
||||
*
|
||||
* ⚠️ Not the conversation's — `Vault.effectiveLifetime` combines the
|
||||
* two, and the shorter always wins.
|
||||
*/
|
||||
val lifetimeSeconds: Int = 0,
|
||||
)
|
||||
|
||||
/** Where an outbound message got to. */
|
||||
@@ -155,6 +162,7 @@ class Session(
|
||||
link = m.optJSONObject("lnk")?.let { Content.LinkPreview.fromJson(it) },
|
||||
messageId = m.optString("mid").takeIf { it.isNotEmpty() },
|
||||
about = m.optJSONObject("re")?.let { Content.Reference.fromJson(it) },
|
||||
lifetimeSeconds = m.optInt("secs", 0),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -173,11 +181,17 @@ class Session(
|
||||
messageId: String? = null,
|
||||
/** What this message quotes, when it is a reply (§6.9). */
|
||||
about: Content.Reference? = null,
|
||||
/** A timer this one message carries, in seconds (§6.17); 0 for none. */
|
||||
lifetimeSeconds: Int = 0,
|
||||
) {
|
||||
vault.append(threadId, JSONObject()
|
||||
.put("author", author)
|
||||
.also { if (messageId != null) it.put("mid", messageId) }
|
||||
.put("text", text).put("mine", mine).put("at", at)
|
||||
// ⚠️ Written only when there is one. A zero on every message would
|
||||
// grow the transcript for a feature almost nobody uses, and would
|
||||
// make "no timer" indistinguishable from "a timer of nothing".
|
||||
.also { if (lifetimeSeconds > 0) it.put("secs", lifetimeSeconds) }
|
||||
.also { if (delivery != null) it.put("delivery", delivery.name) }
|
||||
.also {
|
||||
if (sticker != null) {
|
||||
@@ -350,6 +364,8 @@ class Session(
|
||||
* for every message that has no link in it.
|
||||
*/
|
||||
link: Content.LinkPreview? = null,
|
||||
/** A timer for this one message, in seconds (§6.17); 0 for none. */
|
||||
lifetimeSeconds: Int = 0,
|
||||
): Conversations.Sent {
|
||||
// 🔴 **Every message gets an id now, so this is always an envelope.**
|
||||
// It used to be the raw body byte for byte, which is cheaper on the
|
||||
@@ -358,11 +374,15 @@ class Session(
|
||||
// both engines read a bare body as text, so an older peer is
|
||||
// unaffected — see `isUnknownControl`.
|
||||
val mid = java.util.UUID.randomUUID().toString()
|
||||
val plaintext = Content.text(text, messageId = mid, link = link)
|
||||
val plaintext = Content.text(text, messageId = mid, link = link,
|
||||
lifetimeSeconds = lifetimeSeconds)
|
||||
val sent = conversations.send(peerUserId, plaintext)
|
||||
val at = System.currentTimeMillis()
|
||||
// 🔴 The sender's own copy expires too. A timer that only cleared the
|
||||
// recipient's phone would be the opposite of the promise: the person
|
||||
// who set it would be the one still holding the message.
|
||||
record(Vault.threadForUser(peerUserId), text, mine = true, at = at,
|
||||
link = link, messageId = mid,
|
||||
link = link, messageId = mid, lifetimeSeconds = lifetimeSeconds,
|
||||
delivery = if (sent.reachedSomeone) Delivery.DELIVERED else Delivery.PENDING)
|
||||
// 🔴 Queued, not abandoned. A friend who is simply signed out should
|
||||
// not cost the sender a reminder to try again later.
|
||||
@@ -1008,7 +1028,8 @@ class Session(
|
||||
// to it — an id invented here would match nothing on their
|
||||
// side.
|
||||
messageId = message.content.messageId,
|
||||
about = message.content.about)
|
||||
about = message.content.about,
|
||||
lifetimeSeconds = message.content.lifetimeSeconds ?: 0)
|
||||
touched += thread
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user