From 3ea01a9c3f77343e6a94d07c522cbe3b9ce82f6e Mon Sep 17 00:00:00 2001 From: heaven Date: Sat, 9 May 2026 12:18:02 +0300 Subject: [PATCH] =?UTF-8?q?chore(deps):=20bump=20matrix-js-sdk=2039.4.0=20?= =?UTF-8?q?=E2=86=92=2040.2.0=20adapting=20sessionMembershipsForRoom=20rem?= =?UTF-8?q?oval=20and=20dedup-key=20trichotomy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 18 ++++---- package.json | 2 +- src/app/features/call/CallMemberCard.tsx | 2 +- src/app/features/room/RoomViewHeaderDm.tsx | 2 +- src/app/hooks/useCall.ts | 6 +-- src/app/hooks/useCallEmbed.ts | 4 +- src/app/hooks/useCallerAutoHangup.ts | 2 +- src/app/hooks/useIncomingRtcNotifications.ts | 45 +++++++++++++++----- src/app/hooks/useSwitchOrStartDmCall.ts | 4 +- src/app/pages/IncomingCallStripRenderer.tsx | 3 +- src/app/utils/rtcNotification.ts | 14 +++++- 11 files changed, 66 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56137dc1..88e658c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "jotai": "2.6.0", "linkify-react": "4.3.2", "linkifyjs": "4.3.2", - "matrix-js-sdk": "39.4.0", + "matrix-js-sdk": "40.2.0", "matrix-widget-api": "1.17.0", "millify": "6.1.0", "pdfjs-dist": "4.2.67", @@ -2856,9 +2856,9 @@ } }, "node_modules/@matrix-org/matrix-sdk-crypto-wasm": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-15.3.0.tgz", - "integrity": "sha512-QyxHvncvkl7nf+tnn92PjQ54gMNV8hMSpiukiDgNrqF6IYwgySTlcSdkPYdw8QjZJ0NR6fnVrNzMec0OohM3wA==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-17.1.0.tgz", + "integrity": "sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g==", "license": "Apache-2.0", "engines": { "node": ">= 18" @@ -10647,20 +10647,20 @@ "license": "Apache-2.0" }, "node_modules/matrix-js-sdk": { - "version": "39.4.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-39.4.0.tgz", - "integrity": "sha512-0RZLcwbMxMTU+ORPhpFUnX8nDbKwLtdW20T6VesSxEwjKL5j2TM/mIt4u3h0HJiMh63PpAb2QUcNr0R82YfTOA==", + "version": "40.2.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-40.2.0.tgz", + "integrity": "sha512-wqb1Oq34WB9r0njxw8XiNsm8DIvYeGfCn3wrVrDwj8HMoTI0TvLSY1sQ+x6J2Eg27abfVwInxLKyxLp+dROFXQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^15.3.0", + "@matrix-org/matrix-sdk-crypto-wasm": "^17.0.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", "jwt-decode": "^4.0.0", "loglevel": "^1.9.2", "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.14.0", + "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", "p-retry": "7", "sdp-transform": "^3.0.0", diff --git a/package.json b/package.json index 10ef8ca0..10886b63 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "jotai": "2.6.0", "linkify-react": "4.3.2", "linkifyjs": "4.3.2", - "matrix-js-sdk": "39.4.0", + "matrix-js-sdk": "40.2.0", "matrix-widget-api": "1.17.0", "millify": "6.1.0", "pdfjs-dist": "4.2.67", diff --git a/src/app/features/call/CallMemberCard.tsx b/src/app/features/call/CallMemberCard.tsx index f96b9a06..6e3485d0 100644 --- a/src/app/features/call/CallMemberCard.tsx +++ b/src/app/features/call/CallMemberCard.tsx @@ -28,7 +28,7 @@ export function CallMemberCard({ member }: CallMemberCardProps) { const openUserProfile = useOpenUserRoomProfile(); - const userId = member.sender; + const { userId } = member; if (!userId) return null; const name = getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId; diff --git a/src/app/features/room/RoomViewHeaderDm.tsx b/src/app/features/room/RoomViewHeaderDm.tsx index 8373e51c..a44765c6 100644 --- a/src/app/features/room/RoomViewHeaderDm.tsx +++ b/src/app/features/room/RoomViewHeaderDm.tsx @@ -394,7 +394,7 @@ function DmCallButton({ room }: { room: Room }) { const inCallHere = currentEmbed?.roomId === room.roomId; if (inCallHere) return null; - const ongoingByOthers = members.length > 0 && !members.some((m) => m.sender === myUserId); + const ongoingByOthers = members.length > 0 && !members.some((m) => m.userId === myUserId); const disabled = !livekitSupported; let tooltipText: string; if (!livekitSupported) tooltipText = t('Call.unavailable'); diff --git a/src/app/hooks/useCall.ts b/src/app/hooks/useCall.ts index 1692a57c..b33991cd 100644 --- a/src/app/hooks/useCall.ts +++ b/src/app/hooks/useCall.ts @@ -34,13 +34,11 @@ export const useCallSession = (room: Room): MatrixRTCSession => { }; export const useCallMembers = (room: Room, session: MatrixRTCSession): CallMembership[] => { - const [memberships, setMemberships] = useState( - MatrixRTCSession.sessionMembershipsForRoom(room, session.slotDescription) - ); + const [memberships, setMemberships] = useState(session.memberships); useEffect(() => { const updateMemberships = () => { - setMemberships(MatrixRTCSession.sessionMembershipsForRoom(room, session.slotDescription)); + setMemberships(session.memberships); }; updateMemberships(); diff --git a/src/app/hooks/useCallEmbed.ts b/src/app/hooks/useCallEmbed.ts index 6d215458..54a567df 100644 --- a/src/app/hooks/useCallEmbed.ts +++ b/src/app/hooks/useCallEmbed.ts @@ -1,5 +1,4 @@ import { createContext, RefObject, useCallback, useContext, useEffect, useState } from 'react'; -import { MatrixRTCSession } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession'; import { MatrixClient, Room } from 'matrix-js-sdk'; import { useSetAtom } from 'jotai'; import { @@ -46,8 +45,7 @@ export const createCallEmbed = ( voiceOnly = false ): CallEmbed => { const rtcSession = mx.matrixRTC.getRoomSession(room); - const ongoing = - MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.slotDescription).length > 0; + const ongoing = rtcSession.memberships.length > 0; const intent = CallEmbed.getIntent(dm, ongoing, voiceOnly); const widget = CallEmbed.getWidget(mx, room, intent, themeKind); diff --git a/src/app/hooks/useCallerAutoHangup.ts b/src/app/hooks/useCallerAutoHangup.ts index 9381dd20..6bb8b8c4 100644 --- a/src/app/hooks/useCallerAutoHangup.ts +++ b/src/app/hooks/useCallerAutoHangup.ts @@ -77,7 +77,7 @@ export const useCallerAutoHangup = (): void => { const selfDeviceId = mx.getDeviceId(); const isSelf = (m: CallMembership): boolean => - m.sender === selfId && (!selfDeviceId || m.deviceId === selfDeviceId); + m.userId === selfId && (!selfDeviceId || m.deviceId === selfDeviceId); const session = mx.matrixRTC.getRoomSession(callEmbed.room); diff --git a/src/app/hooks/useIncomingRtcNotifications.ts b/src/app/hooks/useIncomingRtcNotifications.ts index 0d93137b..c4c2e057 100644 --- a/src/app/hooks/useIncomingRtcNotifications.ts +++ b/src/app/hooks/useIncomingRtcNotifications.ts @@ -43,12 +43,28 @@ import { } from '../utils/rtcNotification'; import { callForegroundService } from '../plugins/call/callForegroundService'; -// Returns "" for room-scoped calls (MSC4143/MSC3401v2 — empty call_id / slot id -// means "the only call in this room"). Returns undefined when no membership is -// known. matrix-js-sdk 39+ exposes the room-scoped id as -// `slotDescription.id` (parsed from `slot_id = '{application}#{id}'` for -// `m.rtc.member` or computed from legacy `call_id` for `m.call.member`); the -// runtime `CallMembership.callId` getter was removed. +// Extract the room-scoped call slot id from a raw membership state event, +// covering both the legacy `m.call.member` shape (SessionMembershipData with +// `call_id` string) and the `m.rtc.member` shape (RtcMembershipData with +// `slot_id` of form `'{application}#{id}'`). Returns the bare id portion; +// caller normalizes the legacy `''` ↔ `'ROOM'` equivalence at the dedup-key +// boundary (see `getIncomingCallKey`). Returns undefined for malformed events. +const extractCallIdFromMembershipEvent = (ev: MatrixEvent): string | undefined => { + if (ev.getType() === EventType.RTCMembership) { + const slotId = (ev.getContent() as { slot_id?: unknown }).slot_id; + if (typeof slotId !== 'string') return undefined; + const hashIdx = slotId.indexOf('#'); + return hashIdx >= 0 ? slotId.slice(hashIdx + 1) : ''; + } + return ev.getContent().call_id ?? ''; +}; + +// Returns the room-scoped slot id ('' for legacy or new with implicit default, +// 'ROOM' after SDK 40.2 compat-hack via senderMembership.slotDescription.id; +// dedup-key normalization handled in getIncomingCallKey). Returns undefined +// when no membership is known. matrix-js-sdk 39+ exposes the slot id as +// `slotDescription.id` parsed from new `slot_id` or computed from legacy +// `call_id`; the runtime `CallMembership.callId` getter was removed. const findCallIdSync = ( mx: MatrixClient, room: Room, @@ -61,20 +77,27 @@ const findCallIdSync = ( return senderMembership.slotDescription.id; } - const stateEvents = room.currentState.getStateEvents(EventType.GroupCallMemberPrefix); + // Race-recovery: ring arrived before /sync delivered the membership state + // event into the SDK session. Fall back to direct state lookup, covering + // both legacy `m.call.member` and `m.rtc.member` event types so a peer on + // an MSC4354-capable homeserver doesn't silently produce an empty key here. + const stateEvents = [ + ...room.currentState.getStateEvents(EventType.GroupCallMemberPrefix), + ...room.currentState.getStateEvents(EventType.RTCMembership), + ]; const senderStateEv = stateEvents.find((ev) => ev.getSender() === sender); if (senderStateEv) { - return senderStateEv.getContent().call_id ?? ''; + return extractCallIdFromMembershipEvent(senderStateEv); } const timelineEv = room.findEventById(membershipEventId); if (timelineEv) { - return timelineEv.getContent().call_id ?? ''; + return extractCallIdFromMembershipEvent(timelineEv); } const stateEv = stateEvents.find((ev) => ev.getId() === membershipEventId); if (stateEv) { - return stateEv.getContent().call_id ?? ''; + return extractCallIdFromMembershipEvent(stateEv); } return undefined; @@ -112,7 +135,7 @@ const resolveCallId = async ( try { const raw = await mx.fetchRoomEvent(room.roomId, membershipEventId); const fetched = new MatrixEvent(raw); - return fetched.getContent().call_id ?? ''; + return extractCallIdFromMembershipEvent(fetched); } catch { return undefined; } diff --git a/src/app/hooks/useSwitchOrStartDmCall.ts b/src/app/hooks/useSwitchOrStartDmCall.ts index d4e425e4..de310ae8 100644 --- a/src/app/hooks/useSwitchOrStartDmCall.ts +++ b/src/app/hooks/useSwitchOrStartDmCall.ts @@ -55,7 +55,7 @@ export const useSwitchOrStartDmCall = (): ((roomId: string) => Promise) => const selfDeviceId = mx.getDeviceId(); const selfPresent = (memberships: CallMembership[]): boolean => memberships.some( - (m) => m.sender === selfUserId && (!selfDeviceId || m.deviceId === selfDeviceId) + (m) => m.userId === selfUserId && (!selfDeviceId || m.deviceId === selfDeviceId) ); return new Promise((resolve, reject) => { @@ -118,7 +118,7 @@ export const useSwitchOrStartDmCall = (): ((roomId: string) => Promise) => .getRoomSession(prev.room) .memberships.some( (m: CallMembership) => - m.sender !== selfUserId || (!!selfDeviceId && m.deviceId !== selfDeviceId) + m.userId !== selfUserId || (!!selfDeviceId && m.deviceId !== selfDeviceId) ); if (prev.joined && peerPresent) return; } diff --git a/src/app/pages/IncomingCallStripRenderer.tsx b/src/app/pages/IncomingCallStripRenderer.tsx index 96ec7690..272b5048 100644 --- a/src/app/pages/IncomingCallStripRenderer.tsx +++ b/src/app/pages/IncomingCallStripRenderer.tsx @@ -26,6 +26,7 @@ import { App } from '@capacitor/app'; import { incomingCallsAtom } from '../state/incomingCalls'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { IncomingCallStrip } from '../features/call-status'; +import { getIncomingCallKey } from '../utils/rtcNotification'; import { isAndroidPlatform } from '../utils/capacitor'; // eslint-disable-next-line import/no-relative-packages import RingSoundOgg from '../../../public/sound/ring.ogg'; @@ -126,7 +127,7 @@ export function IncomingCallStripRenderer() { if (!room) return null; return ( diff --git a/src/app/utils/rtcNotification.ts b/src/app/utils/rtcNotification.ts index 1ffac03a..58e32dd3 100644 --- a/src/app/utils/rtcNotification.ts +++ b/src/app/utils/rtcNotification.ts @@ -17,5 +17,15 @@ export const isRtcNotificationExpired = (ev: MatrixEvent, now = Date.now()): boo return now - getNotificationEventSendTs(ev) > lifetime; }; -export const getIncomingCallKey = (callId: string, roomId: string): string => - `call_${callId}_${roomId}`; +// Normalize the legacy '' ↔ 'ROOM' equivalence for room-scoped DM call slots. +// matrix-js-sdk 40.2 introduced a `slotDescription.id` compat hack that +// surfaces 'ROOM' for room-scoped m.call.member memberships (which still go on +// the wire as `call_id: ''`); pre-40.2 SDKs and direct raw-event reads in +// `findCallIdSync` race-recovery paths still yield ''. Without this normalize +// step, a single logical ring acquires three distinct dedup keys depending on +// peer SDK version and whether we read happy-path vs fallback — and registry +// dedup, decline-IDs and atom REMOVE/ADD all key on the result. +export const getIncomingCallKey = (callId: string, roomId: string): string => { + const normalized = callId === '' ? 'ROOM' : callId; + return `call_${normalized}_${roomId}`; +};