Support for local deletes synced to all your devices

This commit is contained in:
Scott Nonnenberg 2024-05-29 01:56:00 +10:00 committed by GitHub
parent 06f71a7ef8
commit 11eb1782a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 2094 additions and 72 deletions

View file

@ -3,6 +3,7 @@
import type { ThunkAction } from 'redux-thunk';
import {
chunk,
difference,
fromPairs,
isEqual,
@ -184,6 +185,16 @@ import { getConversationIdForLogging } from '../../util/idForLogging';
import { singleProtoJobQueue } from '../../jobs/singleProtoJobQueue';
import MessageSender from '../../textsecure/SendMessage';
import { AttachmentDownloadUrgency } from '../../jobs/AttachmentDownloadManager';
import type {
DeleteForMeSyncEventData,
MessageToDelete,
} from '../../textsecure/messageReceiverEvents';
import {
getConversationToDelete,
getMessageToDelete,
} from '../../util/deleteForMe';
import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types';
import { isEnabled } from '../../RemoteConfig';
// State
@ -1703,21 +1714,27 @@ function deleteMessages({
throw new Error('deleteMessage: No conversation found');
}
await Promise.all(
messageIds.map(async messageId => {
const message = await __DEPRECATED$getMessageById(messageId);
if (!message) {
throw new Error(`deleteMessages: Message ${messageId} missing!`);
}
const messages = (
await Promise.all(
messageIds.map(
async (messageId): Promise<MessageToDelete | undefined> => {
const message = await __DEPRECATED$getMessageById(messageId);
if (!message) {
throw new Error(`deleteMessages: Message ${messageId} missing!`);
}
const messageConversationId = message.get('conversationId');
if (conversationId !== messageConversationId) {
throw new Error(
`deleteMessages: message conversation ${messageConversationId} doesn't match provided conversation ${conversationId}`
);
}
})
);
const messageConversationId = message.get('conversationId');
if (conversationId !== messageConversationId) {
throw new Error(
`deleteMessages: message conversation ${messageConversationId} doesn't match provided conversation ${conversationId}`
);
}
return getMessageToDelete(message.attributes);
}
)
)
).filter(isNotNil);
let nearbyMessageId: string | null = null;
@ -1743,6 +1760,34 @@ function deleteMessages({
if (nearbyMessageId != null) {
dispatch(scrollToMessage(conversationId, nearbyMessageId));
}
if (!isEnabled('desktop.deleteSync.send')) {
return;
}
if (messages.length === 0) {
return;
}
const chunks = chunk(messages, MAX_MESSAGE_COUNT);
const conversationToDelete = getConversationToDelete(
conversation.attributes
);
const timestamp = Date.now();
await Promise.all(
chunks.map(async items => {
const data: DeleteForMeSyncEventData = items.map(item => ({
conversation: conversationToDelete,
message: item,
timestamp,
type: 'delete-message' as const,
}));
await singleProtoJobQueue.add(
MessageSender.getDeleteForMeSyncMessage(data)
);
})
);
};
}
@ -1770,7 +1815,7 @@ function destroyMessages(
undefined
);
await conversation.destroyMessages();
await conversation.destroyMessages({ source: 'local-delete' });
drop(conversation.updateLastMessage());
},
});

View file

@ -127,6 +127,13 @@ export const isInternalUser = createSelector(
}
);
export const getDeleteSyncSendEnabled = createSelector(
getRemoteConfig,
(remoteConfig: ConfigMapType): boolean => {
return isRemoteConfigFlagEnabled(remoteConfig, 'desktop.deleteSync.send');
}
);
// Note: ts/util/stories is the other place this check is done
export const getStoriesEnabled = createSelector(
getItems,
@ -242,3 +249,9 @@ export const getShowStickerPickerHint = createSelector(
return state.showStickerPickerHint ?? false;
}
);
export const getLocalDeleteWarningShown = createSelector(
getItems,
(state: ItemsStateType): boolean =>
Boolean(state.localDeleteWarningShown ?? false)
);

View file

