signal-desktop/ts/components/conversation/ContactSpoofingReviewDialog.tsx

386 lines
13 KiB
TypeScript
Raw Normal View History

2021-04-21 16:31:12 +00:00
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
2022-11-18 00:45:19 +00:00
import type { ReactChild, ReactNode } from 'react';
import React, { useState } from 'react';
2021-04-21 16:31:12 +00:00
2021-11-30 10:07:24 +00:00
import type { LocalizerType, ThemeType } from '../../types/Util';
import type { ConversationType } from '../../state/ducks/conversations';
2021-11-30 10:07:24 +00:00
import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
2021-04-21 16:31:12 +00:00
import {
MessageRequestActionsConfirmation,
MessageRequestState,
} from './MessageRequestActionsConfirmation';
2021-06-01 23:30:25 +00:00
import { ContactSpoofingType } from '../../util/contactSpoofing';
2021-04-21 16:31:12 +00:00
import { Modal } from '../Modal';
2021-06-01 23:30:25 +00:00
import { RemoveGroupMemberConfirmationDialog } from './RemoveGroupMemberConfirmationDialog';
2021-04-21 16:31:12 +00:00
import { ContactSpoofingReviewDialogPerson } from './ContactSpoofingReviewDialogPerson';
import { Button, ButtonVariant } from '../Button';
import { assertDev } from '../../util/assert';
2021-06-01 23:30:25 +00:00
import { missingCaseError } from '../../util/missingCaseError';
import { isInSystemContacts } from '../../util/isInSystemContacts';
export type ReviewPropsType = Readonly<
| {
type: ContactSpoofingType.DirectConversationWithSameTitle;
possiblyUnsafe: {
conversation: ConversationType;
isSignalConnection: boolean;
};
safe: {
conversation: ConversationType;
isSignalConnection: boolean;
};
}
| {
type: ContactSpoofingType.MultipleGroupMembersWithSameTitle;
group: ConversationType;
collisionInfoByTitle: Record<
string,
Array<{
oldName?: string;
isSignalConnection: boolean;
conversation: ConversationType;
}>
>;
}
>;
2021-04-21 16:31:12 +00:00
export type PropsType = {
conversationId: string;
2022-12-06 19:03:09 +00:00
acceptConversation: (conversationId: string) => unknown;
2024-03-12 16:29:31 +00:00
reportSpam: (conversationId: string) => unknown;
2022-12-06 19:03:09 +00:00
blockAndReportSpam: (conversationId: string) => unknown;
blockConversation: (conversationId: string) => unknown;
deleteConversation: (conversationId: string) => unknown;
toggleSignalConnectionsModal: () => void;
updateSharedGroups: (conversationId: string) => void;
2021-11-30 10:07:24 +00:00
getPreferredBadge: PreferredBadgeSelectorType;
2021-04-21 16:31:12 +00:00
i18n: LocalizerType;
onClose: () => void;
showContactModal: (contactId: string, conversationId?: string) => unknown;
removeMember: (
conversationId: string,
memberConversationId: string
) => unknown;
2021-11-30 10:07:24 +00:00
theme: ThemeType;
} & ReviewPropsType;
2021-04-21 16:31:12 +00:00
2021-06-01 23:30:25 +00:00
enum ConfirmationStateType {
ConfirmingDelete,
ConfirmingBlock,
ConfirmingGroupRemoval,
}
2021-04-21 16:31:12 +00:00
2022-11-18 00:45:19 +00:00
export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
2022-03-22 20:45:34 +00:00
const {
2022-12-06 19:03:09 +00:00
acceptConversation,
2024-03-12 16:29:31 +00:00
reportSpam,
2022-12-06 19:03:09 +00:00
blockAndReportSpam,
blockConversation,
conversationId,
2022-12-06 19:03:09 +00:00
deleteConversation,
toggleSignalConnectionsModal,
updateSharedGroups,
2022-03-22 20:45:34 +00:00
getPreferredBadge,
i18n,
onClose,
showContactModal,
2022-03-22 20:45:34 +00:00
removeMember,
theme,
} = props;
2021-04-21 16:31:12 +00:00
2022-03-22 20:45:34 +00:00
const [confirmationState, setConfirmationState] = useState<
| undefined
| {
type: ConfirmationStateType.ConfirmingGroupRemoval;
affectedConversation: ConversationType;
group: ConversationType;
}
| {
type:
| ConfirmationStateType.ConfirmingDelete
| ConfirmationStateType.ConfirmingBlock;
affectedConversation: ConversationType;
}
>();
2021-06-01 23:30:25 +00:00
2022-03-22 20:45:34 +00:00
if (confirmationState) {
const { type, affectedConversation } = confirmationState;
switch (type) {
case ConfirmationStateType.ConfirmingDelete:
case ConfirmationStateType.ConfirmingBlock:
return (
<MessageRequestActionsConfirmation
2024-03-12 16:29:31 +00:00
addedByName={affectedConversation}
2022-12-06 19:03:09 +00:00
conversationId={affectedConversation.id}
2024-03-12 16:29:31 +00:00
conversationType={affectedConversation.type}
conversationName={affectedConversation}
2022-03-22 20:45:34 +00:00
i18n={i18n}
2024-03-12 16:29:31 +00:00
isBlocked={affectedConversation.isBlocked ?? false}
isReported={affectedConversation.isReported ?? false}
2022-03-22 20:45:34 +00:00
state={
type === ConfirmationStateType.ConfirmingDelete
? MessageRequestState.deleting
: MessageRequestState.blocking
}
2024-03-12 16:29:31 +00:00
acceptConversation={acceptConversation}
reportSpam={reportSpam}
blockAndReportSpam={blockAndReportSpam}
blockConversation={blockConversation}
deleteConversation={deleteConversation}
2022-03-22 20:45:34 +00:00
onChangeState={messageRequestState => {
switch (messageRequestState) {
case MessageRequestState.blocking:
setConfirmationState({
type: ConfirmationStateType.ConfirmingBlock,
affectedConversation,
});
break;
case MessageRequestState.deleting:
setConfirmationState({
type: ConfirmationStateType.ConfirmingDelete,
affectedConversation,
});
break;
2024-03-12 16:29:31 +00:00
case MessageRequestState.reportingAndMaybeBlocking:
case MessageRequestState.acceptedOptions:
2022-03-22 20:45:34 +00:00
case MessageRequestState.unblocking:
assertDev(
2022-03-22 20:45:34 +00:00
false,
2024-03-12 16:29:31 +00:00
`Got unexpected MessageRequestState.${MessageRequestState[messageRequestState]} state. Clearing confiration state`
2022-03-22 20:45:34 +00:00
);
setConfirmationState(undefined);
break;
case MessageRequestState.default:
setConfirmationState(undefined);
break;
default:
throw missingCaseError(messageRequestState);
2021-06-01 23:30:25 +00:00
}
2022-03-22 20:45:34 +00:00
}}
/>
);
case ConfirmationStateType.ConfirmingGroupRemoval: {
const { group } = confirmationState;
return (
<RemoveGroupMemberConfirmationDialog
conversation={affectedConversation}
group={group}
i18n={i18n}
onClose={() => {
setConfirmationState(undefined);
}}
onRemove={() => {
removeMember(conversationId, affectedConversation.id);
2022-03-22 20:45:34 +00:00
}}
/>
);
2021-11-11 22:43:05 +00:00
}
2022-03-22 20:45:34 +00:00
default:
throw missingCaseError(type);
2021-06-01 23:30:25 +00:00
}
2022-03-22 20:45:34 +00:00
}
2021-06-01 23:30:25 +00:00
2022-03-22 20:45:34 +00:00
let title: string;
let contents: ReactChild;
2021-06-01 23:30:25 +00:00
2022-03-22 20:45:34 +00:00
switch (props.type) {
case ContactSpoofingType.DirectConversationWithSameTitle: {
const { possiblyUnsafe, safe } = props;
assertDev(
possiblyUnsafe.conversation.type === 'direct',
2022-03-22 20:45:34 +00:00
'<ContactSpoofingReviewDialog> expected a direct conversation for the "possibly unsafe" conversation'
);
assertDev(
safe.conversation.type === 'direct',
2022-03-22 20:45:34 +00:00
'<ContactSpoofingReviewDialog> expected a direct conversation for the "safe" conversation'
);
2023-03-30 00:03:25 +00:00
title = i18n('icu:ContactSpoofingReviewDialog__title');
2022-03-22 20:45:34 +00:00
contents = (
<>
2023-03-30 00:03:25 +00:00
<p>{i18n('icu:ContactSpoofingReviewDialog__description')}</p>
<h2>
{i18n('icu:ContactSpoofingReviewDialog__possibly-unsafe-title')}
</h2>
2022-03-22 20:45:34 +00:00
<ContactSpoofingReviewDialogPerson
conversation={possiblyUnsafe.conversation}
2022-03-22 20:45:34 +00:00
getPreferredBadge={getPreferredBadge}
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
updateSharedGroups={updateSharedGroups}
2022-03-22 20:45:34 +00:00
i18n={i18n}
theme={theme}
isSignalConnection={possiblyUnsafe.isSignalConnection}
oldName={undefined}
2022-03-22 20:45:34 +00:00
>
<div className="module-ContactSpoofingReviewDialog__buttons">
<Button
variant={ButtonVariant.SecondaryDestructive}
onClick={() => {
setConfirmationState({
type: ConfirmationStateType.ConfirmingDelete,
affectedConversation: possiblyUnsafe.conversation,
2022-03-22 20:45:34 +00:00
});
}}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:MessageRequests--delete')}
2022-03-22 20:45:34 +00:00
</Button>
<Button
variant={ButtonVariant.SecondaryDestructive}
onClick={() => {
setConfirmationState({
type: ConfirmationStateType.ConfirmingBlock,
affectedConversation: possiblyUnsafe.conversation,
2022-03-22 20:45:34 +00:00
});
}}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:MessageRequests--block')}
2022-03-22 20:45:34 +00:00
</Button>
</div>
</ContactSpoofingReviewDialogPerson>
<hr />
2023-03-30 00:03:25 +00:00
<h2>{i18n('icu:ContactSpoofingReviewDialog__safe-title')}</h2>
2022-03-22 20:45:34 +00:00
<ContactSpoofingReviewDialogPerson
conversation={safe.conversation}
2022-03-22 20:45:34 +00:00
getPreferredBadge={getPreferredBadge}
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
updateSharedGroups={updateSharedGroups}
2022-03-22 20:45:34 +00:00
i18n={i18n}
onClick={() => {
showContactModal(safe.conversation.id);
2022-03-22 20:45:34 +00:00
}}
theme={theme}
isSignalConnection={safe.isSignalConnection}
oldName={undefined}
2022-03-22 20:45:34 +00:00
/>
</>
);
break;
}
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
const { group, collisionInfoByTitle } = props;
const sharedTitles = Object.keys(collisionInfoByTitle);
const numSharedTitles = sharedTitles.length;
const totalConversations = Object.values(collisionInfoByTitle).reduce(
(sum, conversationInfos) => sum + conversationInfos.length,
0
2022-03-22 20:45:34 +00:00
);
2023-03-30 00:03:25 +00:00
title = i18n('icu:ContactSpoofingReviewDialog__group__title');
2022-03-22 20:45:34 +00:00
contents = (
<>
<p className="module-ContactSpoofingReviewDialog__description">
{numSharedTitles > 1
? i18n(
'icu:ContactSpoofingReviewDialog__group__multiple-conflicts__description',
{
count: numSharedTitles,
}
)
: i18n('icu:ContactSpoofingReviewDialog__group__description', {
count: totalConversations,
})}
2022-03-22 20:45:34 +00:00
</p>
2021-11-11 22:43:05 +00:00
{Object.values(collisionInfoByTitle)
.map((conversationInfos, titleIdx) =>
conversationInfos.map((conversationInfo, conversationIdx) => {
let button: ReactNode;
if (group.areWeAdmin) {
button = (
<Button
variant={ButtonVariant.SecondaryAffirmative}
onClick={() => {
setConfirmationState({
type: ConfirmationStateType.ConfirmingGroupRemoval,
affectedConversation: conversationInfo.conversation,
group,
});
}}
>
{i18n('icu:RemoveGroupMemberConfirmation__remove-button')}
</Button>
);
} else if (conversationInfo.conversation.isBlocked) {
button = (
<Button
variant={ButtonVariant.SecondaryAffirmative}
onClick={() => {
acceptConversation(conversationInfo.conversation.id);
}}
>
{i18n('icu:MessageRequests--unblock')}
</Button>
);
} else if (!isInSystemContacts(conversationInfo.conversation)) {
button = (
<Button
variant={ButtonVariant.SecondaryDestructive}
onClick={() => {
setConfirmationState({
type: ConfirmationStateType.ConfirmingBlock,
affectedConversation: conversationInfo.conversation,
});
}}
>
{i18n('icu:MessageRequests--block')}
</Button>
);
}
2021-11-11 22:43:05 +00:00
const { oldName, isSignalConnection } = conversationInfo;
2022-11-10 04:59:36 +00:00
return (
<>
<ContactSpoofingReviewDialogPerson
key={conversationInfo.conversation.id}
conversation={conversationInfo.conversation}
toggleSignalConnectionsModal={
toggleSignalConnectionsModal
}
updateSharedGroups={updateSharedGroups}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
theme={theme}
oldName={oldName}
isSignalConnection={isSignalConnection}
>
{button && (
<div className="module-ContactSpoofingReviewDialog__buttons">
{button}
</div>
)}
</ContactSpoofingReviewDialogPerson>
{titleIdx < sharedTitles.length - 1 ||
conversationIdx < conversationInfos.length - 1 ? (
<hr />
) : null}
</>
);
})
)
.flat()}
2022-03-22 20:45:34 +00:00
</>
);
break;
2021-06-01 23:30:25 +00:00
}
2022-03-22 20:45:34 +00:00
default:
throw missingCaseError(props);
}
2021-04-21 16:31:12 +00:00
2022-03-22 20:45:34 +00:00
return (
<Modal
2022-09-27 20:24:21 +00:00
modalName="ContactSpoofingReviewDialog"
2022-03-22 20:45:34 +00:00
hasXButton
i18n={i18n}
moduleClassName="module-ContactSpoofingReviewDialog"
onClose={onClose}
title={title}
>
{contents}
</Modal>
);
2022-11-18 00:45:19 +00:00
}