signal-desktop/ts/state/smart/ContactSpoofingReviewDialog.tsx
2024-02-07 13:33:34 -08:00

146 lines
4.2 KiB
TypeScript

// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { mapValues } from 'lodash';
import type { StateType } from '../reducer';
import { ContactSpoofingReviewDialog } from '../../components/conversation/ContactSpoofingReviewDialog';
import { useConversationsActions } from '../ducks/conversations';
import type { GetConversationByIdType } from '../selectors/conversations';
import {
getConversationSelector,
getConversationByServiceIdSelector,
getSafeConversationWithSameTitle,
} from '../selectors/conversations';
import { getOwn } from '../../util/getOwn';
import { assertDev } from '../../util/assert';
import { ContactSpoofingType } from '../../util/contactSpoofing';
import { getGroupMemberships } from '../../util/getGroupMemberships';
import { isSignalConnection } from '../../util/getSignalConnections';
import {
getCollisionsFromMemberships,
invertIdsByTitle,
} from '../../util/groupMemberNameCollisions';
import { useGlobalModalActions } from '../ducks/globalModals';
import { getPreferredBadgeSelector } from '../selectors/badges';
import { getIntl, getTheme } from '../selectors/user';
export type PropsType = Readonly<{
conversationId: string;
onClose: () => void;
}>;
export function SmartContactSpoofingReviewDialog(
props: PropsType
): JSX.Element | null {
const { conversationId } = props;
const getConversation = useSelector<StateType, GetConversationByIdType>(
getConversationSelector
);
const {
acceptConversation,
blockAndReportSpam,
blockConversation,
deleteConversation,
removeMember,
updateSharedGroups,
} = useConversationsActions();
const { showContactModal, toggleSignalConnectionsModal } =
useGlobalModalActions();
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const i18n = useSelector(getIntl);
const theme = useSelector(getTheme);
const getConversationByServiceId = useSelector(
getConversationByServiceIdSelector
);
const conversation = getConversation(conversationId);
// Just binding the options argument
const safeConversationSelector = useCallback(
(state: StateType) => {
return getSafeConversationWithSameTitle(state, {
possiblyUnsafeConversation: conversation,
});
},
[conversation]
);
const safeConvo = useSelector(safeConversationSelector);
const sharedProps = {
...props,
acceptConversation,
blockAndReportSpam,
blockConversation,
deleteConversation,
getPreferredBadge,
i18n,
removeMember,
updateSharedGroups,
showContactModal,
toggleSignalConnectionsModal,
theme,
};
if (conversation.type === 'group') {
const { memberships } = getGroupMemberships(
conversation,
getConversationByServiceId
);
const groupNameCollisions = getCollisionsFromMemberships(memberships);
const previouslyAcknowledgedTitlesById = invertIdsByTitle(
conversation.acknowledgedGroupNameCollisions
);
const collisionInfoByTitle = mapValues(groupNameCollisions, collisions =>
collisions.map(collision => ({
conversation: collision,
isSignalConnection: isSignalConnection(collision),
oldName: getOwn(previouslyAcknowledgedTitlesById, collision.id),
}))
);
return (
<ContactSpoofingReviewDialog
{...sharedProps}
type={ContactSpoofingType.MultipleGroupMembersWithSameTitle}
group={conversation}
collisionInfoByTitle={collisionInfoByTitle}
/>
);
}
const possiblyUnsafeConvo = conversation;
assertDev(
possiblyUnsafeConvo.type === 'direct',
'DirectConversationWithSameTitle: expects possibly unsafe direct ' +
'conversation'
);
if (!safeConvo) {
return null;
}
const possiblyUnsafe = {
conversation: possiblyUnsafeConvo,
isSignalConnection: isSignalConnection(possiblyUnsafeConvo),
};
const safe = {
conversation: safeConvo,
isSignalConnection: isSignalConnection(safeConvo),
};
return (
<ContactSpoofingReviewDialog
{...sharedProps}
type={ContactSpoofingType.DirectConversationWithSameTitle}
possiblyUnsafe={possiblyUnsafe}
safe={safe}
/>
);
}