fix: handle Element X to-device encryption key delivery

Element X (26.03.3+) sends io.element.call.encryption_keys as
to-device messages, not room timeline events. Added
UnknownToDeviceEvent callback to catch these and deliver keys
to active voice sessions.

Also added m.room.encrypted decryption attempt in timeline scan
as fallback for older Element versions that send encrypted timeline
events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-03-24 09:08:09 +02:00
parent c11dd73ce3
commit c604b5f644
2 changed files with 57 additions and 1 deletions

View File

@@ -598,7 +598,7 @@ class VoiceSession:
except Exception as e:
logger.debug("call.member state key fetch failed: %s", e)
# Legacy: timeline scan
# Legacy: timeline scan (decrypts m.room.encrypted events to find encryption_keys)
url = f"{homeserver}/_matrix/client/v3/rooms/{self.room_id}/messages"
try:
async with httpx.AsyncClient(timeout=10.0) as http:
@@ -614,6 +614,22 @@ class VoiceSession:
now_ms = int(time.time() * 1000)
for evt in events:
evt_type = evt.get("type", "")
# Decrypt m.room.encrypted events to find encryption_keys inside
if evt_type == "m.room.encrypted" and self.nio_client.olm:
try:
from nio import Event as NioEvent
parsed = NioEvent.parse_encrypted_event(evt)
if parsed:
decrypted = self.nio_client.decrypt_event(parsed)
if hasattr(decrypted, "source") and decrypted.source:
dec_type = decrypted.source.get("type", "")
if dec_type == "io.element.call.encryption_keys":
evt = decrypted.source
evt_type = dec_type
logger.info("Decrypted m.room.encrypted -> %s from %s",
dec_type, evt.get("sender", ""))
except Exception as dec_err:
logger.debug("Failed to decrypt timeline event: %s", dec_err)
if evt_type == "io.element.call.encryption_keys":
sender = evt.get("sender", "")
if sender == user_id: