From c7913e8397cca7061ba31d0002e6121d74bd0c01 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Thu, 16 Jul 2026 13:12:31 -0500 Subject: [PATCH 1/2] dont require livekit to answer/join calls --- src/app/components/IncomingCallModal.test.tsx | 29 ++++--------------- src/app/components/IncomingCallModal.tsx | 5 +--- src/app/features/call/CallView.tsx | 7 +++-- .../features/call/callStartCapabilities.ts | 10 +++++++ .../call/getIncomingCallBlockers.test.ts | 9 +----- .../features/call/getIncomingCallBlockers.ts | 9 ------ 6 files changed, 21 insertions(+), 48 deletions(-) diff --git a/src/app/components/IncomingCallModal.test.tsx b/src/app/components/IncomingCallModal.test.tsx index 608902ab32..2f1874df5f 100644 --- a/src/app/components/IncomingCallModal.test.tsx +++ b/src/app/components/IncomingCallModal.test.tsx @@ -3,13 +3,11 @@ import type { Room } from '$types/matrix-sdk'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { IncomingCallInternal } from './IncomingCallModal'; -const { navigateRoomMock, sendRtcDeclineMock, webRtcSupportedMock, livekitSupportedMock } = - vi.hoisted(() => ({ - navigateRoomMock: vi.fn<(roomId: string) => void>(), - sendRtcDeclineMock: vi.fn<(roomId: string, eventId: string) => Promise>(), - webRtcSupportedMock: vi.fn<() => boolean>(), - livekitSupportedMock: vi.fn<() => boolean>(), - })); +const { navigateRoomMock, sendRtcDeclineMock, webRtcSupportedMock } = vi.hoisted(() => ({ + navigateRoomMock: vi.fn<(roomId: string) => void>(), + sendRtcDeclineMock: vi.fn<(roomId: string, eventId: string) => Promise>(), + webRtcSupportedMock: vi.fn<() => boolean>(), +})); vi.mock('$hooks/useMatrixClient', () => ({ useMatrixClient: () => ({ @@ -19,10 +17,6 @@ vi.mock('$hooks/useMatrixClient', () => ({ }), })); -vi.mock('$hooks/useLivekitSupport', () => ({ - useLivekitSupport: () => livekitSupportedMock(), -})); - vi.mock('$hooks/useCallEmbed', () => ({ useCallEmbed: () => undefined, })); @@ -101,7 +95,6 @@ describe('IncomingCallInternal', () => { navigateRoomMock.mockReset(); sendRtcDeclineMock.mockReset().mockResolvedValue(undefined); webRtcSupportedMock.mockReset().mockReturnValue(true); - livekitSupportedMock.mockReset().mockReturnValue(true); }); it('closes the modal when decline is pressed', async () => { @@ -152,16 +145,4 @@ describe('IncomingCallInternal', () => { }); expect(sendRtcDeclineMock).not.toHaveBeenCalled(); }); - - it('shows homeserver capability issues and blocks answer when LiveKit is unavailable', () => { - livekitSupportedMock.mockReturnValue(false); - const onClose = vi.fn<() => void>(); - render(); - - expect( - screen.getByText(/homeserver does not expose a livekit call focus/i) - ).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /answer voice call/i })).toBeDisabled(); - expect(screen.getByText(/homeserver call focus is unavailable/i)).toBeInTheDocument(); - }); }); diff --git a/src/app/components/IncomingCallModal.tsx b/src/app/components/IncomingCallModal.tsx index ecb674cb8f..ff072788da 100644 --- a/src/app/components/IncomingCallModal.tsx +++ b/src/app/components/IncomingCallModal.tsx @@ -16,7 +16,6 @@ import { import { useMemo, type KeyboardEvent as ReactKeyboardEvent } from 'react'; import type { Room } from '$types/matrix-sdk'; import { useMatrixClient } from '$hooks/useMatrixClient'; -import { useLivekitSupport } from '$hooks/useLivekitSupport'; import { useRoomName } from '$hooks/useRoomMeta'; import { useCallEmbed } from '$hooks/useCallEmbed'; import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; @@ -63,7 +62,6 @@ export function IncomingCallInternal({ room, incomingCall, onClose }: IncomingCa const screenSize = useScreenSizeContext(); const compact = screenSize === ScreenSize.Mobile; const roomName = useRoomName(room); - const livekitSupported = useLivekitSupport(); const callEmbed = useCallEmbed(); const { navigateRoom } = useRoomNavigate(); const roomAvatarUrl = getRoomAvatarUrl(mx, room, 96); @@ -93,11 +91,10 @@ export function IncomingCallInternal({ room, incomingCall, onClose }: IncomingCa () => getIncomingCallBlockers({ canUseWebRTC, - livekitSupported, hasCallMemberPermission, inAnotherCall, }), - [canUseWebRTC, livekitSupported, hasCallMemberPermission, inAnotherCall] + [canUseWebRTC, hasCallMemberPermission, inAnotherCall] ); const canAnswer = capabilityIssues.length === 0; diff --git a/src/app/features/call/CallView.tsx b/src/app/features/call/CallView.tsx index 36a5108b9a..1317914d2e 100644 --- a/src/app/features/call/CallView.tsx +++ b/src/app/features/call/CallView.tsx @@ -12,6 +12,7 @@ import * as css from './styles.css'; import { CallMemberRenderer } from './CallMemberCard'; import { PrescreenControls } from './PrescreenControls'; import { callEmbedAtom, callEmbedStartErrorAtom } from '$state/callEmbed'; +import { canJoinCall } from './callStartCapabilities'; function LivekitServerMissingMessage() { return ( @@ -42,12 +43,12 @@ function JoinMessage({ return ; } + if (hasParticipant) return null; + if (livekitSupported === false) { return ; } - if (hasParticipant) return null; - return ( Voice chat's empty - be the first to hop in! @@ -96,7 +97,7 @@ function CallPrescreen() { ? 'Call setup failed because required call capabilities were rejected.' : 'Call setup failed while preparing the embedded call app.'; - const canJoin = callStartCapabilities.canStart; + const canJoin = canJoinCall(callStartCapabilities, hasParticipant); return ( diff --git a/src/app/features/call/callStartCapabilities.ts b/src/app/features/call/callStartCapabilities.ts index c2f72a7a1a..9de7772303 100644 --- a/src/app/features/call/callStartCapabilities.ts +++ b/src/app/features/call/callStartCapabilities.ts @@ -18,6 +18,16 @@ export type CallStartCapabilities = { inAnotherCall: boolean; }; +export const canJoinCall = ( + capabilities: CallStartCapabilities, + hasParticipant: boolean +): boolean => + capabilities.canStart || + (hasParticipant && + capabilities.webRTCSupported && + capabilities.hasCallMemberPermission && + !capabilities.inAnotherCall); + type EvaluateCallStartCapabilitiesInput = { room: Room; myUserId: string; diff --git a/src/app/features/call/getIncomingCallBlockers.test.ts b/src/app/features/call/getIncomingCallBlockers.test.ts index 1e4cdb1afd..250d20e24f 100644 --- a/src/app/features/call/getIncomingCallBlockers.test.ts +++ b/src/app/features/call/getIncomingCallBlockers.test.ts @@ -6,7 +6,6 @@ describe('getIncomingCallBlockers', () => { expect( getIncomingCallBlockers({ canUseWebRTC: true, - livekitSupported: true, hasCallMemberPermission: true, inAnotherCall: false, }) @@ -16,16 +15,10 @@ describe('getIncomingCallBlockers', () => { it('returns blockers in priority order', () => { const issues = getIncomingCallBlockers({ canUseWebRTC: false, - livekitSupported: false, hasCallMemberPermission: false, inAnotherCall: true, }); - expect(issues.map((issue) => issue.id)).toEqual([ - 'webrtc', - 'livekit', - 'permission', - 'another_call', - ]); + expect(issues.map((issue) => issue.id)).toEqual(['webrtc', 'permission', 'another_call']); }); }); diff --git a/src/app/features/call/getIncomingCallBlockers.ts b/src/app/features/call/getIncomingCallBlockers.ts index a542e47499..5759fdc4f8 100644 --- a/src/app/features/call/getIncomingCallBlockers.ts +++ b/src/app/features/call/getIncomingCallBlockers.ts @@ -6,14 +6,12 @@ export type IncomingCallBlocker = { export type IncomingCallBlockerInput = { canUseWebRTC: boolean; - livekitSupported: boolean; hasCallMemberPermission: boolean; inAnotherCall: boolean; }; export const getIncomingCallBlockers = ({ canUseWebRTC, - livekitSupported, hasCallMemberPermission, inAnotherCall, }: IncomingCallBlockerInput): IncomingCallBlocker[] => { @@ -26,13 +24,6 @@ export const getIncomingCallBlockers = ({ shortReason: 'WebRTC is unavailable in this browser.', }); } - if (!livekitSupported) { - issues.push({ - id: 'livekit', - message: 'Your homeserver does not expose a LiveKit call focus.', - shortReason: 'Homeserver call focus is unavailable.', - }); - } if (!hasCallMemberPermission) { issues.push({ id: 'permission', From 08fd77431271377c6212720e418a937c63afb657 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Thu, 16 Jul 2026 13:16:10 -0500 Subject: [PATCH 2/2] Create fix-join-existing-calls-without-livekit.md --- .changeset/fix-join-existing-calls-without-livekit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-join-existing-calls-without-livekit.md diff --git a/.changeset/fix-join-existing-calls-without-livekit.md b/.changeset/fix-join-existing-calls-without-livekit.md new file mode 100644 index 0000000000..1236a65ecc --- /dev/null +++ b/.changeset/fix-join-existing-calls-without-livekit.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Allow users without a locally configured LiveKit focus to join calls started by others.