snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Skeleton } from '@rocket.chat/fuselage';
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import {
useVideoConfSetPreferences,
VideoConfPopup,
VideoConfPopupContent,
VideoConfPopupControllers,
VideoConfController,
useVideoConfControllers,
VideoConfButton,
VideoConfPopupFooter,
VideoConfPopupFooterButtons,
VideoConfPopupTitle,
VideoConfPopupHeader,
} from '@rocket.chat/ui-video-conf';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import VideoConfPopupRoomInfo from './VideoConfPopupRoomInfo';
import { useVideoConfRoomName } from '../../hooks/useVideoConfRoomName';
export type IncomingPopupProps = {
id: string;
room: IRoom;
position: number;
onClose: (id: string) => void;
onMute: (id: string) => void;
onConfirm: () => void;
};
const IncomingPopup = ({ id, room, position, onClose, onMute, onConfirm }: IncomingPopupProps) => {
const { t } = useTranslation();
const { controllersConfig, handleToggleMic, handleToggleCam } = useVideoConfControllers();
const setPreferences = useVideoConfSetPreferences();
const roomName = useVideoConfRoomName(room);
const videoConfInfo = useEndpoint('GET', '/v1/video-conference.info');
const { data, isPending, isSuccess } = useQuery({
queryKey: ['getVideoConferenceInfo', id],
queryFn: async () => videoConfInfo({ callId: id }),
});
const showMic = Boolean(data?.capabilities?.mic);
const showCam = Boolean(data?.capabilities?.cam);
const handleJoinCall = useStableCallback(() => {
setPreferences(controllersConfig);
onConfirm();
});
return (
<VideoConfPopup position={position} id={id} aria-label={t('Incoming_call_from__roomName__', { roomName })}>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Incoming_call_from')} />
{isPending && <Skeleton />}
{isSuccess && (showMic || showCam) && (
<VideoConfPopupControllers>
{showCam && (
<VideoConfController
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
onClick={handleToggleCam}
/>
)}
{showMic && (
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
onClick={handleToggleMic}
/>
)}
</VideoConfPopupControllers>
)}
</VideoConfPopupHeader>
<VideoConfPopupContent>
<VideoConfPopupRoomInfo room={room} />
</VideoConfPopupContent>
<VideoConfPopupFooter>
<VideoConfPopupFooterButtons>
<VideoConfButton primary onClick={handleJoinCall}>
{t('Accept')}
</VideoConfButton>
{onClose && (
<VideoConfButton danger secondary onClick={(): void => onClose(id)}>
{t('Decline')}
</VideoConfButton>
)}
<VideoConfController small={false} secondary title={t('Mute_and_dismiss')} icon='cross' onClick={(): void => onMute(id)} />
</VideoConfPopupFooterButtons>
</VideoConfPopupFooter>
</VideoConfPopup>
);
};
export default IncomingPopup;

View File

@@ -0,0 +1,74 @@
import type { IRoom } from '@rocket.chat/core-typings';
import {
VideoConfPopup,
VideoConfPopupContent,
VideoConfPopupControllers,
VideoConfController,
useVideoConfControllers,
VideoConfButton,
VideoConfPopupFooter,
VideoConfPopupFooterButtons,
VideoConfPopupTitle,
VideoConfPopupHeader,
useVideoConfCapabilities,
useVideoConfPreferences,
} from '@rocket.chat/ui-video-conf';
import { useTranslation } from 'react-i18next';
import VideoConfPopupRoomInfo from './VideoConfPopupRoomInfo';
import { useVideoConfRoomName } from '../../hooks/useVideoConfRoomName';
export type OutgoingPopupProps = {
id: string;
room: IRoom;
onClose: (id: string) => void;
};
const OutgoingPopup = ({ room, onClose, id }: OutgoingPopupProps) => {
const { t } = useTranslation();
const videoConfPreferences = useVideoConfPreferences();
const { controllersConfig } = useVideoConfControllers(videoConfPreferences);
const capabilities = useVideoConfCapabilities();
const roomName = useVideoConfRoomName(room);
const showCam = !!capabilities.cam;
const showMic = !!capabilities.mic;
return (
<VideoConfPopup aria-label={t('Calling__roomName__', { roomName })}>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Calling')} counter />
{(showCam || showMic) && (
<VideoConfPopupControllers>
{showCam && (
<VideoConfController
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
disabled
/>
)}
{showMic && (
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
disabled
/>
)}
</VideoConfPopupControllers>
)}
</VideoConfPopupHeader>
<VideoConfPopupContent>
<VideoConfPopupRoomInfo room={room} />
</VideoConfPopupContent>
<VideoConfPopupFooter>
<VideoConfPopupFooterButtons>
{onClose && <VideoConfButton onClick={(): void => onClose(id)}>{t('Cancel')}</VideoConfButton>}
</VideoConfPopupFooterButtons>
</VideoConfPopupFooter>
</VideoConfPopup>
);
};
export default OutgoingPopup;

View File

