fix(calls): sidebar voice-channel join respects the voice limit
channelFull was computed only in the prescreen; a second click on the channel in the room nav joined a full channel. Extract useVoiceChannelFull, use it in both places, and refuse with a "Channel full (N/N)" toast. Fixes #30 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -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<VoiceLimitContent>().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() {
|
||||
<Box className={css.PrescreenMessage} alignItems="Center">
|
||||
{!inOtherCall && !hasPermission && <NoPermissionMessage />}
|
||||
{!inOtherCall && hasPermission && channelFull && (
|
||||
<ChannelFullMessage current={callMembers.length} max={maxUsers} />
|
||||
<ChannelFullMessage current={callMembersCount} max={maxUsers} />
|
||||
)}
|
||||
{!inOtherCall && hasPermission && !channelFull && (
|
||||
<JoinMessage
|
||||
|
||||
@@ -80,6 +80,7 @@ import {
|
||||
} from '../../hooks/useRoomMeta';
|
||||
import { useCallMembers, useCallSession } from '../../hooks/useCall';
|
||||
import { useCallEmbed, useCallStart } from '../../hooks/useCallEmbed';
|
||||
import { useVoiceChannelFull } from '../../hooks/useVoiceChannelFull';
|
||||
import { callChatAtom } from '../../state/callEmbed';
|
||||
import { createErrorToast, toastQueueAtom } from '../../state/toast';
|
||||
import { useCallPreferencesAtom } from '../../state/hooks/callPreferences';
|
||||
@@ -691,8 +692,12 @@ function RoomNavItem_({
|
||||
const callMembers = useCallMembers(callSession);
|
||||
const startCall = useCallStart(direct);
|
||||
const callEmbed = useCallEmbed();
|
||||
// [Gitea #30] Same voice-limit check the call prescreen uses, so the sidebar
|
||||
// second-click join path can't bypass a full channel.
|
||||
const { channelFull, current: voiceCurrent, max: voiceMax } = useVoiceChannelFull(room);
|
||||
const callPref = useAtomValue(useCallPreferencesAtom());
|
||||
const autoDiscoveryInfo = useAutoDiscoveryInfo();
|
||||
const setToast = useSetAtom(toastQueueAtom);
|
||||
|
||||
const handleStartCall: MouseEventHandler<HTMLAnchorElement> = (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();
|
||||
|
||||
@@ -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<VoiceLimitContent>().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]);
|
||||
};
|
||||
Reference in New Issue
Block a user