@ -40,6 +40,11 @@ import {
} from '../selectors/conversations';
import { getHasStoriesSelector } from '../selectors/stories2';
import { getIntl, getTheme, getUserACI } from '../selectors/user';
import { useItemsActions } from '../ducks/items';
import {
getDeleteSyncSendEnabled,
getLocalDeleteWarningShown,
} from '../selectors/items';
export type OwnProps = {
id: string;
@ -146,6 +151,7 @@ export const SmartConversationHeader = memo(function SmartConversationHeader({
const conversationName = useContactNameData(conversation);
strictAssert(conversationName, 'conversationName is required');
const isDeleteSyncSendEnabled = useSelector(getDeleteSyncSendEnabled);
const isMissingMandatoryProfileSharing =
getIsMissingRequiredProfileSharing(conversation);
@ -248,6 +254,11 @@ export const SmartConversationHeader = memo(function SmartConversationHeader({
const minimalConversation = useMinimalConversation(conversation);
const localDeleteWarningShown = useSelector(getLocalDeleteWarningShown);
const { putItem } = useItemsActions();
const setLocalDeleteWarningShown = () =>
putItem('localDeleteWarningShown', true);
return (
<ConversationHeader
addedByName={addedByName}
@ -258,6 +269,8 @@ export const SmartConversationHeader = memo(function SmartConversationHeader({
hasPanelShowing={hasPanelShowing}
hasStories={hasStories}
i18n={i18n}
localDeleteWarningShown={localDeleteWarningShown}
isDeleteSyncSendEnabled={isDeleteSyncSendEnabled}
isMissingMandatoryProfileSharing={isMissingMandatoryProfileSharing}
isSelectMode={isSelectMode}
isSignalConversation={isSignalConversation(conversation)}
@ -287,6 +300,7 @@ export const SmartConversationHeader = memo(function SmartConversationHeader({
onViewRecentMedia={onViewRecentMedia}
onViewUserStories={onViewUserStories}
outgoingCallButtonStyle={outgoingCallButtonStyle}
setLocalDeleteWarningShown={setLocalDeleteWarningShown}
sharedGroupNames={conversation.sharedGroupNames}
theme={theme}
/>

View file

@ -16,6 +16,12 @@ import {
getLastSelectedMessage,
} from '../selectors/conversations';
import { getDeleteMessagesProps } from '../selectors/globalModals';
import { useItemsActions } from '../ducks/items';
import {
getLocalDeleteWarningShown,
getDeleteSyncSendEnabled,
} from '../selectors/items';
import { LocalDeleteWarningModal } from '../../components/LocalDeleteWarningModal';
export const SmartDeleteMessagesModal = memo(
function SmartDeleteMessagesModal() {
@ -36,6 +42,7 @@ export const SmartDeleteMessagesModal = memo(
[messageIds, isMe]
);
const canDeleteForEveryone = useSelector(getCanDeleteForEveryone);
const isDeleteSyncSendEnabled = useSelector(getDeleteSyncSendEnabled);
const lastSelectedMessage = useSelector(getLastSelectedMessage);
const i18n = useSelector(getIntl);
const { toggleDeleteMessagesModal } = useGlobalModalActions();
@ -69,11 +76,25 @@ export const SmartDeleteMessagesModal = memo(
onDelete?.();
}, [deleteMessagesForEveryone, messageIds, onDelete]);
const localDeleteWarningShown = useSelector(getLocalDeleteWarningShown);
const { putItem } = useItemsActions();
if (!localDeleteWarningShown && isDeleteSyncSendEnabled) {
return (
<LocalDeleteWarningModal
i18n={i18n}
onClose={() => {
putItem('localDeleteWarningShown', true);
}}
/>
);
}
return (
<DeleteMessagesModal
isMe={isMe}
canDeleteForEveryone={canDeleteForEveryone}
i18n={i18n}
isDeleteSyncSendEnabled={isDeleteSyncSendEnabled}
messageCount={messageCount}
onClose={handleClose}
onDeleteForMe={handleDeleteForMe}