Peek group calls when opening conversations and leaving calls
This commit is contained in:
parent
5ce26eb91a
commit
f5a4cd9ce8
13 changed files with 202 additions and 373 deletions
|
@ -70,7 +70,7 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
fakeGetGroupCallVideoFrameSource(demuxId),
|
fakeGetGroupCallVideoFrameSource(demuxId),
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
getPresentingSources: action('get-presenting-sources'),
|
getPresentingSources: action('get-presenting-sources'),
|
||||||
hangUp: action('hang-up'),
|
hangUpActiveCall: action('hang-up-active-call'),
|
||||||
i18n,
|
i18n,
|
||||||
isGroupCallOutboundRingEnabled: true,
|
isGroupCallOutboundRingEnabled: true,
|
||||||
keyChangeOk: action('key-change-ok'),
|
keyChangeOk: action('key-change-ok'),
|
||||||
|
|
|
@ -31,7 +31,6 @@ import type {
|
||||||
AcceptCallType,
|
AcceptCallType,
|
||||||
CancelCallType,
|
CancelCallType,
|
||||||
DeclineCallType,
|
DeclineCallType,
|
||||||
HangUpType,
|
|
||||||
KeyChangeOkType,
|
KeyChangeOkType,
|
||||||
SetGroupCallVideoRequestType,
|
SetGroupCallVideoRequestType,
|
||||||
SetLocalAudioType,
|
SetLocalAudioType,
|
||||||
|
@ -97,7 +96,7 @@ export type PropsType = {
|
||||||
setPresenting: (_?: PresentedSource) => void;
|
setPresenting: (_?: PresentedSource) => void;
|
||||||
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
setRendererCanvas: (_: SetRendererCanvasType) => void;
|
||||||
stopRingtone: () => unknown;
|
stopRingtone: () => unknown;
|
||||||
hangUp: (_: HangUpType) => void;
|
hangUpActiveCall: () => void;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
toggleScreenRecordingPermissionsDialog: () => unknown;
|
toggleScreenRecordingPermissionsDialog: () => unknown;
|
||||||
|
@ -114,7 +113,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
availableCameras,
|
availableCameras,
|
||||||
cancelCall,
|
cancelCall,
|
||||||
closeNeedPermissionScreen,
|
closeNeedPermissionScreen,
|
||||||
hangUp,
|
hangUpActiveCall,
|
||||||
i18n,
|
i18n,
|
||||||
isGroupCallOutboundRingEnabled,
|
isGroupCallOutboundRingEnabled,
|
||||||
keyChangeOk,
|
keyChangeOk,
|
||||||
|
@ -270,7 +269,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
<CallingPip
|
<CallingPip
|
||||||
activeCall={activeCall}
|
activeCall={activeCall}
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
|
||||||
hangUp={hangUp}
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
hasLocalVideo={hasLocalVideo}
|
hasLocalVideo={hasLocalVideo}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
|
setGroupCallVideoRequest={setGroupCallVideoRequestForConversation}
|
||||||
|
@ -307,7 +306,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
getPresentingSources={getPresentingSources}
|
getPresentingSources={getPresentingSources}
|
||||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSourceForActiveCall}
|
||||||
groupMembers={groupMembers}
|
groupMembers={groupMembers}
|
||||||
hangUp={hangUp}
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
joinedAt={joinedAt}
|
joinedAt={joinedAt}
|
||||||
me={me}
|
me={me}
|
||||||
|
@ -350,9 +349,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
|
||||||
contacts={activeCall.conversationsWithSafetyNumberChanges}
|
contacts={activeCall.conversationsWithSafetyNumberChanges}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onCancel={() => {
|
onCancel={hangUpActiveCall}
|
||||||
hangUp({ conversationId: activeCall.conversation.id });
|
|
||||||
}}
|
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
keyChangeOk({ conversationId: activeCall.conversation.id });
|
keyChangeOk({ conversationId: activeCall.conversation.id });
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -148,7 +148,7 @@ const createProps = (
|
||||||
activeCall: createActiveCallProp(overrideProps),
|
activeCall: createActiveCallProp(overrideProps),
|
||||||
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
|
||||||
getPresentingSources: action('get-presenting-sources'),
|
getPresentingSources: action('get-presenting-sources'),
|
||||||
hangUp: action('hang-up'),
|
hangUpActiveCall: action('hang-up'),
|
||||||
i18n,
|
i18n,
|
||||||
me: {
|
me: {
|
||||||
color: AvatarColors[1],
|
color: AvatarColors[1],
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { noop } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { VideoFrameSource } from 'ringrtc';
|
import type { VideoFrameSource } from 'ringrtc';
|
||||||
import type {
|
import type {
|
||||||
HangUpType,
|
|
||||||
SetLocalAudioType,
|
SetLocalAudioType,
|
||||||
SetLocalPreviewType,
|
SetLocalPreviewType,
|
||||||
SetLocalVideoType,
|
SetLocalVideoType,
|
||||||
|
@ -47,7 +46,7 @@ export type PropsType = {
|
||||||
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'>>;
|
||||||
hangUp: (_: HangUpType) => void;
|
hangUpActiveCall: () => void;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
joinedAt?: number;
|
joinedAt?: number;
|
||||||
me: {
|
me: {
|
||||||
|
@ -115,7 +114,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
hangUp,
|
hangUpActiveCall,
|
||||||
i18n,
|
i18n,
|
||||||
joinedAt,
|
joinedAt,
|
||||||
me,
|
me,
|
||||||
|
@ -510,9 +509,7 @@ export const CallScreen: React.FC<PropsType> = ({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onMouseEnter={onControlsMouseEnter}
|
onMouseEnter={onControlsMouseEnter}
|
||||||
onMouseLeave={onControlsMouseLeave}
|
onMouseLeave={onControlsMouseLeave}
|
||||||
onClick={() => {
|
onClick={hangUpActiveCall}
|
||||||
hangUp({ conversationId: conversation.id });
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -60,7 +60,7 @@ const defaultCall: ActiveCallType = {
|
||||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
activeCall: overrideProps.activeCall || defaultCall,
|
activeCall: overrideProps.activeCall || defaultCall,
|
||||||
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
|
||||||
hangUp: action('hang-up'),
|
hangUpActiveCall: action('hang-up-active-call'),
|
||||||
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
|
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
|
||||||
i18n,
|
i18n,
|
||||||
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
setGroupCallVideoRequest: action('set-group-call-video-request'),
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { CallingPipRemoteVideo } from './CallingPipRemoteVideo';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { ActiveCallType, GroupCallVideoRequest } from '../types/Calling';
|
import type { ActiveCallType, GroupCallVideoRequest } from '../types/Calling';
|
||||||
import type {
|
import type {
|
||||||
HangUpType,
|
|
||||||
SetLocalPreviewType,
|
SetLocalPreviewType,
|
||||||
SetRendererCanvasType,
|
SetRendererCanvasType,
|
||||||
} from '../state/ducks/calling';
|
} from '../state/ducks/calling';
|
||||||
|
@ -52,7 +51,7 @@ type SnapCandidate = {
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
|
getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource;
|
||||||
hangUp: (_: HangUpType) => void;
|
hangUpActiveCall: () => void;
|
||||||
hasLocalVideo: boolean;
|
hasLocalVideo: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
||||||
|
@ -70,7 +69,7 @@ const PIP_PADDING = 8;
|
||||||
export const CallingPip = ({
|
export const CallingPip = ({
|
||||||
activeCall,
|
activeCall,
|
||||||
getGroupCallVideoFrameSource,
|
getGroupCallVideoFrameSource,
|
||||||
hangUp,
|
hangUpActiveCall,
|
||||||
hasLocalVideo,
|
hasLocalVideo,
|
||||||
i18n,
|
i18n,
|
||||||
setGroupCallVideoRequest,
|
setGroupCallVideoRequest,
|
||||||
|
@ -293,9 +292,7 @@ export const CallingPip = ({
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('calling__hangup')}
|
aria-label={i18n('calling__hangup')}
|
||||||
className="module-calling-pip__button--hangup"
|
className="module-calling-pip__button--hangup"
|
||||||
onClick={() => {
|
onClick={hangUpActiveCall}
|
||||||
hangUp({ conversationId: activeCall.conversation.id });
|
|
||||||
}}
|
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -392,6 +392,8 @@ const actions = () => ({
|
||||||
removeMember: action('removeMember'),
|
removeMember: action('removeMember'),
|
||||||
|
|
||||||
unblurAvatar: action('unblurAvatar'),
|
unblurAvatar: action('unblurAvatar'),
|
||||||
|
|
||||||
|
peekGroupCallForTheFirstTime: action('peekGroupCallForTheFirstTime'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderItem = ({
|
const renderItem = ({
|
||||||
|
|
|
@ -170,6 +170,7 @@ export type PropsActionsType = {
|
||||||
onBlockAndReportSpam: (conversationId: string) => unknown;
|
onBlockAndReportSpam: (conversationId: string) => unknown;
|
||||||
onDelete: (conversationId: string) => unknown;
|
onDelete: (conversationId: string) => unknown;
|
||||||
onUnblock: (conversationId: string) => unknown;
|
onUnblock: (conversationId: string) => unknown;
|
||||||
|
peekGroupCallForTheFirstTime: (conversationId: string) => unknown;
|
||||||
removeMember: (conversationId: string) => unknown;
|
removeMember: (conversationId: string) => unknown;
|
||||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
selectMessage: (messageId: string, conversationId: string) => unknown;
|
||||||
clearSelectedMessage: () => unknown;
|
clearSelectedMessage: () => unknown;
|
||||||
|
@ -263,6 +264,7 @@ const getActions = createSelector(
|
||||||
'onBlockAndReportSpam',
|
'onBlockAndReportSpam',
|
||||||
'onDelete',
|
'onDelete',
|
||||||
'onUnblock',
|
'onUnblock',
|
||||||
|
'peekGroupCallForTheFirstTime',
|
||||||
'removeMember',
|
'removeMember',
|
||||||
'selectMessage',
|
'selectMessage',
|
||||||
'clearSelectedMessage',
|
'clearSelectedMessage',
|
||||||
|
@ -327,6 +329,8 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
|
|
||||||
private hasRecentlyScrolledTimeout?: NodeJS.Timeout;
|
private hasRecentlyScrolledTimeout?: NodeJS.Timeout;
|
||||||
|
|
||||||
|
private delayedPeekTimeout?: NodeJS.Timeout;
|
||||||
|
|
||||||
private containerRefMerger = createRefMerger();
|
private containerRefMerger = createRefMerger();
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
|
@ -958,10 +962,21 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
public override componentDidMount(): void {
|
public override componentDidMount(): void {
|
||||||
this.updateWithVisibleRows();
|
this.updateWithVisibleRows();
|
||||||
window.registerForActive(this.updateWithVisibleRows);
|
window.registerForActive(this.updateWithVisibleRows);
|
||||||
|
|
||||||
|
this.delayedPeekTimeout = setTimeout(() => {
|
||||||
|
const { id, peekGroupCallForTheFirstTime } = this.props;
|
||||||
|
peekGroupCallForTheFirstTime(id);
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override componentWillUnmount(): void {
|
public override componentWillUnmount(): void {
|
||||||
|
const { delayedPeekTimeout } = this;
|
||||||
|
|
||||||
window.unregisterForActive(this.updateWithVisibleRows);
|
window.unregisterForActive(this.updateWithVisibleRows);
|
||||||
|
|
||||||
|
if (delayedPeekTimeout) {
|
||||||
|
clearTimeout(delayedPeekTimeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override componentDidUpdate(
|
public override componentDidUpdate(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import type { ThunkAction } from 'redux-thunk';
|
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
|
||||||
import { CallEndedReason } from 'ringrtc';
|
import { CallEndedReason } from 'ringrtc';
|
||||||
import {
|
import {
|
||||||
hasScreenCapturePermission,
|
hasScreenCapturePermission,
|
||||||
|
@ -36,9 +36,14 @@ import { isGroupCallOutboundRingEnabled } from '../../util/isGroupCallOutboundRi
|
||||||
import { sleep } from '../../util/sleep';
|
import { sleep } from '../../util/sleep';
|
||||||
import { LatestQueue } from '../../util/LatestQueue';
|
import { LatestQueue } from '../../util/LatestQueue';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
import type { ConversationChangedActionType } from './conversations';
|
import type {
|
||||||
|
ConversationChangedActionType,
|
||||||
|
ConversationRemovedActionType,
|
||||||
|
} from './conversations';
|
||||||
|
import { getConversationCallMode } from './conversations';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { waitForOnline } from '../../util/waitForOnline';
|
||||||
import * as setUtil from '../../util/setUtil';
|
import * as setUtil from '../../util/setUtil';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
@ -88,7 +93,7 @@ export type GroupCallStateType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
connectionState: GroupCallConnectionState;
|
connectionState: GroupCallConnectionState;
|
||||||
joinState: GroupCallJoinState;
|
joinState: GroupCallJoinState;
|
||||||
peekInfo: GroupCallPeekInfoType;
|
peekInfo?: GroupCallPeekInfoType;
|
||||||
remoteParticipants: Array<GroupCallParticipantInfoType>;
|
remoteParticipants: Array<GroupCallParticipantInfoType>;
|
||||||
speakingDemuxIds?: Set<number>;
|
speakingDemuxIds?: Set<number>;
|
||||||
} & GroupCallRingStateType;
|
} & GroupCallRingStateType;
|
||||||
|
@ -161,7 +166,7 @@ type GroupCallStateChangeActionPayloadType =
|
||||||
ourUuid: UUIDStringType;
|
ourUuid: UUIDStringType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HangUpType = {
|
type HangUpActionPayloadType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -285,9 +290,9 @@ export const getIncomingCall = (
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isAnybodyElseInGroupCall = (
|
export const isAnybodyElseInGroupCall = (
|
||||||
{ uuids }: Readonly<GroupCallPeekInfoType>,
|
peekInfo: undefined | Readonly<Pick<GroupCallPeekInfoType, 'uuids'>>,
|
||||||
ourUuid: UUIDStringType
|
ourUuid: UUIDStringType
|
||||||
): boolean => uuids.some(id => id !== ourUuid);
|
): boolean => Boolean(peekInfo?.uuids.some(id => id !== ourUuid));
|
||||||
|
|
||||||
const getGroupCallRingState = (
|
const getGroupCallRingState = (
|
||||||
call: Readonly<undefined | GroupCallStateType>
|
call: Readonly<undefined | GroupCallStateType>
|
||||||
|
@ -296,6 +301,90 @@ const getGroupCallRingState = (
|
||||||
? {}
|
? {}
|
||||||
: { ringId: call.ringId, ringerUuid: call.ringerUuid };
|
: { ringId: call.ringId, ringerUuid: call.ringerUuid };
|
||||||
|
|
||||||
|
// We might call this function many times in rapid succession (for example, if lots of
|
||||||
|
// people are joining and leaving at once). We want to make sure to update eventually
|
||||||
|
// (if people join and leave for an hour, we don't want you to have to wait an hour to
|
||||||
|
// get an update), and we also don't want to update too often. That's why we use a
|
||||||
|
// "latest queue".
|
||||||
|
const peekQueueByConversation = new Map<string, LatestQueue>();
|
||||||
|
const doGroupCallPeek = (
|
||||||
|
conversationId: string,
|
||||||
|
dispatch: ThunkDispatch<
|
||||||
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
PeekGroupCallFulfilledActionType
|
||||||
|
>,
|
||||||
|
getState: () => RootStateType
|
||||||
|
) => {
|
||||||
|
const conversation = getOwn(
|
||||||
|
getState().conversations.conversationLookup,
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!conversation ||
|
||||||
|
getConversationCallMode(conversation) !== CallMode.Group
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let queue = peekQueueByConversation.get(conversationId);
|
||||||
|
if (!queue) {
|
||||||
|
queue = new LatestQueue();
|
||||||
|
queue.onceEmpty(() => {
|
||||||
|
peekQueueByConversation.delete(conversationId);
|
||||||
|
});
|
||||||
|
peekQueueByConversation.set(conversationId, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.add(async () => {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
// We make sure we're not trying to peek at a connected (or connecting, or
|
||||||
|
// reconnecting) call. Because this is asynchronous, it's possible that the call
|
||||||
|
// will connect by the time we dispatch, so we also need to do a similar check in
|
||||||
|
// the reducer.
|
||||||
|
const existingCall = getOwn(
|
||||||
|
state.calling.callsByConversation,
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
existingCall?.callMode === CallMode.Group &&
|
||||||
|
existingCall.connectionState !== GroupCallConnectionState.NotConnected
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we peek right after receiving the message, we may get outdated information.
|
||||||
|
// This is most noticeable when someone leaves. We add a delay and then make sure
|
||||||
|
// to only be peeking once.
|
||||||
|
await Promise.all([sleep(1000), waitForOnline(navigator, window)]);
|
||||||
|
|
||||||
|
let peekInfo;
|
||||||
|
try {
|
||||||
|
peekInfo = await calling.peekGroupCall(conversationId);
|
||||||
|
} catch (err) {
|
||||||
|
log.error('Group call peeking failed', Errors.toLogFormat(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!peekInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await calling.updateCallHistoryForGroupCall(conversationId, peekInfo);
|
||||||
|
|
||||||
|
const formattedPeekInfo = calling.formatGroupCallPeekInfoForRedux(peekInfo);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: PEEK_GROUP_CALL_FULFILLED,
|
||||||
|
payload: {
|
||||||
|
conversationId,
|
||||||
|
peekInfo: formattedPeekInfo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const ACCEPT_CALL_PENDING = 'calling/ACCEPT_CALL_PENDING';
|
const ACCEPT_CALL_PENDING = 'calling/ACCEPT_CALL_PENDING';
|
||||||
|
@ -315,8 +404,7 @@ const INCOMING_GROUP_CALL = 'calling/INCOMING_GROUP_CALL';
|
||||||
const MARK_CALL_TRUSTED = 'calling/MARK_CALL_TRUSTED';
|
const MARK_CALL_TRUSTED = 'calling/MARK_CALL_TRUSTED';
|
||||||
const MARK_CALL_UNTRUSTED = 'calling/MARK_CALL_UNTRUSTED';
|
const MARK_CALL_UNTRUSTED = 'calling/MARK_CALL_UNTRUSTED';
|
||||||
const OUTGOING_CALL = 'calling/OUTGOING_CALL';
|
const OUTGOING_CALL = 'calling/OUTGOING_CALL';
|
||||||
const PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED =
|
const PEEK_GROUP_CALL_FULFILLED = 'calling/PEEK_GROUP_CALL_FULFILLED';
|
||||||
'calling/PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED';
|
|
||||||
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';
|
||||||
|
@ -390,7 +478,7 @@ export type GroupCallStateChangeActionType = {
|
||||||
|
|
||||||
type HangUpActionType = {
|
type HangUpActionType = {
|
||||||
type: 'calling/HANG_UP';
|
type: 'calling/HANG_UP';
|
||||||
payload: HangUpType;
|
payload: HangUpActionPayloadType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IncomingDirectCallActionType = {
|
type IncomingDirectCallActionType = {
|
||||||
|
@ -420,12 +508,11 @@ type OutgoingCallActionType = {
|
||||||
payload: StartDirectCallType;
|
payload: StartDirectCallType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PeekNotConnectedGroupCallFulfilledActionType = {
|
export type PeekGroupCallFulfilledActionType = {
|
||||||
type: 'calling/PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED';
|
type: 'calling/PEEK_GROUP_CALL_FULFILLED';
|
||||||
payload: {
|
payload: {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
peekInfo: GroupCallPeekInfoType;
|
peekInfo: GroupCallPeekInfoType;
|
||||||
ourUuid: UUIDStringType;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -512,6 +599,7 @@ export type CallingActionType =
|
||||||
| ChangeIODeviceFulfilledActionType
|
| ChangeIODeviceFulfilledActionType
|
||||||
| CloseNeedPermissionScreenActionType
|
| CloseNeedPermissionScreenActionType
|
||||||
| ConversationChangedActionType
|
| ConversationChangedActionType
|
||||||
|
| ConversationRemovedActionType
|
||||||
| DeclineCallActionType
|
| DeclineCallActionType
|
||||||
| GroupCallAudioLevelsChangeActionType
|
| GroupCallAudioLevelsChangeActionType
|
||||||
| GroupCallStateChangeActionType
|
| GroupCallStateChangeActionType
|
||||||
|
@ -521,7 +609,7 @@ export type CallingActionType =
|
||||||
| KeyChangedActionType
|
| KeyChangedActionType
|
||||||
| KeyChangeOkActionType
|
| KeyChangeOkActionType
|
||||||
| OutgoingCallActionType
|
| OutgoingCallActionType
|
||||||
| PeekNotConnectedGroupCallFulfilledActionType
|
| PeekGroupCallFulfilledActionType
|
||||||
| RefreshIODevicesActionType
|
| RefreshIODevicesActionType
|
||||||
| RemoteSharingScreenChangeActionType
|
| RemoteSharingScreenChangeActionType
|
||||||
| RemoteVideoChangeActionType
|
| RemoteVideoChangeActionType
|
||||||
|
@ -762,22 +850,13 @@ function groupCallStateChange(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hangUp(payload: HangUpType): HangUpActionType {
|
|
||||||
calling.hangup(payload.conversationId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: HANG_UP,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function hangUpActiveCall(): ThunkAction<
|
function hangUpActiveCall(): ThunkAction<
|
||||||
void,
|
void,
|
||||||
RootStateType,
|
RootStateType,
|
||||||
unknown,
|
unknown,
|
||||||
HangUpActionType
|
HangUpActionType
|
||||||
> {
|
> {
|
||||||
return (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const activeCall = getActiveCall(state.calling);
|
const activeCall = getActiveCall(state.calling);
|
||||||
|
@ -787,12 +866,20 @@ function hangUpActiveCall(): ThunkAction<
|
||||||
|
|
||||||
const { conversationId } = activeCall;
|
const { conversationId } = activeCall;
|
||||||
|
|
||||||
|
calling.hangup(conversationId);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: HANG_UP,
|
type: HANG_UP,
|
||||||
payload: {
|
payload: {
|
||||||
conversationId,
|
conversationId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (activeCall.callMode === CallMode.Group) {
|
||||||
|
// We want to give the group call time to disconnect.
|
||||||
|
await sleep(1000);
|
||||||
|
doGroupCallPeek(conversationId, dispatch, getState);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,83 +969,25 @@ function outgoingCall(payload: StartDirectCallType): OutgoingCallActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// We might call this function many times in rapid succession (for example, if lots of
|
function peekGroupCallForTheFirstTime(
|
||||||
// people are joining and leaving at once). We want to make sure to update eventually
|
conversationId: string
|
||||||
// (if people join and leave for an hour, we don't want you to have to wait an hour to
|
): ThunkAction<void, RootStateType, unknown, PeekGroupCallFulfilledActionType> {
|
||||||
// get an update), and we also don't want to update too often. That's why we use a
|
return (dispatch, getState) => {
|
||||||
// "latest queue".
|
const call = getOwn(getState().calling.callsByConversation, conversationId);
|
||||||
const peekQueueByConversation = new Map<string, LatestQueue>();
|
const shouldPeek =
|
||||||
|
!call || (call.callMode === CallMode.Group && !call.peekInfo);
|
||||||
|
if (shouldPeek) {
|
||||||
|
doGroupCallPeek(conversationId, dispatch, getState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function peekNotConnectedGroupCall(
|
function peekNotConnectedGroupCall(
|
||||||
payload: PeekNotConnectedGroupCallType
|
payload: PeekNotConnectedGroupCallType
|
||||||
): ThunkAction<
|
): ThunkAction<void, RootStateType, unknown, PeekGroupCallFulfilledActionType> {
|
||||||
void,
|
|
||||||
RootStateType,
|
|
||||||
unknown,
|
|
||||||
PeekNotConnectedGroupCallFulfilledActionType
|
|
||||||
> {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { conversationId } = payload;
|
const { conversationId } = payload;
|
||||||
|
doGroupCallPeek(conversationId, dispatch, getState);
|
||||||
let queue = peekQueueByConversation.get(conversationId);
|
|
||||||
if (!queue) {
|
|
||||||
queue = new LatestQueue();
|
|
||||||
queue.onceEmpty(() => {
|
|
||||||
peekQueueByConversation.delete(conversationId);
|
|
||||||
});
|
|
||||||
peekQueueByConversation.set(conversationId, queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.add(async () => {
|
|
||||||
const state = getState();
|
|
||||||
|
|
||||||
// We make sure we're not trying to peek at a connected (or connecting, or
|
|
||||||
// reconnecting) call. Because this is asynchronous, it's possible that the call
|
|
||||||
// will connect by the time we dispatch, so we also need to do a similar check in
|
|
||||||
// the reducer.
|
|
||||||
const existingCall = getOwn(
|
|
||||||
state.calling.callsByConversation,
|
|
||||||
conversationId
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
existingCall?.callMode === CallMode.Group &&
|
|
||||||
existingCall.connectionState !== GroupCallConnectionState.NotConnected
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we peek right after receiving the message, we may get outdated information.
|
|
||||||
// This is most noticeable when someone leaves. We add a delay and then make sure
|
|
||||||
// to only be peeking once.
|
|
||||||
await sleep(1000);
|
|
||||||
|
|
||||||
let peekInfo;
|
|
||||||
try {
|
|
||||||
peekInfo = await calling.peekGroupCall(conversationId);
|
|
||||||
} catch (err) {
|
|
||||||
log.error('Group call peeking failed', Errors.toLogFormat(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!peekInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ourUuid } = state.user;
|
|
||||||
|
|
||||||
await calling.updateCallHistoryForGroupCall(conversationId, peekInfo);
|
|
||||||
|
|
||||||
const formattedPeekInfo =
|
|
||||||
calling.formatGroupCallPeekInfoForRedux(peekInfo);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED,
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
peekInfo: formattedPeekInfo,
|
|
||||||
ourUuid,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1150,7 +1179,7 @@ function startCallingLobby({
|
||||||
// The group call device count is considered 0 for a direct call.
|
// The group call device count is considered 0 for a direct call.
|
||||||
const groupCall = getGroupCall(conversationId, state.calling);
|
const groupCall = getGroupCall(conversationId, state.calling);
|
||||||
const groupCallDeviceCount =
|
const groupCallDeviceCount =
|
||||||
groupCall?.peekInfo.deviceCount ||
|
groupCall?.peekInfo?.deviceCount ||
|
||||||
groupCall?.remoteParticipants.length ||
|
groupCall?.remoteParticipants.length ||
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
@ -1264,12 +1293,12 @@ export const actions = {
|
||||||
getPresentingSources,
|
getPresentingSources,
|
||||||
groupCallAudioLevelsChange,
|
groupCallAudioLevelsChange,
|
||||||
groupCallStateChange,
|
groupCallStateChange,
|
||||||
hangUp,
|
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
keyChangeOk,
|
keyChangeOk,
|
||||||
keyChanged,
|
keyChanged,
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
outgoingCall,
|
outgoingCall,
|
||||||
|
peekGroupCallForTheFirstTime,
|
||||||
peekNotConnectedGroupCall,
|
peekNotConnectedGroupCall,
|
||||||
receiveIncomingDirectCall,
|
receiveIncomingDirectCall,
|
||||||
receiveIncomingGroupCall,
|
receiveIncomingGroupCall,
|
||||||
|
@ -1375,7 +1404,7 @@ export function reducer(
|
||||||
outgoingRing =
|
outgoingRing =
|
||||||
isGroupCallOutboundRingEnabled() &&
|
isGroupCallOutboundRingEnabled() &&
|
||||||
!ringState.ringId &&
|
!ringState.ringId &&
|
||||||
!call.peekInfo.uuids.length &&
|
!call.peekInfo?.uuids.length &&
|
||||||
!call.remoteParticipants.length &&
|
!call.remoteParticipants.length &&
|
||||||
!action.payload.isConversationTooBigToRing;
|
!action.payload.isConversationTooBigToRing;
|
||||||
break;
|
break;
|
||||||
|
@ -1481,10 +1510,6 @@ export function reducer(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupCall.connectionState === GroupCallConnectionState.NotConnected) {
|
|
||||||
return removeConversationFromState(state, conversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
callsByConversation: {
|
callsByConversation: {
|
||||||
|
@ -1513,6 +1538,10 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === 'CONVERSATION_REMOVED') {
|
||||||
|
return removeConversationFromState(state, action.payload.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type === DECLINE_DIRECT_CALL) {
|
if (action.type === DECLINE_DIRECT_CALL) {
|
||||||
return removeConversationFromState(state, action.payload.conversationId);
|
return removeConversationFromState(state, action.payload.conversationId);
|
||||||
}
|
}
|
||||||
|
@ -1709,32 +1738,17 @@ export function reducer(
|
||||||
};
|
};
|
||||||
|
|
||||||
let newActiveCallState: ActiveCallStateType | undefined;
|
let newActiveCallState: ActiveCallStateType | undefined;
|
||||||
|
if (state.activeCallState?.conversationId === conversationId) {
|
||||||
if (connectionState === GroupCallConnectionState.NotConnected) {
|
|
||||||
newActiveCallState =
|
newActiveCallState =
|
||||||
state.activeCallState?.conversationId === conversationId
|
connectionState === GroupCallConnectionState.NotConnected
|
||||||
? undefined
|
? undefined
|
||||||
: state.activeCallState;
|
: {
|
||||||
|
|
||||||
if (
|
|
||||||
!isAnybodyElseInGroupCall(newPeekInfo, ourUuid) &&
|
|
||||||
(!existingCall || !existingCall.ringerUuid)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
callsByConversation: omit(callsByConversation, conversationId),
|
|
||||||
activeCallState: newActiveCallState,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newActiveCallState =
|
|
||||||
state.activeCallState?.conversationId === conversationId
|
|
||||||
? {
|
|
||||||
...state.activeCallState,
|
...state.activeCallState,
|
||||||
hasLocalAudio,
|
hasLocalAudio,
|
||||||
hasLocalVideo,
|
hasLocalVideo,
|
||||||
}
|
};
|
||||||
: state.activeCallState;
|
} else {
|
||||||
|
newActiveCallState = state.activeCallState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -1774,8 +1788,8 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED) {
|
if (action.type === PEEK_GROUP_CALL_FULFILLED) {
|
||||||
const { conversationId, peekInfo, ourUuid } = action.payload;
|
const { conversationId, peekInfo } = action.payload;
|
||||||
|
|
||||||
const existingCall: GroupCallStateType = getGroupCall(
|
const existingCall: GroupCallStateType = getGroupCall(
|
||||||
conversationId,
|
conversationId,
|
||||||
|
@ -1806,13 +1820,6 @@ export function reducer(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
!isAnybodyElseInGroupCall(peekInfo, ourUuid) &&
|
|
||||||
!existingCall.ringerUuid
|
|
||||||
) {
|
|
||||||
return removeConversationFromState(state, conversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
callsByConversation: {
|
callsByConversation: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2019-2021 Signal Messenger, LLC
|
// Copyright 2019-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -480,7 +480,7 @@ export type ConversationChangedActionType = {
|
||||||
data: ConversationType;
|
data: ConversationType;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
type ConversationRemovedActionType = {
|
export type ConversationRemovedActionType = {
|
||||||
type: 'CONVERSATION_REMOVED';
|
type: 'CONVERSATION_REMOVED';
|
||||||
payload: {
|
payload: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -1138,7 +1138,6 @@ export function getPropsForCallHistory(
|
||||||
throw new Error('getPropsForCallHistory: missing conversation ID');
|
throw new Error('getPropsForCallHistory: missing conversation ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
const creator = conversationSelector(callHistoryDetails.creatorUuid);
|
|
||||||
let call = callSelector(conversationId);
|
let call = callSelector(conversationId);
|
||||||
if (call && call.callMode !== CallMode.Group) {
|
if (call && call.callMode !== CallMode.Group) {
|
||||||
log.error(
|
log.error(
|
||||||
|
@ -1147,14 +1146,18 @@ export function getPropsForCallHistory(
|
||||||
call = undefined;
|
call = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const creator = conversationSelector(callHistoryDetails.creatorUuid);
|
||||||
|
const deviceCount = call?.peekInfo?.deviceCount ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeCallConversationId: activeCall?.conversationId,
|
activeCallConversationId: activeCall?.conversationId,
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
conversationId,
|
conversationId,
|
||||||
creator,
|
creator,
|
||||||
deviceCount: call?.peekInfo.deviceCount ?? 0,
|
deviceCount,
|
||||||
ended: callHistoryDetails.eraId !== call?.peekInfo.eraId,
|
ended:
|
||||||
maxDevices: call?.peekInfo.maxDevices ?? Infinity,
|
callHistoryDetails.eraId !== call?.peekInfo?.eraId || !deviceCount,
|
||||||
|
maxDevices: call?.peekInfo?.maxDevices ?? Infinity,
|
||||||
startedTime: callHistoryDetails.startedTime,
|
startedTime: callHistoryDetails.startedTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,17 @@ const mapStateToActiveCallProp = (
|
||||||
const peekedParticipants: Array<ConversationType> = [];
|
const peekedParticipants: Array<ConversationType> = [];
|
||||||
|
|
||||||
const { memberships = [] } = conversation;
|
const { memberships = [] } = conversation;
|
||||||
|
|
||||||
|
// Active calls should have peek info, but TypeScript doesn't know that so we have a
|
||||||
|
// fallback.
|
||||||
|
const {
|
||||||
|
peekInfo = {
|
||||||
|
deviceCount: 0,
|
||||||
|
maxDevices: Infinity,
|
||||||
|
uuids: [],
|
||||||
|
},
|
||||||
|
} = call;
|
||||||
|
|
||||||
for (let i = 0; i < memberships.length; i += 1) {
|
for (let i = 0; i < memberships.length; i += 1) {
|
||||||
const { uuid } = memberships[i];
|
const { uuid } = memberships[i];
|
||||||
|
|
||||||
|
@ -226,8 +237,8 @@ const mapStateToActiveCallProp = (
|
||||||
conversationsWithSafetyNumberChanges.push(remoteConversation);
|
conversationsWithSafetyNumberChanges.push(remoteConversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < call.peekInfo.uuids.length; i += 1) {
|
for (let i = 0; i < peekInfo.uuids.length; i += 1) {
|
||||||
const peekedParticipantUuid = call.peekInfo.uuids[i];
|
const peekedParticipantUuid = peekInfo.uuids[i];
|
||||||
|
|
||||||
const peekedConversation = conversationSelectorByUuid(
|
const peekedConversation = conversationSelectorByUuid(
|
||||||
peekedParticipantUuid
|
peekedParticipantUuid
|
||||||
|
@ -245,10 +256,10 @@ const mapStateToActiveCallProp = (
|
||||||
callMode: CallMode.Group,
|
callMode: CallMode.Group,
|
||||||
connectionState: call.connectionState,
|
connectionState: call.connectionState,
|
||||||
conversationsWithSafetyNumberChanges,
|
conversationsWithSafetyNumberChanges,
|
||||||
deviceCount: call.peekInfo.deviceCount,
|
deviceCount: peekInfo.deviceCount,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
joinState: call.joinState,
|
joinState: call.joinState,
|
||||||
maxDevices: call.peekInfo.maxDevices,
|
maxDevices: peekInfo.maxDevices,
|
||||||
peekedParticipants,
|
peekedParticipants,
|
||||||
remoteParticipants,
|
remoteParticipants,
|
||||||
speakingDemuxIds: call.speakingDemuxIds || new Set<number>(),
|
speakingDemuxIds: call.speakingDemuxIds || new Set<number>(),
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
GroupCallJoinState,
|
GroupCallJoinState,
|
||||||
} from '../../../types/Calling';
|
} from '../../../types/Calling';
|
||||||
import { UUID } from '../../../types/UUID';
|
import { UUID } from '../../../types/UUID';
|
||||||
import type { UUIDStringType } from '../../../types/UUID';
|
|
||||||
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';
|
||||||
|
|
||||||
|
@ -607,35 +606,7 @@ describe('calling duck', () => {
|
||||||
assert.strictEqual(result, stateWithIncomingGroupCall);
|
assert.strictEqual(result, stateWithIncomingGroupCall);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes the call from the state if it's not connected", () => {
|
it('removes the ring state, but not the call', () => {
|
||||||
const state = {
|
|
||||||
...stateWithGroupCall,
|
|
||||||
callsByConversation: {
|
|
||||||
...stateWithGroupCall.callsByConversation,
|
|
||||||
'fake-group-call-conversation-id': {
|
|
||||||
...stateWithGroupCall.callsByConversation[
|
|
||||||
'fake-group-call-conversation-id'
|
|
||||||
],
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
ringId: BigInt(123),
|
|
||||||
ringerUuid: UUID.generate().toString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const action = cancelIncomingGroupCallRing({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
ringId: BigInt(123),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = reducer(state, action);
|
|
||||||
|
|
||||||
assert.notProperty(
|
|
||||||
result.callsByConversation,
|
|
||||||
'fake-group-call-conversation-id'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes the ring state, but not the call, if it's connected", () => {
|
|
||||||
const action = cancelIncomingGroupCallRing({
|
const action = cancelIncomingGroupCallRing({
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
conversationId: 'fake-group-call-conversation-id',
|
||||||
ringId: BigInt(123),
|
ringId: BigInt(123),
|
||||||
|
@ -850,117 +821,6 @@ describe('calling duck', () => {
|
||||||
return dispatch.getCall(0).args[0];
|
return dispatch.getCall(0).args[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
it('ignores non-connected calls with no peeked participants', () => {
|
|
||||||
const result = reducer(
|
|
||||||
getEmptyState(),
|
|
||||||
getAction({
|
|
||||||
conversationId: 'abc123',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: [],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 0,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.deepEqual(result, getEmptyState());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes the call from the map of conversations if the call is not connected and has no peeked participants or ringer', () => {
|
|
||||||
const result = reducer(
|
|
||||||
stateWithGroupCall,
|
|
||||||
getAction({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: [],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 0,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.notProperty(
|
|
||||||
result.callsByConversation,
|
|
||||||
'fake-group-call-conversation-id'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes the call from the map of conversations if the call is not connected and has 1 peeked participant: you', () => {
|
|
||||||
const result = reducer(
|
|
||||||
stateWithGroupCall,
|
|
||||||
getAction({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: [ourUuid],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 1,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.notProperty(
|
|
||||||
result.callsByConversation,
|
|
||||||
'fake-group-call-conversation-id'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('drops the active call if it is disconnected with no peeked participants', () => {
|
|
||||||
const result = reducer(
|
|
||||||
stateWithActiveGroupCall,
|
|
||||||
getAction({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: [],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 0,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.isUndefined(result.activeCallState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('drops the active call if it is disconnected with 1 peeked participant (you)', () => {
|
|
||||||
const result = reducer(
|
|
||||||
stateWithActiveGroupCall,
|
|
||||||
getAction({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: [ourUuid],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 1,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.isUndefined(result.activeCallState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('saves a new call to the map of conversations', () => {
|
it('saves a new call to the map of conversations', () => {
|
||||||
const result = reducer(
|
const result = reducer(
|
||||||
getEmptyState(),
|
getEmptyState(),
|
||||||
|
@ -1020,64 +880,6 @@ describe('calling duck', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves a new call to the map of conversations if the call is disconnected by has peeked participants that are not you', () => {
|
|
||||||
const result = reducer(
|
|
||||||
stateWithGroupCall,
|
|
||||||
getAction({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: ['1b9e4d42-1f56-45c5-b6f4-d1be5a54fefa'],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 1,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.deepEqual(
|
|
||||||
result.callsByConversation['fake-group-call-conversation-id'],
|
|
||||||
{
|
|
||||||
callMode: CallMode.Group,
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: ['1b9e4d42-1f56-45c5-b6f4-d1be5a54fefa'],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 1,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('saves a call to the map of conversations if the call had a ringer, even if it was otherwise ignorable', () => {
|
|
||||||
const result = reducer(
|
|
||||||
stateWithIncomingGroupCall,
|
|
||||||
getAction({
|
|
||||||
conversationId: 'fake-group-call-conversation-id',
|
|
||||||
connectionState: GroupCallConnectionState.NotConnected,
|
|
||||||
joinState: GroupCallJoinState.NotJoined,
|
|
||||||
hasLocalAudio: false,
|
|
||||||
hasLocalVideo: false,
|
|
||||||
peekInfo: {
|
|
||||||
uuids: [],
|
|
||||||
maxDevices: 16,
|
|
||||||
deviceCount: 0,
|
|
||||||
},
|
|
||||||
remoteParticipants: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.isDefined(
|
|
||||||
result.callsByConversation['fake-group-call-conversation-id']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates a call in the map of conversations', () => {
|
it('updates a call in the map of conversations', () => {
|
||||||
const result = reducer(
|
const result = reducer(
|
||||||
stateWithGroupCall,
|
stateWithGroupCall,
|
||||||
|
@ -2225,32 +2027,30 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isAnybodyElseInGroupCall', () => {
|
describe('isAnybodyElseInGroupCall', () => {
|
||||||
const fakePeekInfo = (uuids: Array<UUIDStringType>) => ({
|
it('returns false with no peek info', () => {
|
||||||
uuids,
|
assert.isFalse(isAnybodyElseInGroupCall(undefined, remoteUuid));
|
||||||
maxDevices: 5,
|
|
||||||
deviceCount: uuids.length,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if the peek info has no participants', () => {
|
it('returns false if the peek info has no participants', () => {
|
||||||
assert.isFalse(isAnybodyElseInGroupCall(fakePeekInfo([]), remoteUuid));
|
assert.isFalse(isAnybodyElseInGroupCall({ uuids: [] }, remoteUuid));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if the peek info has one participant, you', () => {
|
it('returns false if the peek info has one participant, you', () => {
|
||||||
assert.isFalse(
|
assert.isFalse(
|
||||||
isAnybodyElseInGroupCall(fakePeekInfo([creatorUuid]), creatorUuid)
|
isAnybodyElseInGroupCall({ uuids: [creatorUuid] }, creatorUuid)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the peek info has one participant, someone else', () => {
|
it('returns true if the peek info has one participant, someone else', () => {
|
||||||
assert.isTrue(
|
assert.isTrue(
|
||||||
isAnybodyElseInGroupCall(fakePeekInfo([creatorUuid]), remoteUuid)
|
isAnybodyElseInGroupCall({ uuids: [creatorUuid] }, remoteUuid)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the peek info has two participants, you and someone else', () => {
|
it('returns true if the peek info has two participants, you and someone else', () => {
|
||||||
assert.isTrue(
|
assert.isTrue(
|
||||||
isAnybodyElseInGroupCall(
|
isAnybodyElseInGroupCall(
|
||||||
fakePeekInfo([creatorUuid, remoteUuid]),
|
{ uuids: [creatorUuid, remoteUuid] },
|
||||||
remoteUuid
|
remoteUuid
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue