diff --git a/src/app/features/call/CallView.tsx b/src/app/features/call/CallView.tsx index 8d6000342..7611aa39d 100644 --- a/src/app/features/call/CallView.tsx +++ b/src/app/features/call/CallView.tsx @@ -18,8 +18,7 @@ import { useMatrixClient } from '../../hooks/useMatrixClient'; import { StateEvent } from '../../../types/matrix/room'; import { useCallMembers, useCallSession } from '../../hooks/useCall'; import { LotusDecorationPusher } from '../lotus/LotusDecorationPusher'; -import { useStateEvent } from '../../hooks/useStateEvent'; -import { VoiceLimitContent } from '../common-settings/general/RoomVoiceLimit'; +import { useVoiceChannelFull } from '../../hooks/useVoiceChannelFull'; import { CallMemberRenderer } from './CallMemberCard'; import * as css from './styles.css'; import { CallControls } from './CallControls'; @@ -114,12 +113,9 @@ function CallPrescreen() { const callEmbed = useCallEmbed(); const inOtherCall = callEmbed && callEmbed.roomId !== room.roomId; - // Voice channel user limit (io.lotus.voice_limit). 0 / absent means no limit. - const limitEvent = useStateEvent(room, StateEvent.LotusVoiceLimit); - const maxUsers = limitEvent?.getContent().max_users ?? 0; - // A user already counted in the session is rejoining and should not be blocked. - const alreadyMember = callMembers.some((m) => m.sender === mx.getSafeUserId()); - const channelFull = maxUsers > 0 && !alreadyMember && callMembers.length >= maxUsers; + // [Gitea #30] Voice channel user limit (io.lotus.voice_limit), shared with the + // room-nav join path via useVoiceChannelFull so both agree on "full". + const { channelFull, current: callMembersCount, max: maxUsers } = useVoiceChannelFull(room); const canJoin = hasPermission && livekitSupported && rtcSupported && !channelFull; @@ -144,7 +140,7 @@ function CallPrescreen() { {!inOtherCall && !hasPermission && } {!inOtherCall && hasPermission && channelFull && ( - + )} {!inOtherCall && hasPermission && !channelFull && ( = (evt) => { const powerLevelsEvent = getStateEvent(room, StateEvent.RoomPowerLevels); @@ -714,6 +719,22 @@ function RoomNavItem_({ if (callEmbed) { return; } + + // [Gitea #30] Refuse to start a call into a full voice channel — the + // prescreen already blocks this, but the sidebar second-click join path + // skipped the check entirely. + if (channelFull) { + evt.preventDefault(); + setToast( + createErrorToast( + `Channel full (${voiceCurrent}/${voiceMax})`, + Icons.Warning, + 'Cannot join', + ), + ); + return; + } + // Start call in second click if (selected) { evt.preventDefault(); diff --git a/src/app/hooks/useVoiceChannelFull.ts b/src/app/hooks/useVoiceChannelFull.ts new file mode 100644 index 000000000..638ae1d67 --- /dev/null +++ b/src/app/hooks/useVoiceChannelFull.ts @@ -0,0 +1,34 @@ +import { Room } from 'matrix-js-sdk'; +import { useMemo } from 'react'; +import { useStateEvent } from './useStateEvent'; +import { StateEvent } from '../../types/matrix/room'; +import { VoiceLimitContent } from '../features/common-settings/general/RoomVoiceLimit'; +import { useCallMembers, useCallSession } from './useCall'; +import { useMatrixClient } from './useMatrixClient'; + +export type VoiceChannelFull = { + channelFull: boolean; + current: number; + max: number; +}; + +/** + * [Gitea #30] Voice channel user limit (`io.lotus.voice_limit`), shared between + * `CallPrescreen` and any other join path (e.g. the room-nav second-click join) + * so they agree on when a channel is full. 0/absent `max_users` means no limit. + */ +export const useVoiceChannelFull = (room: Room): VoiceChannelFull => { + const mx = useMatrixClient(); + const callSession = useCallSession(room); + const callMembers = useCallMembers(callSession); + + const limitEvent = useStateEvent(room, StateEvent.LotusVoiceLimit); + + return useMemo(() => { + const maxUsers = limitEvent?.getContent().max_users ?? 0; + // A user already counted in the session is rejoining and should not be blocked. + const alreadyMember = callMembers.some((m) => m.sender === mx.getSafeUserId()); + const channelFull = maxUsers > 0 && !alreadyMember && callMembers.length >= maxUsers; + return { channelFull, current: callMembers.length, max: maxUsers }; + }, [limitEvent, callMembers, mx]); +};