DOE stories when they are part of deleted lists
This commit is contained in:
parent
b12de415f4
commit
b2792639aa
6 changed files with 226 additions and 189 deletions
|
@ -5516,7 +5516,7 @@
|
||||||
"description": "Button label to delete a private distribution list"
|
"description": "Button label to delete a private distribution list"
|
||||||
},
|
},
|
||||||
"StoriesSettings__delete-list--confirm": {
|
"StoriesSettings__delete-list--confirm": {
|
||||||
"message": "Delete private story?",
|
"message": "Are you sure you want to delete \"$name$\"? Updates shared to this story will also be deleted.",
|
||||||
"description": "Confirmation text to delete a private distribution list"
|
"description": "Confirmation text to delete a private distribution list"
|
||||||
},
|
},
|
||||||
"StoriesSettings__choose-viewers": {
|
"StoriesSettings__choose-viewers": {
|
||||||
|
|
|
@ -214,8 +214,8 @@ export const SendStoryModal = ({
|
||||||
const [confirmRemoveGroupId, setConfirmRemoveGroupId] = useState<
|
const [confirmRemoveGroupId, setConfirmRemoveGroupId] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
const [confirmDeleteListId, setConfirmDeleteListId] = useState<
|
const [confirmDeleteList, setConfirmDeleteList] = useState<
|
||||||
string | undefined
|
{ id: string; name: string } | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const [listIdToEdit, setListIdToEdit] = useState<string | undefined>();
|
const [listIdToEdit, setListIdToEdit] = useState<string | undefined>();
|
||||||
|
@ -397,7 +397,7 @@ export const SendStoryModal = ({
|
||||||
listToEdit={listToEdit}
|
listToEdit={listToEdit}
|
||||||
onRemoveMember={onRemoveMember}
|
onRemoveMember={onRemoveMember}
|
||||||
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
||||||
setConfirmDeleteListId={setConfirmDeleteListId}
|
setConfirmDeleteList={setConfirmDeleteList}
|
||||||
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
||||||
setPage={setPage}
|
setPage={setPage}
|
||||||
setSelectedContacts={setSelectedContacts}
|
setSelectedContacts={setSelectedContacts}
|
||||||
|
@ -719,7 +719,7 @@ export const SendStoryModal = ({
|
||||||
{
|
{
|
||||||
label: i18n('SendStoryModal__delete-story'),
|
label: i18n('SendStoryModal__delete-story'),
|
||||||
icon: 'SendStoryModal__icon--delete',
|
icon: 'SendStoryModal__icon--delete',
|
||||||
onClick: () => setConfirmDeleteListId(list.id),
|
onClick: () => setConfirmDeleteList(list),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -897,19 +897,19 @@ export const SendStoryModal = ({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setConfirmRemoveGroupId(undefined);
|
setConfirmRemoveGroupId(undefined);
|
||||||
}}
|
}}
|
||||||
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
{i18n('SendStoryModal__confirm-remove-group')}
|
{i18n('SendStoryModal__confirm-remove-group')}
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
)}
|
)}
|
||||||
{confirmDeleteListId && (
|
{confirmDeleteList && (
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
dialogName="SendStoryModal.confirmDeleteList"
|
dialogName="SendStoryModal.confirmDeleteList"
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
action: () => {
|
action: () => {
|
||||||
onDeleteList(confirmDeleteListId);
|
onDeleteList(confirmDeleteList.id);
|
||||||
setConfirmDeleteListId(undefined);
|
setConfirmDeleteList(undefined);
|
||||||
// setListToEditId(undefined);
|
|
||||||
},
|
},
|
||||||
style: 'negative',
|
style: 'negative',
|
||||||
text: i18n('delete'),
|
text: i18n('delete'),
|
||||||
|
@ -917,10 +917,13 @@ export const SendStoryModal = ({
|
||||||
]}
|
]}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setConfirmDeleteListId(undefined);
|
setConfirmDeleteList(undefined);
|
||||||
}}
|
}}
|
||||||
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
{i18n('StoriesSettings__delete-list--confirm')}
|
{i18n('StoriesSettings__delete-list--confirm', [
|
||||||
|
confirmDeleteList.name,
|
||||||
|
])}
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
)}
|
)}
|
||||||
{confirmDiscardModal}
|
{confirmDiscardModal}
|
||||||
|
|
|
@ -126,8 +126,8 @@ export const StoriesSettingsModal = ({
|
||||||
setPage(Page.DistributionLists);
|
setPage(Page.DistributionLists);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [confirmDeleteListId, setConfirmDeleteListId] = useState<
|
const [confirmDeleteList, setConfirmDeleteList] = useState<
|
||||||
string | undefined
|
{ id: string; name: string } | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
let modal: RenderModalPage | null;
|
let modal: RenderModalPage | null;
|
||||||
|
@ -188,7 +188,7 @@ export const StoriesSettingsModal = ({
|
||||||
listToEdit={listToEdit}
|
listToEdit={listToEdit}
|
||||||
onRemoveMember={onRemoveMember}
|
onRemoveMember={onRemoveMember}
|
||||||
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
||||||
setConfirmDeleteListId={setConfirmDeleteListId}
|
setConfirmDeleteList={setConfirmDeleteList}
|
||||||
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
||||||
setPage={setPage}
|
setPage={setPage}
|
||||||
setSelectedContacts={setSelectedContacts}
|
setSelectedContacts={setSelectedContacts}
|
||||||
|
@ -297,13 +297,13 @@ export const StoriesSettingsModal = ({
|
||||||
{modal}
|
{modal}
|
||||||
</PagedModal>
|
</PagedModal>
|
||||||
)}
|
)}
|
||||||
{confirmDeleteListId && (
|
{confirmDeleteList && (
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
dialogName="StoriesSettings.deleteList"
|
dialogName="StoriesSettings.deleteList"
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
action: () => {
|
action: () => {
|
||||||
onDeleteList(confirmDeleteListId);
|
onDeleteList(confirmDeleteList.id);
|
||||||
setListToEditId(undefined);
|
setListToEditId(undefined);
|
||||||
},
|
},
|
||||||
style: 'negative',
|
style: 'negative',
|
||||||
|
@ -312,10 +312,13 @@ export const StoriesSettingsModal = ({
|
||||||
]}
|
]}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setConfirmDeleteListId(undefined);
|
setConfirmDeleteList(undefined);
|
||||||
}}
|
}}
|
||||||
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
{i18n('StoriesSettings__delete-list--confirm')}
|
{i18n('StoriesSettings__delete-list--confirm', [
|
||||||
|
confirmDeleteList.name,
|
||||||
|
])}
|
||||||
</ConfirmationDialog>
|
</ConfirmationDialog>
|
||||||
)}
|
)}
|
||||||
{confirmDiscardModal}
|
{confirmDiscardModal}
|
||||||
|
@ -326,7 +329,7 @@ export const StoriesSettingsModal = ({
|
||||||
type DistributionListSettingsModalPropsType = {
|
type DistributionListSettingsModalPropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
listToEdit: StoryDistributionListWithMembersDataType;
|
listToEdit: StoryDistributionListWithMembersDataType;
|
||||||
setConfirmDeleteListId: (id: string) => unknown;
|
setConfirmDeleteList: (_: { id: string; name: string }) => unknown;
|
||||||
setPage: (page: Page) => unknown;
|
setPage: (page: Page) => unknown;
|
||||||
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
||||||
onBackButtonClick: (() => void) | undefined;
|
onBackButtonClick: (() => void) | undefined;
|
||||||
|
@ -348,7 +351,7 @@ export const DistributionListSettingsModal = ({
|
||||||
onRepliesNReactionsChanged,
|
onRepliesNReactionsChanged,
|
||||||
onBackButtonClick,
|
onBackButtonClick,
|
||||||
onClose,
|
onClose,
|
||||||
setConfirmDeleteListId,
|
setConfirmDeleteList,
|
||||||
setMyStoriesToAllSignalConnections,
|
setMyStoriesToAllSignalConnections,
|
||||||
setPage,
|
setPage,
|
||||||
setSelectedContacts,
|
setSelectedContacts,
|
||||||
|
@ -504,7 +507,7 @@ export const DistributionListSettingsModal = ({
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="StoriesSettingsModal__delete-list"
|
className="StoriesSettingsModal__delete-list"
|
||||||
onClick={() => setConfirmDeleteListId(listToEdit.id)}
|
onClick={() => setConfirmDeleteList(listToEdit)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{i18n('StoriesSettings__delete-list')}
|
{i18n('StoriesSettings__delete-list')}
|
||||||
|
@ -530,6 +533,7 @@ export const DistributionListSettingsModal = ({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setConfirmRemoveMember(undefined);
|
setConfirmRemoveMember(undefined);
|
||||||
}}
|
}}
|
||||||
|
theme={Theme.Dark}
|
||||||
title={i18n('StoriesSettings__remove--title', [
|
title={i18n('StoriesSettings__remove--title', [
|
||||||
confirmRemoveMember.title,
|
confirmRemoveMember.title,
|
||||||
])}
|
])}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
|
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
|
||||||
import { isEqual, noop, pick } from 'lodash';
|
import { isEqual, pick } from 'lodash';
|
||||||
import type { AttachmentType } from '../../types/Attachment';
|
import type { AttachmentType } from '../../types/Attachment';
|
||||||
import type { BodyRangeType } from '../../types/Util';
|
import type { BodyRangeType } from '../../types/Util';
|
||||||
import type { ConversationModel } from '../../models/conversations';
|
import type { ConversationModel } from '../../models/conversations';
|
||||||
|
@ -19,20 +19,18 @@ import type { SyncType } from '../../jobs/helpers/syncHelpers';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import dataInterface from '../../sql/Client';
|
import dataInterface from '../../sql/Client';
|
||||||
import { DAY } from '../../util/durations';
|
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
||||||
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
|
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
|
||||||
import { StoryRecipientUpdateEvent } from '../../textsecure/messageReceiverEvents';
|
|
||||||
import { ToastReactionFailed } from '../../components/ToastReactionFailed';
|
import { ToastReactionFailed } from '../../components/ToastReactionFailed';
|
||||||
import { assertDev } from '../../util/assert';
|
import { assertDev } from '../../util/assert';
|
||||||
import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified';
|
import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified';
|
||||||
|
import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
||||||
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { getMessageById } from '../../messages/getMessageById';
|
||||||
import { markViewed } from '../../services/MessageUpdater';
|
import { markViewed } from '../../services/MessageUpdater';
|
||||||
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
||||||
import { replaceIndex } from '../../util/replaceIndex';
|
import { replaceIndex } from '../../util/replaceIndex';
|
||||||
import { sendDeleteForEveryoneMessage } from '../../util/sendDeleteForEveryoneMessage';
|
|
||||||
import { showToast } from '../../util/showToast';
|
import { showToast } from '../../util/showToast';
|
||||||
import {
|
import {
|
||||||
hasFailed,
|
hasFailed,
|
||||||
|
@ -44,13 +42,11 @@ import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getHideStoryConversationIds,
|
getHideStoryConversationIds,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { getSendOptions } from '../../util/getSendOptions';
|
|
||||||
import { getStories } from '../selectors/stories';
|
import { getStories } from '../selectors/stories';
|
||||||
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
||||||
import { isGroup } from '../../util/whatTypeOfConversation';
|
import { isGroup } from '../../util/whatTypeOfConversation';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
import { isStory } from '../../messages/helpers';
|
import { isStory } from '../../messages/helpers';
|
||||||
import { onStoryRecipientUpdate } from '../../util/onStoryRecipientUpdate';
|
|
||||||
import { sendStoryMessage as doSendStoryMessage } from '../../util/sendStoryMessage';
|
import { sendStoryMessage as doSendStoryMessage } from '../../util/sendStoryMessage';
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
import { verifyStoryListMembers as doVerifyStoryListMembers } from '../../util/verifyStoryListMembers';
|
import { verifyStoryListMembers as doVerifyStoryListMembers } from '../../util/verifyStoryListMembers';
|
||||||
|
@ -214,169 +210,13 @@ function deleteStoryForEveryone(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationIds = new Set(
|
|
||||||
story.sendState.map(({ recipient }) => recipient.id)
|
|
||||||
);
|
|
||||||
const updatedStoryRecipients = new Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
distributionListIds: Set<string>;
|
|
||||||
isAllowedToReply: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const ourConversation =
|
|
||||||
window.ConversationController.getOurConversationOrThrow();
|
|
||||||
|
|
||||||
// Remove ourselves from the DOE.
|
|
||||||
conversationIds.delete(ourConversation.id);
|
|
||||||
|
|
||||||
// Find stories that were sent to other distribution lists so that we don't
|
|
||||||
// send a DOE request to the members of those lists.
|
|
||||||
const { stories } = getState().stories;
|
const { stories } = getState().stories;
|
||||||
stories.forEach(item => {
|
const storyData = stories.find(item => item.messageId === story.messageId);
|
||||||
const { sendStateByConversationId } = item;
|
if (!storyData) {
|
||||||
// We only want matching timestamp stories which are stories that were
|
log.warn('deleteStoryForEveryone: Could not find story in redux data');
|
||||||
// sent to multi distribution lists.
|
return;
|
||||||
// We don't want the story we just passed in.
|
|
||||||
// Don't need to check for stories that have already been deleted.
|
|
||||||
// And only for sent stories, not incoming.
|
|
||||||
if (
|
|
||||||
item.timestamp !== story.timestamp ||
|
|
||||||
item.messageId === story.messageId ||
|
|
||||||
item.deletedForEveryone ||
|
|
||||||
!sendStateByConversationId
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(sendStateByConversationId).forEach(conversationId => {
|
|
||||||
if (conversationId === ourConversation.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const destinationUuid =
|
|
||||||
window.ConversationController.get(conversationId)?.get('uuid');
|
|
||||||
|
|
||||||
if (!destinationUuid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const distributionListIds =
|
|
||||||
updatedStoryRecipients.get(destinationUuid)?.distributionListIds ||
|
|
||||||
new Set();
|
|
||||||
|
|
||||||
// These are the remaining distribution list ids that the user has
|
|
||||||
// access to.
|
|
||||||
updatedStoryRecipients.set(destinationUuid, {
|
|
||||||
distributionListIds: item.storyDistributionListId
|
|
||||||
? new Set([...distributionListIds, item.storyDistributionListId])
|
|
||||||
: distributionListIds,
|
|
||||||
isAllowedToReply:
|
|
||||||
sendStateByConversationId[conversationId]
|
|
||||||
.isAllowedToReplyToStory !== false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove this conversationId so we don't send the DOE to those that
|
|
||||||
// still have access.
|
|
||||||
conversationIds.delete(conversationId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send the DOE
|
|
||||||
conversationIds.forEach(cid => {
|
|
||||||
// Don't DOE yourself!
|
|
||||||
if (cid === ourConversation.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const conversation = window.ConversationController.get(cid);
|
|
||||||
|
|
||||||
if (!conversation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendDeleteForEveryoneMessage(conversation.attributes, {
|
|
||||||
deleteForEveryoneDuration: DAY,
|
|
||||||
id: story.messageId,
|
|
||||||
timestamp: story.timestamp,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// If it's the last story sent to a distribution list we don't have to send
|
|
||||||
// the sync message, but to be consistent let's build up the updated
|
|
||||||
// storyMessageRecipients and send the sync message.
|
|
||||||
if (!updatedStoryRecipients.size) {
|
|
||||||
story.sendState.forEach(item => {
|
|
||||||
if (item.recipient.id === ourConversation.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const destinationUuid = window.ConversationController.get(
|
|
||||||
item.recipient.id
|
|
||||||
)?.get('uuid');
|
|
||||||
|
|
||||||
if (!destinationUuid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedStoryRecipients.set(destinationUuid, {
|
|
||||||
distributionListIds: new Set(),
|
|
||||||
isAllowedToReply: item.isAllowedToReplyToStory !== false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the sync message with the updated storyMessageRecipients list
|
|
||||||
const sender = window.textsecure.messaging;
|
|
||||||
if (sender) {
|
|
||||||
const options = await getSendOptions(ourConversation.attributes, {
|
|
||||||
syncMessage: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const storyMessageRecipients: Array<{
|
|
||||||
destinationUuid: string;
|
|
||||||
distributionListIds: Array<string>;
|
|
||||||
isAllowedToReply: boolean;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
updatedStoryRecipients.forEach((recipientData, destinationUuid) => {
|
|
||||||
storyMessageRecipients.push({
|
|
||||||
destinationUuid,
|
|
||||||
distributionListIds: Array.from(recipientData.distributionListIds),
|
|
||||||
isAllowedToReply: recipientData.isAllowedToReply,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const destinationUuid = ourConversation.get('uuid');
|
|
||||||
|
|
||||||
if (!destinationUuid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync message for other devices
|
|
||||||
sender.sendSyncMessage({
|
|
||||||
destination: undefined,
|
|
||||||
destinationUuid,
|
|
||||||
storyMessageRecipients,
|
|
||||||
expirationStartTimestamp: null,
|
|
||||||
isUpdate: true,
|
|
||||||
options,
|
|
||||||
timestamp: story.timestamp,
|
|
||||||
urgent: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sync message for Desktop
|
|
||||||
const ev = new StoryRecipientUpdateEvent(
|
|
||||||
{
|
|
||||||
destinationUuid,
|
|
||||||
timestamp: story.timestamp,
|
|
||||||
storyMessageRecipients,
|
|
||||||
},
|
|
||||||
noop
|
|
||||||
);
|
|
||||||
onStoryRecipientUpdate(ev);
|
|
||||||
}
|
}
|
||||||
|
await doDeleteStoryForEveryone(stories, storyData);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: DOE_STORY,
|
type: DOE_STORY,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as log from '../../logging/log';
|
||||||
import dataInterface from '../../sql/Client';
|
import dataInterface from '../../sql/Client';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORIES_ID } from '../../types/Stories';
|
||||||
import { UUID } from '../../types/UUID';
|
import { UUID } from '../../types/UUID';
|
||||||
|
import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
||||||
import { replaceIndex } from '../../util/replaceIndex';
|
import { replaceIndex } from '../../util/replaceIndex';
|
||||||
import { storageServiceUploadJob } from '../../services/storage';
|
import { storageServiceUploadJob } from '../../services/storage';
|
||||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
@ -201,7 +202,7 @@ function createDistributionList(
|
||||||
function deleteDistributionList(
|
function deleteDistributionList(
|
||||||
listId: string
|
listId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, DeleteListActionType> {
|
): ThunkAction<void, RootStateType, unknown, DeleteListActionType> {
|
||||||
return async dispatch => {
|
return async (dispatch, getState) => {
|
||||||
const deletedAtTimestamp = Date.now();
|
const deletedAtTimestamp = Date.now();
|
||||||
|
|
||||||
const storyDistribution =
|
const storyDistribution =
|
||||||
|
@ -225,6 +226,14 @@ function deleteDistributionList(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { stories } = getState().stories;
|
||||||
|
const storiesToDelete = stories.filter(
|
||||||
|
story => story.storyDistributionListId === listId
|
||||||
|
);
|
||||||
|
await Promise.all(
|
||||||
|
storiesToDelete.map(story => deleteStoryForEveryone(stories, story))
|
||||||
|
);
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
'storyDistributionLists.deleteDistributionList: list deleted',
|
'storyDistributionLists.deleteDistributionList: list deleted',
|
||||||
listId
|
listId
|
||||||
|
|
181
ts/util/deleteStoryForEveryone.ts
Normal file
181
ts/util/deleteStoryForEveryone.ts
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { noop } from 'lodash';
|
||||||
|
import type { StoryDataType } from '../state/ducks/stories';
|
||||||
|
import { DAY } from './durations';
|
||||||
|
import { StoryRecipientUpdateEvent } from '../textsecure/messageReceiverEvents';
|
||||||
|
import { getSendOptions } from './getSendOptions';
|
||||||
|
import { onStoryRecipientUpdate } from './onStoryRecipientUpdate';
|
||||||
|
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
||||||
|
|
||||||
|
export async function deleteStoryForEveryone(
|
||||||
|
stories: Array<StoryDataType>,
|
||||||
|
story: StoryDataType
|
||||||
|
): Promise<void> {
|
||||||
|
if (!story.sendStateByConversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationIds = new Set(Object.keys(story.sendStateByConversationId));
|
||||||
|
const updatedStoryRecipients = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
distributionListIds: Set<string>;
|
||||||
|
isAllowedToReply: boolean;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
const ourConversation =
|
||||||
|
window.ConversationController.getOurConversationOrThrow();
|
||||||
|
|
||||||
|
// Remove ourselves from the DOE.
|
||||||
|
conversationIds.delete(ourConversation.id);
|
||||||
|
|
||||||
|
// Find stories that were sent to other distribution lists so that we don't
|
||||||
|
// send a DOE request to the members of those lists.
|
||||||
|
stories.forEach(item => {
|
||||||
|
const { sendStateByConversationId } = item;
|
||||||
|
// We only want matching timestamp stories which are stories that were
|
||||||
|
// sent to multi distribution lists.
|
||||||
|
// We don't want the story we just passed in.
|
||||||
|
// Don't need to check for stories that have already been deleted.
|
||||||
|
// And only for sent stories, not incoming.
|
||||||
|
if (
|
||||||
|
item.timestamp !== story.timestamp ||
|
||||||
|
item.messageId === story.messageId ||
|
||||||
|
item.deletedForEveryone ||
|
||||||
|
!sendStateByConversationId
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(sendStateByConversationId).forEach(conversationId => {
|
||||||
|
if (conversationId === ourConversation.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationUuid =
|
||||||
|
window.ConversationController.get(conversationId)?.get('uuid');
|
||||||
|
|
||||||
|
if (!destinationUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distributionListIds =
|
||||||
|
updatedStoryRecipients.get(destinationUuid)?.distributionListIds ||
|
||||||
|
new Set();
|
||||||
|
|
||||||
|
// These are the remaining distribution list ids that the user has
|
||||||
|
// access to.
|
||||||
|
updatedStoryRecipients.set(destinationUuid, {
|
||||||
|
distributionListIds: item.storyDistributionListId
|
||||||
|
? new Set([...distributionListIds, item.storyDistributionListId])
|
||||||
|
: distributionListIds,
|
||||||
|
isAllowedToReply:
|
||||||
|
sendStateByConversationId[conversationId].isAllowedToReplyToStory !==
|
||||||
|
false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove this conversationId so we don't send the DOE to those that
|
||||||
|
// still have access.
|
||||||
|
conversationIds.delete(conversationId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the DOE
|
||||||
|
conversationIds.forEach(cid => {
|
||||||
|
// Don't DOE yourself!
|
||||||
|
if (cid === ourConversation.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversation = window.ConversationController.get(cid);
|
||||||
|
|
||||||
|
if (!conversation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendDeleteForEveryoneMessage(conversation.attributes, {
|
||||||
|
deleteForEveryoneDuration: DAY,
|
||||||
|
id: story.messageId,
|
||||||
|
timestamp: story.timestamp,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// If it's the last story sent to a distribution list we don't have to send
|
||||||
|
// the sync message, but to be consistent let's build up the updated
|
||||||
|
// storyMessageRecipients and send the sync message.
|
||||||
|
if (!updatedStoryRecipients.size) {
|
||||||
|
Object.entries(story.sendStateByConversationId).forEach(
|
||||||
|
([recipientId, sendState]) => {
|
||||||
|
if (recipientId === ourConversation.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationUuid =
|
||||||
|
window.ConversationController.get(recipientId)?.get('uuid');
|
||||||
|
|
||||||
|
if (!destinationUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedStoryRecipients.set(destinationUuid, {
|
||||||
|
distributionListIds: new Set(),
|
||||||
|
isAllowedToReply: sendState.isAllowedToReplyToStory !== false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the sync message with the updated storyMessageRecipients list
|
||||||
|
const sender = window.textsecure.messaging;
|
||||||
|
if (sender) {
|
||||||
|
const options = await getSendOptions(ourConversation.attributes, {
|
||||||
|
syncMessage: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const storyMessageRecipients: Array<{
|
||||||
|
destinationUuid: string;
|
||||||
|
distributionListIds: Array<string>;
|
||||||
|
isAllowedToReply: boolean;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
updatedStoryRecipients.forEach((recipientData, destinationUuid) => {
|
||||||
|
storyMessageRecipients.push({
|
||||||
|
destinationUuid,
|
||||||
|
distributionListIds: Array.from(recipientData.distributionListIds),
|
||||||
|
isAllowedToReply: recipientData.isAllowedToReply,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const destinationUuid = ourConversation.get('uuid');
|
||||||
|
|
||||||
|
if (!destinationUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync message for other devices
|
||||||
|
sender.sendSyncMessage({
|
||||||
|
destination: undefined,
|
||||||
|
destinationUuid,
|
||||||
|
storyMessageRecipients,
|
||||||
|
expirationStartTimestamp: null,
|
||||||
|
isUpdate: true,
|
||||||
|
options,
|
||||||
|
timestamp: story.timestamp,
|
||||||
|
urgent: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync message for Desktop
|
||||||
|
const ev = new StoryRecipientUpdateEvent(
|
||||||
|
{
|
||||||
|
destinationUuid,
|
||||||
|
timestamp: story.timestamp,
|
||||||
|
storyMessageRecipients,
|
||||||
|
},
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
onStoryRecipientUpdate(ev);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue