96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 1 | # macltok |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 2 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 3 | `macltok` is a compact Rust application backend built above `matrix-sdk` and |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 4 | `matrix-sdk-ui`. It aims to provide the stateful behavior needed by a useful |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 5 | Matrix client without depending on a GUI toolkit or inventing another protocol |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 6 | SDK. |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 7 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 8 | The crate currently provides |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 9 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 10 | - typed commands, owned UI-safe events and bounded per-client transport |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 11 | - Sliding Sync with normalized connection and room-list state |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 12 | - independently retained live room timelines and backwards pagination |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 13 | - text, emote, reply, edit, redaction, reaction, receipt and typing operations |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 14 | - raw encrypted or unencrypted media acquisition with configurable byte limits |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 15 | - encryption state, recovery-key import and SAS verification |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 16 | - password login and session restoration with caller-owned encrypted SQLite or |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 17 | IndexedDB stores |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 18 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 19 | Matrix room, event, user, device and transaction identifiers remain strongly |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 20 | typed. A toolkit timeline identity covers local echoes without pretending they |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 21 | already have remote event IDs. |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 22 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 23 | ## Consuming the engine |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 24 | |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 25 | spawn `Engine::run`, drain the `EventStream` from its own task, and talk to the |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 26 | engine through `ClientHandle`. every command answers once, awaited |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 27 | (`handle.send_message(..).await?`) or as a `Pending` you can await later or |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 28 | drop. a sent message answers when the sdk send queue has it, delivery shows up |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 29 | as the timeline item's send state. reads like `rooms`, `members` and `media` |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 30 | go straight to the sdk. the stream only carries state |
31ce2453c82f | atagen | 2026-09-24 13:07 UTC | 31 | |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 32 | some commands put an event on the stream before they answer. `Open`, `Close`, |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 33 | `OpenThread`, `CloseThread`, `Resync`, `Paginate`, `Logout` and the |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 34 | verification actions all do. the stream is bounded, so a task that drains it |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 35 | and awaits one of those answers deadlocks once it fills. from the draining |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 36 | task, `try_send` the command and drop the `Pending`, the events tell you how it |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 37 | went |
b2b7e7479384 | atagen | 2026-09-26 09:40 UTC | 38 | |
d5d8097c3afd | atagen | 2026-09-26 09:33 UTC | 39 | start a timeline from `TimelineReset`, then apply each `TimelineChanged` whole |
d5d8097c3afd | atagen | 2026-09-26 09:33 UTC | 40 | with `TimelineDiff::try_apply_all`. snapshots are `imbl::Vector`s, re-exported |
d5d8097c3afd | atagen | 2026-09-26 09:33 UTC | 41 | as `macltok::imbl`, so a copy costs nothing. when a batch doesn't fit none of it |
d5d8097c3afd | atagen | 2026-09-26 09:33 UTC | 42 | lands. stop applying, call `resync`, and pick up from the next `TimelineReset` |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 43 | |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 44 | with `sqlite` or `indexeddb`, a `SessionStore` holds one login's record and its |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 45 | store. `EncryptedSessionStore` seals the record with the store's passphrase, in a |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 46 | file you pick natively or an IndexedDB database beside the store in a browser. |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 47 | logging in saves the first record and `Session::restore` loads it. on every |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 48 | `SessionTokensChanged`, `save` a fresh `ClientHandle::session_record()` before |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 49 | the next refresh, refresh tokens only work once |
fa9114fcf75f | atagen | 2026-09-24 12:51 UTC | 50 | |
fe4133dbcd47 | atagen | 2026-09-26 09:05 UTC | 51 | `Engine::run` resolves to a `Termination` saying why it stopped, and |
fe4133dbcd47 | atagen | 2026-09-26 09:05 UTC | 52 | `Termination::retain` says what to do about it. after `LoggedOut` or |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 53 | `AccessRevoked` the record and the store are dead, `discard` them once the |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 54 | client is dropped, since an open store won't go quietly. `SoftLoggedOut` wants |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 55 | the record forgotten, the store kept and a fresh login with |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 56 | `PasswordLogin::device_id` set to the device it carries, or the e2ee keys go |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 57 | with it. `ShutDown` and `HandlesDropped` keep everything. an `Err` is a real |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 58 | failure and kills nothing. one store directory per login, the sdk won't share a |
713205a95af2 | atagen | 2026-09-26 09:24 UTC | 59 | crypto store between devices |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 60 | |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 61 | ## wasm |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 62 | |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 63 | macltok builds for `wasm32-unknown-unknown`. the `indexeddb` feature gives you |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 64 | `StoreConfig::indexeddb` in place of `StoreConfig::sqlite`, and asking for either |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 65 | on the wrong target won't compile. `Engine::run` hands back a `LocalBoxFuture` |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 66 | there, so spawn it with `wasm_bindgen_futures::spawn_local` |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 67 | |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 68 | getrandom picks its browser backend from a cfg flag, and `.cargo/config.toml` |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 69 | only reaches this crate's own builds. every cdylib that pulls macltok in needs |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 70 | it too |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 71 | |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 72 | ```toml |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 73 | [target.wasm32-unknown-unknown] |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 74 | rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""] |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 75 | ``` |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 76 | |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 77 | what's different in a browser |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 78 | |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 79 | - a panic aborts the whole instance. natively a panicking command costs its |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 80 | caller an `Interrupted`, here it takes the engine and everything else with it |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 81 | - `MediaUploadProgress` only ever reports completion, the sdk's wasm http path |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 82 | has no upload progress |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 83 | - the sdk doesn't retry requests or time them out individually. sync still |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 84 | restarts itself with backoff, that part is ours |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 85 | - two tabs or workers on one store each need `StoreConfig::with_lock_holder` |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 86 | with a name of their own, or they both think they hold the crypto store |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 87 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 88 | ## Dependency policy |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 89 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 90 | UI frameworks, image decoders, application-directory discovery and logging |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 91 | subscribers do not belong in this crate. SQLite remains optional through the |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 92 | `sqlite` and `bundled-sqlite` features, IndexedDB through `indexeddb`. Media |
62fe32759529 | atagen | 2026-09-26 08:50 UTC | 93 | decoding belongs to the consumer. |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 94 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 95 | ## Verification |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 96 | |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 97 | ```sh |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 98 | nix develop --command cargo fmt -- --check |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 99 | nix develop --command cargo test --all-targets |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 100 | nix develop --command cargo test --all-targets --features sqlite |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 101 | nix develop --command cargo clippy --all-targets -- -D warnings |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 102 | nix develop --command cargo clippy --all-targets --features sqlite -- -D warnings |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 103 | nix flake check |
96fdf90aacd6 | atagen | 2026-09-08 08:24 UTC | 104 | ``` |