Support for local deletes synced to all your devices
This commit is contained in:
parent
06f71a7ef8
commit
11eb1782a7
39 changed files with 2094 additions and 72 deletions
|
@ -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());
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue