End-to-end encrypted calls between two contacts over WebRTC. Call
signalling (offer, answer, hang-up) travels as ordinary Signal-encrypted
messages through the relay; media is DTLS-SRTP, peer to peer when
possible and through the operator's TURN server otherwise.
- Non-trickle ICE (candidates gathered for up to 3 s), 45 s ring
timeout, busy handling, and a call log entry in the conversation.
- The system call UI rings even when the app is closed: CallKit from a
PushKit VoIP push on iOS, a full-screen incoming call from an FCM
wake-up on Android. The wake-up carries no caller; the app learns who
is calling from the queued offer once it is unlocked.
- New permissions: camera for video, audio routing and Bluetooth
headsets, network state; iOS background modes audio and voip.
Push on Android: a fresh install now picks UnifiedPush when a
distributor is installed and FCM otherwise, instead of leaving wake-ups
off. Without a wake-up Android puts the app to sleep and messages wait
until it is opened. Users can switch at any time in Settings.
The version was a hand-written constant that had to be kept in sync with
`version:` in pubspec.yaml. It was not: the constant still said 1.1.0 while
the shipped build was 1.1.2+22, so every abuse report carried a version that
no build ever had — and reports could not be traced back to a build.
AppInfo reads versionName / CFBundleShortVersionString, which Flutter derives
from the pubspec, so the two cannot drift apart again. Reports now carry the
build number as well; when the version is not known yet the field is omitted
rather than guessed.
Also adds the timestamp regression test that covers MessagingService.wireSentAt,
which shipped in 1.1.2 without its test.
Fixes reported by testers and found in an app review:
- First message right after install no longer fails. Before the app
finished registering with the relay, the outgoing message could crash on
a null delivery address. The profile field is now optional and the send
path throws a clear "still connecting, try again shortly" error while
registration completes in the background.
- The QR code and share link now appear only once the delivery address is
ready. Previously a link generated before the first connection lacked
routing, so the other side could not send the first message. The screen
now shows a connecting state and reactively swaps in the QR when ready.
- Incomplete contact links are rejected on import instead of failing at
first send, with a clear prompt to ask for a fresh link.
- Auto-lock no longer silences the app: after locking and unlocking,
messaging is restarted (it previously stayed dead until app restart).
- Group messages are marked failed on a non-per-member send error instead
of hanging in the "sending" state forever.
- Hardened the relay client against a rare duplicate connection when
toggling Tor or locking mid-connect, and fromShareString now rejects a
delivery address of the wrong length.
- Pin unifiedpush below 6.2.0 to drop the webcrypto dependency (its native
framework lacks LC_ENCRYPTION_INFO and blocked the iOS upload).
- Skip Android release lint (network dependency check hangs on the build
host and adds no value for a Flutter app).
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).