Skip to content

feat: add Matrix transport (Element X, Element Web, FluffyChat)#3

Open
jchidley wants to merge 9 commits intotintinweb:masterfrom
jchidley:add-matrix-transport
Open

feat: add Matrix transport (Element X, Element Web, FluffyChat)#3
jchidley wants to merge 9 commits intotintinweb:masterfrom
jchidley:add-matrix-transport

Conversation

@jchidley
Copy link

Adds Matrix as a transport alongside Telegram, WhatsApp, Slack, and Discord.

New MatrixProvider using matrix-bot-sdk. Auto-joins rooms, OTP auth, rich HTML formatting, typing indicators, group chat support with mention detection.

Configure via command or env vars. Works with Element X, Element Web, FluffyChat, any Matrix client.

Add MatrixProvider implementing ITransportProvider using matrix-bot-sdk.

- Auto-joins rooms on invite
- Challenge-based auth (same OTP flow as other transports)
- Rich HTML formatting for code blocks, bold, italic, links
- Typing indicators
- Group chat support with bot mention detection
- Configure via /msg-bridge command or env vars (PI_MATRIX_HOMESERVER, PI_MATRIX_ACCESS_TOKEN)
- Persistent sync state in ~/.pi/msg-bridge-matrix-store.json

Setup: create a bot account on any Matrix homeserver, get an access token,
then: /msg-bridge configure matrix <homeserver-url> <access-token>
Initial sync replays events from all rooms including ones the bot
previously left. The handleMessage handler would then call
getJoinedRoomMembers and checkAuthorization on rooms where the bot
has no access, causing M_FORBIDDEN errors.

Guard early by checking getJoinedRooms() before processing.
- Cache botUserId at connect time (was API call per message)
- Cache joinedRooms as Set, updated via room.join/room.leave events
  (was getJoinedRooms() API call per message)
- Skip events with origin_server_ts before connectedAt (prevents
  processing stale messages replayed during initial sync)
- Simplify sendMessage to single call with conditional spread
- Clean up state on disconnect

Analysis artifacts in analysis/
- Constructor takes config object instead of raw args (matches Discord/Slack)
- Add console.log/console.error logging (matches Discord pattern)
- Input validation in connect() (matches Discord pattern)
- Move escapeHtml to private method (matches Discord splitMessage pattern)
- Add 'mx' abbreviation to status widget
Add persistent E2EE encryption using matrix-bot-sdk's built-in
RustSdkCryptoStorageProvider backed by @matrix-org/matrix-sdk-crypto-nodejs
(native Rust, SQLite on disk).

- Crypto state persists at ~/.pi/msg-bridge-matrix-crypto/ (SQLite)
- Same device and keys across restarts (no device proliferation)
- Encrypted rooms are decrypted automatically
- Device must be verified once from another Matrix client
- encryption config option (defaults to enabled)
- Falls back gracefully if crypto module not available
…failure

- Cache per-room member count to eliminate getJoinedRoomMembers() API call
  on every incoming message. Seeded at connect, refreshed on join/leave,
  lazy-fetched on cache miss.
- Add cleanup in connect() catch block: if client.start() throws, reset
  client, botUserId, joinedRooms, roomMemberCount so connect() can be
  retried cleanly.
- Fresh code-overhaul analysis (v0.3.0) in analysis/.
Extract formatForMatrix, escapeHtml, shouldSkipEvent, extractUsername,
wasBotMentioned, stripBotMention from MatrixProvider class into
matrix-utils.ts for testability without mocks.

Tests cover: HTML escaping, markdown→HTML conversion, code block
protection, event filter chain (own/stale/non-text/edit/left-room),
username extraction, mention detection, mention stripping.

Boundary tests: connectedAt exact boundary, missing timestamps,
homeservers with ports, empty strings.
Property tests:
- formatForMatrix: body always preserves original input
- formatForMatrix: no formattedBody when no markdown chars
- stripBotMention: result never contains the bot MXID
- escapeHtml: output never contains raw <, >, &, or "

47 tests total (43 EBTs + 4 property tests).
All mutation gates passed (10/10 caught, 1 equivalent mutant).
@jchidley jchidley force-pushed the add-matrix-transport branch 3 times, most recently from e7ceb51 to 692aae5 Compare February 27, 2026 17:29
matrix-bot-sdk replays historical events during initial sync and tries
to decrypt them. For E2EE rooms this produces known errors that our
connectedAt filter already handles. Instead of blanket log suppression,
use a filtering logger that drops only:

1. MatrixClientLite 'Decryption error' — old messages without keys
2. MatrixHttpClient 'M_NOT_FOUND' — stale sync token references

All other errors pass through normally. Filters are removed after
initial sync completes — real errors during operation are never hidden.

Note: Node.js DEP0060 (util._extend from upstream htmlencode dep)
fires at import time before any runtime code executes. Cannot be
suppressed without dynamic imports or patching the dependency.
@jchidley jchidley force-pushed the add-matrix-transport branch from 692aae5 to d7cd0ea Compare February 27, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant