After a network change (Wi-Fi to mobile data) the app reconnects right
away, while the relay only notices the old connection is dead once the
idle timeout expires. When that stale handler finally exited it removed
the device from the online map by address alone, which dropped the new,
healthy connection. From then on every message was stored as offline and
only a push wake-up could fetch it, even though the app was connected.
The online map is now its own type: a newly authenticated connection
takes over delivery for the address, and a closing connection removes
the entry only if it still points at itself. Tests cover the registry
and the full route after a stale connection ends.
Messages arrived late or not at all, for two independent reasons.
Dead connections: neither side sent a ping, and after AUTH the server
waited for a frame without a timeout. Mobile NAT drops idle TCP silently,
so the client wrote into a void while the relay still held the device in
its online map — and therefore never sent a push wake-up either. The ping
is a protocol-level one rather than v1::Ping, because dart:io answers
those on its own; the fix works with clients already in the wild.
Poisoned queue: a client decrypts an envelope (ratchet advances), stores
it and acknowledges it. If that ACK is lost, a second decryption attempt
can only fail, so the envelope is never acknowledged — and was redelivered
on every connect until its 30-day TTL, without ever reaching the recipient.
attempt_count existed from the first migration but was never counted; it
now bounds redelivery.
Receipts reported DELIVERED_ONLINE whenever a write into the bounded mpsc
channel succeeded, which it does even for a long-dead socket. Online is now
claimed only for devices that recently sent something.
Push send failures were logged at debug while the relay runs at info, so a
rejected wake-up looked exactly like a working one.
Initial public release. Anonymous on-device identity (no phone number,
e-mail, or password), Signal-protocol cryptography (X3DH + PQXDH, Double
Ratchet, sealed sender) via libsignal, a metadata-minimizing relay,
group chats, disappearing messages, encrypted local storage, optional
Tor transport, and a UI localized into Czech, English, German, Russian,
and Italian.
Monorepo: Flutter client (app/), Rust relay + wire types (rust/),
protobuf definitions (proto/), self-host deploy (deploy/).
Licensed under AGPL-3.0 with an app-store distribution exception (§7).