@@ -0,0 +1,109 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useOutsideClick, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
VideoConfPopup,
VideoConfPopupHeader,
VideoConfPopupContent,
VideoConfPopupControllers,
VideoConfController,
useVideoConfControllers,
VideoConfButton,
VideoConfPopupFooter,
VideoConfPopupTitle,
VideoConfPopupFooterButtons,
useVideoConfSetPreferences,
useVideoConfCapabilities,
useVideoConfPreferences,
} from '@rocket.chat/ui-video-conf';
import { useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import VideoConfPopupRoomInfo from './VideoConfPopupRoomInfo';
import { useVideoConfRoomName } from '../../hooks/useVideoConfRoomName';
export type StartCallPopupProps = {
id: string;
loading: boolean;
room: IRoom;
onClose: () => void;
onConfirm: () => void;
};
const StartCallPopup = ({ id, loading, room, onClose, onConfirm }: StartCallPopupProps) => {
const { t } = useTranslation();
const ref = useRef<HTMLElement | null>(null);
useOutsideClick([ref], !loading ? onClose : () => undefined);
const setPreferences = useVideoConfSetPreferences();
const videoConfPreferences = useVideoConfPreferences();
const { controllersConfig, handleToggleMic, handleToggleCam } = useVideoConfControllers(videoConfPreferences);
const capabilities = useVideoConfCapabilities();
const roomName = useVideoConfRoomName(room);
const dialogLabel =
room.t === 'd' ? `${t('Start_a_call_with__roomName__', { roomName })}` : `${t('Start_a_call_in__roomName__', { roomName })}`;
const showCam = !!capabilities.cam;
const showMic = !!capabilities.mic;
const handleStartCall = useStableCallback(() => {
setPreferences(controllersConfig);
onConfirm();
});
const callbackRef = useCallback(
(node: HTMLElement | null) => {
if (!node) {
return;
}
ref.current = node;
node.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
});
},
[onClose],
);
return (
<VideoConfPopup ref={callbackRef} id={id} aria-label={dialogLabel}>
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Start_a_call')} />
{(showCam || showMic) && (
<VideoConfPopupControllers>
{showCam && (
<VideoConfController
active={controllersConfig.cam}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
onClick={handleToggleCam}
/>
)}
{showMic && (
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
onClick={handleToggleMic}
/>
)}
</VideoConfPopupControllers>
)}
</VideoConfPopupHeader>
<VideoConfPopupContent>
<VideoConfPopupRoomInfo room={room} />
</VideoConfPopupContent>
<VideoConfPopupFooter>
<VideoConfPopupFooterButtons>
<VideoConfButton disabled={loading} primary onClick={handleStartCall}>
{t('Start_call')}
</VideoConfButton>
</VideoConfPopupFooterButtons>
</VideoConfPopupFooter>
</VideoConfPopup>
);
};
export default StartCallPopup;

View File

@@ -0,0 +1,79 @@
import { useFocusManager } from '@react-aria/focus';
import type { IRoom } from '@rocket.chat/core-typings';
import { useUserRoom } from '@rocket.chat/ui-contexts';
import {
useVideoConfAcceptCall,
useVideoConfAbortCall,
useVideoConfRejectIncomingCall,
useVideoConfDismissCall,
useVideoConfStartCall,
useVideoConfDismissOutgoing,
} from '@rocket.chat/ui-video-conf';
import { useEffect, useState } from 'react';
import IncomingPopup from './IncomingPopup';
import OutgoingPopup from './OutgoingPopup';
import StartCallPopup from './StartCallPopup';
export type TimedVideoConfPopupProps = {
id: string;
rid: IRoom['_id'];
isReceiving?: boolean;
isCalling?: boolean;
position: number;
onClose?: (id: string) => void;
};
const TimedVideoConfPopup = ({ id, rid, isReceiving = false, isCalling = false, position }: TimedVideoConfPopupProps) => {
const [starting, setStarting] = useState(false);
const acceptCall = useVideoConfAcceptCall();
const abortCall = useVideoConfAbortCall();
const rejectCall = useVideoConfRejectIncomingCall();
const dismissCall = useVideoConfDismissCall();
const startCall = useVideoConfStartCall();
const dismissOutgoing = useVideoConfDismissOutgoing();
const focusManager = useFocusManager();
const room = useUserRoom(rid);
useEffect(() => {
focusManager?.focusFirst();
}, [focusManager]);
if (!room) {
return null;
}
const handleConfirm = (): void => {
acceptCall(id);
};
const handleClose = (id: string): void => {
if (isReceiving) {
rejectCall(id);
return;
}
abortCall();
};
const handleMute = (): void => {
dismissCall(id);
};
const handleStartCall = async (): Promise<void> => {
setStarting(true);
startCall(rid);
};
if (isReceiving) {
return <IncomingPopup room={room} id={id} position={position} onClose={handleClose} onMute={handleMute} onConfirm={handleConfirm} />;
}
if (isCalling) {
return <OutgoingPopup room={room} id={id} onClose={handleClose} />;
}
return <StartCallPopup loading={starting} room={room} id={id} onClose={dismissOutgoing} onConfirm={handleStartCall} />;
};
export default TimedVideoConfPopup;

