- TypeScript 77.4%
- Rust 7.4%
- Kotlin 6.2%
- Python 4.5%
- JavaScript 2%
- Other 2.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| docker | ||
| docs | ||
| flatpak | ||
| libtacky@47df7fd7c2 | ||
| src-tauri | ||
| tools | ||
| ui | ||
| worker | ||
| .gitignore | ||
| .gitmodules | ||
| audit.toml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| webrtc.mk | ||
anotherim-js
Another IM is an XMPP messenger. This repository holds its client: one UI in TypeScript with lit, in a browser tab, a desktop app for Linux, Windows and macOS (a Tauri 2 shell), and an Android app (the same shell, plus Kotlin for push, calls and files).
Version 0.7.11. Breaking changes are still possible. Licence MPL-2.0.

A chat on the desktop; a group chat with @-mentions and the chat list on a
phone. The people are invented: make screenshots draws them from the mock
backend.
Features
- Several accounts; sign in by password, OAuth or an SSO broker; sign up with CAPTCHA or proof-of-work
- 1:1 and group chats with server history, replies, edits, reactions, threads, pins and search
- Delivery ticks and read markers; retracted messages kept, with edit history
- Albums with captions, silent and scheduled messages, polls, places, stickers
- @-mentions, rooms on "mentions only", roles and moderation
- OMEMO in chats and private rooms, keys verified by QR code
- Telegram and other networks through gateways, Telegram secret chats in the apps
- Local store encrypted by default with the system keyring
- HTTP upload and direct device-to-device transfer, voice and round video messages
- 1:1 audio and video calls
- Push while closed: Web Push, UnifiedPush, FCM
- Night and Day themes, five languages, backup and restore
The whole list is in docs/features.md, with what has not been tried on a real device yet. Tests run against a mock backend, not real servers.
Download
Builds of each release are on the
releases page:
the Android apk (UnifiedPush, or FCM in the -gms one), the Linux AppImage
and Flatpak, the Windows installer and portable zip, and the browser client
as a tarball to serve yourself (docs/building.md).
| platform | minimum |
|---|---|
| Chromium browsers | 97 ('wasm-unsafe-eval' in the CSP) |
| Firefox | 114 (module workers) |
| Safari, iOS | 16 |
| Android app | Android 11, arm64; WebView 80 |
| Linux AppImage | glibc 2.35, or EL9's 2.34; x86_64 |
| Linux Flatpak | GNOME 50 runtime, x86_64 |
| Windows | 10 or 11, x86_64, with WebView2 |
| macOS | 11, Apple silicon |
An engine too old for the bundle shows "This browser is too old" (or, on Android, "Update Android System WebView") instead of a blank page.
Build
Everything builds in this repository's container images. The host needs git
and podman (or docker); make <goal> runs the goal in the container. A Mac
builds on the Mac (docs/macos.md).
git clone --recurse-submodules https://dev.another.im/another-im/anotherim-js.git
cd anotherim-js
make docker-build # backend, dist/web, web tarball, AppImage
make test-ui check test-shell # the tests that need no backend
make dev # the UI on http://localhost:5173 (DEV_BACKEND=mock: no backend)
make release-check runs make check-pins and make audit, the RustSec audit of src-tauri/Cargo.lock, before a release; make check stays offline (docs/building.md).
| build | target |
|---|---|
| Browser | make web-tar |
| Linux | make bundle-appimage, make bundle-flatpak |
| Windows | make bundle-windows |
| Android | make bundle-android, make bundle-android-gms ANOTHER_IM_GOOGLE_SERVICES=<file> |
| macOS | make bundle-macos, on a Mac |
make help lists every target. Outputs, signing, build knobs, command-line
options and serving the browser client are in
docs/building.md.
How it fits together
The UI does not speak XMPP itself. Protocol, storage, encryption, calls and file transfer all come from libtacky, through its JSON wire API.
ui/ TypeScript + lit: views, state, the wire API
│ one Transport interface, three implementations
┌───────────┼────────────┐
mock worker tauri
in-page libtacky as libtacky.a in the Tauri shell
(dev) wasm (browser) (desktop, Android)
| component | language | what it is | code | tests | coverage |
|---|---|---|---|---|---|
ui/ |
TypeScript | the client: views (41,838), state (24,224), protocol and the mock backend (9,667), the rest (1,756) | 77,485 | 82,057 | 99.0% |
| JS | two boot scripts that catch an old browser | 73 | -- | not measured | |
src-tauri/ |
Rust | the shell: libtacky binding, files, notices, push, tray, windows | 8,768 | 5,807 | 95.4% (all of src-tauri/src on Linux) |
src-tauri/gen/android/ |
Kotlin | push, notifications, calls, files, share target | 5,437 | 7,204 | 96.5% |
worker/ |
JS | the worker, its OPFS store and file system | 880 | 1,997 | 99.9% |
| C | the backend host compiled to wasm | 439 | -- | not measured | |
| Tcl | a package index for stubs | 4 | -- | not measured | |
| total | TypeScript 77,485 · Rust 8,768 · Kotlin 5,437 · JS 953 · C 439 · Tcl 4 | 93,086 | 97,065 | ||
| everything else | make, sh, Python, JS | tools/, docker/, flatpak/, the Makefiles; not in the total |
9,092 | -- | -- |
Measured on 2026-10-27 at 7a0cf4ed, v0.7.10 (libtacky 1b5b33de, v1.1.10).
- code: non-blank, non-comment lines of the files git tracks, by the
rules of libtacky's
tools/stats.py. The generated wire API (ui/src/protocol/generated/api.ts, 15,991 lines), JSON, CSS, HTML and the language catalogues are not counted. - tests:
ui/test/,src-tauri/tests/, Rust#[cfg(test)]modules, Kotlinsrc/test/andsrc/testGms/,worker/test/. - coverage: line coverage.
make coverage-uiruns the UI tests under V8 over everyui/srcfile but the generated API: 58,663 of 59,277 lines, 90.8% of branches.make coverage-shell-allruns all ofsrc-tauri/srcon Linux under LLVM, with libtacky faked (featurefake-tacky): 95.4%.make coverage-androidruns the Kotlin unit tests under JaCoCo with Robolectric: 3,380 of 3,501 lines (96.5%).make coverage-workerruns the worker's node suites: 873 of 874 lines. Measured 2026-10-27.
Tests
| suite | command | files | cases | last run |
|---|---|---|---|---|
| UI (vitest, happy-dom) | make test-ui |
388 | 4,300 | 2026-10-27: 4,292 passed, 8 skipped |
| Types and catalogues | make check |
-- | -- | 2026-10-27: passed, 2,122 strings in all five languages |
| Shell, plain-std modules | make test-shell |
34 | 274 | 2026-10-27: all passed |
Shell, whole crate with libtacky.a |
make test-embed |
41 | 263 | not run for this count |
| Android JVM unit tests | gradle in the android image (make coverage-android) |
48 | 492 | 2026-10-27: all passed |
| wasm worker under node | make test-worker |
5 | -- | 2026-10-27: all passed |
| WebKitGTK layout smoke | make webkit-smoke |
1 | 7,315 checks | 2026-10-27: all passed |
Chromium smoke of dist/web |
make web-smoke |
1 | 1 | not run for this count |
Docs
- features: everything the client does, by area
- building and running: builds, targets, knobs, data, serving
- push, delivery: how messages reach a closed app
- discovery and gateways, Telegram secret chats
- Android calls, macOS
- i18n: the language catalogues
- one client, how features are added
Licence
anotherim-js is MPL-2.0 (LICENSE). libtacky is GPL-3.0, so a build as a
whole is distributed under the GPL-3.0. Bundled third-party parts:
- Fira Sans Condensed, SIL Open Font License 1.1 (
ui/public/fonts/OFL.txt) - Lucide icons, ISC, some from Feather, MIT (
ui/public/licenses/lucide.txt) - FFmpeg decoders for H.264, HEVC and AAC in the AppImage, LGPL-2.1+ (patent-encumbered in some countries)
The two sounds are generated by tools/sounds/make-sounds.py.
