macltok
macltok is a compact Rust application backend built above matrix-sdk and
matrix-sdk-ui. It aims to provide the stateful behavior needed by a useful
Matrix client without depending on a GUI toolkit or inventing another protocol
SDK.
The crate currently provides
- typed commands, owned UI-safe events and bounded per-client transport
- Sliding Sync with normalized connection and room-list state
- independently retained live room timelines and backwards pagination
- text, emote, reply, edit, redaction, reaction, receipt and typing operations
- raw encrypted or unencrypted media acquisition with configurable byte limits
- encryption state, recovery-key import and SAS verification
- password login and session restoration with caller-owned encrypted SQLite or IndexedDB stores
Matrix room, event, user, device and transaction identifiers remain strongly typed. A toolkit timeline identity covers local echoes without pretending they already have remote event IDs.
Consuming the engine
spawn Engine::run, drain the EventStream from its own task, and talk to the
engine through ClientHandle. every command answers once, awaited
(handle.send_message(..).await?) or as a Pending you can await later or
drop. a sent message answers when the sdk send queue has it, delivery shows up
as the timeline item’s send state. reads like rooms, members and media
go straight to the sdk. the stream only carries state
some commands put an event on the stream before they answer. Open, Close,
OpenThread, CloseThread, Resync, Paginate, Logout and the
verification actions all do. the stream is bounded, so a task that drains it
and awaits one of those answers deadlocks once it fills. from the draining
task, try_send the command and drop the Pending, the events tell you how it
went
start a timeline from TimelineReset, then apply each TimelineChanged whole
with TimelineDiff::try_apply_all. snapshots are imbl::Vectors, re-exported
as macltok::imbl, so a copy costs nothing. when a batch doesn’t fit none of it
lands. stop applying, call resync, and pick up from the next TimelineReset
With sqlite or indexeddb, persist the initial Session::record()
separately from its encrypted store. On SessionTokensChanged, obtain a new record from
ClientHandle::session_record() and replace the saved record atomically in
protected storage. Restore with the same store location and passphrase.
Engine::run resolves to a Termination saying why it stopped, and
Termination::retain says what to do about it. after LoggedOut or
AccessRevoked the record and the store are dead, bin them. SoftLoggedOut
wants the store kept and a fresh login with PasswordLogin::device_id set to the
device it carries, or the e2ee keys go with it. ShutDown and HandlesDropped
keep everything. an Err is a real failure and kills nothing. one store
directory per login, the sdk won’t share a crypto store between devices.
The headless example is
login-only; it does not persist sessions across runs.
wasm
macltok builds for wasm32-unknown-unknown. the indexeddb feature gives you
StoreConfig::indexeddb in place of StoreConfig::sqlite, and asking for either
on the wrong target won’t compile. Engine::run hands back a LocalBoxFuture
there, so spawn it with wasm_bindgen_futures::spawn_local
getrandom picks its browser backend from a cfg flag, and .cargo/config.toml
only reaches this crate’s own builds. every cdylib that pulls macltok in needs
it too
[target.wasm32-unknown-unknown]
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]what’s different in a browser
- a panic aborts the whole instance. natively a panicking command costs its
caller an
Interrupted, here it takes the engine and everything else with it MediaUploadProgressonly ever reports completion, the sdk’s wasm http path has no upload progress- the sdk doesn’t retry requests or time them out individually. sync still restarts itself with backoff, that part is ours
- two tabs or workers on one store each need
StoreConfig::with_lock_holderwith a name of their own, or they both think they hold the crypto store
Dependency policy
UI frameworks, image decoders, application-directory discovery and logging
subscribers do not belong in this crate. SQLite remains optional through the
sqlite and bundled-sqlite features, IndexedDB through indexeddb. Media
decoding belongs to the consumer.
Verification
nix develop --command cargo fmt -- --check
nix develop --command cargo test --all-targets
nix develop --command cargo test --all-targets --features sqlite
nix develop --command cargo clippy --all-targets -- -D warnings
nix develop --command cargo clippy --all-targets --features sqlite -- -D warnings
nix flake check