chore(deps): bump matrix-js-sdk 39.4.0 → 40.2.0 adapting sessionMembershipsForRoom removal and dedup-key trichotomy

This commit is contained in:
heaven 2026-05-09 12:18:02 +03:00
parent 0d93a223d0
commit 3ea01a9c3f
11 changed files with 66 additions and 36 deletions

18
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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;

View file

@ -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');

View file

@ -34,13 +34,11 @@ export const useCallSession = (room: Room): MatrixRTCSession => {
};
export const useCallMembers = (room: Room, session: MatrixRTCSession): CallMembership[] => {
const [memberships, setMemberships] = useState<CallMembership[]>(
MatrixRTCSession.sessionMembershipsForRoom(room, session.slotDescription)
);
const [memberships, setMemberships] = useState<CallMembership[]>(session.memberships);
useEffect(() => {
const updateMemberships = () => {
setMemberships(MatrixRTCSession.sessionMembershipsForRoom(room, session.slotDescription));
setMemberships(session.memberships);
};
updateMemberships();

View file

@ -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);

View file

@ -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);

View file

@ -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<SessionMembershipData>().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<SessionMembershipData>().call_id ?? '';
return extractCallIdFromMembershipEvent(senderStateEv);
}
const timelineEv = room.findEventById(membershipEventId);
if (timelineEv) {
return timelineEv.getContent<SessionMembershipData>().call_id ?? '';
return extractCallIdFromMembershipEvent(timelineEv);
}
const stateEv = stateEvents.find((ev) => ev.getId() === membershipEventId);
if (stateEv) {
return stateEv.getContent<SessionMembershipData>().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<SessionMembershipData>().call_id ?? '';
return extractCallIdFromMembershipEvent(fetched);
} catch {
return undefined;
}

View file

@ -55,7 +55,7 @@ export const useSwitchOrStartDmCall = (): ((roomId: string) => Promise<void>) =>
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<void>((resolve, reject) => {
@ -118,7 +118,7 @@ export const useSwitchOrStartDmCall = (): ((roomId: string) => Promise<void>) =>
.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;
}

View file

@ -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 (
<IncomingCallStrip
key={`call_${call.callId}_${call.roomId}`}
key={getIncomingCallKey(call.callId, call.roomId)}
call={call}
room={room}
/>

View file

@ -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}`;
};