diff --git a/package-lock.json b/package-lock.json index 054ee12..c30696c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "jotai": "2.6.0", "linkify-react": "4.3.2", "linkifyjs": "4.3.2", - "matrix-js-sdk": "38.2.0", + "matrix-js-sdk": "41.5.0", "matrix-widget-api": "1.16.1", "millify": "6.1.0", "pdfjs-dist": "4.2.67", @@ -2449,9 +2449,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": "18.3.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-18.3.0.tgz", + "integrity": "sha512-9a4feyt8QLysARu7PHKaRWT+wcCd+IYH074LXp9QK5WqfN4zUXueRhiSSMNT18Bm+8q3sBR/4zxDxOSDR0M8Kg==", "license": "Apache-2.0", "engines": { "node": ">= 18" @@ -5684,12 +5684,6 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, "node_modules/@types/sanitize-html": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.1.tgz", @@ -10808,6 +10802,18 @@ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "dev": true }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -12105,43 +12111,29 @@ "license": "Apache-2.0" }, "node_modules/matrix-js-sdk": { - "version": "38.2.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-38.2.0.tgz", - "integrity": "sha512-R3jzK8rDGi/3OXOax8jFFyxblCG9KTT5yuXAbvnZCGcpTm8lZ4mHQAn5UydVD8qiyUMNMpaaMd6/k7N+5I/yaQ==", + "version": "41.5.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-41.5.0.tgz", + "integrity": "sha512-CK3h+qQJ4wkVEUgEWc5MdLjccXyiFqncCC53P+auqOhnX2U6tAFsRfnbML1QQiKIsFMzqTrAnF/4a5LUUOIeXg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^15.1.0", + "@matrix-org/matrix-sdk-crypto-wasm": "^18.2.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.10.0", + "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", - "p-retry": "4", - "sdp-transform": "^2.14.1", - "unhomoglyph": "^1.0.6", - "uuid": "11" + "p-retry": "8", + "sdp-transform": "^3.0.0", + "unhomoglyph": "^1.0.6" }, "engines": { "node": ">=22.0.0" } }, - "node_modules/matrix-js-sdk/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/matrix-widget-api": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.16.1.tgz", @@ -14929,16 +14921,18 @@ } }, "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz", + "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==", "license": "MIT", "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "is-network-error": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { @@ -16020,15 +16014,6 @@ "node": ">=8" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -16254,9 +16239,9 @@ } }, "node_modules/sdp-transform": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz", - "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==", "license": "MIT", "bin": { "sdp-verify": "checker.js" diff --git a/package.json b/package.json index 54fff09..e8e0360 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "jotai": "2.6.0", "linkify-react": "4.3.2", "linkifyjs": "4.3.2", - "matrix-js-sdk": "38.2.0", + "matrix-js-sdk": "41.5.0", "matrix-widget-api": "1.16.1", "millify": "6.1.0", "pdfjs-dist": "4.2.67", diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index 650cf05..94784e1 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -1,7 +1,6 @@ /* eslint-disable jsx-a11y/media-has-caption */ import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; -import { MatrixRTCSession } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession'; import FocusTrap from 'focus-trap-react'; import { Avatar, @@ -93,12 +92,14 @@ function IncomingCall({ dm, info, onIgnore, onAnswer, onReject }: IncomingCallPr const session = useCallSession(room); useCallMembersChange( session, - useCallback(() => { - const members = MatrixRTCSession.sessionMembershipsForRoom(room, session.sessionDescription); - if (members.length === 0) { - onIgnore(); - } - }, [room, session, onIgnore]) + useCallback( + (members) => { + if (members.length === 0) { + onIgnore(); + } + }, + [onIgnore] + ) ); const playSound = useCallback(() => { @@ -264,7 +265,8 @@ function IncomingCallListener({ callEmbed, joined }: IncomingCallListenerProps) const refEventId = relation?.event_id; const mention = - content['m.mentions'].room || content['m.mentions'].user_ids?.includes(mx.getSafeUserId()); + content['m.mentions']?.room || + content['m.mentions']?.user_ids?.includes(mx.getSafeUserId()); if (!sender || !refEventId || !mention || Date.now() >= senderTs + lifetime) { return; } diff --git a/src/app/features/call-status/CallStatus.tsx b/src/app/features/call-status/CallStatus.tsx index 1d30d1b..1ff3b83 100644 --- a/src/app/features/call-status/CallStatus.tsx +++ b/src/app/features/call-status/CallStatus.tsx @@ -22,7 +22,7 @@ export function CallStatus({ callEmbed }: CallStatusProps) { const { room } = callEmbed; const callSession = useCallSession(room); - const callMembers = useCallMembers(room, callSession); + const callMembers = useCallMembers(callSession); const screenSize = useScreenSize(); const callJoined = useCallJoined(callEmbed); const speakers = useCallSpeakers(callEmbed); diff --git a/src/app/features/call-status/LiveChip.tsx b/src/app/features/call-status/LiveChip.tsx index a5d00a5..32df328 100644 --- a/src/app/features/call-status/LiveChip.tsx +++ b/src/app/features/call-status/LiveChip.tsx @@ -82,7 +82,7 @@ export function LiveChip({ count, room, members }: LiveChipProps) { return ( {visibleMembers.map((callMember) => { - const userId = callMember.sender; + const { userId } = callMember; if (!userId) return null; const name = getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId; const avatarMxc = getMemberAvatarMxc(room, userId); @@ -39,7 +39,7 @@ export function MemberGlance({ room, members, speakers, max = 6 }: MemberGlanceP return ( {truncatedMembers.map((member) => ( - + ))} {members.length > max && ( 0; const callEmbed = useCallEmbed(); diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index 77e4159..0834111 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -282,7 +282,7 @@ export function RoomNavItem({ const optionsVisible = hover || !!menuAnchor; const callSession = useCallSession(room); - const callMembers = useCallMembers(room, callSession); + const callMembers = useCallMembers(callSession); const startCall = useCallStart(direct); const callEmbed = useCallEmbed(); const callPref = useAtomValue(useCallPreferencesAtom()); diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index b3e8a4e..ae42da8 100644 --- a/src/app/features/room/Room.tsx +++ b/src/app/features/room/Room.tsx @@ -27,7 +27,7 @@ export function Room() { const mx = useMatrixClient(); const callSession = useCallSession(room); - const callMembers = useCallMembers(room, callSession); + const callMembers = useCallMembers(callSession); const callEmbed = useCallEmbed(); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 39d7e50..c533528 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -27,7 +27,6 @@ import { HTMLReactParserOptions } from 'html-react-parser'; import classNames from 'classnames'; import { ReactEditor } from 'slate-react'; import { Editor } from 'slate'; -import { SessionMembershipData } from 'matrix-js-sdk/lib/matrixrtc/CallMembership'; import to from 'await-to-js'; import { useAtomValue, useSetAtom } from 'jotai'; import { @@ -1475,7 +1474,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const senderId = mEvent.getSender() ?? ''; const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId); - const content = mEvent.getContent(); + const content = mEvent.getContent(); const prevContent = mEvent.getPrevContent(); const callJoined = content.application; diff --git a/src/app/hooks/useCall.ts b/src/app/hooks/useCall.ts index 1364e45..817e12f 100644 --- a/src/app/hooks/useCall.ts +++ b/src/app/hooks/useCall.ts @@ -2,6 +2,7 @@ import { Room } from 'matrix-js-sdk'; import { MatrixRTCSession, MatrixRTCSessionEvent, + MatrixRTCSessionEventHandlerMap, } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession'; import { CallMembership } from 'matrix-js-sdk/lib/matrixrtc/CallMembership'; import { useEffect, useState } from 'react'; @@ -33,32 +34,27 @@ export const useCallSession = (room: Room): MatrixRTCSession => { return session; }; -export const useCallMembers = (room: Room, session: MatrixRTCSession): CallMembership[] => { - const [memberships, setMemberships] = useState( - MatrixRTCSession.sessionMembershipsForRoom(room, session.sessionDescription) - ); - +export const useCallMembersChange = ( + session: MatrixRTCSession, + callback: (members: CallMembership[]) => void +): void => { useEffect(() => { - const updateMemberships = () => { - setMemberships(MatrixRTCSession.sessionMembershipsForRoom(room, session.sessionDescription)); - }; + const handleMembershipsChange: MatrixRTCSessionEventHandlerMap[MatrixRTCSessionEvent.MembershipsChanged] = + (oldestMembership, newMemberships) => { + callback(newMemberships); + }; - updateMemberships(); - - session.on(MatrixRTCSessionEvent.MembershipsChanged, updateMemberships); + session.on(MatrixRTCSessionEvent.MembershipsChanged, handleMembershipsChange); return () => { - session.removeListener(MatrixRTCSessionEvent.MembershipsChanged, updateMemberships); - }; - }, [session, room]); - - return memberships; -}; - -export const useCallMembersChange = (session: MatrixRTCSession, callback: () => void): void => { - useEffect(() => { - session.on(MatrixRTCSessionEvent.MembershipsChanged, callback); - return () => { - session.removeListener(MatrixRTCSessionEvent.MembershipsChanged, callback); + session.removeListener(MatrixRTCSessionEvent.MembershipsChanged, handleMembershipsChange); }; }, [session, callback]); }; + +export const useCallMembers = (session: MatrixRTCSession): CallMembership[] => { + const [memberships, setMemberships] = useState(session.memberships); + + useCallMembersChange(session, setMemberships); + + return memberships; +}; diff --git a/src/app/hooks/useCallEmbed.ts b/src/app/hooks/useCallEmbed.ts index b50dad9..af6f47b 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 { @@ -45,8 +44,7 @@ export const createCallEmbed = ( pref?: CallPreferences ): CallEmbed => { const rtcSession = mx.matrixRTC.getRoomSession(room); - const ongoing = - MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.sessionDescription).length > 0; + const ongoing = rtcSession.memberships.length > 0; const intent = CallEmbed.getIntent(dm, ongoing, pref?.video); const widget = CallEmbed.getWidget(mx, room, intent, themeKind); diff --git a/src/app/hooks/useCallSpeakers.ts b/src/app/hooks/useCallSpeakers.ts index 2400367..7f72007 100644 --- a/src/app/hooks/useCallSpeakers.ts +++ b/src/app/hooks/useCallSpeakers.ts @@ -8,7 +8,7 @@ import { useCallJoined } from './useCallEmbed'; export const useCallSpeakers = (callEmbed: CallEmbed): Set => { const [speakers, setSpeakers] = useState(new Set()); const callSession = useCallSession(callEmbed.room); - const callMembers = useCallMembers(callEmbed.room, callSession); + const callMembers = useCallMembers(callSession); const joined = useCallJoined(callEmbed); const videoContainers = useMemo(() => { diff --git a/src/app/plugins/call/CallWidgetDriver.ts b/src/app/plugins/call/CallWidgetDriver.ts index babe90e..d21eaa7 100644 --- a/src/app/plugins/call/CallWidgetDriver.ts +++ b/src/app/plugins/call/CallWidgetDriver.ts @@ -8,7 +8,6 @@ import { type IWidgetApiErrorResponseDataDetails, type ISearchUserDirectoryResult, type IGetMediaConfigResult, - type UpdateDelayedEventAction, OpenIDRequestState, SimpleObservable, IOpenIDUpdate, @@ -53,14 +52,11 @@ export class CallWidgetDriver extends WidgetDriver { stateKey: string | null = null, targetRoomId: string | null = null ): Promise { - const client = this.mx; const roomId = targetRoomId || this.inRoomId; - if (!client || !roomId) throw new Error('Not in a room or not attached to a client'); - let r: { event_id: string } | null; if (typeof stateKey === 'string') { - r = await client.sendStateEvent( + r = await this.mx.sendStateEvent( roomId, eventType as keyof StateEvents, content as StateEvents[keyof StateEvents], @@ -68,9 +64,9 @@ export class CallWidgetDriver extends WidgetDriver { ); } else if (eventType === EventType.RoomRedaction) { // special case: extract the `redacts` property and call redact - r = await client.redactEvent(roomId, content.redacts); + r = await this.mx.redactEvent(roomId, content.redacts); } else { - r = await client.sendEvent( + r = await this.mx.sendEvent( roomId, eventType as keyof TimelineEvents, content as TimelineEvents[keyof TimelineEvents] @@ -88,11 +84,8 @@ export class CallWidgetDriver extends WidgetDriver { stateKey: string | null = null, targetRoomId: string | null = null ): Promise { - const client = this.mx; const roomId = targetRoomId || this.inRoomId; - if (!client || !roomId) throw new Error('Not in a room or not attached to a client'); - let delayOpts; if (delay !== null) { delayOpts = { @@ -110,7 +103,7 @@ export class CallWidgetDriver extends WidgetDriver { let r: SendDelayedEventResponse | null; if (stateKey !== null) { // state event - r = await client._unstable_sendDelayedStateEvent( + r = await this.mx._unstable_sendDelayedStateEvent( roomId, delayOpts, eventType as keyof StateEvents, @@ -119,7 +112,7 @@ export class CallWidgetDriver extends WidgetDriver { ); } else { // message event - r = await client._unstable_sendDelayedEvent( + r = await this.mx._unstable_sendDelayedEvent( roomId, delayOpts, null, @@ -134,15 +127,16 @@ export class CallWidgetDriver extends WidgetDriver { }; } - public async updateDelayedEvent( - delayId: string, - action: UpdateDelayedEventAction - ): Promise { - const client = this.mx; + public async cancelScheduledDelayedEvent(delayId: string): Promise { + await this.mx._unstable_cancelScheduledDelayedEvent(delayId); + } - if (!client) throw new Error('Not in a room or not attached to a client'); + public async restartScheduledDelayedEvent(delayId: string): Promise { + await this.mx._unstable_restartScheduledDelayedEvent(delayId); + } - await client._unstable_updateDelayedEvent(delayId, action); + public async sendScheduledDelayedEvent(delayId: string): Promise { + await this.mx._unstable_sendScheduledDelayedEvent(delayId); } public async sendToDevice( @@ -150,10 +144,8 @@ export class CallWidgetDriver extends WidgetDriver { encrypted: boolean, contentMap: { [userId: string]: { [deviceId: string]: object } } ): Promise { - const client = this.mx; - if (encrypted) { - const crypto = client.getCrypto(); + const crypto = this.mx.getCrypto(); if (!crypto) throw new Error('E2EE not enabled'); // attempt to re-batch these up into a single request @@ -179,11 +171,11 @@ export class CallWidgetDriver extends WidgetDriver { JSON.parse(stringifiedContent) ); - await client.queueToDevice(batch); + await this.mx.queueToDevice(batch); }) ); } else { - await client.queueToDevice({ + await this.mx.queueToDevice({ eventType, batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) => Object.entries(userContentMap).map(([deviceId, content]) => ({ @@ -263,7 +255,6 @@ export class CallWidgetDriver extends WidgetDriver { limit?: number, direction?: 'f' | 'b' ): Promise { - const client = this.mx; const dir = direction as Direction; const targetRoomId = roomId ?? this.inRoomId ?? undefined; @@ -271,7 +262,7 @@ export class CallWidgetDriver extends WidgetDriver { throw new Error('Error while reading the current room'); } - const { events, nextBatch, prevBatch } = await client.relations( + const { events, nextBatch, prevBatch } = await this.mx.relations( targetRoomId, eventId, relationType ?? null, @@ -290,9 +281,7 @@ export class CallWidgetDriver extends WidgetDriver { searchTerm: string, limit?: number ): Promise { - const client = this.mx; - - const { limited, results } = await client.searchUserDirectory({ term: searchTerm, limit }); + const { limited, results } = await this.mx.searchUserDirectory({ term: searchTerm, limit }); return { limited, @@ -305,15 +294,11 @@ export class CallWidgetDriver extends WidgetDriver { } public async getMediaConfig(): Promise { - const client = this.mx; - - return client.getMediaConfig(); + return this.mx.getMediaConfig(); } public async uploadFile(file: XMLHttpRequestBodyInit): Promise<{ contentUri: string }> { - const client = this.mx; - - const uploadResult = await client.uploadContent(file); + const uploadResult = await this.mx.uploadContent(file); return { contentUri: uploadResult.content_uri }; }