Stateful Matrix client helpers for R.
mx.api owns the raw Matrix Client-Server HTTP endpoints. mx.crypto
owns the Olm and Megolm cryptographic primitives. mx.client is the
layer between them: local configuration, room resolution, sync cursor
handling, event extraction, invite acceptance, markdown-formatted
messages, and end-to-end encryption orchestration.
# From GitHub while the CRAN submission is in flight:
remotes::install_github("cornball-ai/mx.client")
# Optional, for end-to-end encryption (needs a Rust toolchain to build):
install.packages("mx.crypto")library(mx.client)
# One-time setup: log in, join the room, persist credentials
# (mode 0600, under tools::R_user_dir()).
mx_client_configure(
server = "https://matrix.example.org",
user = "bot",
password = "secret",
room = "#general:example.org",
app = "myapp"
)
# Every later session:
client <- mx_client_load(app = "myapp")
mx_send_text(client, "hello from R") # default room
mx_send_text(client, "**bold** and `code`", room = "general",
markdown = TRUE) # named room, HTML
# Poll for new messages and advance the stored sync cursor:
res <- mx_sync_update(client, timeout = 30000L)
msgs <- mx_extract_text_events(res$sync, client$user_id)Both need mx.api (>= 0.3.0), which mx.client now pins.
# Send a file with the stored client; the room resolves by name and the
# msgtype comes from the MIME type (a .png posts as m.image).
mx_send_media(client, "plot.png", room = "general")
# Recover from an invalidated access token without babysitting:
# catches M_UNKNOWN_TOKEN, re-logs in with the stored password
# (preserving the device id, so an E2EE identity survives), saves the
# refreshed token, and retries once.
mx_with_relogin(client, function(cl) {
mx_send_text(cl, "still here after a token rotation")
})Olm/Megolm send/receive orchestration over mx.crypto, aimed at bots
and controlled deployments. mx.crypto is a Suggests and is only called
from the E2EE entry points, so plaintext Matrix clients install and run
without a Rust toolchain.
The mental model: an account holds this device's identity keys, the store persists everything on disk, sessions hold the live Olm (per peer device) and Megolm (per room) ratchet state, and a room key is what lets a device decrypt a room. mx.client moves all four around for you.
# Once: create the crypto store and publish this device's keys.
store <- mx_crypto_store_dir("myapp")
acct <- mx_crypto_account(store)
mx_crypto_publish_keys(client, acct, store)
# Send into an encrypted room. Device discovery, one-time-key claims,
# and Megolm key sharing happen inside.
sessions <- mx_crypto_sessions_load(store)
room_id <- mx_resolve_room(client, "secret plans") # or a literal !id
res <- mx_send_encrypted(client, acct, sessions, room_id,
list(msgtype = "m.text", body = "secret"),
store, member_ids = "@friend:example.org")
sessions <- res$sessions
# Receive: recover room keys from to-device messages and decrypt
# timeline events in one pass.
my_curve <- mx.crypto::mxc_account_identity_keys(acct)$curve25519
sync <- mx_sync_update(client)$sync
out <- mx_crypto_process_sync(acct, sessions, sync,
my_curve, self_id = client$user_id)
out$events # same shape as mx_extract_text_events()Security model, in brief: device keys are trusted on first use (no
cross-signing trust store yet), and there is no key-request or
forwarded-key flow. See vignette("e2ee", package = "mx.client") for
the full flow, what happens on the wire, and the current limitations.
| Package | Role | Depends on |
|---|---|---|
| mx.api | HTTP transport (CRAN) | — |
| mx.crypto | Olm/Megolm primitives (vodozemac) | — |
| mx.client | stateful client + E2EE orchestration | Imports mx.api, Suggests mx.crypto |