View File

@@ -0,0 +1,39 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isDirectMessageRoom, isMultipleDirectMessageRoom } from '@rocket.chat/core-typings';
import { RoomAvatar } from '@rocket.chat/ui-avatar';
import { useUser } from '@rocket.chat/ui-contexts';
import { VideoConfPopupInfo } from '@rocket.chat/ui-video-conf';
import { RoomIcon } from '../../../../../../components/RoomIcon';
import ReactiveUserStatus from '../../../../../../components/UserStatus/ReactiveUserStatus';
import { useVideoConfRoomName } from '../../hooks/useVideoConfRoomName';
export type VideoConfPopupRoomInfoProps = { room: IRoom };
const VideoConfPopupRoomInfo = ({ room }: VideoConfPopupRoomInfoProps) => {
const ownUser = useUser();
const [userId] = room?.uids?.filter((uid) => uid !== ownUser?._id) || [];
const roomName = useVideoConfRoomName(room);
const avatar = <RoomAvatar room={room} size='x40' />;
if (isDirectMessageRoom(room)) {
return (
<VideoConfPopupInfo
avatar={avatar}
{...(userId && {
icon: isMultipleDirectMessageRoom(room) ? <RoomIcon placement='default' room={room} /> : <ReactiveUserStatus uid={userId} />,
})}
>
{roomName}
</VideoConfPopupInfo>
);
}
return (
<VideoConfPopupInfo avatar={avatar} icon={<RoomIcon placement='default' room={room} />}>
{roomName}
</VideoConfPopupInfo>
);
};
export default VideoConfPopupRoomInfo;

View File

@@ -0,0 +1 @@
export { default } from './TimedVideoConfPopup';

View File

@@ -0,0 +1,22 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import VideoConfPopups from './VideoConfPopups';
import { createFakeRoom } from '../../../../../../tests/mocks/data';
import { createFakeVideoConfCall, createFakeIncomingCall } from '../../../../../../tests/mocks/utils/video-conference';
const fakeRoom = createFakeRoom({ t: 'd' });
const fakeDirectVideoConfCall = createFakeVideoConfCall({ type: 'direct', rid: fakeRoom._id });
const fakeIncomingCall = createFakeIncomingCall({ rid: fakeRoom._id });
test('should render video conference incoming popup', async () => {
render(<VideoConfPopups />, {
wrapper: mockAppRoot()
.withRoom(fakeRoom)
.withEndpoint('GET', '/v1/video-conference.info', () => fakeDirectVideoConfCall as any)
.withIncomingCalls([fakeIncomingCall])
.build(),
});
expect(await screen.findByRole('dialog')).toBeInTheDocument();
});

View File

@@ -0,0 +1,67 @@
import { FocusScope } from '@react-aria/focus';
import { useCustomSound } from '@rocket.chat/ui-contexts';
import type { VideoConfPopupPayload } from '@rocket.chat/ui-video-conf';
import {
VideoConfPopupBackdrop,
useVideoConfIsCalling,
useVideoConfIsRinging,
useVideoConfIncomingCalls,
VideoConfPopupSkeleton,
} from '@rocket.chat/ui-video-conf';
import { lazy, Suspense, useEffect, useMemo } from 'react';
import VideoConfPopupPortal from '../../../../../portals/VideoConfPopupPortal';
const VideoConfPopup = lazy(() => import('./VideoConfPopup'));
export type VideoConfPopupsProps = { children?: VideoConfPopupPayload };
const VideoConfPopups = ({ children }: VideoConfPopupsProps) => {
const { callSounds } = useCustomSound();
const incomingCalls = useVideoConfIncomingCalls();
const isRinging = useVideoConfIsRinging();
const isCalling = useVideoConfIsCalling();
const popups = useMemo(
() =>
incomingCalls
.filter((incomingCall) => !incomingCall.dismissed)
.map((incomingCall) => ({ id: incomingCall.callId, rid: incomingCall.rid, isReceiving: true })),
[incomingCalls],
);
useEffect(() => {
if (isRinging) {
callSounds.playRinger();
}
if (isCalling) {
callSounds.playDialer();
}
return (): void => {
callSounds.stopRinger();
callSounds.stopDialer();
};
}, [isRinging, isCalling, callSounds]);
return (
<>
{(children || popups?.length > 0) && (
<VideoConfPopupPortal>
{(children ? [children, ...popups] : popups).map(({ id, rid, isReceiving }, index = 1) => (
<VideoConfPopupBackdrop key={id}>
<Suspense fallback={<VideoConfPopupSkeleton />}>
<FocusScope restoreFocus>
<VideoConfPopup id={id} rid={rid} isReceiving={isReceiving} isCalling={isCalling} position={index * 10} />
</FocusScope>
</Suspense>
</VideoConfPopupBackdrop>
))}
</VideoConfPopupPortal>
)}
</>
);
};
export default VideoConfPopups;

View File

@@ -0,0 +1 @@
export { default } from './VideoConfPopups';