Call link admin key fix and in-call approve, deny, remove
This commit is contained in:
parent
5df8924197
commit
8ec585d54c
20 changed files with 599 additions and 43 deletions
|
@ -1685,6 +1685,18 @@
|
||||||
"messageformat": "More options",
|
"messageformat": "More options",
|
||||||
"description": "Tooltip label for button in the calling screen that opens a menu with other call actions such as React or Raise Hand."
|
"description": "Tooltip label for button in the calling screen that opens a menu with other call actions such as React or Raise Hand."
|
||||||
},
|
},
|
||||||
|
"icu:CallingPendingParticipants__ApproveUser": {
|
||||||
|
"messageformat": "Approve join request",
|
||||||
|
"description": "Tooltip label for check mark button to approve a user's request to join a call."
|
||||||
|
},
|
||||||
|
"icu:CallingPendingParticipants__DenyUser": {
|
||||||
|
"messageformat": "Deny join request",
|
||||||
|
"description": "Tooltip label for check mark button to deny a user's request to join a call."
|
||||||
|
},
|
||||||
|
"icu:CallingPendingParticipants__RequestsToJoin": {
|
||||||
|
"messageformat": "{count, plural, one {# request} other {# requests}} to join the call",
|
||||||
|
"description": "Shown in the call pending join request list to describe how many people are requesting to join"
|
||||||
|
},
|
||||||
"icu:CallingRaisedHandsList__Title": {
|
"icu:CallingRaisedHandsList__Title": {
|
||||||
"messageformat": "Raised hands · {count, plural, one {# person} other {# people}}",
|
"messageformat": "Raised hands · {count, plural, one {# person} other {# people}}",
|
||||||
"description": "Shown in the call raised hands list to describe how many people have active raised hands"
|
"description": "Shown in the call raised hands list to describe how many people have active raised hands"
|
||||||
|
@ -3654,6 +3666,10 @@
|
||||||
"messageformat": "Copy link",
|
"messageformat": "Copy link",
|
||||||
"description": "Menu item in the in-call info popup for call link calls. The action is to add the call link to the clipboard."
|
"description": "Menu item in the in-call info popup for call link calls. The action is to add the call link to the clipboard."
|
||||||
},
|
},
|
||||||
|
"icu:CallingAdhocCallInfo__RemoveClient": {
|
||||||
|
"messageformat": "Remove this person from the call",
|
||||||
|
"description": "Button in the in-call info popup for call link calls showing all participants. The action is to remove the participant from the call."
|
||||||
|
},
|
||||||
"icu:callingDeviceSelection__label--video": {
|
"icu:callingDeviceSelection__label--video": {
|
||||||
"messageformat": "Video",
|
"messageformat": "Video",
|
||||||
"description": "Label for video input selector"
|
"description": "Label for video input selector"
|
||||||
|
|
|
@ -4511,6 +4511,13 @@ button.module-image__border-overlay:focus {
|
||||||
$color-white
|
$color-white
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__remove {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v3/minus/minus-circle-compact.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-call-need-permission-screen {
|
.module-call-need-permission-screen {
|
||||||
|
|
|
@ -81,3 +81,11 @@
|
||||||
margin-inline: 10px;
|
margin-inline: 10px;
|
||||||
border: 1px solid $color-gray-65;
|
border: 1px solid $color-gray-65;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CallingAdhocCallInfo__RemoveClient {
|
||||||
|
@include button-reset;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-inline: 8px;
|
||||||
|
background: $color-white;
|
||||||
|
}
|
||||||
|
|
34
stylesheets/components/CallingPendingParticipants.scss
Normal file
34
stylesheets/components/CallingPendingParticipants.scss
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.CallingPendingParticipants {
|
||||||
|
width: 420px;
|
||||||
|
height: auto;
|
||||||
|
padding-block-end: 2px;
|
||||||
|
margin-block-start: auto;
|
||||||
|
margin-block-end: 36px;
|
||||||
|
margin-inline-start: auto;
|
||||||
|
margin-inline-end: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallingPendingParticipants__PendingActionButton {
|
||||||
|
padding-inline: 0;
|
||||||
|
margin-inline-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallingPendingParticipants__PendingActionButton:last-child {
|
||||||
|
margin-inline-end: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallingPendingParticipants__PendingActionButtonIcon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallingPendingParticipants__PendingActionButtonIcon--Approve {
|
||||||
|
@include color-svg('../images/icons/v3/check/check.svg', $color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallingPendingParticipants__PendingActionButtonIcon--Deny {
|
||||||
|
@include color-svg('../images/icons/v3/x/x.svg', $color-white);
|
||||||
|
}
|
|
@ -45,6 +45,7 @@
|
||||||
@import './components/CallControls.scss';
|
@import './components/CallControls.scss';
|
||||||
@import './components/CallSettingsButton.scss';
|
@import './components/CallSettingsButton.scss';
|
||||||
@import './components/CallingLobby.scss';
|
@import './components/CallingLobby.scss';
|
||||||
|
@import './components/CallingPendingParticipants.scss';
|
||||||
@import './components/CallingPreCallInfo.scss';
|
@import './components/CallingPreCallInfo.scss';
|
||||||
@import './components/CallingScreenSharingController.scss';
|
@import './components/CallingScreenSharingController.scss';
|
||||||
@import './components/CallingSelectPresentingSourcesModal.scss';
|
@import './components/CallingSelectPresentingSourcesModal.scss';
|
||||||
|
|
|
@ -59,12 +59,14 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
...storyProps,
|
...storyProps,
|
||||||
availableCameras: [],
|
availableCameras: [],
|
||||||
acceptCall: action('accept-call'),
|
acceptCall: action('accept-call'),
|
||||||
|
approveUser: action('approve-user'),
|
||||||
bounceAppIconStart: action('bounce-app-icon-start'),
|
bounceAppIconStart: action('bounce-app-icon-start'),
|
||||||
bounceAppIconStop: action('bounce-app-icon-stop'),
|
bounceAppIconStop: action('bounce-app-icon-stop'),
|
||||||
cancelCall: action('cancel-call'),
|
cancelCall: action('cancel-call'),
|
||||||
changeCallView: action('change-call-view'),
|
changeCallView: action('change-call-view'),
|
||||||
closeNeedPermissionScreen: action('close-need-permission-screen'),
|
closeNeedPermissionScreen: action('close-need-permission-screen'),
|
||||||
declineCall: action('decline-call'),
|
declineCall: action('decline-call'),
|
||||||
|
denyUser: action('deny-user'),
|
||||||
getGroupCallVideoFrameSource: (_: string, demuxId: number) =>
|
getGroupCallVideoFrameSource: (_: string, demuxId: number) =>
|
||||||
fakeGetGroupCallVideoFrameSource(demuxId),
|
fakeGetGroupCallVideoFrameSource(demuxId),
|
||||||
getPresentingSources: action('get-presenting-sources'),
|
getPresentingSources: action('get-presenting-sources'),
|
||||||
|
@ -84,6 +86,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
notifyForCall: action('notify-for-call'),
|
notifyForCall: action('notify-for-call'),
|
||||||
openSystemPreferencesAction: action('open-system-preferences-action'),
|
openSystemPreferencesAction: action('open-system-preferences-action'),
|
||||||
playRingtone: action('play-ringtone'),
|
playRingtone: action('play-ringtone'),
|
||||||
|
removeClient: action('remove-client'),
|
||||||
renderDeviceSelection: () => <div />,
|
renderDeviceSelection: () => <div />,
|
||||||
renderEmojiPicker: () => <>EmojiPicker</>,
|
renderEmojiPicker: () => <>EmojiPicker</>,
|
||||||
renderReactionPicker: () => <div />,
|
renderReactionPicker: () => <div />,
|
||||||
|
@ -156,6 +159,7 @@ export function OngoingGroupCall(): JSX.Element {
|
||||||
groupMembers: [],
|
groupMembers: [],
|
||||||
isConversationTooBigToRing: false,
|
isConversationTooBigToRing: false,
|
||||||
peekedParticipants: [],
|
peekedParticipants: [],
|
||||||
|
pendingParticipants: [],
|
||||||
raisedHands: new Set<number>(),
|
raisedHands: new Set<number>(),
|
||||||
remoteParticipants: [],
|
remoteParticipants: [],
|
||||||
remoteAudioLevels: new Map<number, number>(),
|
remoteAudioLevels: new Map<number, number>(),
|
||||||
|
|
|
@ -31,6 +31,8 @@ import type {
|
||||||
CancelCallType,
|
CancelCallType,
|
||||||
DeclineCallType,
|
DeclineCallType,
|
||||||
GroupCallParticipantInfoType,
|
GroupCallParticipantInfoType,
|
||||||
|
PendingUserActionPayloadType,
|
||||||
|
RemoveClientType,
|
||||||
SendGroupCallRaiseHandType,
|
SendGroupCallRaiseHandType,
|
||||||
SendGroupCallReactionType,
|
SendGroupCallReactionType,
|
||||||
SetGroupCallVideoRequestType,
|
SetGroupCallVideoRequestType,
|
||||||
|
@ -95,9 +97,11 @@ export type PropsType = {
|
||||||
startCall: (payload: StartCallType) => void;
|
startCall: (payload: StartCallType) => void;
|
||||||
toggleParticipants: () => void;
|
toggleParticipants: () => void;
|
||||||
acceptCall: (_: AcceptCallType) => void;
|
acceptCall: (_: AcceptCallType) => void;
|
||||||
|
approveUser: (payload: PendingUserActionPayloadType) => void;
|
||||||
bounceAppIconStart: () => unknown;
|
bounceAppIconStart: () => unknown;
|
||||||
bounceAppIconStop: () => unknown;
|
bounceAppIconStop: () => unknown;
|
||||||
declineCall: (_: DeclineCallType) => void;
|
declineCall: (_: DeclineCallType) => void;
|
||||||
|
denyUser: (payload: PendingUserActionPayloadType) => void;
|
||||||
hasInitialLoadCompleted: boolean;
|
hasInitialLoadCompleted: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGroupCallRaiseHandEnabled: boolean;
|
isGroupCallRaiseHandEnabled: boolean;
|
||||||
|
@ -109,6 +113,7 @@ export type PropsType = {
|
||||||
) => unknown;
|
) => unknown;
|
||||||
openSystemPreferencesAction: () => unknown;
|
openSystemPreferencesAction: () => unknown;
|
||||||
playRingtone: () => unknown;
|
playRingtone: () => unknown;
|
||||||
|
removeClient: (payload: RemoveClientType) => void;
|
||||||
sendGroupCallRaiseHand: (payload: SendGroupCallRaiseHandType) => void;
|
sendGroupCallRaiseHand: (payload: SendGroupCallRaiseHandType) => void;
|
||||||
sendGroupCallReaction: (payload: SendGroupCallReactionType) => void;
|
sendGroupCallReaction: (payload: SendGroupCallReactionType) => void;
|
||||||
setGroupCallVideoRequest: (_: SetGroupCallVideoRequestType) => void;
|
setGroupCallVideoRequest: (_: SetGroupCallVideoRequestType) => void;
|
||||||
|
@ -151,11 +156,13 @@ type ActiveCallManagerPropsType = {
|
||||||
|
|
||||||
function ActiveCallManager({
|
function ActiveCallManager({
|
||||||
activeCall,
|
activeCall,
|
||||||
|
approveUser,
|
||||||
availableCameras,
|
availableCameras,
|
||||||
callLink,
|
callLink,
|
||||||
cancelCall,
|
cancelCall,
|
||||||
changeCallView,
|
changeCallView,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
|
denyUser,
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
i18n,
|
i18n,
|
||||||
isGroupCallRaiseHandEnabled,
|
isGroupCallRaiseHandEnabled,
|
||||||
|
@ -166,6 +173,7 @@ function ActiveCallManager({
|
||||||
renderDeviceSelection,
|
renderDeviceSelection,
|
||||||
renderEmojiPicker,
|
renderEmojiPicker,
|
||||||
renderReactionPicker,
|
renderReactionPicker,
|
||||||
|
removeClient,
|
||||||
sendGroupCallRaiseHand,
|
sendGroupCallRaiseHand,
|
||||||
sendGroupCallReaction,
|
sendGroupCallReaction,
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
|
@ -258,6 +266,7 @@ function ActiveCallManager({
|
||||||
let isConvoTooBigToRing = false;
|
let isConvoTooBigToRing = false;
|
||||||
let isAdhocAdminApprovalRequired = false;
|
let isAdhocAdminApprovalRequired = false;
|
||||||
let isAdhocJoinRequestPending = false;
|
let isAdhocJoinRequestPending = false;
|
||||||
|
let isCallLinkAdmin = false;
|
||||||
|
|
||||||
switch (activeCall.callMode) {
|
switch (activeCall.callMode) {
|
||||||
case CallMode.Direct: {
|
case CallMode.Direct: {
|
||||||
|
@ -292,6 +301,7 @@ function ActiveCallManager({
|
||||||
isAdhocJoinRequestPending =
|
isAdhocJoinRequestPending =
|
||||||
isAdhocAdminApprovalRequired &&
|
isAdhocAdminApprovalRequired &&
|
||||||
activeCall.joinState === GroupCallJoinState.Pending;
|
activeCall.joinState === GroupCallJoinState.Pending;
|
||||||
|
isCallLinkAdmin = Boolean(callLink?.adminKey);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -352,10 +362,12 @@ function ActiveCallManager({
|
||||||
<CallingAdhocCallInfo
|
<CallingAdhocCallInfo
|
||||||
callLink={callLink}
|
callLink={callLink}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isCallLinkAdmin={isCallLinkAdmin}
|
||||||
ourServiceId={me.serviceId}
|
ourServiceId={me.serviceId}
|
||||||
participants={peekedParticipants}
|
participants={peekedParticipants}
|
||||||
onClose={toggleParticipants}
|
onClose={toggleParticipants}
|
||||||
onCopyCallLink={onCopyCallLink}
|
onCopyCallLink={onCopyCallLink}
|
||||||
|
removeClient={removeClient}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CallingParticipantsList
|
<CallingParticipantsList
|
||||||
|
@ -388,6 +400,7 @@ function ActiveCallManager({
|
||||||
hasRemoteVideo: hasLocalVideo,
|
hasRemoteVideo: hasLocalVideo,
|
||||||
isHandRaised,
|
isHandRaised,
|
||||||
presenting: Boolean(activeCall.presentingSource),
|
presenting: Boolean(activeCall.presentingSource),
|
||||||
|
demuxId: activeCall.localDemuxId,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
@ -396,12 +409,15 @@ function ActiveCallManager({
|
||||||
<>
|
<>
|
||||||
<CallScreen
|
<CallScreen
|
||||||
activeCall={activeCall}
|
activeCall={activeCall}
|
||||||
|
approveUser={approveUser}
|
||||||
changeCallView={changeCallView}
|
changeCallView={changeCallView}
|
||||||
|
denyUser={denyUser}
|
||||||
getPresentingSources={getPresentingSources}
|
getPresentingSources={getPresentingSources}
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
|
||||||
groupMembers={groupMembers}
|
groupMembers={groupMembers}
|
||||||
hangUpActiveCall={hangUpActiveCall}
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isCallLinkAdmin={isCallLinkAdmin}
|
||||||
isGroupCallRaiseHandEnabled={isGroupCallRaiseHandEnabled}
|
isGroupCallRaiseHandEnabled={isGroupCallRaiseHandEnabled}
|
||||||
me={me}
|
me={me}
|
||||||
openSystemPreferencesAction={openSystemPreferencesAction}
|
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||||
|
@ -438,10 +454,12 @@ function ActiveCallManager({
|
||||||
<CallingAdhocCallInfo
|
<CallingAdhocCallInfo
|
||||||
callLink={callLink}
|
callLink={callLink}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isCallLinkAdmin={isCallLinkAdmin}
|
||||||
ourServiceId={me.serviceId}
|
ourServiceId={me.serviceId}
|
||||||
participants={groupCallParticipantsForParticipantsList}
|
participants={groupCallParticipantsForParticipantsList}
|
||||||
onClose={toggleParticipants}
|
onClose={toggleParticipants}
|
||||||
onCopyCallLink={onCopyCallLink}
|
onCopyCallLink={onCopyCallLink}
|
||||||
|
removeClient={removeClient}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CallingParticipantsList
|
<CallingParticipantsList
|
||||||
|
@ -458,6 +476,7 @@ function ActiveCallManager({
|
||||||
export function CallManager({
|
export function CallManager({
|
||||||
acceptCall,
|
acceptCall,
|
||||||
activeCall,
|
activeCall,
|
||||||
|
approveUser,
|
||||||
availableCameras,
|
availableCameras,
|
||||||
bounceAppIconStart,
|
bounceAppIconStart,
|
||||||
bounceAppIconStop,
|
bounceAppIconStop,
|
||||||
|
@ -466,6 +485,7 @@ export function CallManager({
|
||||||
changeCallView,
|
changeCallView,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
declineCall,
|
declineCall,
|
||||||
|
denyUser,
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
|
@ -479,6 +499,7 @@ export function CallManager({
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
pauseVoiceNotePlayer,
|
pauseVoiceNotePlayer,
|
||||||
playRingtone,
|
playRingtone,
|
||||||
|
removeClient,
|
||||||
renderDeviceSelection,
|
renderDeviceSelection,
|
||||||
renderEmojiPicker,
|
renderEmojiPicker,
|
||||||
renderReactionPicker,
|
renderReactionPicker,
|
||||||
|
@ -552,10 +573,12 @@ export function CallManager({
|
||||||
<ActiveCallManager
|
<ActiveCallManager
|
||||||
activeCall={activeCall}
|
activeCall={activeCall}
|
||||||
availableCameras={availableCameras}
|
availableCameras={availableCameras}
|
||||||
|
approveUser={approveUser}
|
||||||
callLink={callLink}
|
callLink={callLink}
|
||||||
cancelCall={cancelCall}
|
cancelCall={cancelCall}
|
||||||
changeCallView={changeCallView}
|
changeCallView={changeCallView}
|
||||||
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
||||||
|
denyUser={denyUser}
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||||
getPresentingSources={getPresentingSources}
|
getPresentingSources={getPresentingSources}
|
||||||
hangUpActiveCall={hangUpActiveCall}
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
|
@ -564,6 +587,7 @@ export function CallManager({
|
||||||
me={me}
|
me={me}
|
||||||
openSystemPreferencesAction={openSystemPreferencesAction}
|
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||||
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
||||||
|
removeClient={removeClient}
|
||||||
renderDeviceSelection={renderDeviceSelection}
|
renderDeviceSelection={renderDeviceSelection}
|
||||||
renderEmojiPicker={renderEmojiPicker}
|
renderEmojiPicker={renderEmojiPicker}
|
||||||
renderReactionPicker={renderReactionPicker}
|
renderReactionPicker={renderReactionPicker}
|
||||||
|
|
|
@ -67,6 +67,7 @@ type GroupCallOverrideProps = OverridePropsBase & {
|
||||||
callMode: CallMode.Group;
|
callMode: CallMode.Group;
|
||||||
connectionState?: GroupCallConnectionState;
|
connectionState?: GroupCallConnectionState;
|
||||||
peekedParticipants?: Array<ConversationType>;
|
peekedParticipants?: Array<ConversationType>;
|
||||||
|
pendingParticipants?: Array<ConversationType>;
|
||||||
raisedHands?: Set<number>;
|
raisedHands?: Set<number>;
|
||||||
remoteParticipants?: Array<GroupCallRemoteParticipantType>;
|
remoteParticipants?: Array<GroupCallRemoteParticipantType>;
|
||||||
remoteAudioLevel?: number;
|
remoteAudioLevel?: number;
|
||||||
|
@ -135,6 +136,7 @@ const createActiveGroupCallProp = (overrideProps: GroupCallOverrideProps) => ({
|
||||||
isConversationTooBigToRing: false,
|
isConversationTooBigToRing: false,
|
||||||
peekedParticipants:
|
peekedParticipants:
|
||||||
overrideProps.peekedParticipants || overrideProps.remoteParticipants || [],
|
overrideProps.peekedParticipants || overrideProps.remoteParticipants || [],
|
||||||
|
pendingParticipants: overrideProps.pendingParticipants || [],
|
||||||
raisedHands:
|
raisedHands:
|
||||||
overrideProps.raisedHands ||
|
overrideProps.raisedHands ||
|
||||||
getRaisedHands(overrideProps) ||
|
getRaisedHands(overrideProps) ||
|
||||||
|
@ -181,11 +183,14 @@ const createProps = (
|
||||||
}
|
}
|
||||||
): PropsType => ({
|
): PropsType => ({
|
||||||
activeCall: createActiveCallProp(overrideProps),
|
activeCall: createActiveCallProp(overrideProps),
|
||||||
|
approveUser: action('approve-user'),
|
||||||
changeCallView: action('change-call-view'),
|
changeCallView: action('change-call-view'),
|
||||||
|
denyUser: action('deny-user'),
|
||||||
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
|
||||||
getPresentingSources: action('get-presenting-sources'),
|
getPresentingSources: action('get-presenting-sources'),
|
||||||
hangUpActiveCall: action('hang-up'),
|
hangUpActiveCall: action('hang-up'),
|
||||||
i18n,
|
i18n,
|
||||||
|
isCallLinkAdmin: true,
|
||||||
isGroupCallRaiseHandEnabled: true,
|
isGroupCallRaiseHandEnabled: true,
|
||||||
me: getDefaultConversation({
|
me: getDefaultConversation({
|
||||||
color: AvatarColors[1],
|
color: AvatarColors[1],
|
||||||
|
|
|
@ -8,6 +8,7 @@ import classNames from 'classnames';
|
||||||
import type { VideoFrameSource } from '@signalapp/ringrtc';
|
import type { VideoFrameSource } from '@signalapp/ringrtc';
|
||||||
import type {
|
import type {
|
||||||
ActiveCallStateType,
|
ActiveCallStateType,
|
||||||
|
PendingUserActionPayloadType,
|
||||||
SendGroupCallRaiseHandType,
|
SendGroupCallRaiseHandType,
|
||||||
SendGroupCallReactionType,
|
SendGroupCallReactionType,
|
||||||
SetLocalAudioType,
|
SetLocalAudioType,
|
||||||
|
@ -88,14 +89,18 @@ import {
|
||||||
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
|
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
|
||||||
import { assertDev } from '../util/assert';
|
import { assertDev } from '../util/assert';
|
||||||
import { emojiToData } from './emoji/lib';
|
import { emojiToData } from './emoji/lib';
|
||||||
|
import { CallingPendingParticipants } from './CallingPendingParticipants';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
approveUser: (payload: PendingUserActionPayloadType) => void;
|
||||||
|
denyUser: (payload: PendingUserActionPayloadType) => void;
|
||||||
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
|
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
|
||||||
getPresentingSources: () => void;
|
getPresentingSources: () => void;
|
||||||
groupMembers?: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>;
|
groupMembers?: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>;
|
||||||
hangUpActiveCall: (reason: string) => void;
|
hangUpActiveCall: (reason: string) => void;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
isCallLinkAdmin: boolean;
|
||||||
isGroupCallRaiseHandEnabled: boolean;
|
isGroupCallRaiseHandEnabled: boolean;
|
||||||
me: ConversationType;
|
me: ConversationType;
|
||||||
openSystemPreferencesAction: () => unknown;
|
openSystemPreferencesAction: () => unknown;
|
||||||
|
@ -178,12 +183,15 @@ function CallDuration({
|
||||||
|
|
||||||
export function CallScreen({
|
export function CallScreen({
|
||||||
activeCall,
|
activeCall,
|
||||||
|
approveUser,
|
||||||
changeCallView,
|
changeCallView,
|
||||||
|
denyUser,
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
i18n,
|
i18n,
|
||||||
|
isCallLinkAdmin,
|
||||||
isGroupCallRaiseHandEnabled,
|
isGroupCallRaiseHandEnabled,
|
||||||
me,
|
me,
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
|
@ -396,6 +404,11 @@ export function CallScreen({
|
||||||
throw missingCaseError(activeCall);
|
throw missingCaseError(activeCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pendingParticipants =
|
||||||
|
activeCall.callMode === CallMode.Adhoc && isCallLinkAdmin
|
||||||
|
? activeCall.pendingParticipants
|
||||||
|
: [];
|
||||||
|
|
||||||
let lonelyInCallNode: ReactNode;
|
let lonelyInCallNode: ReactNode;
|
||||||
let localPreviewNode: ReactNode;
|
let localPreviewNode: ReactNode;
|
||||||
|
|
||||||
|
@ -811,6 +824,15 @@ export function CallScreen({
|
||||||
renderRaisedHandsToast={renderRaisedHandsToast}
|
renderRaisedHandsToast={renderRaisedHandsToast}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
|
{pendingParticipants.length ? (
|
||||||
|
<CallingPendingParticipants
|
||||||
|
i18n={i18n}
|
||||||
|
ourServiceId={me.serviceId}
|
||||||
|
participants={pendingParticipants}
|
||||||
|
approveUser={approveUser}
|
||||||
|
denyUser={denyUser}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{/* We render the local preview first and set the footer flex direction to row-reverse
|
{/* We render the local preview first and set the footer flex direction to row-reverse
|
||||||
to ensure the preview is visible at low viewport widths. */}
|
to ensure the preview is visible at low viewport widths. */}
|
||||||
<div className="module-ongoing-call__footer">
|
<div className="module-ongoing-call__footer">
|
||||||
|
|
|
@ -61,10 +61,12 @@ function getCallLink(overrideProps: Partial<CallLinkType> = {}): CallLinkType {
|
||||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
callLink: getCallLink(overrideProps.callLink || {}),
|
callLink: getCallLink(overrideProps.callLink || {}),
|
||||||
i18n,
|
i18n,
|
||||||
|
isCallLinkAdmin: overrideProps.isCallLinkAdmin || false,
|
||||||
ourServiceId: generateAci(),
|
ourServiceId: generateAci(),
|
||||||
participants: overrideProps.participants || [],
|
participants: overrideProps.participants || [],
|
||||||
onClose: action('on-close'),
|
onClose: action('on-close'),
|
||||||
onCopyCallLink: action('on-copy-call-link'),
|
onCopyCallLink: action('on-copy-call-link'),
|
||||||
|
removeClient: overrideProps.removeClient || action('remove-client'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -16,29 +16,35 @@ import { sortByTitle } from '../util/sortByTitle';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import { ModalHost } from './ModalHost';
|
import { ModalHost } from './ModalHost';
|
||||||
import { isInSystemContacts } from '../util/isInSystemContacts';
|
import { isInSystemContacts } from '../util/isInSystemContacts';
|
||||||
|
import type { RemoveClientType } from '../state/ducks/calling';
|
||||||
|
|
||||||
type ParticipantType = ConversationType & {
|
type ParticipantType = ConversationType & {
|
||||||
hasRemoteAudio?: boolean;
|
hasRemoteAudio?: boolean;
|
||||||
hasRemoteVideo?: boolean;
|
hasRemoteVideo?: boolean;
|
||||||
isHandRaised?: boolean;
|
isHandRaised?: boolean;
|
||||||
presenting?: boolean;
|
presenting?: boolean;
|
||||||
|
demuxId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
readonly callLink: CallLinkType;
|
readonly callLink: CallLinkType;
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
|
readonly isCallLinkAdmin: boolean;
|
||||||
readonly ourServiceId: ServiceIdString | undefined;
|
readonly ourServiceId: ServiceIdString | undefined;
|
||||||
readonly participants: Array<ParticipantType>;
|
readonly participants: Array<ParticipantType>;
|
||||||
readonly onClose: () => void;
|
readonly onClose: () => void;
|
||||||
readonly onCopyCallLink: () => void;
|
readonly onCopyCallLink: () => void;
|
||||||
|
readonly removeClient: ((payload: RemoveClientType) => void) | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CallingAdhocCallInfo({
|
export function CallingAdhocCallInfo({
|
||||||
i18n,
|
i18n,
|
||||||
|
isCallLinkAdmin,
|
||||||
ourServiceId,
|
ourServiceId,
|
||||||
participants,
|
participants,
|
||||||
onClose,
|
onClose,
|
||||||
onCopyCallLink,
|
onCopyCallLink,
|
||||||
|
removeClient,
|
||||||
}: PropsType): JSX.Element | null {
|
}: PropsType): JSX.Element | null {
|
||||||
const sortedParticipants = React.useMemo<Array<ParticipantType>>(
|
const sortedParticipants = React.useMemo<Array<ParticipantType>>(
|
||||||
() => sortByTitle(participants),
|
() => sortByTitle(participants),
|
||||||
|
@ -137,6 +143,26 @@ export function CallingAdhocCallInfo({
|
||||||
'module-calling-participants-list__muted--audio'
|
'module-calling-participants-list__muted--audio'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{isCallLinkAdmin &&
|
||||||
|
removeClient &&
|
||||||
|
participant.demuxId &&
|
||||||
|
!(ourServiceId && participant.serviceId === ourServiceId) ? (
|
||||||
|
<button
|
||||||
|
aria-label={i18n('icu:CallingAdhocCallInfo__RemoveClient')}
|
||||||
|
className={classNames(
|
||||||
|
'CallingAdhocCallInfo__RemoveClient',
|
||||||
|
'module-calling-participants-list__status-icon',
|
||||||
|
'module-calling-participants-list__remove'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!participant.demuxId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeClient({ demuxId: participant.demuxId });
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
101
ts/components/CallingPendingParticipants.tsx
Normal file
101
ts/components/CallingPendingParticipants.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
/* eslint-disable react/no-array-index-key */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Avatar, AvatarSize } from './Avatar';
|
||||||
|
import { ContactName } from './conversation/ContactName';
|
||||||
|
import { InContactsIcon } from './InContactsIcon';
|
||||||
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
|
import { isInSystemContacts } from '../util/isInSystemContacts';
|
||||||
|
import type { PendingUserActionPayloadType } from '../state/ducks/calling';
|
||||||
|
import type { ServiceIdString } from '../types/ServiceId';
|
||||||
|
import { Button, ButtonVariant } from './Button';
|
||||||
|
|
||||||
|
export type PropsType = {
|
||||||
|
readonly i18n: LocalizerType;
|
||||||
|
readonly ourServiceId: ServiceIdString | undefined;
|
||||||
|
readonly participants: Array<ConversationType>;
|
||||||
|
readonly approveUser: (payload: PendingUserActionPayloadType) => void;
|
||||||
|
readonly denyUser: (payload: PendingUserActionPayloadType) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CallingPendingParticipants({
|
||||||
|
i18n,
|
||||||
|
ourServiceId,
|
||||||
|
participants,
|
||||||
|
approveUser,
|
||||||
|
denyUser,
|
||||||
|
}: PropsType): JSX.Element | null {
|
||||||
|
return (
|
||||||
|
<div className="CallingPendingParticipants module-calling-participants-list">
|
||||||
|
<div className="module-calling-participants-list__header">
|
||||||
|
<div className="module-calling-participants-list__title">
|
||||||
|
{i18n('icu:CallingPendingParticipants__RequestsToJoin', {
|
||||||
|
count: participants.length,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="module-calling-participants-list__list">
|
||||||
|
{participants.map((participant: ConversationType, index: number) => (
|
||||||
|
<li className="module-calling-participants-list__contact" key={index}>
|
||||||
|
<div className="module-calling-participants-list__avatar-and-name">
|
||||||
|
<Avatar
|
||||||
|
acceptedMessageRequest={participant.acceptedMessageRequest}
|
||||||
|
avatarPath={participant.avatarPath}
|
||||||
|
badge={undefined}
|
||||||
|
color={participant.color}
|
||||||
|
conversationType="direct"
|
||||||
|
i18n={i18n}
|
||||||
|
isMe={participant.isMe}
|
||||||
|
profileName={participant.profileName}
|
||||||
|
title={participant.title}
|
||||||
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
/>
|
||||||
|
{ourServiceId && participant.serviceId === ourServiceId ? (
|
||||||
|
<span className="module-calling-participants-list__name">
|
||||||
|
{i18n('icu:you')}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ContactName
|
||||||
|
module="module-calling-participants-list__name"
|
||||||
|
title={participant.title}
|
||||||
|
/>
|
||||||
|
{isInSystemContacts(participant) ? (
|
||||||
|
<span>
|
||||||
|
{' '}
|
||||||
|
<InContactsIcon
|
||||||
|
className="module-calling-participants-list__contact-icon"
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
aria-label={i18n('icu:CallingPendingParticipants__DenyUser')}
|
||||||
|
className="CallingPendingParticipants__PendingActionButton CallingButton__icon"
|
||||||
|
onClick={() => denyUser({ serviceId: participant.serviceId })}
|
||||||
|
variant={ButtonVariant.Destructive}
|
||||||
|
>
|
||||||
|
<span className="CallingPendingParticipants__PendingActionButtonIcon CallingPendingParticipants__PendingActionButtonIcon--Deny" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label={i18n('icu:CallingPendingParticipants__ApproveUser')}
|
||||||
|
className="CallingPendingParticipants__PendingActionButton CallingButton__icon"
|
||||||
|
onClick={() => approveUser({ serviceId: participant.serviceId })}
|
||||||
|
variant={ButtonVariant.Calling}
|
||||||
|
>
|
||||||
|
<span className="CallingPendingParticipants__PendingActionButtonIcon CallingPendingParticipants__PendingActionButtonIcon--Approve" />
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -139,6 +139,7 @@ export function GroupCall(args: PropsType): JSX.Element {
|
||||||
maxDevices: 5,
|
maxDevices: 5,
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
peekedParticipants: [],
|
peekedParticipants: [],
|
||||||
|
pendingParticipants: [],
|
||||||
raisedHands: new Set<number>(),
|
raisedHands: new Set<number>(),
|
||||||
remoteParticipants: [],
|
remoteParticipants: [],
|
||||||
remoteAudioLevels: new Map<number, number>(),
|
remoteAudioLevels: new Map<number, number>(),
|
||||||
|
|
|
@ -40,7 +40,7 @@ import {
|
||||||
RingRTC,
|
RingRTC,
|
||||||
RingUpdate,
|
RingUpdate,
|
||||||
} from '@signalapp/ringrtc';
|
} from '@signalapp/ringrtc';
|
||||||
import { uniqBy, noop } from 'lodash';
|
import { uniqBy, noop, compact } from 'lodash';
|
||||||
|
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import type { CallLinkAuthCredentialPresentation } from '@signalapp/libsignal-client/zkgroup';
|
import type { CallLinkAuthCredentialPresentation } from '@signalapp/libsignal-client/zkgroup';
|
||||||
|
@ -125,11 +125,13 @@ import {
|
||||||
} from '../util/callDisposition';
|
} from '../util/callDisposition';
|
||||||
import { isNormalNumber } from '../util/isNormalNumber';
|
import { isNormalNumber } from '../util/isNormalNumber';
|
||||||
import { LocalCallEvent } from '../types/CallDisposition';
|
import { LocalCallEvent } from '../types/CallDisposition';
|
||||||
import { isServiceIdString, type ServiceIdString } from '../types/ServiceId';
|
import type { AciString, ServiceIdString } from '../types/ServiceId';
|
||||||
|
import { isServiceIdString } from '../types/ServiceId';
|
||||||
import { isInSystemContacts } from '../util/isInSystemContacts';
|
import { isInSystemContacts } from '../util/isInSystemContacts';
|
||||||
import {
|
import {
|
||||||
getRoomIdFromRootKey,
|
getRoomIdFromRootKey,
|
||||||
getCallLinkAuthCredentialPresentation,
|
getCallLinkAuthCredentialPresentation,
|
||||||
|
toAdminKeyBytes,
|
||||||
} from '../util/callLinks';
|
} from '../util/callLinks';
|
||||||
import { isAdhocCallingEnabled } from '../util/isAdhocCallingEnabled';
|
import { isAdhocCallingEnabled } from '../util/isAdhocCallingEnabled';
|
||||||
import { conversationJobQueue } from '../jobs/conversationJobQueue';
|
import { conversationJobQueue } from '../jobs/conversationJobQueue';
|
||||||
|
@ -580,10 +582,12 @@ export class CallingClass {
|
||||||
|
|
||||||
async startCallLinkLobby({
|
async startCallLinkLobby({
|
||||||
callLinkRootKey,
|
callLinkRootKey,
|
||||||
|
adminPasskey,
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
hasLocalVideo = true,
|
hasLocalVideo = true,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
callLinkRootKey: CallLinkRootKey;
|
callLinkRootKey: CallLinkRootKey;
|
||||||
|
adminPasskey: Buffer | undefined;
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo?: boolean;
|
hasLocalVideo?: boolean;
|
||||||
}>): Promise<
|
}>): Promise<
|
||||||
|
@ -610,7 +614,7 @@ export class CallingClass {
|
||||||
roomId,
|
roomId,
|
||||||
authCredentialPresentation,
|
authCredentialPresentation,
|
||||||
callLinkRootKey,
|
callLinkRootKey,
|
||||||
adminPasskey: undefined,
|
adminPasskey,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupCall.setOutgoingAudioMuted(!hasLocalAudio);
|
groupCall.setOutgoingAudioMuted(!hasLocalAudio);
|
||||||
|
@ -1210,11 +1214,13 @@ export class CallingClass {
|
||||||
public async joinCallLinkCall({
|
public async joinCallLinkCall({
|
||||||
roomId,
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
|
adminKey,
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
hasLocalVideo,
|
hasLocalVideo,
|
||||||
}: {
|
}: {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
rootKey: string;
|
rootKey: string;
|
||||||
|
adminKey: string | undefined;
|
||||||
hasLocalAudio: boolean;
|
hasLocalAudio: boolean;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
@ -1228,13 +1234,16 @@ export class CallingClass {
|
||||||
const callLinkRootKey = CallLinkRootKey.parse(rootKey);
|
const callLinkRootKey = CallLinkRootKey.parse(rootKey);
|
||||||
const authCredentialPresentation =
|
const authCredentialPresentation =
|
||||||
await getCallLinkAuthCredentialPresentation(callLinkRootKey);
|
await getCallLinkAuthCredentialPresentation(callLinkRootKey);
|
||||||
|
const adminPasskey = adminKey
|
||||||
|
? Buffer.from(toAdminKeyBytes(adminKey))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// RingRTC reuses the same type GroupCall between Adhoc and Group calls.
|
// RingRTC reuses the same type GroupCall between Adhoc and Group calls.
|
||||||
const groupCall = this.connectCallLinkCall({
|
const groupCall = this.connectCallLinkCall({
|
||||||
roomId,
|
roomId,
|
||||||
authCredentialPresentation,
|
authCredentialPresentation,
|
||||||
callLinkRootKey,
|
callLinkRootKey,
|
||||||
adminPasskey: undefined,
|
adminPasskey,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupCall.setOutgoingAudioMuted(!hasLocalAudio);
|
groupCall.setOutgoingAudioMuted(!hasLocalAudio);
|
||||||
|
@ -1267,6 +1276,33 @@ export class CallingClass {
|
||||||
groupCall.setGroupMembers(this.getGroupCallMembers(conversationId));
|
groupCall.setGroupMembers(this.getGroupCallMembers(conversationId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public approveUser(conversationId: string, aci: AciString): void {
|
||||||
|
const groupCall = this.getGroupCall(conversationId);
|
||||||
|
if (!groupCall) {
|
||||||
|
throw new Error('Could not find matching call');
|
||||||
|
}
|
||||||
|
|
||||||
|
groupCall.approveUser(Buffer.from(uuidToBytes(aci)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public denyUser(conversationId: string, aci: AciString): void {
|
||||||
|
const groupCall = this.getGroupCall(conversationId);
|
||||||
|
if (!groupCall) {
|
||||||
|
throw new Error('Could not find matching call');
|
||||||
|
}
|
||||||
|
|
||||||
|
groupCall.denyUser(Buffer.from(uuidToBytes(aci)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeClient(conversationId: string, demuxId: number): void {
|
||||||
|
const groupCall = this.getGroupCall(conversationId);
|
||||||
|
if (!groupCall) {
|
||||||
|
throw new Error('Could not find matching call');
|
||||||
|
}
|
||||||
|
|
||||||
|
groupCall.removeClient(demuxId);
|
||||||
|
}
|
||||||
|
|
||||||
// See the comment in types/Calling.ts to explain why we have to do this conversion.
|
// See the comment in types/Calling.ts to explain why we have to do this conversion.
|
||||||
private convertRingRtcConnectionState(
|
private convertRingRtcConnectionState(
|
||||||
connectionState: ConnectionState
|
connectionState: ConnectionState
|
||||||
|
@ -1301,6 +1337,18 @@ export class CallingClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private formatUserId(userId: Buffer): AciString | null {
|
||||||
|
const uuid = bytesToUuid(userId);
|
||||||
|
if (uuid && isAciString(uuid)) {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error(
|
||||||
|
'Calling.formatUserId: could not convert participant UUID Uint8Array to string'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public formatGroupCallPeekInfoForRedux(
|
public formatGroupCallPeekInfoForRedux(
|
||||||
peekInfo: PeekInfo
|
peekInfo: PeekInfo
|
||||||
): GroupCallPeekInfoType {
|
): GroupCallPeekInfoType {
|
||||||
|
@ -1308,17 +1356,10 @@ export class CallingClass {
|
||||||
return {
|
return {
|
||||||
acis: peekInfo.devices.map(peekDeviceInfo => {
|
acis: peekInfo.devices.map(peekDeviceInfo => {
|
||||||
if (peekDeviceInfo.userId) {
|
if (peekDeviceInfo.userId) {
|
||||||
const uuid = bytesToUuid(peekDeviceInfo.userId);
|
const uuid = this.formatUserId(peekDeviceInfo.userId);
|
||||||
if (uuid) {
|
if (uuid) {
|
||||||
assertDev(
|
|
||||||
isAciString(uuid),
|
|
||||||
'peeked participant uuid must be an ACI'
|
|
||||||
);
|
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
log.error(
|
|
||||||
'Calling.formatGroupCallPeekInfoForRedux: could not convert peek UUID Uint8Array to string; using fallback UUID'
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
'Calling.formatGroupCallPeekInfoForRedux: device had no user ID; using fallback UUID'
|
'Calling.formatGroupCallPeekInfoForRedux: device had no user ID; using fallback UUID'
|
||||||
|
@ -1329,6 +1370,9 @@ export class CallingClass {
|
||||||
'formatGrouPCallPeekInfoForRedux'
|
'formatGrouPCallPeekInfoForRedux'
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
pendingAcis: compact(
|
||||||
|
peekInfo.pendingUsers.map(userId => this.formatUserId(userId))
|
||||||
|
),
|
||||||
creatorAci:
|
creatorAci:
|
||||||
creatorAci !== undefined
|
creatorAci !== undefined
|
||||||
? normalizeAci(
|
? normalizeAci(
|
||||||
|
|
|
@ -49,11 +49,12 @@ import { requestCameraPermissions } from '../../util/callingPermissions';
|
||||||
import {
|
import {
|
||||||
CALL_LINK_DEFAULT_STATE,
|
CALL_LINK_DEFAULT_STATE,
|
||||||
getRoomIdFromRootKey,
|
getRoomIdFromRootKey,
|
||||||
|
toAdminKeyBytes,
|
||||||
} from '../../util/callLinks';
|
} from '../../util/callLinks';
|
||||||
import { sendCallLinkUpdateSync } from '../../util/sendCallLinkUpdateSync';
|
import { sendCallLinkUpdateSync } from '../../util/sendCallLinkUpdateSync';
|
||||||
import { sleep } from '../../util/sleep';
|
import { sleep } from '../../util/sleep';
|
||||||
import { LatestQueue } from '../../util/LatestQueue';
|
import { LatestQueue } from '../../util/LatestQueue';
|
||||||
import type { AciString } from '../../types/ServiceId';
|
import type { AciString, ServiceIdString } from '../../types/ServiceId';
|
||||||
import type {
|
import type {
|
||||||
ConversationChangedActionType,
|
ConversationChangedActionType,
|
||||||
ConversationRemovedActionType,
|
ConversationRemovedActionType,
|
||||||
|
@ -81,11 +82,13 @@ import { SHOW_ERROR_MODAL } from './globalModals';
|
||||||
import { ButtonVariant } from '../../components/Button';
|
import { ButtonVariant } from '../../components/Button';
|
||||||
import { getConversationIdForLogging } from '../../util/idForLogging';
|
import { getConversationIdForLogging } from '../../util/idForLogging';
|
||||||
import dataInterface from '../../sql/Client';
|
import dataInterface from '../../sql/Client';
|
||||||
|
import { isAciString } from '../../util/isAciString';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type GroupCallPeekInfoType = ReadonlyDeep<{
|
export type GroupCallPeekInfoType = ReadonlyDeep<{
|
||||||
acis: Array<AciString>;
|
acis: Array<AciString>;
|
||||||
|
pendingAcis: Array<AciString>;
|
||||||
creatorAci?: AciString;
|
creatorAci?: AciString;
|
||||||
eraId?: string;
|
eraId?: string;
|
||||||
maxDevices: number;
|
maxDevices: number;
|
||||||
|
@ -250,7 +253,7 @@ type HangUpActionPayloadType = ReadonlyDeep<{
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type HandleCallLinkUpdateType = ReadonlyDeep<{
|
export type HandleCallLinkUpdateType = ReadonlyDeep<{
|
||||||
rootKey: string;
|
rootKey: string;
|
||||||
adminKey: string | null;
|
adminKey: string | null;
|
||||||
}>;
|
}>;
|
||||||
|
@ -309,6 +312,10 @@ type RemoteSharingScreenChangeType = ReadonlyDeep<{
|
||||||
isSharingScreen: boolean;
|
isSharingScreen: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type RemoveClientType = ReadonlyDeep<{
|
||||||
|
demuxId: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type SetLocalAudioType = ReadonlyDeep<{
|
export type SetLocalAudioType = ReadonlyDeep<{
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
@ -558,10 +565,12 @@ const doGroupCallPeek = ({
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const ACCEPT_CALL_PENDING = 'calling/ACCEPT_CALL_PENDING';
|
const ACCEPT_CALL_PENDING = 'calling/ACCEPT_CALL_PENDING';
|
||||||
|
const APPROVE_USER = 'calling/APPROVE_USER';
|
||||||
const CANCEL_CALL = 'calling/CANCEL_CALL';
|
const CANCEL_CALL = 'calling/CANCEL_CALL';
|
||||||
const CANCEL_INCOMING_GROUP_CALL_RING =
|
const CANCEL_INCOMING_GROUP_CALL_RING =
|
||||||
'calling/CANCEL_INCOMING_GROUP_CALL_RING';
|
'calling/CANCEL_INCOMING_GROUP_CALL_RING';
|
||||||
const CHANGE_CALL_VIEW = 'calling/CHANGE_CALL_VIEW';
|
const CHANGE_CALL_VIEW = 'calling/CHANGE_CALL_VIEW';
|
||||||
|
const DENY_USER = 'calling/DENY_USER';
|
||||||
const START_CALLING_LOBBY = 'calling/START_CALLING_LOBBY';
|
const START_CALLING_LOBBY = 'calling/START_CALLING_LOBBY';
|
||||||
const START_CALL_LINK_LOBBY = 'calling/START_CALL_LINK_LOBBY';
|
const START_CALL_LINK_LOBBY = 'calling/START_CALL_LINK_LOBBY';
|
||||||
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
|
const CALL_STATE_CHANGE_FULFILLED = 'calling/CALL_STATE_CHANGE_FULFILLED';
|
||||||
|
@ -584,6 +593,7 @@ const RAISE_HAND_GROUP_CALL = 'calling/RAISE_HAND_GROUP_CALL';
|
||||||
const REFRESH_IO_DEVICES = 'calling/REFRESH_IO_DEVICES';
|
const REFRESH_IO_DEVICES = 'calling/REFRESH_IO_DEVICES';
|
||||||
const REMOTE_SHARING_SCREEN_CHANGE = 'calling/REMOTE_SHARING_SCREEN_CHANGE';
|
const REMOTE_SHARING_SCREEN_CHANGE = 'calling/REMOTE_SHARING_SCREEN_CHANGE';
|
||||||
const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
|
const REMOTE_VIDEO_CHANGE = 'calling/REMOTE_VIDEO_CHANGE';
|
||||||
|
const REMOVE_CLIENT = 'calling/REMOVE_CLIENT';
|
||||||
const RETURN_TO_ACTIVE_CALL = 'calling/RETURN_TO_ACTIVE_CALL';
|
const RETURN_TO_ACTIVE_CALL = 'calling/RETURN_TO_ACTIVE_CALL';
|
||||||
const SEND_GROUP_CALL_REACTION = 'calling/SEND_GROUP_CALL_REACTION';
|
const SEND_GROUP_CALL_REACTION = 'calling/SEND_GROUP_CALL_REACTION';
|
||||||
const SET_LOCAL_AUDIO_FULFILLED = 'calling/SET_LOCAL_AUDIO_FULFILLED';
|
const SET_LOCAL_AUDIO_FULFILLED = 'calling/SET_LOCAL_AUDIO_FULFILLED';
|
||||||
|
@ -605,6 +615,10 @@ type AcceptCallPendingActionType = ReadonlyDeep<{
|
||||||
payload: AcceptCallType;
|
payload: AcceptCallType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type ApproveUserActionType = ReadonlyDeep<{
|
||||||
|
type: 'calling/APPROVE_USER';
|
||||||
|
}>;
|
||||||
|
|
||||||
type CancelCallActionType = ReadonlyDeep<{
|
type CancelCallActionType = ReadonlyDeep<{
|
||||||
type: 'calling/CANCEL_CALL';
|
type: 'calling/CANCEL_CALL';
|
||||||
}>;
|
}>;
|
||||||
|
@ -614,6 +628,10 @@ type CancelIncomingGroupCallRingActionType = ReadonlyDeep<{
|
||||||
payload: CancelIncomingGroupCallRingType;
|
payload: CancelIncomingGroupCallRingType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type DenyUserActionType = ReadonlyDeep<{
|
||||||
|
type: 'calling/DENY_USER';
|
||||||
|
}>;
|
||||||
|
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
type StartCallingLobbyActionType = {
|
type StartCallingLobbyActionType = {
|
||||||
type: 'calling/START_CALLING_LOBBY';
|
type: 'calling/START_CALLING_LOBBY';
|
||||||
|
@ -751,6 +769,10 @@ export type PeekGroupCallFulfilledActionType = ReadonlyDeep<{
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type PendingUserActionPayloadType = ReadonlyDeep<{
|
||||||
|
serviceId: ServiceIdString | undefined;
|
||||||
|
}>;
|
||||||
|
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
type RefreshIODevicesActionType = {
|
type RefreshIODevicesActionType = {
|
||||||
type: 'calling/REFRESH_IO_DEVICES';
|
type: 'calling/REFRESH_IO_DEVICES';
|
||||||
|
@ -767,6 +789,10 @@ type RemoteVideoChangeActionType = ReadonlyDeep<{
|
||||||
payload: RemoteVideoChangeType;
|
payload: RemoteVideoChangeType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type RemoveClientActionType = ReadonlyDeep<{
|
||||||
|
type: 'calling/REMOVE_CLIENT';
|
||||||
|
}>;
|
||||||
|
|
||||||
type ReturnToActiveCallActionType = ReadonlyDeep<{
|
type ReturnToActiveCallActionType = ReadonlyDeep<{
|
||||||
type: 'calling/RETURN_TO_ACTIVE_CALL';
|
type: 'calling/RETURN_TO_ACTIVE_CALL';
|
||||||
}>;
|
}>;
|
||||||
|
@ -833,10 +859,12 @@ type SwitchFromPresentationViewActionType = ReadonlyDeep<{
|
||||||
|
|
||||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
export type CallingActionType =
|
export type CallingActionType =
|
||||||
|
| ApproveUserActionType
|
||||||
| AcceptCallPendingActionType
|
| AcceptCallPendingActionType
|
||||||
| CancelCallActionType
|
| CancelCallActionType
|
||||||
| CancelIncomingGroupCallRingActionType
|
| CancelIncomingGroupCallRingActionType
|
||||||
| ChangeCallViewActionType
|
| ChangeCallViewActionType
|
||||||
|
| DenyUserActionType
|
||||||
| StartCallingLobbyActionType
|
| StartCallingLobbyActionType
|
||||||
| StartCallLinkLobbyActionType
|
| StartCallLinkLobbyActionType
|
||||||
| CallStateChangeFulfilledActionType
|
| CallStateChangeFulfilledActionType
|
||||||
|
@ -860,6 +888,7 @@ export type CallingActionType =
|
||||||
| RefreshIODevicesActionType
|
| RefreshIODevicesActionType
|
||||||
| RemoteSharingScreenChangeActionType
|
| RemoteSharingScreenChangeActionType
|
||||||
| RemoteVideoChangeActionType
|
| RemoteVideoChangeActionType
|
||||||
|
| RemoveClientActionType
|
||||||
| ReturnToActiveCallActionType
|
| ReturnToActiveCallActionType
|
||||||
| SendGroupCallReactionActionType
|
| SendGroupCallReactionActionType
|
||||||
| SetLocalAudioActionType
|
| SetLocalAudioActionType
|
||||||
|
@ -911,6 +940,68 @@ function acceptCall(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function approveUser(
|
||||||
|
payload: PendingUserActionPayloadType
|
||||||
|
): ThunkAction<void, RootStateType, unknown, ApproveUserActionType> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const activeCall = getActiveCall(getState().calling);
|
||||||
|
if (!activeCall || !isGroupOrAdhocCallMode(activeCall.callMode)) {
|
||||||
|
log.warn(
|
||||||
|
'approveUser: Trying to approve pending user without active group or adhoc call'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isAciString(payload.serviceId)) {
|
||||||
|
log.warn(
|
||||||
|
'approveUser: Trying to approve pending user without valid aci serviceid'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calling.approveUser(activeCall.conversationId, payload.serviceId);
|
||||||
|
dispatch({ type: APPROVE_USER });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function denyUser(
|
||||||
|
payload: PendingUserActionPayloadType
|
||||||
|
): ThunkAction<void, RootStateType, unknown, DenyUserActionType> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const activeCall = getActiveCall(getState().calling);
|
||||||
|
if (!activeCall || !isGroupOrAdhocCallMode(activeCall.callMode)) {
|
||||||
|
log.warn(
|
||||||
|
'approveUser: Trying to approve pending user without active group or adhoc call'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isAciString(payload.serviceId)) {
|
||||||
|
log.warn(
|
||||||
|
'approveUser: Trying to approve pending user without valid aci serviceid'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calling.denyUser(activeCall.conversationId, payload.serviceId);
|
||||||
|
dispatch({ type: DENY_USER });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function removeClient(
|
||||||
|
payload: RemoveClientType
|
||||||
|
): ThunkAction<void, RootStateType, unknown, RemoveClientActionType> {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const activeCall = getActiveCall(getState().calling);
|
||||||
|
if (!activeCall || !isGroupOrAdhocCallMode(activeCall.callMode)) {
|
||||||
|
log.warn(
|
||||||
|
'approveUser: Trying to approve pending user without active group or adhoc call'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
calling.removeClient(activeCall.conversationId, payload.demuxId);
|
||||||
|
dispatch({ type: REMOVE_CLIENT });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function callStateChange(
|
function callStateChange(
|
||||||
payload: CallStateChangeType
|
payload: CallStateChangeType
|
||||||
): ThunkAction<
|
): ThunkAction<
|
||||||
|
@ -1869,8 +1960,13 @@ const _startCallLinkLobby = async ({
|
||||||
groupCall?.remoteParticipants.length ||
|
groupCall?.remoteParticipants.length ||
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
const { adminKey } = getOwn(state.calling.callLinks, roomId) ?? {};
|
||||||
|
const adminPasskey = adminKey
|
||||||
|
? Buffer.from(toAdminKeyBytes(adminKey))
|
||||||
|
: undefined;
|
||||||
const callLobbyData = await calling.startCallLinkLobby({
|
const callLobbyData = await calling.startCallLinkLobby({
|
||||||
callLinkRootKey,
|
callLinkRootKey,
|
||||||
|
adminPasskey,
|
||||||
hasLocalAudio: groupCallDeviceCount < 8,
|
hasLocalAudio: groupCallDeviceCount < 8,
|
||||||
});
|
});
|
||||||
if (!callLobbyData) {
|
if (!callLobbyData) {
|
||||||
|
@ -2003,6 +2099,7 @@ function startCall(
|
||||||
await calling.joinCallLinkCall({
|
await calling.joinCallLinkCall({
|
||||||
roomId: conversationId,
|
roomId: conversationId,
|
||||||
rootKey: callLink.rootKey,
|
rootKey: callLink.rootKey,
|
||||||
|
adminKey: callLink.adminKey ?? undefined,
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
hasLocalVideo,
|
hasLocalVideo,
|
||||||
});
|
});
|
||||||
|
@ -2061,6 +2158,7 @@ function switchFromPresentationView(): SwitchFromPresentationViewActionType {
|
||||||
}
|
}
|
||||||
export const actions = {
|
export const actions = {
|
||||||
acceptCall,
|
acceptCall,
|
||||||
|
approveUser,
|
||||||
callStateChange,
|
callStateChange,
|
||||||
cancelCall,
|
cancelCall,
|
||||||
cancelIncomingGroupCallRing,
|
cancelIncomingGroupCallRing,
|
||||||
|
@ -2068,6 +2166,7 @@ export const actions = {
|
||||||
changeIODevice,
|
changeIODevice,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
declineCall,
|
declineCall,
|
||||||
|
denyUser,
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
groupCallAudioLevelsChange,
|
groupCallAudioLevelsChange,
|
||||||
groupCallEnded,
|
groupCallEnded,
|
||||||
|
@ -2089,6 +2188,7 @@ export const actions = {
|
||||||
refreshIODevices,
|
refreshIODevices,
|
||||||
remoteSharingScreenChange,
|
remoteSharingScreenChange,
|
||||||
remoteVideoChange,
|
remoteVideoChange,
|
||||||
|
removeClient,
|
||||||
returnToActiveCall,
|
returnToActiveCall,
|
||||||
sendGroupCallRaiseHand,
|
sendGroupCallRaiseHand,
|
||||||
sendGroupCallReaction,
|
sendGroupCallReaction,
|
||||||
|
@ -2237,6 +2337,7 @@ export function reducer(
|
||||||
peekInfo: peekInfo ||
|
peekInfo: peekInfo ||
|
||||||
existingCall?.peekInfo || {
|
existingCall?.peekInfo || {
|
||||||
acis: remoteParticipants.map(({ aci }) => aci),
|
acis: remoteParticipants.map(({ aci }) => aci),
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: remoteParticipants.length,
|
deviceCount: remoteParticipants.length,
|
||||||
},
|
},
|
||||||
|
@ -2286,8 +2387,10 @@ export function reducer(
|
||||||
...callLinks,
|
...callLinks,
|
||||||
[conversationId]: {
|
[conversationId]: {
|
||||||
...action.payload.callLinkState,
|
...action.payload.callLinkState,
|
||||||
rootKey: action.payload.callLinkRootKey,
|
rootKey:
|
||||||
adminKey: null,
|
callLinks[conversationId]?.rootKey ??
|
||||||
|
action.payload.callLinkRootKey,
|
||||||
|
adminKey: callLinks[conversationId]?.adminKey,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: callLinks,
|
: callLinks,
|
||||||
|
@ -2478,6 +2581,7 @@ export function reducer(
|
||||||
localDemuxId: undefined,
|
localDemuxId: undefined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
},
|
},
|
||||||
|
@ -2676,6 +2780,7 @@ export function reducer(
|
||||||
const newPeekInfo = peekInfo ||
|
const newPeekInfo = peekInfo ||
|
||||||
existingCall?.peekInfo || {
|
existingCall?.peekInfo || {
|
||||||
acis: remoteParticipants.map(({ aci }) => aci),
|
acis: remoteParticipants.map(({ aci }) => aci),
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: remoteParticipants.length,
|
deviceCount: remoteParticipants.length,
|
||||||
};
|
};
|
||||||
|
@ -2755,6 +2860,7 @@ export function reducer(
|
||||||
localDemuxId: undefined,
|
localDemuxId: undefined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
},
|
},
|
||||||
|
|
|
@ -211,6 +211,7 @@ const mapStateToActiveCallProp = (
|
||||||
const groupMembers: Array<ConversationType> = [];
|
const groupMembers: Array<ConversationType> = [];
|
||||||
const remoteParticipants: Array<GroupCallRemoteParticipantType> = [];
|
const remoteParticipants: Array<GroupCallRemoteParticipantType> = [];
|
||||||
const peekedParticipants: Array<ConversationType> = [];
|
const peekedParticipants: Array<ConversationType> = [];
|
||||||
|
const pendingParticipants: Array<ConversationType> = [];
|
||||||
const conversationsByDemuxId: ConversationsByDemuxIdType = new Map();
|
const conversationsByDemuxId: ConversationsByDemuxIdType = new Map();
|
||||||
const { localDemuxId } = call;
|
const { localDemuxId } = call;
|
||||||
const raisedHands: Set<number> = new Set(call.raisedHands ?? []);
|
const raisedHands: Set<number> = new Set(call.raisedHands ?? []);
|
||||||
|
@ -224,6 +225,7 @@ const mapStateToActiveCallProp = (
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
},
|
},
|
||||||
} = call;
|
} = call;
|
||||||
|
|
||||||
|
@ -294,6 +296,20 @@ const mapStateToActiveCallProp = (
|
||||||
peekedParticipants.push(peekedConversation);
|
peekedParticipants.push(peekedConversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < peekInfo.pendingAcis.length; i += 1) {
|
||||||
|
const aci = peekInfo.pendingAcis[i];
|
||||||
|
|
||||||
|
// In call links, pending users may be unknown until they share profile keys.
|
||||||
|
// conversationSelectorByAci should create conversations for new contacts.
|
||||||
|
const pendingConversation = conversationSelectorByAci(aci);
|
||||||
|
if (!pendingConversation) {
|
||||||
|
log.error('Pending participant has no corresponding conversation');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingParticipants.push(pendingConversation);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...baseResult,
|
...baseResult,
|
||||||
callMode: call.callMode,
|
callMode: call.callMode,
|
||||||
|
@ -306,6 +322,7 @@ const mapStateToActiveCallProp = (
|
||||||
localDemuxId,
|
localDemuxId,
|
||||||
maxDevices: peekInfo.maxDevices,
|
maxDevices: peekInfo.maxDevices,
|
||||||
peekedParticipants,
|
peekedParticipants,
|
||||||
|
pendingParticipants,
|
||||||
raisedHands,
|
raisedHands,
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
remoteAudioLevels: call.remoteAudioLevels || new Map<number, number>(),
|
remoteAudioLevels: call.remoteAudioLevels || new Map<number, number>(),
|
||||||
|
@ -407,6 +424,8 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
approveUser,
|
||||||
|
denyUser,
|
||||||
changeCallView,
|
changeCallView,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
|
@ -416,6 +435,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
acceptCall,
|
acceptCall,
|
||||||
declineCall,
|
declineCall,
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
|
removeClient,
|
||||||
sendGroupCallRaiseHand,
|
sendGroupCallRaiseHand,
|
||||||
sendGroupCallReaction,
|
sendGroupCallReaction,
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
|
@ -440,6 +460,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
<CallManager
|
<CallManager
|
||||||
acceptCall={acceptCall}
|
acceptCall={acceptCall}
|
||||||
activeCall={activeCall}
|
activeCall={activeCall}
|
||||||
|
approveUser={approveUser}
|
||||||
availableCameras={availableCameras}
|
availableCameras={availableCameras}
|
||||||
bounceAppIconStart={bounceAppIconStart}
|
bounceAppIconStart={bounceAppIconStart}
|
||||||
bounceAppIconStop={bounceAppIconStop}
|
bounceAppIconStop={bounceAppIconStop}
|
||||||
|
@ -448,6 +469,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
changeCallView={changeCallView}
|
changeCallView={changeCallView}
|
||||||
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
||||||
declineCall={declineCall}
|
declineCall={declineCall}
|
||||||
|
denyUser={denyUser}
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||||
getPresentingSources={getPresentingSources}
|
getPresentingSources={getPresentingSources}
|
||||||
hangUpActiveCall={hangUpActiveCall}
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
|
@ -461,6 +483,7 @@ export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
openSystemPreferencesAction={openSystemPreferencesAction}
|
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||||
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
||||||
playRingtone={playRingtone}
|
playRingtone={playRingtone}
|
||||||
|
removeClient={removeClient}
|
||||||
renderDeviceSelection={renderDeviceSelection}
|
renderDeviceSelection={renderDeviceSelection}
|
||||||
renderEmojiPicker={renderEmojiPicker}
|
renderEmojiPicker={renderEmojiPicker}
|
||||||
renderReactionPicker={renderReactionPicker}
|
renderReactionPicker={renderReactionPicker}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import type { CallLinkStateType, CallLinkType } from '../../types/CallLink';
|
||||||
import type { CallingConversationType } from '../../types/Calling';
|
import type { CallingConversationType } from '../../types/Calling';
|
||||||
import type { CallLinkType } from '../../types/CallLink';
|
|
||||||
import { CallLinkRestrictions } from '../../types/CallLink';
|
import { CallLinkRestrictions } from '../../types/CallLink';
|
||||||
import { MONTH } from '../../util/durations/constants';
|
import { MONTH } from '../../util/durations/constants';
|
||||||
|
|
||||||
|
@ -26,6 +26,11 @@ export const FAKE_CALL_LINK_WITH_ADMIN_KEY: CallLinkType = {
|
||||||
rootKey: 'bpmc-mrgn-hntf-mffd-mndd-xbxk-zmgq-qszg',
|
rootKey: 'bpmc-mrgn-hntf-mffd-mndd-xbxk-zmgq-qszg',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getCallLinkState(callLink: CallLinkType): CallLinkStateType {
|
||||||
|
const { name, restrictions, expiration, revoked } = callLink;
|
||||||
|
return { name, restrictions, expiration, revoked };
|
||||||
|
}
|
||||||
|
|
||||||
export function getDefaultCallLinkConversation(
|
export function getDefaultCallLinkConversation(
|
||||||
callLinkOverrideProps: Partial<CallLinkType> = {}
|
callLinkOverrideProps: Partial<CallLinkType> = {}
|
||||||
): CallingConversationType {
|
): CallingConversationType {
|
||||||
|
|
|
@ -10,12 +10,15 @@ import { reducer as rootReducer } from '../../../state/reducer';
|
||||||
import { noopAction } from '../../../state/ducks/noop';
|
import { noopAction } from '../../../state/ducks/noop';
|
||||||
import type {
|
import type {
|
||||||
ActiveCallStateType,
|
ActiveCallStateType,
|
||||||
|
CallingActionType,
|
||||||
CallingStateType,
|
CallingStateType,
|
||||||
DirectCallStateType,
|
DirectCallStateType,
|
||||||
GroupCallReactionsReceivedActionType,
|
GroupCallReactionsReceivedActionType,
|
||||||
GroupCallStateChangeActionType,
|
GroupCallStateChangeActionType,
|
||||||
GroupCallStateType,
|
GroupCallStateType,
|
||||||
|
HandleCallLinkUpdateType,
|
||||||
SendGroupCallReactionActionType,
|
SendGroupCallReactionActionType,
|
||||||
|
StartCallLinkLobbyType,
|
||||||
} from '../../../state/ducks/calling';
|
} from '../../../state/ducks/calling';
|
||||||
import {
|
import {
|
||||||
actions,
|
actions,
|
||||||
|
@ -36,8 +39,11 @@ import {
|
||||||
import { generateAci } from '../../../types/ServiceId';
|
import { generateAci } from '../../../types/ServiceId';
|
||||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||||
import type { UnwrapPromise } from '../../../types/Util';
|
import type { UnwrapPromise } from '../../../types/Util';
|
||||||
import { CallLinkRestrictions } from '../../../types/CallLink';
|
import {
|
||||||
import { FAKE_CALL_LINK } from '../../../test-both/helpers/fakeCallLink';
|
FAKE_CALL_LINK,
|
||||||
|
FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||||
|
getCallLinkState,
|
||||||
|
} from '../../../test-both/helpers/fakeCallLink';
|
||||||
|
|
||||||
const ACI_1 = generateAci();
|
const ACI_1 = generateAci();
|
||||||
const NOW = new Date('2020-01-23T04:56:00.000');
|
const NOW = new Date('2020-01-23T04:56:00.000');
|
||||||
|
@ -109,6 +115,7 @@ describe('calling duck', () => {
|
||||||
localDemuxId: 1,
|
localDemuxId: 1,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [creatorAci],
|
acis: [creatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci,
|
creatorAci,
|
||||||
eraId: 'xyz',
|
eraId: 'xyz',
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
|
@ -902,6 +909,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [creatorAci],
|
acis: [creatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci,
|
creatorAci,
|
||||||
eraId: 'xyz',
|
eraId: 'xyz',
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
|
@ -932,6 +940,7 @@ describe('calling duck', () => {
|
||||||
localDemuxId: 1,
|
localDemuxId: 1,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [creatorAci],
|
acis: [creatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci,
|
creatorAci,
|
||||||
eraId: 'xyz',
|
eraId: 'xyz',
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
|
@ -967,6 +976,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -995,6 +1005,7 @@ describe('calling duck', () => {
|
||||||
localDemuxId: 1,
|
localDemuxId: 1,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1041,6 +1052,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1095,6 +1107,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1136,6 +1149,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: false,
|
hasLocalVideo: false,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1170,6 +1184,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1216,6 +1231,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1262,6 +1278,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
},
|
},
|
||||||
|
@ -1292,6 +1309,7 @@ describe('calling duck', () => {
|
||||||
hasLocalVideo: true,
|
hasLocalVideo: true,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
@ -1304,42 +1322,42 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleCallLinkUpdate', () => {
|
describe('handleCallLinkUpdate', () => {
|
||||||
const { roomId, rootKey, expiration } = FAKE_CALL_LINK;
|
const {
|
||||||
|
roomId,
|
||||||
|
name,
|
||||||
|
restrictions,
|
||||||
|
expiration,
|
||||||
|
revoked,
|
||||||
|
rootKey,
|
||||||
|
adminKey,
|
||||||
|
} = FAKE_CALL_LINK;
|
||||||
|
|
||||||
beforeEach(function (this: Mocha.Context) {
|
beforeEach(function (this: Mocha.Context) {
|
||||||
this.callingServiceReadCallLink = this.sandbox
|
this.callingServiceReadCallLink = this.sandbox
|
||||||
.stub(callingService, 'readCallLink')
|
.stub(callingService, 'readCallLink')
|
||||||
.resolves({
|
.resolves({
|
||||||
callLinkState: {
|
callLinkState: getCallLinkState(FAKE_CALL_LINK),
|
||||||
name: 'Signal Call',
|
|
||||||
restrictions: CallLinkRestrictions.None,
|
|
||||||
expiration,
|
|
||||||
revoked: false,
|
|
||||||
},
|
|
||||||
errorStatusCode: undefined,
|
errorStatusCode: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads the call link from calling service', async function (this: Mocha.Context) {
|
const doAction = async (
|
||||||
|
payload: HandleCallLinkUpdateType
|
||||||
|
): Promise<{ dispatch: sinon.SinonSpy }> => {
|
||||||
const { handleCallLinkUpdate } = actions;
|
const { handleCallLinkUpdate } = actions;
|
||||||
const dispatch = sinon.spy();
|
const dispatch = sinon.spy();
|
||||||
await handleCallLinkUpdate({ rootKey, adminKey: null })(
|
await handleCallLinkUpdate(payload)(dispatch, getEmptyRootState, null);
|
||||||
dispatch,
|
return { dispatch };
|
||||||
getEmptyRootState,
|
};
|
||||||
null
|
|
||||||
);
|
it('reads the call link from calling service', async function (this: Mocha.Context) {
|
||||||
|
await doAction({ rootKey, adminKey: null });
|
||||||
|
|
||||||
sinon.assert.calledOnce(this.callingServiceReadCallLink);
|
sinon.assert.calledOnce(this.callingServiceReadCallLink);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('dispatches HANDLE_CALL_LINK_UPDATE', async () => {
|
it('dispatches HANDLE_CALL_LINK_UPDATE', async () => {
|
||||||
const { handleCallLinkUpdate } = actions;
|
const { dispatch } = await doAction({ rootKey, adminKey: null });
|
||||||
const dispatch = sinon.spy();
|
|
||||||
await handleCallLinkUpdate({ rootKey, adminKey: null })(
|
|
||||||
dispatch,
|
|
||||||
getEmptyRootState,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatch);
|
sinon.assert.calledOnce(dispatch);
|
||||||
sinon.assert.calledWith(dispatch, {
|
sinon.assert.calledWith(dispatch, {
|
||||||
|
@ -1347,16 +1365,115 @@ describe('calling duck', () => {
|
||||||
payload: {
|
payload: {
|
||||||
roomId,
|
roomId,
|
||||||
callLinkDetails: {
|
callLinkDetails: {
|
||||||
name: 'Signal Call',
|
name,
|
||||||
restrictions: CallLinkRestrictions.None,
|
restrictions,
|
||||||
expiration,
|
expiration,
|
||||||
revoked: false,
|
revoked,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey: null,
|
adminKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can save adminKey', async () => {
|
||||||
|
const { dispatch } = await doAction({ rootKey, adminKey: 'banana' });
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatch);
|
||||||
|
sinon.assert.calledWith(dispatch, {
|
||||||
|
type: 'calling/HANDLE_CALL_LINK_UPDATE',
|
||||||
|
payload: {
|
||||||
|
roomId,
|
||||||
|
callLinkDetails: {
|
||||||
|
name,
|
||||||
|
restrictions,
|
||||||
|
expiration,
|
||||||
|
revoked,
|
||||||
|
rootKey,
|
||||||
|
adminKey: 'banana',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('startCallLinkLobby', () => {
|
||||||
|
const callLobbyData = {
|
||||||
|
callMode: CallMode.Adhoc,
|
||||||
|
connectionState: GroupCallConnectionState.NotConnected,
|
||||||
|
hasLocalAudio: true,
|
||||||
|
hasLocalVideo: true,
|
||||||
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
|
peekInfo: [],
|
||||||
|
remoteParticipants: [],
|
||||||
|
};
|
||||||
|
const callLinkState = getCallLinkState(FAKE_CALL_LINK);
|
||||||
|
|
||||||
|
const getStateWithAdminKey = (): RootStateType => ({
|
||||||
|
...getEmptyRootState(),
|
||||||
|
calling: {
|
||||||
|
...getEmptyState(),
|
||||||
|
callLinks: {
|
||||||
|
[FAKE_CALL_LINK_WITH_ADMIN_KEY.roomId]:
|
||||||
|
FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function (this: Mocha.Context) {
|
||||||
|
this.callingServiceReadCallLink = this.sandbox
|
||||||
|
.stub(callingService, 'readCallLink')
|
||||||
|
.resolves({
|
||||||
|
callLinkState,
|
||||||
|
errorStatusCode: undefined,
|
||||||
|
});
|
||||||
|
this.callingServiceStartCallLinkLobby = this.sandbox
|
||||||
|
.stub(callingService, 'startCallLinkLobby')
|
||||||
|
.resolves(callLobbyData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const doAction = async (
|
||||||
|
payload: StartCallLinkLobbyType
|
||||||
|
): Promise<{ dispatch: sinon.SinonSpy }> => {
|
||||||
|
const { startCallLinkLobby } = actions;
|
||||||
|
const dispatch = sinon.spy();
|
||||||
|
await startCallLinkLobby(payload)(dispatch, getEmptyRootState, null);
|
||||||
|
return { dispatch };
|
||||||
|
};
|
||||||
|
|
||||||
|
it('reads the link and dispatches START_CALL_LINK_LOBBY', async function (this: Mocha.Context) {
|
||||||
|
const { roomId, rootKey } = FAKE_CALL_LINK;
|
||||||
|
const { dispatch } = await doAction({ rootKey });
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatch);
|
||||||
|
sinon.assert.calledWith(dispatch, {
|
||||||
|
type: 'calling/START_CALL_LINK_LOBBY',
|
||||||
|
payload: {
|
||||||
|
...callLobbyData,
|
||||||
|
callLinkState,
|
||||||
|
callLinkRootKey: rootKey,
|
||||||
|
conversationId: roomId,
|
||||||
|
isConversationTooBigToRing: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves adminKey', () => {
|
||||||
|
const { startCallLinkLobby } = actions;
|
||||||
|
const { roomId, rootKey, adminKey } = FAKE_CALL_LINK_WITH_ADMIN_KEY;
|
||||||
|
const dispatch = sinon.spy();
|
||||||
|
const result = reducer(
|
||||||
|
getStateWithAdminKey().calling,
|
||||||
|
startCallLinkLobby({
|
||||||
|
rootKey,
|
||||||
|
})(
|
||||||
|
dispatch,
|
||||||
|
getStateWithAdminKey,
|
||||||
|
null
|
||||||
|
) as unknown as Readonly<CallingActionType>
|
||||||
|
);
|
||||||
|
assert.equal(result.callLinks[roomId]?.adminKey, adminKey);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('peekNotConnectedGroupCall', () => {
|
describe('peekNotConnectedGroupCall', () => {
|
||||||
|
@ -1503,6 +1620,7 @@ describe('calling duck', () => {
|
||||||
localDemuxId: undefined,
|
localDemuxId: undefined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
},
|
},
|
||||||
|
@ -1956,6 +2074,7 @@ describe('calling duck', () => {
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [creatorAci],
|
acis: [creatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci,
|
creatorAci,
|
||||||
eraId: 'xyz',
|
eraId: 'xyz',
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
|
@ -1983,6 +2102,7 @@ describe('calling duck', () => {
|
||||||
localDemuxId: undefined,
|
localDemuxId: undefined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [creatorAci],
|
acis: [creatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci,
|
creatorAci,
|
||||||
eraId: 'xyz',
|
eraId: 'xyz',
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
|
@ -2022,6 +2142,7 @@ describe('calling duck', () => {
|
||||||
const call = result.callsByConversation['fake-conversation-id'];
|
const call = result.callsByConversation['fake-conversation-id'];
|
||||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: 0,
|
deviceCount: 0,
|
||||||
});
|
});
|
||||||
|
@ -2053,6 +2174,7 @@ describe('calling duck', () => {
|
||||||
result.callsByConversation['fake-group-call-conversation-id'];
|
result.callsByConversation['fake-group-call-conversation-id'];
|
||||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||||
acis: [creatorAci],
|
acis: [creatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci,
|
creatorAci,
|
||||||
eraId: 'xyz',
|
eraId: 'xyz',
|
||||||
maxDevices: 16,
|
maxDevices: 16,
|
||||||
|
@ -2081,6 +2203,7 @@ describe('calling duck', () => {
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
joinState: GroupCallJoinState.NotJoined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [differentCreatorAci],
|
acis: [differentCreatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci: differentCreatorAci,
|
creatorAci: differentCreatorAci,
|
||||||
eraId: 'abc',
|
eraId: 'abc',
|
||||||
maxDevices: 5,
|
maxDevices: 5,
|
||||||
|
@ -2103,6 +2226,7 @@ describe('calling duck', () => {
|
||||||
const call = result.callsByConversation['fake-conversation-id'];
|
const call = result.callsByConversation['fake-conversation-id'];
|
||||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||||
acis: [differentCreatorAci],
|
acis: [differentCreatorAci],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci: differentCreatorAci,
|
creatorAci: differentCreatorAci,
|
||||||
eraId: 'abc',
|
eraId: 'abc',
|
||||||
maxDevices: 5,
|
maxDevices: 5,
|
||||||
|
|
|
@ -100,6 +100,7 @@ describe('state/selectors/calling', () => {
|
||||||
localDemuxId: undefined,
|
localDemuxId: undefined,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [ACI_1],
|
acis: [ACI_1],
|
||||||
|
pendingAcis: [],
|
||||||
creatorAci: ACI_1,
|
creatorAci: ACI_1,
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
|
@ -180,6 +181,7 @@ describe('state/selectors/calling', () => {
|
||||||
...incomingGroupCall,
|
...incomingGroupCall,
|
||||||
peekInfo: {
|
peekInfo: {
|
||||||
acis: [],
|
acis: [],
|
||||||
|
pendingAcis: [],
|
||||||
maxDevices: Infinity,
|
maxDevices: Infinity,
|
||||||
deviceCount: 1,
|
deviceCount: 1,
|
||||||
},
|
},
|
||||||
|
|
|
@ -97,6 +97,7 @@ export type ActiveGroupCallType = ActiveCallBaseType & {
|
||||||
groupMembers: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>;
|
groupMembers: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>;
|
||||||
isConversationTooBigToRing: boolean;
|
isConversationTooBigToRing: boolean;
|
||||||
peekedParticipants: Array<ConversationType>;
|
peekedParticipants: Array<ConversationType>;
|
||||||
|
pendingParticipants: Array<ConversationType>;
|
||||||
raisedHands: Set<number>;
|
raisedHands: Set<number>;
|
||||||
remoteParticipants: Array<GroupCallRemoteParticipantType>;
|
remoteParticipants: Array<GroupCallRemoteParticipantType>;
|
||||||
remoteAudioLevels: Map<number, number>;
|
remoteAudioLevels: Map<number, number>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue